This was a 80 point challenge from AngstromCTF that I competed in with the ENUSEC>_ gang <33 in 2018.
Disclaimer and prerequisites: I’m quite new to RE and I have noticed a severe lack of beginner write-ups that gently introduce you to the topic and actually explain the thought process. So here is mine. It is probably not the best way to do this challenge – I believe it can still help you beginners that have never looked at RE before ! You will need to be able to run an ELF (if you aren’t running Linux then you should probably look at getting a VM with it) and download IDA. I don’t think you need any more knowledge! Please let me know if you think some more explanations are necessary.
root@kali# file rev3_32 root@kali# ELF 32-bit LSB executable
We can run the file and see what it does.
I loaded the file into IDA and took look at the structure. We can immediately see that we have some kind of checks going on and various end outputs before the program finishes.
I’ll take a few moments here to explain what all these cryptic eax, ebp, mov, call things mean, or at least the ones we will be dealing with:
mov eax, [ebp+var_2]
lets say [ebp+var_2] contains the number “8”. The instruction above, puts 8 into the eax register. eax now contains 8
imul eax, ebx
imul means multiply, it will put the product of eax * ebx into eax
cmp eax, 100
cmp compares the contents of eax and 100, depending on if they are the same, which one is smaller etc, the conditional jumps that often come after these statements will go one way or another.
jz is a arithmetic jump. It means “jump if zero”. It will usually come after operations such as
sub. In IDA you can see that these jumps usually come at the end of a box – that means that depending on the result of the previous operations, the code will take a certain path.
We can also see that there are two levels, because we can see the strings that are printed in various steps.
Let’s focus on level 1 first.
We see _printf called (this prints the first prompt for the level 1 number) and then the program must have some way of getting our input. It’s easy enough to conclude that this is done when ___isoc99_scanf is called (scanf is the important bit here. I assume the __isoc99 part is due to compiling with C89 gcc standards, but it doesn’t really matter anyway, don’t worry about this bit for now). So where does the input go? Where is it compared?
call ___isoc99_scanf add esp, 10h mov eax, [ebp+var_1C] cmp eax, 11D7h jz ...... In this bit of code we can see that we put [ebp_var1C] into eax and then compare eax to 11D7h (that’s in hex, we can easily convert it to decimal by right clicking on the value in IDA)
Bingo, level 1 solved. Let’s run the program with the new info, just to check.
So let’s try and do level 2. In retrospect, I should have approached it from the bottom ie. focus on the last function that gets compared before the “success message” rather than trying to go through all the steps, but you live and you learn. I’ll describe my thought process, because it makes this article longer and prospective employers will be more impressed (??)
Due to the fact that the prompt asks for 2 2-digit numbers, the scan_f statements and the logic shown above we can assume these variables hold what we need.
So we will focus on : [ebp+var_18] & [ebp+var_14]
The structure of L2 looked like there was a series of steps checking two variables.
It’s a good idea to take a look at the different possible outcomes – if the supplied numbers fulfil the criteria in the logic shown below, the outcome can either be “Sorry, your guess was incorrect…” or “Congrats…” and the flag. In the boxes below we can see the variables we are concerned with being compared to certain numbers. So we can check out the logic, by running our binary with the numbers supplied: 9, 99 or 99 and 9.
Running the numbers with “99 9” and “9 99” seems to take us to the “Numbers don’t meet specs” – of course, the program asks us for 2 two digit numbers. This means that most of the logic we’ve just looked at means that it’s comparing our variables to these numbers and jumping to the answer if they AREN’T the same. (Of course the better way to do this is to look into what jle, jg, jnz etc jumps actually do. The comment from user johnfound on this stackoverflow post is in my bookmarks if I need to refresh my knowledge. A trial and error method is also ok for simple challenges like this one.)
The code puts our variables into edx and eax:
mov edx, [ebp+var_18] mov eax, [ebp+var_14]
then multiplies them:
imul eax, edx
next, the product is put into [ebp+var_10]
mov [ebp+var_10], eax
and [ebp+var_10] is compared to 0x0D67 which is 3431 in decimal.
cmp [ebp+var_10], 0D67h
Right so the numbers are factors of 3431. Googling that gets us the result: 47, 73.
Yay! Let’s run the binary with our new information
The flag is