For this lab, we are given a program and its corresponding source code:
If we run checksec, we can see that NX, PIE and full RELRO are enabled.
Because PIE is enabled, the base address of the ELF executable segments is randomized.
If we look at the source code, we can see several vulnerabilities.
In make_note() there is an insecure gets() call being used without a stack canary to protect the saved EIP.
Additionally, in setup_account(), memcpy() is used to write a maximum of 128 bytes from temp into an offset into merchant.desc.
This second vulnerability is the one we will focus on to develop our exploit.
The problem with this vulnerability is that merchant.desc is a 128 byte buffer, so writing 128 bytes starting at an offset within this buffer space allows us to overwrite adjacent values on the stack.
Of particular interest to us, is the merchant.sfunc value which sits right next to merchant.desc and which conviently points to print_listing() which it calls if the user selects option 3 from the main menu.
We can overwrite this value to make it call another function instead.
If we make the program call print_name() instead, the program will print out the merchant.name.
However, if we completely fill the merchant.name up, eliminating any null bytes, and do the same for merchant.desc, this function will also leak out other values on the stack, including a pointer to an ELF executable address (print_name()), and a pointer to a libc address.
This will help us bypass ASLR and give us the ability to perform other sorts of attack like setting up a ROP chain using gadgets from libc or the executable to call system("/bin/sh").
Luckily, we don’t even need to build a ROP chain because using a second memcpy(), we can just replace merchant.sfunc so that it points to the address of system() in libc, and since it uses &merchant as its argument, we can write "/bin/sh" as the merchant.name.
Then, selecting option 3 again from the main menu should call system("/bin/sh") and give us a shell.
One problem we need to overcome is determining what the address of print_name() is so that we can write it to merchant.desc.
Because ASLR and PIE are enabled, this address will be randomized everytime the program is run.
Fortunately, this address isn’t THAT randomized.
We can see that print_name() and print_listing() each ALWAYS have the same lower/least significant 3 nibbles and same upper/most significant 3 nibbles.
Only the middle two nibbles are ever randomized.
This allows us to determine the address of print_name() by performing a partial overwrite of the last 2 bytes and brute forcing a nibble.
For clarity, here is the Merchant struct before the first memcpy():
And here is the Merchant struct afterward:
Notice how we only had to overwrite the lower 2 bytes of merchant.sfunc @ 0xffffd46c.
We hope that this new address is the address of print_name(), although realistically, we will only be correct 1/16th of the time, or on average, around once every 8 tries.
Also notice that the pointer after merchant.sfunc points to an address inside libc.
Then, after the address of system() is leaked, this is what the Merchant struct looks like after the second memcpy():
Observe that we have written "/bin/sh\0" to merchant.name and that we have written the leaked address of system() to merchant.sfunc.
Putting everything together, the following exploit will grant us a shell.