• 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:

o1

We can find out that the length of our flag is greater or equal to 42:

o1

Below I have commented out the most interesting part of the code:

o1

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}

References