I’ve had the good fortune to be a part of a group of people working in security on Slack. As a contribution to the community I have started writing some challenges based on reverse engineering concepts I have been picking up. I’m going to write up the solutions to some of these challenges here and hopefully they will be useful to others. I will post the challenges on my Github page as well so you can download them and play around with them as well. I would recommend working with the binary before reading the walkthrough.
Don’t Lose the Forest for the Trees:
As I was reading through Practical Malware Analysis one of the things that really stood out to me was the idea that you don’t need to understand everything to understand what’s going on. It’s with that in mind that I made this challenge.
The point of the challenge is to get the flag to print out without the program taking input from the user. We need to find out how the control flow of the program works. The best way to do that is with a control flow graph. This is a screenshot of Binary Ninja displaying the control flow graph of the binary.
The first thing that jumps out to me is that we have a branch, one direction leads to the flag the other to a “Try Harder” message. The branch is controlled by a cmp and a je instruction pair. We are comparing a value to zero and then if it equals zero we are jumping to the flag. That’s not too difficult to understand. What gets a bit confusing to follow is what exactly we are comparing.
Looking at code that I’m trying to follow I always ask myself what I need to be paying attention to. In this case we have a value that’s being compared and we want to figure out what that value comes from. If we skim over all the instructions moving backwards towards the top of the main function we see a call to rand.
There are two ways to get data into a program, include that data internally in the source or prompt that data from external sources. There’s no call in this program to read any data from an external source other than standard libraries. That says to me that this program is comparing something internally.
Reading the man page for rand() in C we have that the function returns a number between 0 and RAND_MAX. The key word in that statement is between. So the number rand() returns will always be greater than 0 unless there is some added functionality in the program. I wouldn’t discount that possibility but there are no indications of negating anything in the slew of instructions between the call to rand() and cmp.
Take a Guess:
We have a value that’s always greater than 0 in generated by the program and we have a cmp instruction that says if a value is equal to zero display the flag. There are a few ways we can approach this:
- We change the value of the comparison. We could always set 0x0 to another number, but it’s a random number generator and we don’t know the exact implementation of the value. There are lots of things you can do to a random number before a comparison and there are several instructions between these two operations.
- We can change the instruction. This is the solution that was easiest to me. In Binary Ninja, and several other tools, you right click the instruction and change it to ja, jump above, which will execute the jump for any number larger than 0.
- You can also do this directly in a hex editor. Find out where the opcode for je is in the hex editor and find an opcode the same size to replace that instruction with.
Once we do that we have the following output in Binary Ninja:
Now if we save the modified binary and run it the flag prints out and we’re done. We didn’t need to know what happened in the intermediate instructions between call rand and cmp.
It Was an Educated Guess:
We went off an educated guess to solve this problem. It was quite possible that there was functionality we were missing in the cursory glance. In this case the worst that would have happened is we get a message saying to try harder. Then we would have had to start going through the intermediate instructions and figuring out more functionality.
This was a simple challenge intended to be more mental than technical. I find messing with instructions in binaries really fun so I enjoy that part too. But I am one of those people that will get lost in picking apart each instruction trying to get everything out of the binary. It’s not always necessary and the point is to understand just enough to get the flag in this case.
As a last point notice that the second branch turned into nop instructions. So we destroyed partial functionality of the binary by altering it. I always save my modified binaries as a different file so I still have the original to work with for this reason.