# Reversing a Go stripped binary

@
20 min. read | 1337 views

You are a brand new member of the Gophers team wohoo! But before you become an official member, you need to check the Gopher Archive and see if you know someone in there so that you can talk to them. One small issue: You weren’t given the password to that archive, just the archive itself. It shouldn’t be a problem for you to find the password to that archive thanks to your Gopher reverse engineering talents!

I chose to write a long blog post about this challenge as it is a Go binary, which isn’t faced a lot in CTFs, and it is also stripped - which makes it harder. With that explained and the description of the challenge above, let’s get right into reversing it

## File Analysis

When first downloading the file the main idea is to check what kind of file we are facing:

As we can see, it is a ‘normal’ linux executable file. One special thing is that we can see it’s a Go build, so the file is a Go binary. We can also see that the binary is stripped, meaning that it will most likely be horrible to read the assembly code and recognize what the binary does.

When executing the binary we have a normal welcome message, with a Gopher ASCII art. It is asking for a password to access the archive. We can try some overflow but that of course does not work, and in the end it’s a reversing challenge; not a pwn challenge

## First look at disassembled program

### Functions

Let’s open up the binary in a disassembler; as expected it’s kind of a mess due to the binary being stripped. We see lots of sub_XXXXXX function names and none of them directly seem to look like the main function of the binary. We however see a _start function but once again, that doesn’t look like it’s the main function.

### Strings

In Go binaries strings are handled differently than C binaries, a pointer to the string is first loaded and then the length we need is moved in a register, as per Go’s source code:

Looking at the strings, when searching for the string that appears when the password entered was incorrect we get the following:

Our string is inside that one, but it’s definitely not used only at the place where we print the error message. Looking at the cross references of that string:

Code references, where it is used, of the long string

We see that it’s used at only 4 places. We can now use GDB to place a breakpoint on each of the calls and see if it reaches the breakpoint before printing the welcome message.

## Debugging

### Finding the main function

Once we reach a breakpoint before the welcome message gets printed, we can simply use ni to get to the next instruction and hold Enter pressed until we see the welcome message with user prompt, here we must have called the main function. And indeed, before printing the welome message and asking for user input we had two important instructions:

We make a call to $rax which has the value of the address of the sub_489920 function. With that information, we’ve found the main function! ## Disassembling the main function ### Understanding the logic Once we’ve found the main function we need to understand how the password checking system works. Now we can go inside the main function (at 0x489920) and look at the assembly code there. We first see lots of calls being made one after the other to always the same function. The graph view confirms that there are no checks being made and the functions are just called with no specific behavior. We have a Gopher ASCII art printed when running the file, we can safely consider these calls as being responsible for printing this text. So we can rename the function sub_484100 to print and not look at its content, as it’s not what we care about. End of the main function in graph view Once we come at the end of that list of calls, we see two calls followed by a condition. If the condition succeeded we go and print a single time. If the condition failed we go and print twice. So out of the error message we can say that left is the error message and right the success message. Now we just have to look at the functions sub_44afc0 and sub_48a3c0 to see what they do. One, or maybe both, will be the password check. ## Debugging the two unknown functions ### Second unknown function Let’s put a breakpoint on the first function, sub_44afc0. We don’t hit it before having to put user input, so it’s not responsible of asking for user input. But let’s continue the debugging and use ni to get to the next function call. After calling the function we can look at the content of $al to see if the condition will pass or fail.

So the condition will succeed (0x0 & 0x0 = 0x0) and we will jump to 0x489df1. We can see that the function sub_48a3c0 will check if the password is valid, so let’s rename it to isPasswordValid.

### First unknown function

Before jumping in the isPasswordValid function we need to find where the user input is asked, just to gain more debugging experience

## Debugging first string validation function

### Disassembling the rough logic

The first function, sub_48a0e0, is responsible for validating the first part of the password, we can rename it. Looking at it with the disassembler we don’t get a lot of information because there are lots of calls being made and at the end a check with a 0x28 long string - "df4c2d865b38db152e7f6bb4ad2f325de9570185".

I renamed the function sub_402940 to compare as it’s pretty obvious by looking at the disassembled code of it. Now let’s jump into a debugging session to see what this function does.

### String getting lowered

When breaking at 0x48a0e0 and going one instruction after the other, we can see that after calling the function sub_468fc0 our first part of the string got lowered:

So we can now rename this function, we don’t need to dig deep into how that function works.

### String getting reversed

After continuing we see nothing really big going on until we face a call at 0x48a1a9 on the function sub_44b240 that will pass the "abcdef" string as an argument.

Call to an unknown function with 'abcdef' as argument

