write-up for dont_panic
- Event: hxp CTF 2017
- Category: RE
- Points: 100
- Solves: 19
The goal of the task is to reverse engineer given binary and find a valid flag. The binary can be downloaded here
a@x:~/Desktop/dont_panic$ file main_strip
main_strip: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
a@x:~/Desktop/dont_panic$ ./main_strip
usage: ./main_strip flag
a@x:~/Desktop/dont_panic$ ./main_strip aaaaa
Nope.
Reverse engineering the program
By inspecting strings of the binary it can be noted that the binary has been written in Go
language.
Googling some of the strings was enough to discover that.
A .gopclntab
section can be another indicator.
Programs compiled in Go are big and linked statically with go library. The first stage of our task is to find main function.
The binary is stripped so things are more complicated, but hopefully not much.
Function names in programs compiled in Go are additionally saved in .gopclntab
section. It means that function names can be recovered.
At this point I encourage you to read the entire article about how to recover stripped function names. It can be found here.
We can grab this script and paste it to the idapython window in IDA Pro
Now, when all functions have their appropriate names, we can navigate to main_main
(which in reality is main.main
, but IDA cannot into dots in function names) function - this is our main.
We can spot the messages which are shown when typing bad or good flag:
We can find out that the length of our flag
is greater or equal to 42
:
Below I have commented out the most interesting part of the code:
This can be represented as the following C code:
for (int i=0; i<length(provided_flag); i++)
{
if (main_mapanic(provided_flag[i]) != constant_binary_blob[i])
{
bad_boy();
exit();
}
goodboy();
}
A fancy way to solve the task
The standard way of solving this would be to dig deeper and reverse the main_mapanic
function.
Instead of this, we can do it in some unusual way.
One can notice that, the longer the password is, the more instructions inside of main will be executed.
We can find out a way to count these instructions and then brute-force byte-by-byte the flag.
We can do this by instrumenting the binary. I used Intel’s Pin tool for this.
Counting executed instructions with Pin
Pin comes with a set of example modules that can be simply used.
After downloading Pin to folder /home/a/pintool
, the example modules can be found here:
a@x:~/pintool/source/tools/ManualExamples$ ls
buffer_linux.cpp fibonacci.cpp inscount0.cpp itrace.cpp pinatrace.cpp stack-debugger-tutorial.vcxproj
buffer_windows.cpp follow_child_app1.cpp inscount0_orig.cpp little_malloc.c pin.log stack-debugger-tutorial.vcxproj.filters
countreps.cpp follow_child_app2.cpp inscount1.cpp makefile proccount.cpp statica.cpp
detach.cpp follow_child_tool.cpp inscount2.cpp makefile.rules replacesigprobed.cpp staticcount.cpp
divide_by_zero_unix.c fork_app.cpp inscount_tls.cpp malloc_mt.cpp safecopy.cpp strace.cpp
divide_by_zero_win.c fork_jit_tool.cpp invocation.cpp malloctrace.cpp stack-debugger.cpp w_malloctrace.cpp
emudiv.cpp imageload.cpp isampling.cpp nonstatica.cpp stack-debugger-tutorial.sln
There are several modules to count executed instructions. Their names start with inscount
.
We can compile them with command make all
. After this, the compiled modules can be found in a folder named obj-intel64
.
Then, we can use Pin to count our instructions:
a@x:~/Desktop/dont_panic$ /home/a/pintool/pin -t /home/a/pintool/source/tools/ManualExamples/obj-intel64/inscount0.so -- /home/a/Desktop/dont_panic/main_strip hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
Nope.
a@x:~/Desktop/dont_panic$ cat inscount.out
Count 706114
a@x:~/Desktop/dont_panic$ /home/a/pintool/pin -t /home/a/pintool/source/tools/ManualExamples/obj-intel64/inscount0.so -- /home/a/Desktop/dont_panic/main_strip hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
Nope.
a@x:~/Desktop/dont_panic$ cat inscount.out
Count 727167
Oh no! It looks like the binary is nondeterministic.
Hopefully the nondeterminism comes from library functions, not from the main()
itself.
The solution is simple.
We can modify our inscount0.cpp
module to count only instructions inside of main()
.
Or simpler.
It is enough to count only the instructions at address 0x47B96E
.
.text:000000000047B96E cmp al, cl ; cmp mapanic(provided_flag[i]), constant_binary_blob[i]
Now, I encourage you to see this presentation about Pin. It teaches how to write modules.
We can modify original script a bit. Here is a modified version.
The differences are following:
VOID docount() { icount++; }
has been changed to:
VOID docount(void *ip)
{
if ((long long int)ip == 0x000000000047B96E)
icount++;
}
and:
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
has been changed to:
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, IARG_END);
Let’s check it:
a@x:~/Desktop/dont_panic$ /home/a/pintool/pin -t /home/a/pintool/source/tools/ManualExamples/obj-intel64/inscount0.so -- /home/a/Desktop/dont_panic/main_strip hxp{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
Nope.
a@x:~/Desktop/dont_panic$ cat inscount.out
Count 5
a@x:~/Desktop/dont_panic$ /home/a/pintool/pin -t /home/a/pintool/source/tools/ManualExamples/obj-intel64/inscount0.so -- /home/a/Desktop/dont_panic/main_strip hxpaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
Nope.
a@x:~/Desktop/dont_panic$ cat inscount.out
Count 4
It looks like it works.
Now, we need to wrap this in a nice python script:
import os
def run(msg):
os.system("/home/a/pintool/pin -t /home/a/pintool/source/tools/ManualExamples/obj-intel64/inscount0.so -- /home/a/Desktop/dont_panic/main_strip \""+msg+"\"")
return int(read("inscount.out").split(" ")[1])
def read(fname):
with open(fname) as f:
return f.read()
charset = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_{}123456780'"
l = []
flag = ""
counter = 0
while len(flag) != 42:
l = []
for a in charset:
count = run((flag + a).ljust(42, "*"))
l.append((count, a))
l = sorted(l, reverse=True)
best=l[0][1] # choose the letter with the biggest number of executed instructions
flag=flag + best
print flag
The script took around 1 hour
to run on my laptop and gave me the flag: hxp{k3eP_C4lM_AnD_D0n't_P4n1c__G0_i5_S4F3}