This is a classic C++ menu challenge that features a UAF and heap overflow with a vtable pointer overwrite. The main heap techniques involve using a free unsorted bin chunk to leak a libc and heap address, as well as some feng shui to place an object in an overflow-able region on the heap. fortenforge, qzqxq, and I combined to reverse the binary and discover the 3 separate vulnerabilities.
When you first create a new Customer or login to this pizza kitchen, you have the option to order, cook, or admire Pizzas. When you order, you can specify Unicode emoji ingredients like 🍍 and 🍅. If you try to order a pizza with 🍍, you are kicked out and banned from ever logging in again.
Wellcom my friende!! It's-a me, Mario! Ready for pizza italiana vera?
------------------- MAIN MENU -------------------
(L)ogin as customer
>> Welcome ghostly
------------------- USER MENU -------------------
(O)rder more pizzas
(C)ook all pizzas
(A)dmire cooked pizzas
When you ask to have your pizzas cooked, the ingredients are strcated together and checked with strstr — if a pizza contains only 🍅, it’s “good”, if it has 🍍, it’s “criminal” — then, a new Pizza object of the correct type is created. You can also provide an explanation, which is malloced onto the heap and stored in the Customer. This explanation is then freed at the end of cook, if the number of good pizzas == total number of pizzas.
In pseudo-code, the Customer struct looks like
+0 name ptr
+8 vector of ordered pizzas
+32 explanation ptr
+40 vector of cooked pizzas
Stage 1: Leak
You want to trick Mario into cooking a pineapple pizza first. We make a pizza with two ingredients, one ending in \xf0\x9f and another starting with \x8d\x8d that, when strcated together, give a Unicode pineapple (\xf0\x9f\x8d\x8d). Now you’ve made Mario upset! Your customer is then stored as a global variable upset_user. You also unlock a menu option to print the explanation field, which would be a UAF if the explanation were freed in cook. We can ensure that the explanation is freed by overflowing the 1-byte counter of total number of pizzas, with (16 bad + 1 good pizza) = 17 total pizzas % 16 = 1 total pizza.
"your friend %s ordered a pizza with %s and I should stay calm?\n",
"'That must be a mistake', you may say. But I asked, and this is what he had to say: %s\n",
return puts("niente scuse");
What are the contents of a freed chunk that we can leak? If the chunk is >fastbin size (>0x80), when it is freed, it will be inserted into the unsorted bin, which is a doubly-linked list of recently freed chunks. The fd and bk pointers will thus be populated with the address of the unsorted bin, which is in the main_arena struct in libc. This gives us our libc leak.
We can also get a heap leak if the unsorted bin has another chunk when we free our explanation. In that case, when the explanation chunk is inserted, the fd pointer will be set to the other free chunk. The explanation chunk looks as follows:
This image from sploitfun is great for understanding the linked list structure of heap bins.
Stage 2: Overflow
If we successfully cook a pineapple pizza, we are also given a new menu option to “explain ourselves” by overwriting our explanation. It reads 300 bytes into the explanation on the heap, even though our explanation was previously malloced with a smaller length (the length of our original explanation). IDA can’t detect/decompile jump tables for some reason, so here’s the assembly of the overflow:
.text:00000000000020F1 lea rdi, aTooBadNoExplan ; "too bad, no explanation is reasonable. "...
.text:00000000000020F8 call puts
.text:00000000000020FD lea rax, global_customer
.text:0000000000002104 mov rax, [rax]
.text:0000000000002107 mov byte ptr [rax+41h], 1
.text:000000000000210B jmp short loc_2154
This overflow allows us to overwrite whatever is after the explanation in the heap. With some grooming, we can try to get a Pizza object placed right after our explanation, so we can overwrite its contents — namely, the C++ vtable pointers for class functions.
0x5555557731D0: 000055555575FC00 0000555555773210 | ..uUUU...> <---- ptr to ingredients
ptr to a "printPizza" function
The goal, of course, is to overwrite the printPizza vtable ptr to a pointer to one_gadget, so that the next time we print the pizza in admire, we get a shell.
How do we ensure that the Pizza is placed right after our Customer’s explanation? If we malloc and free a large explanation, its free space will be used for other objects, and if we are lucky, a Pizza.