lemfao (Upsolved)
Pretty straightforward(???) GOT overwrite
NOTE: Solved after the competition has ended with the help of NeoZap
I didn't get the chance to participate in ARA CTF last year, so I (and my team) registered for this year's. This is one of the binary exploitation problems made by my friend HyggeHalcyon. The general idea of the solution is GOT overwrite, since the binary has no PIE protection and only partial RELRO (meaning the GOT section is writeable). He said this was unintended though XD
Phase 1: Analysis
We're given this C code:
The binary has these protections:
The Exploit Entry
The code has a straightforward flow at first glance. But there is an arbitrary write exploit located inside the loop.
The first scanf
points to the memory address of the where
variable, meaning whatever we input will get written onto that address, and therefore, into the variable. The second scanf
, on the other hand, doesn't point to a memory address, instead it points to the value of whatever is in the variable where
. We can exploit this. If the value of the variable where
is an address, then we can do some arbitrary write to that address. One of the possible exploits with this is GOT overwriting.
We can also note that the first output of the code gives us malloc()
's address. That way, we can easily calculate the libc
base address without having to leak anything.
How Does It Work?
This part needs further verification and improvements.
I'm not gonna explain too much of how PLT and GOT works in C. But on GOT overwrites, we generally modify the GOT entry of a specific function so that it points to another function. If you're not familiar, C functions in dynamically linked binaries (specifically functions in libc
) usually undergo a specific calling flow.
Take this as an example: You're calling puts()
. Because puts()
is a libc
function, the binary "doesn't know" where the address of puts()
is. So instead of calling puts()
, it calls the PLT entry of puts()
instead. From that, it'll find the GOT entry of puts()
and call the actual function itself from the libc
. CMIIW.
But, What Do We Write?
Okay, back to the problem. What do we write? Since there's no win function or any other function that outputs the flag, we need to get the shell by calling system("/bin/sh")
. With that, the approach here is we're gonna try and overwrite a function's GOT to system()
's GOT entry and somehow pass "/bin/sh"
to the first argument of the function. With this approach, when the function is called, it's as if the function is calling system("/bin/sh")
.
But where? Which function? Let's take another look at the code.
We obviously don't want to replace scanf()
's GOT entry. It's our exploit entry. The other functions like printf()
and puts()
doesn't have a variable on its first argument, meaning that we can't replace it or pass "/bin/sh"
to its argument. So there's only one possible entry left:
The fgets
call doesn't really contribute to the code logic, so we can modify it without 'breaking the code'. The function call also has a variable as its first argument, meaning we can pass whatever we want as an argument to the call (if it is NOT an fgets()
). Considering those reasons, this fgets()
call is the perfect target. We can overwrite the fgets()
GOT entry with system()
's, and then pass "/bin/sh"
to lemfao
.
There's just one problem. The fgets()
is called before our exploit entry. Even if we successfully overwrite fgets()
's GOT entry, the function has been called, so it looks like it's gonna be no use. The only way we can call it again is if we can modify the flow of the program so that the particular fgets()
gets called again. Luckily, we actually can.
Using the same principles as the fgets()
GOT overwrite, we can overwrite the GOT entry of the exit(0)
call with the main()
function address. With this, when exit(0)
is called, it is actually calling the main() function again, so the program runs again from the first line along with the modifications we have done to the binary.
Phase 2: Exploit
With the analysis and exploit flow done, we can craft the payload. First of all, let's utilize the free address given to calculate the libc
base address. It is obvious that ASLR is turned on, so we need to "calibrate" the libc
base address in order to be able to correctly use addresses to exploit.
For the next input, we want to pass "/bin/sh"
to lemfao
.
Now, the variable lemfao
contains the string "/bin/sh"
. If we call fgets(lemfao, ..., ...)
after the GOT entry has been overwritten, it will be as if we're calling system("/bin/sh")
.
For the next two inputs, we're going to overwrite fgets()
's GOT. We need to pass the address of fgets()
's GOT entry and overwrite the GOT with system()
's, so when fgets()
is called, it is actually calling system()
. The two inputs are ordered this way by referencing to the section The Exploit Entry . After the first input, the variable where
has fgets()
's GOT entry address, so the second scanf()
writes into that address.
The next two inputs, we're overwriting exit()
's GOT so that it calls the main()
function again.
Now when the for loop ends and exit(0)
gets called, it is actually calling main()
again. Because main()
is called again, the fgets()
is called again, but it is now actually calling system("bin/sh")
because we overwrote the GOT entry.
That should be it. Don't forget to switch to interactive mode.
Full exploit:
Apparently, the flag file has CRLF line endings, so outputting it on the shell doesn't show anything. You might want to turn on pwntools
' debug mode (context.log_level = debug).
Flag
ARA5{LEMFAO_HES_A_CHINESE_HACKERS!!!!!!_sorry_this_chall_dibuat_dadakan_tanpa_persiapan_yang_matang}
EOF
This is one of my earlier attempts of doing a successful GOT overwrite. I'm still learning, and decided that writing this would be a good media to actually learn again. Feedbacks (if possible, lmao) are appreciated. Thank you for reading!
References
Last updated