[KOR] Wanna Play a Game? - reversing

mntly·2024년 12월 28일
0

CTF

목록 보기
7/9

0xL4ugh CTF의 Wanna Play a Game 문제에서 제공된 바이너리 내부의 각 함수의 동작 과정에 대해 설명하였다.

다행히 strip이 되어 있지 않아 분석에 용이했다.

main 함수

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // [rsp+0h] [rbp-10h]
  __int64 v4; // [rsp+8h] [rbp-8h]

  setup();
  printf("[*] NickName> ");
  if ( read(0, &username, 0x40uLL) == -1 )
  {
    perror("READ ERROR");
    exit(-1);
  }
  while ( 1 )
  {
    menu();
    func = read_int();
    printf("[*] Guess>");
    param = read_int();
    ((void (__fastcall *)(__int64))conv[func - 1])(param);
  }
}

setup 함수로 기본적인 환경 설정을 한 후, 이름을 받아 username에 저장한다. 이후 사용자로부터 입력을 받아 menu에 있는 기능을 수행한다. 이때, menu에 해당하는 기능은 conditional jump로 구현하지 않고, index(func)를 입력 받아 함수 배열(conv)을 참조하여 함수를 호출하는 방식으로 구현되었다.

setup 함수

void setup()
{
  unsigned int v0; // eax

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  signal(14, handler);
  alarm(0x3Cu);
  change_passcode();
  v0 = time(0LL);
  srand(v0);
}

Buffered I/O가 꽉 찬 이후에만 출력 되는 것을 방지하기 위해, buffer를 초기화해준다. 이후 SIGALRM(14)으로 0x3C초의 exploit 제한 시간을 설정한다. 이후, change_passcode 함수를 호출해 /dev/random으로 passcode를 설정한다. 또한, srand(time(NULL))로 현재 시간을 seed로 pseudo random도 설정한다.

change_passcode 함수

int change_passcode()
{
  int fd; // [rsp+Ch] [rbp-4h]

  fd = open("/dev/random", 0);
  if ( fd < 0 )
  {
    perror("OPEN ERROR");
    exit(-1);
  }
  if ( read(fd, &passcode, 8uLL) == -1 )
  {
    perror("READ ERROR");
    exit(-1);
  }
  puts("[*] PASSCODE CHANGED!");
  return close(fd);
}

system 잡음 등으로 생성한 랜덤값이 저장되어 있는 /dev/random 파일을 열어 passcode를 8byte의 랜덤값으로 설정한다.

menu 함수

int menu()
{
  puts("= = = = = CAN YOU BEAT ME! = = = = =");
  puts("[1] Easy");
  return puts("[2] Hard");
}

passcode leak?의 난이도 (Easy, Hard)에 대한 메뉴를 출력한다.

read_int 함수

__int64 read_int()
{
  __int64 buf[6]; // [rsp+0h] [rbp-30h] BYREF

  buf[5] = __readfsqword(0x28u);
  memset(buf, 0, 32);
  printf("> ");
  if ( read(0, buf, 0x20uLL) == -1 )
  {
    perror("READ ERROR");
    exit(-1);
  }
  return atol((const char *)buf);
}

buf[5] = __readfsqword(0x28u);는 Stack Canary 관련이기에 무시한다.

이 함수는 32byte 크기의 10진 정수를 입력 받아 integer로 변환해서 반환한다.

conv 배열에 있는 함수 - easy

int __fastcall easy(__int64 param)
{
  if ( param == rand() )
    return printf("[+] NICE GUESS!!\n[*] Current Score: %lu\n", score);
  else
    return puts("[-] WRONG GUESS :(");
}

main 함수에서 전달 받은 param이 랜덤값과 동일한지 확인한다. 이때, 이후 출력될 수 있는 score은 전역변수이다.

conv 배열에 있는 함수 - hard

unsigned __int64 __fastcall hard(__int64 param)
{
  int i; // [rsp+14h] [rbp-2Ch]
  char path[8]; // [rsp+2Fh] [rbp-11h] BYREF
  char v4; // [rsp+37h] [rbp-9h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  strcpy(path, "<qz}<`{");
  v4 = 0;
  for ( i = 0; i <= 6; ++i )
    path[i] ^= 0x13u;
  if ( param == passcode )
  {
    puts("[+] WINNNN!");
    execve(path, 0LL, 0LL);
  }
  else
  {
    puts("[-] YOU ARE NOT WORTHY FOR A SHELL!");
  }
  change_passcode();
  return v5 - __readfsqword(0x28u);
}

main 함수에서 전달 받은 param/dev/random으로 가져온 랜덤값과 동일한지 확인한다. 동일하다면 shell을 실행하고, 아니라면 passcode를 변경한다.

[그림 1] execve 함수의 인자로 사용되는 path 변수의 값 변화 확인

0개의 댓글

관련 채용 정보