Since my Binary Bomb class is over, I thought I'd post some of my findings. It's basically a spoiler for other people who Google "Binary Bomb". If a few professors (my school or others) request that I take it down, I can do that. You
are supposed to be learning this on your own, after all...
I mentioned earlier that you could simply use "strings" to find the magic phrase in the bomb. The better way is to look at the assembly itself:
Phase_1: About the 4th instruction down, there is the following disassembled code:
8048bd2: 68 54 98 04 08 push $0x8049854
This is the simplest to determine (even without “strings bomb > theStrings.txt”) the value, since all you need to do to see what this is, is to:
x /s 0x8049854
Phase_2: First, we learn that the function
read_six_numbers() is called before any other tests are done, so we step into this function. We notice that this function, before testing anything itself, tries to pull in 6 values from the specified input. This, in reverse order, contains the 6 strings the user entered. Once all are pushed individually into the stack, we notice the following two lines:
8048f9f: 68 bf 9b 04 08 push $0x8049bbf
8048fa7: e8 88 f9 ff ff call 8048934
When the first address (
0x8049bbf) is dereferenced, we see that it is a string of text:
"%d %d %d %d %d %d"
This shows that
sscanf() is being asked to pull the 6 numbers in, and save the results into
%eax.
Once we return to phase_2, the first result of
sscanf() is placed on the top of the stack:
8048c08: 8b 44 9d d4 mov 0xffffffd4(%ebp,%ebx,4),%eax
This could be more easily understood to be:
a = userInput[0];
The next line shows how we wish to test against the future values:
8048c0c: 83 c0 05 add $0x5,%eaxBasically
a += 5. We don’t get too much out of this until we see the next line is comparing this number with our next number:
8048c0f: 39 44 9d d8 cmp %eax,0xffffffd8(%ebp,%ebx,4)
Again, this can be read as:
if (userInput[x] == userInput[x-1] - 5);
So it simply checks that
userInput[1] == userInput[0] + 5
userInput[2] == userInput[1] + 5, etc.
Phase_3: We see immediately that this function pushes three values onto the stack, followed by a function push:
8048c38: 68 be 98 04 08 push $0x80498be
Dereferencing this shows us that the numbers must be in the format of
"%d %c %d". Another
sscanf() gives us:
a = sscanf(userString, "%d %c %d", userInput1);
Our inputs will be an integer, followed with a single character, followed by another integer. This phase is more dynamic than the previous ones, as it contains a
switch()…case pair, and the second and third values we enter will depend on the first integer specified. In some peoples’ bombs, this
switch() statement actually is not a simple 0..7 number structure (some can be -2..4, or 100..108). If this were the case, there would be an additional statement such as “
subl $0x4,%eax” or similar.
This should give us our first guesses as to the different
case’s. Regardless of which one is selected, the next thing seen is along the lines of:
8048c66: b3 7a mov $0x7a,%bl
The
bl is the lower byte of the
%eax register, and the value
$0x7a actually denotes an ASCII value (in this case, the letter ‘z’). The user’s 3rd input is checked next, before the letter is actually validated, so onto the next line:
8048c68: 81 7d f8 f2 00 00 00 cmpl $0xf2,0xfffffff8(%ebp)The user’s 3rd input needs to be
0xf2 (decimal 242), and if so, we are jumped out of the
switch() statement. It is here that the user’s input and the ASCII letter is compared, and if equal, the phase is complete.
Phase_4: Before any checks are made in this function, the user’s input is pushed to the stack (which is normal) and another
sscanf() is issued. What is unusual is that the string value used to parse out the value, is that there are TWO values specified in the address
0x80498c4:
8048d52: 68 c4 98 04 08 push $0x80498c4
as we can see when we dereference it:
"%d %s"
This calls for an integer and a string of
char’s, but only uses the integer. The string is actually used for the
secret_phase().
The integer value is passed into
func4, which seems to do nothing but test the current value and return when
(value – 1) <= 1. This turns out to be doing a Fibonacci sequence, and the result is returned to phase_4. That result is checked against a fixed value, and if it passes, the phase is complete:
8048d7d: 3d f1 6f 00 00 cmp $0x6ff1,%eax
// When userInput == “22”, %eax == 28657
Phase_5: After the user’s input is pushed onto the stack, its string length is checked. If the user has input exactly 6 ASCII characters, the specific letters are each given a different weight. The total weight of all 6 letters are computed to equal a single value:
8048dc8: 83 f9 20 cmp $0x20,%ecx
I found it easiest to write down all the letters and their values (a..z) by breaking on this line:
8048dc6: 7e f0 jle 8048db8
then simply watching the value of
%ecx change, and writing down that value. My ASCII weights were:
a = 10 b = 6 c = 1 d = 12 e = 16 f = 9 g = 3 h = 4
i = 7 j = 14 k = 5 l = 11 m = 8 n = 15 o = 13 p = 2
q = 10 r = 2 s = 1 t = 12 u = 16 v = 9 w = 3 x = 4
y = 7 z = 14
Once the weights equal the specified value (in the above case,
0x20 or 32 decimal), the phase is complete. I found the simplest to be
bbbbbp (5(
6) + 1(
2)), but any other values that added up would work.
Phase_6: Through trial-and-error, I found that this is actually quite simple to test. You want your output to equal this line of code:
8048e60: 74 05 je 8048e67
// Break here: Test %ebx, and that’s what your input should be
The
fun6() function doesn’t really do too much; it is a linked list that tries to twist around a lot, but does very little otherwise. You can pretty much skip any testing in it if you simply test the output value above.
Secret_Phase: Once the previous phases are complete, a branch in
phase_defused() opens up. It tests whether you have finished all 6 previous phases:
80495bc: 83 3d 6c a8 04 08 06 cmpl $0x6,0x804a86c
// Check whether we're on phase 0x6 (6)
then enters starts to pull strings and values from previous phases. One that it lingered on was the “
%d %s” from
phase_4. It pulls the second value (the string) and tests if anything was entered. The simplest way to get into the
secret_phase() is to look at the check value for the returned
%s. This turned out to be the string “
austinpowers” in a few bombs I worked on (I actually tried about 3 bombs, just for fun, and also to have a more complete guide that I'm writing now):
80495e4: 68 cb 9d 04 08 push $0x8049dcb
// 0x8049dcb contains the value "austinpowers"
The
strings_not_equal() function tests these values, and you are taken into the
secret_phase() to determine the last value.
The
secret_phase() was more tricky than the other phases because it had a lookup array as well as a recursive function (
fun7()). The lookup array contained several different values, and depending on the user input (either at the end of the user’s solutions text, or prompted), some or several of the tables were used. This result was returned and modified by the parent
fun7() functions, until the end result passed back to the
secret_phase() was the value it was looking for:
8048ee7: 83 f8 03 cmp $0x3,%eax
// Test that the (final) return value of fun7() is 3
That’s it! That’s all the bombs, and how they were found. The final phase (
fun7()) took quite a bit of plug-and-chug before I began to see a pattern to how it was calculating which lookup array to use and how different return values affected the parent
fun7()’s results as well.
Please leave comments if you found this useful, and especially if your values or addresses were different. How did you solve it? Are you still stuck on parts of it? What parts of the world are you from? Why are 95% of the readers for this post using Windows and Chrome instead of some Linux variant (I wasn't aware that Binary Bomb ran on anything BUT Linux...)?