Bomb 💣
Table of Contents
1. Bomb
- Class: Malware Analysis and Incident Forencsis
- Topic: Static Analysis
The purpose of this exercise is to defuse the bomb, solving 6 (plus a secret one) puzzles that can be solved only using static analysis. In this page I’ll try to state how I solved each phase, of course, as always, this is not an official solution, nor the best one; I’m open for any correction or consideration on what I’m going to write (or You’re going to read).
One last thing: BE AWARE, SPOILERS
2. Initial steps
2.1. Strings
Before even opening IDA, I used PE Studio to extract some preliminary information that I will use later. The first thing that I checked was the strings section:
There are some interesting strings in here: some format strings maybe used for formatting input or output, the indication of a secret stage and some cryptic strings that sound a little bit strange like: isrveawhobpnutfggiants and Public speaking is very easy.. Also there are a couple of error messages regarding EOF and an too long input line.
2.2. The bomb has been planted.
I executed the binary to see how the program looked like. It is a simple CLI application that present itself with a nice text and then wait for an input.
Let’s try to trigger the “input too long” error:
After those trivial attempts let’s fire up IDA.
3. Prologue: input and company
When opening the executable with IDA, I see the main function that performs some initial steps before even start the real logic of the application itself. In fact the executable checks for the number of command line arguments: if the number of arguments is one the it jumps to the core of the application, else if the number of command line arguments is two it will treat the second as a file name and tries to open it in reading mode to read its content.
At the end of the possible file reading part, the code jumps to the core part of the execution: the six (plus one) stages.
Here I recognized some of the strings that I saw in the strings section of PE Studio, but not all of them. The first interesting function call is the one to sub_401870
; at first sight seems that this function is handling the user input, this hypothesis is enforced by the presence of some error messagges like: “Erorr: premature EOF on stdin” and “Error: input line too long”, with respect to the second we can see that the error messagge is printed at this point of the code:
Now I know that the input has to me at most 79 characters. Going back to the callee function I rename sub_401870
as input_handle
, noticing how it is called at the beginning of each phase. The output of this function is assigned to the Buffer variable.
4. First Act: I’m about to blow up.
The second function call is to sub_401190
; that takes one argument, the above mentioned Buffer. The function itself is quite simple, there is a string literal “Public speaking is very easy.” another function call (to sub_401740
) and a test on the output of the function. If the value returned by the function is different from zero another function (sub_401960
) is called. I inspected this last function first.
4.1. Boom.
sub_401960
is easy to understand, this is the function responsible for the exoplion of the bomb. Nothing more, nothing less. Let’s call this function BOOM
.
4.2. Homemade bread strcmp and strlen
The first function call I saw in the first stage was to sub_401740
. This sub is a little bit more complex that the BOOM
one, but we can easily see that:
- It takes two arguments
- The same function is called twice at the beginning and the return values are compared
- There is a loop
- and just one path lead to a zero output, all the other return 1.
Before entering into the details of what the function that is called at the beginning does I considered the code that is executed is the compare is successful. First of all two local variables are initialized with the content of the two arguments provided here:
Then the loop starts, at each iteration the loop takes the content of the first local variable and checks if its zero (string termination pheraps?) if not it checks if the content of the memory pointed by the two local variables is equal, increase by one the content of the local variables (iterates over a string?) and the loop restarts.
Coming back to the two calls to the same function let’s inspect sub_401700
. This sub is quite simple:
- Takes one argument
- Initializes a local variable with 0, let’s call it
idx
- Initializes a local variable with the content of the argument, let’s call it
input
- deferences the content of
input
- checks if the content is 0, and if not
- increase the value of
idx
by one, and also increase the content ofinput
by one (iterates over a string?).
- increase the value of
- the sub returns
idx
.
I’m quite confindent that this functions returns the number of ASCII characters in a string, so I called it homemade_strlen
. Now everything seems more clear, first the function calls homemade_strlen
on both its argument and compare the respective return values, then the sub compares the two arguments byte by byte and checks if they are equal, if just one byte mismatch or the two argumets have different lenght it returns 1: we have a freshly baked homemade_strcmp
. Back to phase one.
4.3. Phase One solution.
With just a couple of renames and everything seem more clear.
The phase one sub just compares the user input with the string literal “Public speaking is very easy.”. One phase’s defused, five more left.
5. Second Act: Count with me.
I’ll ignore for now all the calls to sub_401990
, and dive in phase 2. The second phase starts with the call to sub_4011B0
. This sub takes the Buffer that contains our input and declares some local variables.
Then a sub_4016B0
is called.
Var_1C
is used to contain the six integers given in inputVar_4
is used as an indexVar_20
, is not initialized anywhere, but is used inside the loop to index the input array.
5.1. Input handling
sub_4016B0
is responsible of the formatting of the content of the Buffer viariable:
and fill an array of six integers
5.2. Inside the Loop
After the input handling phase, the sub checks if the first input is 1, and then it proceeds with a loop. The loop starts from 1 until 6, and inside its body a condition is checked, if the conditionis not verified th ebomb explodes.
It is clear that it checks that the content of the usrinput array respects some kind of property, but there is one dubt still left: what does Var_20
do? Var_20
is recognized as a local variable by IDA, but in reality it was put there by the compiler as an optimization trick. In fact, at each iteration the user_input
array is acessed twice:
- at the current position pointed by the
idx
variable, and - at offset
Var_20
, that is -20h (-32 in decimal). This means that we are accessing the array at positionidx - 1
.
5.3. Phase Two solution.
We have to contstruct the array with respect to that condition, given that the first number should be 1.
6. Third Act: Sultans of Switch.
Phase three begins with the call to sub_401210
, from the beginning we see that an integer, a character and another integer as I see here:
Also the sub performs a check on the return value of the sscanf
: it has to be greater or equal than 3 (why greater or equal and not just equal?). The sub stores the input values inside three local variables:
Var_C
contains the first integerVar_D
contains the characterVar_8
contains the second integer
The fourth local variable Var_1
is used later in the sub.
After the check on the return value of sscanf
there is a switch statement performed on the first integer. Following the jumptable, with the help of IDA is simple to see that there are 7 cases plus a default one, and that for case 3 Var_1
is initialized with character k and Var_8
is compared with 251. After the switch statement the content of Var_D
is compared with the content of Var_1
, if the two variables are equal the phase is solved.
6.1. Phase Three solution.
This phase was quite simple: 3 k 251
can be used as an input to solve it.
7. Fourth Act: fiftyfive rabbits.
The fourth phase starts in sub_401390
, the functions parses Buffer to obtain one integer, and as always, checks that the valure returned by sscanf
is equal to one; then it checks if the input is a non zero positive integer, and then calls sub_401350
with the input as argument.
7.1. Down the rabbit hole
sub_401350
starts with a compare between the argument, and one, if the argument is less or equal than one it just returns one, else if the argument is greater than one the function:
- subtracts one from the argument
- pass the new value to another call of itself, stores the return value in register
esi
, then - subtracts two from the argument and pass the new value to another call of itself, the return value is in
eax
, finally - adds the two obtained values and then returns the just computed sum.
Yes, this sub computes the fibonacci from the second element up two an nth element given in input.
7.2. Phase Four solution.
The return value of sub_401350
is compared with 37h
, 55 in decimal. So our solution is the position of the number 55 in the fibonacci sequence, starting from the second element of the sequence itself.
8. Fifth Act: Encoding on a Star.
sub_4013E0
is responsible for the phase five. the first thing I noted was a call to the afore mentioned homemadestrlen, the first check regards the lenght of the input: it must be a string of six characters. Also the function define four local variables:
- an index:
Var_10
- an array:
Var_C
Var_6
used as an offset to index the last position of the array inVar_C
(compiler optimization)- and an integer that contains the return value of
homemade_strlen
The sub checks if the lenght of the input is six, and then starts a loop that iterates over the input. At each iteration of the loop the code index a memory location that contains an array of characters, using the ASCII value of our input to access it.
Then the constructed string is, after the loop, compared with the string giants via the handmandestrcmp. If they are equal the phase is cleared.
The memory is organized as so:
I changed its view via the Edit menu of IDA to see it like that:
So we need in order indexes: 15, 0, 5, 11, 13, 1.
8.1. Building a giant.
My goal is to find a string such that the result of bitwise AND between the ASCII value of the characters with 15 will produce those indexes in order. But the bitwise AND does nothing more than extracting the last byte and obtaining a number between 0 and 15.
8.2. Phase Five solution.
There are multiple solutions for this phase, I used ?05;=1
as passphrase to solve this phase, but other strings can be used.
9. Sixth Act: Order matters.
The defeat the last phase I have to understand what the sub_401460
does. The sub declares a lot of local variables, for now not all of them seems to have a clear purpose.
Var_3C
is initialized with a pointer to a memory location.Var_8
that is used as an indexVar_20
that contains the user inputVar_40
that is used as a second index
9.1. Input handling
The first thing that the function does is to call sub_4016B0
, this function takes the Buffer that contains the user input and uses sscanf
to fill Var_20
, then checks if the number of integers is greater than six.
9.2. Checking those numbers
After the input handling phase, a loop begins.
The body of this loop looks a little bit intimidating, let’s break it in small parts. Firs of all the loop it’s iterating over the user input, in the first conditional statement checks if the element in the position currently indexed is greater or equal than one, the second conditional statement checks if the element currently indexed is les or equal than six:
So all the input numbers must be integers between 1 and 6. After this preliminary check, an internal loop is started, this time the loop starts from the current index plus one and iterates until the end of the array. At each iteration this inner loop checks that the number pointed by the outer loop’s index is different for all the following numbers. In summary the inner loops checks that all the number in the array are different.
So the first loop of the sixth phase checks that all the six numbers taken in input are integers between one and six and that are all different.
9.3. Cracking the code
After the first loop, a second one begins, let’s uderstand what it does. In this loop a second array is filled with some pointers, the order of the elements of the array is given by the numbers inside the user input array. At each iteration of the loop:
Var_4
is initialized with the value contained inVar_3C
,idx2
is initialized with one, and- if the value of
idx2
is greater then the element currently indexed byidx
in the user input array then:- the value of
Var_4
is inserted in positionidx
of a newly constructed array.
- the value of
- else:
- it adds 8 (moves up a byte) to
Var_4
and increaseidx2
- it adds 8 (moves up a byte) to
So at the end of this first loop we have contructed this array. Let’s inspect the .data section:
Now I know what the addresses point to, going back to the code, I saw that, and the end of the sub_401460
the code checks if the value pointed by the element currenlty considered in the previusly constructed array is greater than the value pointed by the next pointer in the array. Using this information and the values I saw inspecting the .data section It’s possible to deduce that the correct order is:
9.4. Phase Six solution
9.5. What I left behind
When solving the last phase I didn’t analyzed what the second loop considered in Cracking the code part did.