...
Code Block | ||
---|---|---|
| ||
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 |
Main()
- 해당 함수는 while() 함수를 이용하여 MainFunction()함수를 계속 호출합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
__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)
- 해당 함수는 다음과 같은 기능을 합니다.
DefaultSet() 함수를 통해 게임 진행시 필요한 변수의 초기화를 선언합니다.
"operator new(8uLL)" 코드를 이용하여 vtable정보를 저장할 Heap 영역을 할당합니다.
- 할당된 Heap의 크기는 0x20byte 입니다.
- 할당된 Heap 영역의 주소값은 Computer, Human Class 변수에 저장합니다.
while()을 통해 게임을 플레이하기 위한 기능을 실행합니다.
gettimeofday() 함수를 이용하여 플레이어의 게임 플레이 시간을 계산합니다.
rowNumber, colNumber 의 값이 -1과 같다면 게임을 종료합니다.
- rowNumber, colNumber 의 값이 -2일 경우에는 게임 턴을 한차례 돌리는 regret()함수를 호출합니다.
- 그외의 rowNumber, colNumber 값이 입력 되면 플레이어의 플레이시간을 저장합니다.
- 총 플레이시간이 0.0보다 작으면 게임을 종료합니다.
- 총 플레이시간이 0.0보다 크면 SetMarkForBoard()함수를 이용하여 게임 보드 rowNumber,colNumber 위치에 표시합니다.
Struct
- 해당 바이너리를 분석하기 위해 다음과 같은 구조체들이 필요합니다.
- 해당 바이너리는 C++로 개발되어 있으며 vtable을 사용하고 있습니다.
- 아래 구조체는 AI Class의 play함수를 표현하는 구조체 입니다.
Code Block |
---|
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의 게임 정보를 저장하는 구조체 입니다.
Code Block |
---|
struct GameInfo
{
_QWORD board[12];
_DWORD rowNumber;
_DWORD colNumber;
_QWORD player;
double playTimeForAI;
double playTimeForHuman;
}; |
Main()
- 해당 함수는 while() 함수를 이용하여 MainFunction()함수를 계속 호출합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
bool MainFunction() { Class1 *v0; // rax@1 Class1 *Computer; // rbx@1 Class2 *v2; // rax@1 Class2 *Human; // rbx@1 bool__int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 result; // al@13rax@3 __int64 v5v4; // rcx@13rbx@3 unsigned int rowNumber__int64 v5; // [rsp+Ch8h] [rbp-64h18h]@3@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)을 할당합니다.
- 각 Class 별 할당되는 Heap의 크기는 16byte 입니다.
setAIFunction(), setHUMANFunction() 함수를 이용해 할당 받은 영역에 호출할 함수의 주소를 저장합니다.
while()을 통해 게임을 플레이하기 위한 기능을 실행합니다.
gettimeofday() 함수를 이용하여 플레이어의 게임 플레이 시간을 계산합니다.
rowNumber, colNumber 의 값이 -1과 같다면 게임을 종료합니다.
- rowNumber, colNumber 의 값이 -2일 경우에는 게임 턴을 한차례 돌리는 regret()함수를 호출합니다.
- 그외의 rowNumber, colNumber 값이 입력 되면 플레이어의 플레이시간을 저장합니다.
- 총 플레이시간이 0.0보다 작으면 게임을 종료합니다.
- 총 플레이시간이 0.0보다 크면 SetMarkForBoard()함수를 이용하여 게임 보드 rowNumber,colNumber 위치에 표시합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
__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 unsigned int colNumber; // [rsp+10h] [rbp-60h]@3 unsigned int playerInfo; // [rsp+14h] [rbp-5Ch]@1 double playTime; // [rsp+18h] [rbp-58h]@3 struct timeval startTime; // [rsp+20h] [rbp-50h]@3 struct timeval endTime; // [rsp+30h] [rbp-40h]@3 Class1 *Computer_1; // [rsp+40h] [rbp-30h]@1 Class2 *Human_1; // [rsp+48h] [rbp-28h]@1 __int64 canary; // [rsp+58h] [rbp-18h]@1 canary = *MK_FP(__FS__, 40LL); DefaultSet(); playerInfo = 1; operator new(8uLL); Computer = v0; v0->vtable = 0LL; class::ctor(v0); Computer_1 = Computer; operator new(8uLL); Human = v2; v2->vtable = 0LL; class2::ctor(v2); Human_1 = Human; while ( !((unsigned __int8)sub_401202(playerInfo, &gameInfo) ^ 1) ) { gettimeofday(&startTime, 0LL); ((void (__fastcall *)(Class1 *, GameInfo *, _QWORD, unsigned int *, unsigned int *))(&Computer_1)[playerInfo - 1]->vtable->Method_1)( (&Computer_1)[playerInfo - 1], &gameInfo, playerInfo, &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 ) // surrender break; if ( rowNumber != -2 && colNumber != -2 ) // Default { *(double *)&gameInfo.board[(signed int)(playerInfo - 1) + 14LL] = *(double *)&gameInfo.board[(signed int)(playerInfo - 1) + 14LL] + (double)(LODWORD(endTime.tv_sec) - LODWORD(startTime.tv_sec)); if ( rowNumber == -1 || colNumber == -1 ) break; if ( rowNumber != -2 && colNumber != -2 ) - playTime;{ if ( *(double *)&gameInfogPlayerGameInfo.board[(signed int)(playerInfoplayerNum - 1) + 14LL] < 0.0 ) = *(double *)&gPlayerGameInfo.board[playerNum - 1 + 14LL] print("Time's up"); SetMarkForBoard(&gameInfo, rowNumber, colNumber, playerInfo, 0); playerInfo ^= 3u; } else if ( (unsigned __int8)regret() ^ 1 ) // regret { print("No you cant't"); } } CheckResults(); - PlayHistory()playTime; result = PlayAgain(); v5if =( *MK_FP(__FS__, 40LL) ^ canary; return result; } |
vtable
- 해당 프로그램은 C++ 로 개발되어 있으여 클래스를 생성해서 사용하고 있습니다.
- 그리고 해당 클래스에서는 virtual function를 선언해서 사용합니다.
- 다음과 같은 형태를 가집니다.
Code Block | ||||
---|---|---|---|---|
| ||||
class Player {
public:
virtual void Play(GameInfo *info, _QWORD playerInfo, unsigned int *row, unsigned int *col) {};
};
class Computer : public Player { ... };
class Human : public Player { ... }; |
- Computer, Human Class에 호출하는 Player 함수의 주소는 다음과 같습니다.
...
Class
...
Call vtable function
...
Computer
...
40290A()
...
Hummand
...
402C12()
...
(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
- 해당 함수는 다음과 같은 기능을 합니다.
- PrintGameBoard_0() 함수를 이용하여 Go board를 출력합니다.
- scanf() 함수를 이용하여 최대 10개의 문자를 입력 받습니다.
- 입력받은 문자열은 다음과 같이 데이터 처리 합니다.
- 입력한 문자열이 "surrender"는 *colNumber, *rowNumber 변수에 -1을 저장합니다.
- 입력한 문자열이 "regret"는 *colNumber, *rowNumber 변수에 -2을 저장합니다.
- 앞에 두 문자열과 같지 않을 경우 다음과 같이 데이터를 처리합니다.
- sscanf() 함수를 이용하여 문자와 숫자 값을 분리 합니다.
- 그리고 해당 값은 CheckBoardArea()함수를 이용하여 Go board 범위 안에 포함되는지 확인합니다.
- 입력받은 문자열은 다음과 같이 데이터 처리 합니다.
...
Code Block | ||||
---|---|---|---|---|
| ||||
__int64 __fastcall PrintGameBoard(GameInfo *gameInfo, char a2)
{
char v2; // al@6
signed int row; // [rsp+20h] [rbp-10h]@1
signed int col; // [rsp+24h] [rbp-Ch]@5
__int64 v6; // [rsp+28h] [rbp-8h]@1
v6 = *MK_FP(__FS__, 40LL);
puts(" ABCDEFGHIJKLMNOPQRS");
for ( row = 0; row <= 18; ++row )
{
if ( 19 - row <= 9 )
putchar(' ');
else
putchar('1');
putchar((19 - row) % 10 + 48);
putchar(' ');
for ( col = 0; col <= 18; ++col )
{
v2 = GetMarkForBoard(gameInfo, row, col);
putchar(v2);
}
puts(&byte_404E87);
}
if ( a2 )
fprintf(
stdout,
"piece %c play at %c%d\n",
LODWORD(gameInfo->colNumber),
(unsigned int)(HIDWORD(gameInfo->rowNumber) + 'A'),
(unsigned int)(19 - LODWORD(gameInfo->rowNumber)));
fprintf(
stdout,
"Time remain: %c: %6.2lf, %c: %6.2lf\n",
'O',
'X',
*(double *)&gameInfo->player,
gameInfo->playTimeForCom);
puts(&byte_404E87);
return *MK_FP(__FS__, 40LL) ^ v6;
} |
struct GameInfo
- 다음은 해당 프로그램에서 사용하는 Struct 입니다.
- 해당 Struct는 게임 플레이 정보를 기록하는 Struct입니다.
board[12] 변수에는 Go game board 내용을 저장합니다.
rowNumber는 row의 좌표값을 저장합니다.
- colNumber는 Col의 좌표값을 저장합니다.
- Player는 player의 Mark를 저장합니다.(O, X)
playTimeForCom는 컴퓨터의 Play 시간을 저장합니다.
- playTimeForHum는 사람의 Play 시간을 저장합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
struct GameInfo { _QWORD board[12]; _DWORD rowNumber; _DWORD colNumber; _DWORD player; _DWORD tmp; double playTimeForCom; double playTimeForHum; }; at %c%d\n", LODWORD(gameInfo->colNumber), (unsigned int)(HIDWORD(gameInfo->rowNumber) + 'A'), (unsigned int)(19 - LODWORD(gameInfo->rowNumber))); fprintf( stdout, "Time remain: %c: %6.2lf, %c: %6.2lf\n", 'O', 'X', *(double *)&gameInfo->player, gameInfo->playTimeForCom); puts(&byte_404E87); return *MK_FP(__FS__, 40LL) ^ v6; } |
ResetGameinfo - 0x04010AE
...
Code Block | ||||
---|---|---|---|---|
| ||||
__int64 __fastcall ResetGameinfo(GameInfo *gameInfo) { GameInfo *tmp; // ST08_8@1 __int64 canary; // ST18_8@1 tmp = gameInfo; canary = *MK_FP(__FS__, 40LL); memset(gameInfo, 0, 0x60uLL); gameInfo->colNumber = -1; tmp->rowNumber = tmp->colNumber; LODWORD(gameInfo->player) = 0; *(_QWORD *)&gameInfo->playTimeForHum = 4640537203540230144LL; tmp->playTimeForCom = tmp->playTimeForHum; return *MK_FP(__FS__, 40LL) ^ canary; } |
SetMarkForBoard - 0x04025B2
- 해당 함수는 다음과 같은 기능을 합니다.
GetMarkForBoard() 함수를 이용하여 유저가 입력한 좌표의 값이 '.' 문자와 같은지 확인합니다.
- playerInfo의 값을 이용하여 board에 표시할 mark 문자를 설정합니다.
- 그리고 사용자로 부터 입력받은 값이 정상적인 값인지 확인합니다.
- 마지막으로 그 값이 정상적인 값이라면 다음과 같은 처리를 진행합니다.
- "operator new(0x80uLL)" 코드에 의해 Heap 영역을 할당합니다.
- 할당된 Heap 메모리의 크기는 0x90 byte 입니다.
- gameInfo에 보관된 값을 할당된 Heap 영역에 저장합니다.
- 할당된 Heap 영역의 주소 값을 history[]변수에 저장합니다.
- "operator new(0x80uLL)" 코드에 의해 Heap 영역을 할당합니다.
...