intro to reverse engineering
Last meeting Jason taught us about how to reverse engineer executables! Lecture here.
There were 6 tasks:
You can download them by navigating around here or by going to /cybersec/rev/
on the shell server.
For this post I will be using Binja Cloud because that’s what was in the slides. It’s pretty good.
rev 0
Loading it with Binja Cloud,
We can see that the main function branches based on a comparison with what was scanned in (from the call to __isoc99_scanf
, which reads input).
The cmp
instruction compares eax
with 0x401b79, which is 4201337 in decimal.
The jne
jumps to the block that tells us the number is wrong if the cmp
is anything but 0.
So 4201337 is the correct number.
rev 1
This time, the program does a little bit more stuff before the comparison.
First, it XORs our input with 0x539. Then it adds 0x7a69, and finally it multiplies it by 0xd.
Reversing these steps with the desired output number, 0x10fc646, we get the correct input: 1337420.
rev 2
Note the two “%d”s. This means that scanf
is expecting two integer inputs, and so we will give it two.
Looking at the cmp
instructions, it should be clear that we need the two numbers to multiply to 0x170ef161 and differ by 0xaaa2.
I used factordb to factor the number and just added some factors up.
The solution numbers are 12345 and 31337.
rev 3
From the disassembly you can probably just see the answer. But I will analyze it anyway.
First it calls fgets
to read a string in. Then it calls strcspn
. This function is used to find first occurrences in a string. Basically the conventional usage is to remove the first newline character and replace it with a null byte, effectively ending the string there.
Finally, it calls modify_string
on the string.
It looks pretty complicated but that’s just assembly for you. Pro tip you can switch to a “High Level IL” and make Binja try to convert the disassembly into more readable C-like code B).
Basically, the first loop establishes a pointer to the end of the string. It does this by looping through until the end of the string (a null byte) is found.
Then, the second loop does a “meet in the middle” sort of thing. It exchanges characters from the beginning and end of the string, using the pointer from the previous loop.
So modify_string
just reverses a string. Wow.
The solution is josh is a pepega
. <3
rev 4
Similar to the previous one, this one is just an input string modifier.
This time, there seem to be three blocks. The first checks for null bytes (the test al, al
) and breaks out of the loop if a null byte is found.
The second also checks for a null byte, but in the byte following the byte checked by the first loop condition. So you can think of this function as checking two pairs of bytes at a time.
Otherwise, for each pair the final block sets the first byte of the pair to the second byte XOR’d with 3, and then sets the second byte in the pair to the first byte XOR’d with 1.
Now is a good time to say that Jason sucks at coding. First byte is never considered. So multiple inputs work!
rev 5
The first check is just to make sure our input string starts with an f
.
The inner block starts a loop that increments an index counter from 0. It starts looking up values in a magic_table
. It seems we have to get past some comparisons against it, until the terminating 0xff
byte in the table.
Taking a closer look at how the comparisons are made, it looks like for some byte in our flag, the next character lies in <address of magic table> + <byte value>
. We know the first byte is an “f”, or a 0x66. So, the next character is in the 0x66th byte of magic_table
.
You can continue this process to get the whole flag: flag{chAin1NG_FL46s}
rev 6
The disassembly graph is pretty complex so I’m going to leave out the screenshot for this.
This challenge just has a mogrify
function which takes a pointer to 5 elements and returns a pointer to 5 elements.
Inside mogrify
, it uses a magic_values
table. This is just a 5x5 array. For each nth value in the array it was given, the function multiplies it by the sum of the corresponding column in magic_values
. It gives the 5 values back.
mogrify
is called for each 5-long chunk in our input string. It is compared to a 25-element array which is the answer we want.
It doesn’t seem obvious but this is just matrix multiplication. Our string is converted into a matrix and it gets multiplied by the magic_values
matrix. A quick check in Desmos verifies this.
So we have AxB = C
where A is our input string / matrix, B is the magic_values
matrix, and C is the desired matrix.
We want to solve for A
so we right-multiply by (B^-1). Plugging this into Desmos gives us:
We know this is probably right because these are all ASCII values.
Converting into a string: flag{recreating_matrices}
.
See you at the meeting tomorrow! Or maybe not, if it snows.
~ josh
Sorry for the late post, I was busy selling my soul to Eric Gossett.