Looking at the results after the call, we see in $rax register "fedcban:/bin:/snap/bin:/". Do you see something special? Yes! We can see that our abcdef string got reversed to fedcba - remember how strings are handled. Another function to rename :D ### Loop over characters in string If we continue with the next instructions we see we stumble into a loop, it can also be seen in the graph view: Loop with unknown purpose yet Looking at it closer we see that we first give a value to the $edx register to be 0x1ca3 (7331). After that we jump to 0x48a1d2 and compare if $rbx is equals to $rcx if it is, then we jump to 0x48a213 and exit the loop. Otherwise we continue and load into the $rdi register $rcx+0x1, this is a counter. Considering $rbx contains the length of the string, we can say this loop goes over every character of the string. Now if we look at what exactly the loop does, we see the following assembly code: Here is the logic explained when using ABCDEF-GHIJKL as password and i being 1 which means we are iterating over e (remember, it’s reversed): • $esi contains the hex value of the current character and it is being moved to $rsi ($rsi : 0x65)
• We multiply $rsi with $rcx which contains the current value of i (0x65*0x1 is stored in $rcx) • We add to $rsi the result of the multiplication (0x65+0x65 is stored in $rsi) • We add to $rdx the value of $rsi (0x1d09+0xca is stored in $rdx)
• This resulting value is then stored in $rsp+0x28 • We check if $rsp+0x28 is equals to 0x256c more below after calling another unknown function

So we can resume this loop as being like the following:

### Hashing function

When two functions are called one after the other, the return value of the first function is passed as an argument to the second automatically, you will see this in the incoming steps.

As previously said, there is another unknown function going on (sub_489e60). Right after calling it, it is being checked if the length of the resulting string is 0x28 which is a 40, the only hashing algorithm I know that has a resulting length of 40 is SHA1. So let’s try to hash fedcba with SHA1.

The string coming out of the function is "82639466494baa873844ae6cdc593cd54b5c054e" and our input SHA1 hashed is "122fe41b518797f9474d5f6f4665e411c449512c". That doesn’t look like it’s the same, maybe we should go deeper in the function, because Google confirmed SHA1 has a length of 40 characters. When putting a breakpoint on a function call we can use si to jump in the function itself. We enter a loop once again, this time a new string is getting generated by the loop:

If we continue with the loop we see a new function called with string above as argument. Looking at the generated string, it’s the SHA1 value above starting with 826. So what is happening during the first part? The resulting string has a length of 32 characters, this is MD5. So basically what is happening is result = SHA1(MD5(string)). We can confirm it easily by doing it ourselves and doing a SHA1 hash of "fedcba" and then make a MD5 hash of the resulting SHA1 hash.

Phew! That’s it for this hashing function, we can rename it to something like md5sha1.

### Comparison

At the end we check if the resulting string is equals to the value in \$rbx and take 0x28 characters of that string.

So we compare if MD5(SHA1(string)) is equals to "df4c2d865b38db152e7f6bb4ad2f325de9570185".

Finally! The first check is reverse engineered.

## Debugging second string validation function

### Disassembling the logic

The second check function is sub_48a2a0, so let’s rename it.

Looking at it we have some similar pattern to the previous one:

Phew! We have basically everything needed to already finish the reversing of this function.
It does everything the first function does, just without the loop!

## Generating the flag

We are now finished with reversing the functions and we can start generating the flag!

### First string

The first string validity in TL;DR is:

• Reverse the string
• Add the hex values of each character to a counter
• That counter, add the hex values of each character multiplied by the current index of the counter
• If the counter is equals 9580 the first check is valid
• If the reversed string md5 hashed and then sha1 hashed is equals to ?"df4c2d865b38db152e7f6bb4ad2f325de9570185" the second check is valid
• If both checks are valid, the first string is valid

### Second string

The second string validity in TL;DR is:

• Reverse the string
• If the reversed string md5 hashed and then sha1 hashed is equals to "a2ce7d4c220df20b186f5458d9449a56e0e36149" the check is valid
• If the check is valid, the second string is valid

### solver.py

The entire code for the solver would be the following:

I could’ve used a wordlist in the first check but I decided to two different ways of solving it, even though it’s really slow. I also tried with itertools.permutations and using multiprocessing but it wasn’t really faster at getting the permutations. If you want to try yourself you can edit the first check by downloading, for example, a wordlist of English words and make a wordlist bruteforce just like the second check - that’s what I did to actually get the string.

## Conclusion

If you read the post in its entirety until here, you are amazing!

It was an overall awesome challenge and I’ve definitely learned a lot, not only because it was stripped but also because it was a Go binary and not a usual C binary. Took me lots of time to make this writeup and solve the challenge but I am happy about it :) Hopefully you enjoyed this challenge and if you want to try it yourself you can download the binary and have a look at it!

~ Krypton 🐺