Cool binary reversing stuff
What?
Okay so uh lemme explain where I’ve been- basically I haven’t posted any blogs lately due to working on some cool projects and some health issues. I’ve been spending a ton of time developing a HTB machine that I hope is going to end up in use on the site (this would be rly epic for me ngl). I’ve also been working on a ton of other projects that I plan on blogging about. But yea, rn as of this date there aren’t any plans for GCSE exams to take place, so all my teachers are oversetting work and I’m having to keep up with that, working out, and my own projects- so it’s pretty hard finding the time to write blogs atm.`
I won’t be hosting the file here (because I don’t think I’m allowed to) but uh contact me on discord @ JamBot3000#5181
and I’m sure we can figure something out :)
Writeup
Okay so, let’s outline some of the basic binexp stuff we want to do, sorry if this is a little basic- I just want it to be possible for someone new to binexp or reversing to be able to follow along:
- Look for any sus calls, anything like a strcmp against a set variable would be a massive giveaway
- Find weird strings, to get an idea of what data the binary could be using
- See what is actually done with our input, so fuzz- and see what happens
- Maybe play with it in a debugger- that way we can go through each individual assembly instruction and see what is done with our input, if anything.
Enumeration
Let’s start off, simply by running the binary- to find out what type of file it is, try the file
command. This will read file headers aswell as other data to determine the correct type. In our case it’s an ELF 64 bit executable.
jambot3000@pop-os:/tmp/bx01$ ls
bx01.zip flag.txt program
jambot3000@pop-os:/tmp/bx01$ chmod +x program
jambot3000@pop-os:/tmp/bx01$ ./program
Welcome to the Challenge & Auth Server
Please enter your challenge: Idk my G ask someone else
Secret is rFpHzJbcI.EnrFk..qnseKrsphyJlvCumJnEFoyrnoExfamIj
Your challenge was Idk my G ask someone else
The server challenge was rFpHzJbcI.EnrFk..qnseKrsphyJlvCumJnEFoyrnoExfamIj
Sorry, your challenge did not match the server
jambot3000@pop-os:/tmp/bx01$
Okay, so we made the file executable, and ran it with some input. We can also provide input by running it like this:
jambot3000@pop-os:/tmp/bx01$ echo "Idk my G ask someone else" | ./program
Welcome to the Challenge & Auth Server
Please enter your challenge: Secret is IaEoJx.c.BjnalIxBzhBE.KGk.sdqDwcDmiqBhksAlFBwAkkd
Your challenge was test
The server challenge was IaEoJx.c.BjnalIxBzhBE.KGk.sdqDwcDmiqBhksAlFBwAkkd
Insufficient challenge length.
Or we can make it read from a file using ./program < file
.
Anyways- let’s try understand what’s happening here. So, we gave the same input both times- but for whatever reason we recieved different output? That’s strange. So there must be some changing variable right?
Analysis
Let’s try see if it’s making any system calls. Normally I would use ltrace- to incercept d-lib function calls (basically seeing common functions being used. For example strcmp might show us our input is being compared against something). But we can’t actually do that here because this binary sees to be statically linked(ltrace only works for dynamically linked binaries afaik). But we don’t need to worry about what this means for now- let’s just run strace and look at some system calls:
jambot3000@pop-os:/tmp/bx01$ strace ./program
execve("./program", ["./program"], 0x7ffd32cef6e0 /* 58 vars */) = 0
brk(NULL) = 0x2438000
brk(0x24391c0) = 0x24391c0
arch_prctl(ARCH_SET_FS, 0x2438880) = 0
uname({sysname="Linux", nodename="pop-os", ...}) = 0
readlink("/proc/self/exe", "/tmp/bx01/program", 4096) = 17
brk(0x245a1c0) = 0x245a1c0
brk(0x245b000) = 0x245b000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "Welcome to the Challenge & Auth "..., 39Welcome to the Challenge & Auth Server
) = 39
write(1, "Please enter your challenge: ", 29Please enter your challenge: ) = 29
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
read(0, THIS IS MY INPUT HELLO IT'S ME
"THIS IS MY INPUT HELLO IT'S ME\n", 1024) = 31
time(NULL) = 1610022953 (2021-01-07T12:35:53+0000)
write(1, "Secret is HEfpDBj.regIFAfDnrzmHH"..., 60Secret is HEfpDBj.regIFAfDnrzmHHHaJvAwv.CgBHniuo. EseqjFnEKE
) = 60
write(1, "Your challenge was THIS IS MY IN"..., 50Your challenge was THIS IS MY INPUT HELLO IT'S ME
) = 50
write(1, "\n", 1
) = 1
write(1, "The server challenge was HEfpDBj"..., 75The server challenge was HEfpDBj. regIFAfDnrzmHHHaJvAwv.CgBHniuo.EseqjFnEKE
) = 75
write(1, "Sorry, your challenge did not ma"..., 47Sorry, your challenge did not match the server
) = 47
exit_group(0) = ?
+++ exited with 0 +++
Okay so yea- this seems a bit scary, but all we can still see sort of what it’s doing, even if we are just skimming over it. We can see there is a system “write” call to stdout for our terminal output, and we can see after it read’es out input that there is a system call for the time. This time is taken from the CPU, and is very precise- it will be changing constantly.
So, knowing this- we can make a mental note that maybe time is being used in some way here?
There are a few other ways we probably could have figured this out- maybe using a debugger such as GDB
or Radare2
Anyway- now at this point before we try some in depth, low level debugging, let’s just try fuzzing the binary - giving it a ton of different input to see how it reacts. You could do this a number of ways, the easiest would probably be some basic python or bash script- so let’s try that. There probably are some tools for this- but we’ll only be doing some basic testing.
Fuzzing is just giving a ton of random, unexpected inputs, in order to try to find bugs- maybe there are characters it doesnt expect. For example maybe an application blocks semicolons because it passes something into a system command. That would let us know that this is a possibility, so we could try using 2 & signs instead. This isn’t relevant here, but it’s always good to try.
For now let’s just try seeing if we can cause a buffer overflow by giving too much input.
import os
for i in range(300):
os.system(f"echo {'a' * i}|./program")
print("\n", i, "\n")
Here is a super basic python script that simply gives different amounts of input- and prints the amount it gave. Could definitely make something better, but this is just something to quickly test if there is a buffer overflow, and if so how many characters are needed to cause it.
So, after running this we can see that there is no buffer overflow- probably because the variable our input is saved to has a limit of 49 characters as shown. If anymore than this are given- it is not reflected in the output.
But.. ignoring this- something much more interesting is here. There are some repeats in the output?? Ontop of this, they are repeats despite having different inputs. Strange- maybe our input doesnt matter?
Anyway, repeat outputs are great, because supposedly that’s what the program wants, so if we can generate a correct output again somehow- then that’s the challenge done. So let’s try to trigger this again? Because every so often the output changes to be the same thing- I would assume that it is changing every second. Let’s test this by just spamming input lol.
jambot3000@pop-os:/tmp/bx01$ echo "lol" | ./program
Welcome to the Challenge & Auth Server
Please enter your challenge: Secret is FnIEJithjeewAr..tEfawcb.ljCIFscxG.ft.qA.vxu.atCtb
Your challenge was lol
The server challenge was FnIEJithjeewAr..tEfawcb.ljCIFscxG.ft.qA.vxu.atCtb
Insufficient challenge length.
jambot3000@pop-os:/tmp/bx01$ echo "lol" | ./program
Welcome to the Challenge & Auth Server
Please enter your challenge: Secret is FnIEJithjeewAr..tEfawcb.ljCIFscxG.ft.qA.vxu.atCtb
Your challenge was lol
The server challenge was FnIEJithjeewAr..tEfawcb.ljCIFscxG.ft.qA.vxu.atCtb
Insufficient challenge length.
Making an exploit
After this extremely advanced reconnaissance technique we can tell that it’s changing every second. The actual amount of time doesnt matter- so long as there is enough time for us to generate the same output twice it’s fine.
So, let’s try automating this process with a python or bash script, personally I prefer to use python scripts , especially in writeups because they are easier to read and modify, both for myself and others. You don’t need to know python to use it- whereas bash is a lot of pretty specific stuff.
So, all we wanna do is:
- Run the binary with a random input
- Save the output to a variable
- Find the secret(weird string binary wants) inside of a variable
- Run the binary again, this time with the correct secret as the input
- ???
- Profit
Cool, so here’s the script that I used:
import subprocess
output = subprocess.getoutput("echo 'Aylmaoooo' | ./program")
output = output.split("server challenge was")
output = output[1].split("\n")[0] #got the secret
print(subprocess.getoutput(f"echo {output} | ./program"))
And the output from running this is…
jambot3000@pop-os:/tmp/bx01$ python3 otherscript.py
Welcome to the Challenge & Auth Server
Please enter your challenge: Secret is If.tdnHGAFeBEqrijCulcwlbwBfhkyreDqxHwjriavCw.fE.A
Your challenge was If.tdnHGAFeBEqrijCulcwlbwBfhkyreDqxHwjriavCw.fE.A
The server challenge was If.tdnHGAFeBEqrijCulcwlbwBfhkyreDqxHwjriavCw.fE.A
THIS IS A PLACEHOLDER FLAG - SUBMIT THE SOLUTION TO THE NETWORK SERVICE TO GET THE REAL FLAG.
Awesome, so we know it works- so if we assume that we aren’t being lied to by the flag- then all we have to do is convert this to work with open tcp port.
We could do this with python sockets, we could make a bash script like the one created by sockmower- or we could just change all instances of ./program
in our current script to nc ip port
and it should work absolutely fine.
There is a lot more that could have been explored with this- for example before I got to the actual solution I spent way too much time trying to debug using R2 and (attempting to) read the assmelby of some random functions- with varying results lol. The binary itself is quite big- in fact if you run strings on it, you will see a ton of random information- I actually still don’t know why a lot of it was in there, maybe to mess with more experienced binexp people (not that I’m one lmao) who are used to immeditaley opening the binary, and then debugging until they understand it enough to craft an exploit. But yeah, I’m not amazing at binexp- in fact it’s probably my worst area of cyber. This challenge was less about in depth, low-level binary exploitation, and more about how to approach a challenge.
Anyway, I hope this writeup has helped :)
-JamBot