After registration you were given 300 000 dongs, which you can use to buy comics:
Let’s quickly go through the available menu options
Register : Obviously register a user
Show list comic : Show a list of available comics in the shop
Add comic to your cart : Put a specific quantity of comics into your cart
Customer's management : Show customer info and rename user and give feedback
Order's management : Show cart and remove comics from your cart
Checkout : Buy the comics in your cart
Exit : Calling exit(0)
Going through the complete reversing of the binary would take up too much place, but let’s take a look at the important stuff at least.
Pseudo code for the used structures in the binary
The available comics are stored in a linked list with a pointer COMIC_LIST pointing to the head of the comic list.
We can put up to 6 different comics into our cart, which will effectively put a pointer to the entry in the linked list of the corresponding comic into our cart.
The comic list and the user object will be allocated and initialized on startup, so we have no control over this.
Checking for calls to free show, that objects will only be freed on checkout and on rename customer (though renaming the customer is only allowed once), so let’s take a deeper look at those functions.
So renaming the user would end up in doing some kind of realloc if the new length of the string differs from the initial size. Since malloc is used the allocated chunk won’t be zero’d and we could use this for leaking, if we’d have a freed chunk containing a FD/BK pointer to the heap or libc (main arena). Which we currently don’t have, but let’s keep this in mind.
The real important thing in the comic store is the checkout function, which shall lead us to victory later on:
Multiple important things happen here, which should be noted.
Unlinking comics, that are sold out
When we buyout all available comics, so their quantity falls to 0, the checkout method will unlink the ComicEntry for this comic from the available comic list and free the Comic object and the ComicEntry pointer for it. This will create two freed chunks on the heap (which we can use for leaking and some other mean stuff).
Free and recreate the cart
This is not so obvious from a quick glance at the code, but the recreation of the cart is faulty.
It will free the cart object (creating a freed chunk in the fastbin list)
Sets the pointer for the cart to 0 (so everythings fine?)
Directly allocating the cart object again (which will serve us the just freed chunk without initializing it)
The free on the cart object will zero out the first qword of the cart chunk. The following malloc will just serve us the same chunk we just freed, without initializing it.
Now checking the order list will show us an empty cart, so it seems everythings fine. But this only happens because the first entry of the cart got cleared by free, the following comic entries are still there (they just won’t be shown since it breaks on the first empty entry)!
So, if you buy two different comics, this will create two entries in the cart item list. Checking out will effectively only remove the first entry, letting the rest of the cart intact. If we now buy only one (other) comic it will set the first entry, fixing up the cart list. After this all the previous comics will again be available in the cart.
This is important to understand for the exploitation of this binary:
Having the ComicEntry for Comic 2 showing up again alone, won’t help us much, but combining this with the first observation can be used to create a dangling pointer allowing us to exploit it in a kind of use-after-free.
If we’d now be able to overwrite the dangling ComicEntry with something useful, we could use the unsafe unlinking in checkout to overwrite an arbitrary address.
But well, we’re still at square one, because with 300 000 dongs, we won’t be able to buy 1 000 000 comics to start with. So, let’s keep this in mind also and think about a way to “earn” money, instead of spending it :)
For this, we’ll check the process of buying comics in add_comic_to_cart
This will let us specify a comic and the quantity we want to buy and calculates the amount of money we need for this. If we got enough money, this will happily add the comics to our cart.
Quantity and Total price are stored as __int32. Nuff said?
No? Ok, let’s elaborate on this.
We can specify the quantity of comics as __int32 (4 bytes)
The quantity will then be multiplied by the price of one comic (also __int32)
It will then add the current money of the items already in the cart (also __int32) on top of it and compare it to the money our user owns.
Still not?
The total price will be converted to unsigned int
The maximum value for unsigned int is 0xffffffff (4294967295)
If this value gets bigger, it will wrap back to 0x0 and count upwards again
So, if we buy an amount of comics, so that quantity * comic->PtrComic->Price will get bigger than 4294967295 the total_price will jump back to 0 (but the quantity will not).
We could use this repeatedly to buy out all the comics, but we won’t be able to hit exactly 0 with this. Buuut, we can so totally abuse this to make us so rich, we could buy the whole shop.
We just buy 138548Conan comics, which will only cost us 20704 dong thanks to the integer overflow (what a bargain :)).
But then again, why should we read so many times the same comic. Hey shop owner, could you take back some of those?
How about refunding me the money for 138538 comics?
Ok, that should wrap it up on getting rich quickly, and now it’s shopping time :)
So, the shop owner might now have to take some additional mortgages to get out his huge debt, but we’re not finished with hurting him even more.
With all this money we’ll now mess up his nicely set up comic list.
Remember, ASLR, PIE, FULL RELRO? We’ll need some leaks to get over this, so let’s get started with getting some information out of this poor shop owner (though we’ll be helping him a little to get out of the debts, so it should be fine :P).
Some freed chunks would help in this. There are only two ways of getting a free chunk in this binary (Buying out all comics of one type and renaming our user).
So, we’ll be using both to leak heap and main_arena ptr.
Buying 10000000 of Ninja Rantaro and checking out will result in freeing the ComicEntry and the corresponding Comic object for this.
Renaming the user to A will result in freeing the previous username (Size 0x110) and allocating a new username (Size 0x21, which happens to match the size of the previously freed ComicEntry object).
This will create the new username inside of the previously freed ComicEntry which contains a FD pointer pointing to the Comic object, which we now can leak via the username (heap leak).
But renaming the user also freed that 0x110 chunk, which, since it doesn’t fit into fastbin list, now contains a FD pointer to main_arena. Pretty useful, so we create a big feedback, which will then gets served the previously freed username chunk, so we can leak the main_arena pointer via the feedback.
Checking the customer info after this:
Still, we don’t know PIE but we won’t need it to get this finished.
What can we do now?
Overwriting got entries => FULL RELRO - Not an option
Overwriting malloc_hook => Binary only allocs 0x21 or 0x110 chunks - Not an option
Calling one_gadget => Bleh, none of the constraints can be fulfilled :(
Doing some _IO_file magic => Yay, way to go :)
The attack plan:
Forge a fake _IO_file struct on the heap
Free another comic entry to create a dangling pointer
Overwrite this comic entry and forge FD/BK to overwrite _IO_file_all with our fake file struct in the unsafe unlink in checkout
Exit to trigger _IO_flush_all_lockp => _IO_new_file_overflow
Let’s finish this quickly. Creating the fake _IO_file struct is easy, since we still have two feedbacks left (to be honest, I had to rewrite my exploit at this point, since I used up the available feedbacks for leaking heap and libc. So I needed to optimize the leaking, whilst using as few feedbacks as possible).
Since _IO_write_ptr (1) is bigger than _IO_write_base (0), this will call _IO_new_file_overflow when IO_flush_all_lockp is called (which will happen on exiting the store).
A nice thing here is, that the first argument to _IO_new_file_overflow will be a pointer to the _IO_file struct itself (which thus points to the string “/bin/sh”) as angelboy taught us in Play with FILE structure :)
Since we still own enough money to buy this whole shop twice, getting another free ComicEntry chunk is quickly done with
This will free the Conan comic, while keeping it in the cart list (remember to put it in the second slot, though).
Now we can overwrite the ComicEntry for Conan by giving a small feedback:
Since the checkout method will validate every entry in the comic cart, checking if it has a quantity of 0, we’ll just let it point to the Ninja Rantaro chunk, which had a quantity of 0 (or maybe it was the remaining Conan entry, doesn’t really matter). So checkout will think this comic was just sold out and unlink it, which will then overwrite IO_list_all with the address of our fake forged _IO_file struct on the heap.
Adding another random comic into slot 1 to make our freed chunk appear again in the cart list, and free it via checkout.
Now, all there’s left to do is to exit this derelict shop to trigger the magic :)
This will call exit(0), which then will flush the file pointers from IO_list_all, thus trying to flush our fake file struct on the heap.
Since _IO_write_ptr was set to a higher value than _IO_write_base, this will try to call the _IO_new_file_overflow vtable function from the corresponding jump table, which now points to system. Like already stated, this will pass a pointer to the _IO_file struct itself as the first argument. And since we put /bin/sh there, this will effectively be calling system("/bin/sh") et voilà