well_known
Application Logic, Arbitrary Write, GOT Overwrite
Problem
This challenge is personally one of the most I’ve had in a while. Combining some techniques that I’ve previously learned while also learning new techniques and concepts in the way.
Solution
Analysis
Given a binary chall and libc, the first thing we will do is to check the binary type and its security implementation.
basic analysis summary:
x64 least-bit ELF binary
dynamically linked, so it’ll depend on system library
stripped, means function and variable name from the source code is not carried
No RELRO, means that the GOT entry table writeable
Stack Canary, which means there’s additional checks into each function calls if the stack is overflown
NX enabled, means the stack is not executable
PIE enabled, means that the base address of the program is randomized for each running processes
since by default the compiler will apply PARTIAL RELRO to the compiled binary, and indication of No RELRO here is a big hint that we’ll doing some GOT overwrite to solve this challenge
Next, let’s try to decompile the binary, below is the relevant code snippet that ghidra has decompiled and I’ve tidied up and renamed some of the function and variable names.
from this main() section, we get an idea that the program will loop over throughout its execution. Each loop will print out a banner and a menu displaying a function that the user can prompt with an input ranging from 1 to 4.
This menu() function simply prints out the menu and you might notice I have renamed one of the global variable with target which currently holds nothing. This will be relevant to our exploitation as I will explain later on.
In this snippet, if the user prompts the program with the input of 1, it will allocate a new memory and store its address in note_addr global variable. Each time we request (or make a new note), it will allocate a new memory address + 0x100 of its previous memory. We can make up to 16 notes before it hits a limit and can’t allocate anymore memory. Note that there’s a check to the offset we gave so we won’t be able to give negative offsets.
the show_note() function simply prints out the the content of the memory pointed by the note_addr global variable
The edit_note() function allows us to write up to 16 bytes into an offset of our notes (pointed by note_addr). Before allowing us to write, it will also print out 16 bytes of the content of said offset.
The delete_note() function simply is not implemented yet, however it leaks out its address, this will be beneficial for us to bypass the PIE since, even though the base address is randomized, the offset is always the same. Grabbing the offset from ghidra
we can calculate the offset to its base address using the following formula:
base_address = delete_note - offset
we can verify this in gdb-pwndbg:
Using the same technique, we can also calculate the address of note_addr to gain the address of out notes and monitor how it is evolved
note_addr = base_addr + offset
Exploitation
What comes to my mind the first time is to overwrite the GOT entries with a call to system(). However after comparing where the address of our notes starts with the address of the GOT….
we reveal that note starts at 0x00007ffff7fc2000 (using the same address and technique explained above) however the GOT is located around at 0x555555557000 which is way back, means that it requires us to give a negative offset which is not possible.
Next thing comes into my mind is to write into a memory both writable and executable however, as the vmmap shows, there’s no such memory section. Next is to overwrite the return pointer in the stack, that too is not possible since we didn’t get any stack base address leak. Next, I try to smash the notes by requesting as many notes until it hits its limit. And after inspecting what note_addr holds after we smash it, I found something very interesting…
Notice now the note_addr doesn't not point to anything. this is because if the allocate_note() function is unable to allocate more memory, it will return NULL, thus emptying the note_addr. This basically gains us arbitrary write to almost anywhere in the memory of the process including to the GOT because of this code in the edit_note() function:
*(char *)(lVar2 + note_addr + offset) = (char)iVar1;
and because note_addr is NULL, we can just supply our offset as the address we want to write to. Now our attack vector is clear, is to replace one of the library within GOT to the address system() with said library function is also calling one argument that we are also able to overwrite to /bin/sh.
We can figure this out by taking any first argument of any function that exists within got and try to calculate the address of its first argument by calculating the sum of the base address and its offset and locate which section it belongs to in the process memory by using vmmap. For example, I will be taking the first argument of the calls to puts() in show_note() function.
and as we can see the memory of said address falls under the section that is not-writable. Eventually we’ll found that the first call to puts() in the menu() function is using a memory that is writable, and that is exactly the global address of target that I briefly mentioned above. which also coincidentally is the first call to puts() after the edit function has returned. This is perfect since it avoids any potential error.
Next, since we want to overwrite the GOT puts with system(), we also need a leak for libc to calculate the libc base address. Thankfully upon calling the edit function and providing it with the address of GOT (calculated with the same technique explained above, base_address + the corresponding GOT function offset) it will also prints out its contents, leaking the libc puts() address.
new we can calculate the libc base address and the address to system with grabbing the offset by the machine libc provided in the challenge using the following:
libc_base = libc_puts - offset
libc_system= libc_base + offset
Great so to recap, our strategy is:
gain base address by leaking the delete_note() function
smash the note memory until it won’t be able to allocate more memory
replace the contents of the target global variable with /bin/sh string
edit the GOT puts, leaking libc puts(), calculate libc base address and libc system()
replace the GOT puts entry with libc system()
gain shell
Flag
flag{wow_u_learn_a_lot}
*Indeed I learned very much a lot, thank you :3
Last updated