user3071135
user3071135

Reputation: 13

Binary Bomb Defusion. Decoding Dump - Phase 2

0x08048b94 <+0>:push   %esi
0x08048b95 <+1>:    push   %ebx
0x08048b96 <+2>:    sub    $0x34,%esp
0x08048b99 <+5>:    lea    0x18(%esp),%eax
0x08048b9d <+9>:    mov    %eax,0x4(%esp)
0x08048ba1 <+13>:   mov    0x40(%esp),%eax
0x08048ba5 <+17>:   mov    %eax,(%esp)
0x08048ba8 <+20>:   call   0x804920a <read_six_numbers>
0x08048bad <+25>:   cmpl   $0x0,0x18(%esp)
0x08048bb2 <+30>:   jne    0x8048bbb <phase_2+39>
0x08048bb4 <+32>:   cmpl   $0x1,0x1c(%esp)
0x08048bb9 <+37>:   je     0x8048bda <phase_2+70>
0x08048bbb <+39>:   call   0x80490d7 <explode_bomb>
0x08048bc0 <+44>:   jmp    0x8048bda <phase_2+70>
0x08048bc2 <+46>:   mov    -0x8(%ebx),%eax
0x08048bc5 <+49>:   add    -0x4(%ebx),%eax
0x08048bc8 <+52>:   cmp    %eax,(%ebx)
0x08048bca <+54>:   je     0x8048bd1 <phase_2+61>
0x08048bcc <+56>:   call   0x80490d7 <explode_bomb>
0x08048bd1 <+61>:   add    $0x4,%ebx
0x08048bd4 <+64>:   cmp    %esi,%ebx
0x08048bd6 <+66>:   jne    0x8048bc2 <phase_2+46>
---Type <return> to continue, or q <return> to quit---<return>
0x08048bd8 <+68>:   jmp    0x8048be4 <phase_2+80>
0x08048bda <+70>:   lea    0x20(%esp),%ebx
0x08048bde <+74>:   lea    0x30(%esp),%esi
0x08048be2 <+78>:   jmp    0x8048bc2 <phase_2+46>
0x08048be4 <+80>:   add    $0x34,%esp
0x08048be7 <+83>:   pop    %ebx
0x08048be8 <+84>:   pop    %esi
0x08048be9 <+85>:   ret 

This is my assembler dump for a particular phase of a binary bomb defusion lab. I have to enter six numbers to crack the code and move on to the next phase. I'm stuck with the decoding of this, especially in the following part:

0x08048bc2 <+46>:   mov    -0x8(%ebx),%eax
0x08048bc5 <+49>:   add    -0x4(%ebx),%eax

It would be great if someone could help me out with the execution of this assembly code. Thanks!

Upvotes: 0

Views: 1521

Answers (1)

CherryDT
CherryDT

Reputation: 29012

OK this is going to be kind of a long answer, but here you go:

Let me start with your specific question about the +46/+49 lines. What they do is:

  • Write the value of the memory at address [ebx-8] into eax
  • Add the value of the memory at address [ebx-4] to eax
  • So, effectively, it sets eax = [ebx-8] + [ebx-4] where [] means dereference/memory access.

Your code is using the AT&T syntax. I personally find it highly confusing and hard to read. So from now on, I'll use Intel syntax. I'm sorry, but I would probably be unable to go further with my answer if we stayed with AT&T.

The main differences are:

  • AT&T puts the source at the end and the destination at the beginning, while Intel does it the other way round. For example, mov %ebx,%eax in AT&T would be mov eax, ebx in Intel.
  • Intel doesn't have most of the prefixes such as %, *, etc.
  • For memory access (or effective address calculation with lea), AT&T uses offset(base) while Intel uses [base+offset] (which I personally find much easier to read), so mov -0x8(%ebx),%eax from your example becomes mov eax, [ebx-8] in Intel syntax.
  • Instead of command suffixes like l, it specifies operand size where required using a syntax such as dword ptr (e.g. cmpl becomes cmp dword ptr).

OK so I tried to tidy up the whole code a bit.

First, I converted everything to Intel syntax and created labels:

phase_2:
    push esi
    push ebx
    sub esp, 34
    lea eax, [esp+18]
    mov [esp+4], eax
    mov eax, [esp+40]
    mov [esp], eax
    call read_six_numbers
    cmp dword ptr [esp+18], 0
    jne phase_2_39

phase_2_32:
    cmp dword ptr [esp+1c], 1
    je phase_2_70

phase_2_39:
    call explode_bomb
    jmp phase_2_70

phase_2_46:
    mov eax, [ebx-8]
    add eax, [ebx-4]
    cmp [ebx], eax
    je phase_2_61

phase_2_56:
    call explode_bomb

phase_2_61:
    add ebx, 4
    cmp ebx, esi
    jne phase_2_46

phase_2_68:
    jmp phase_2_80

phase_2_70:
    lea ebx, [esp+20]
    lea esi, [esp+30]
    jmp phase_2_46

phase_2_80:
    add esp, 34
    pop ebx
    pop esi
    retn

read_six_numbers:
    int3

explode_bomb:
    int3

Then I actually compiled the code and then loaded it into IDA (a nice disassembler). I set some labels for variables and created a structure SIX_NUMBERS_STRUCT which is basically an array of 6 DWORD numbers. For me it looks like the phase_2 function takes one parameter, probably some kind of string of what numbers you entered. It then calls read_six_numbers and passes the aforementioned input pointer together with a pointer to the struct of 6 DWORD values (which is held at [esp+3C-24]=[esp+18], so one would access the 6 numbers using [esp+18], [esp+1C], [esp+20], [esp+24], [esp+28] and [esp+2C]). Then, several checks and calculations are performed on this data.

A little explanation for the following part: Normally, one uses a base pointer (ebp) in functions with local variables to have a pointer which is always referencing the same point on the stack even when new values are pushed. Here, you don't - instead, 0x34 is subtracted from esp at the start. Additionally, there were two pushes before, which means the stack pointer is now 0x3C bytes below the point it was at the start of the function. This is why IDA is calculating all stack offsets relative to esp+3C.

OK so first, here is the assembly code visualized (where you should be able to understand the program flow more easily): Flowchart

I also ran a decompiler on it to generate a C-like representation of the code and annotated it a bit:

void __cdecl phase_2(void *argument_to_phase_2)
{
    int *currentNumber; // ebx@6
    SIX_NUMBERS_STRUCT numbers_struct; // [sp+18h] [bp-24h]@1
    int dummy_var_after_numbers; // [sp+30h] [bp-Ch]@6

    read_six_numbers(argument_to_phase_2, &numbers_struct);
    if ( numbers_struct.numbers[0] || numbers_struct.numbers[1] != 1 )
        explode_bomb();
    currentNumber = &numbers_struct.numbers[2];
    do
    {
        if ( *currentNumber != *(currentNumber - 1) + *(currentNumber - 2) )
            explode_bomb();
        ++currentNumber;
    }
    while ( currentNumber != &dummy_var_after_numbers );
}

So, my insights here are:

  • First, the code reads the six numbers, by passing argument_to_phase_2 ([esp+40], saved in [esp+0] as part of the argument list on stack) into read_six_numbers, together with a pointer to the 6*DWORD struct which holds the numbers (the struct starts at [esp+18], a pointer to which is stored in [esp+4] as part of the argument list on stack).
  • Then, the code verifies that the first number (numbers[0] = [esp+18]) is 0 and the second number (numbers[1] = [esp+1C]) is 1.
  • Then, the code loops through the rest of the numbers (third to sixth), verifiying that each number numbers[n] equals the sum of the previous two numbers[n - 1] + numbers[n - 2]. The loop is terminated when it passed beyond the end of the list of numbers, which is done by comparing the pointer pointing to the currently-to-be-checked number against dummy_var_after_numbers ([esp+30]) which is just an unused dummy variable right after the numbers list (kind of a "seventh number").

If you have any math background, this logic would immediately spark a thought in your mind: Fibonacci! That's right, basically you have to enter the first 6 numbers of the Fibonacci series including zero: 0, 1, 1, 2, 3, 5 - that's your code!

For reference, this is the list of variables on stack and how they are used:

|-------------------------------------------------------------------------------------|
| rel. to current esp | rel. to esp at start | usage                                  |
|-------------------------------------------------------------------------------------|
|                  +0 |                  -3C | First parameter to read_six_numbers    |
|                  +4 |                  -38 | Second parameter to read_six_numbers   |
|                  +8 |                  -34 | (unused)                               |
|                  +C |                  -30 | (unused)                               |
|                 +10 |                  -2C | (unused)                               |
|                 +14 |                  -28 | (unused)                               |
|                 +18 |                  -24 | First number                           |
|                 +1C |                  -20 | Second number                          |
|                 +20 |                  -1C | Third number                           |
|                 +24 |                  -18 | Fourth number                          |
|                 +28 |                  -14 | Fifth number                           |
|                 +2C |                  -10 | Sixth number                           |
|                 +30 |                   -C | Dummy, compared against to end loop    |
|                 +34 |                   -8 | Original esi register from "push esi"  |
|                 +38 |                   -4 | Original ebx register from "push ebx"  |
|                 +3C |                    0 | Return address from function "phase_2" |
|                 +40 |                   +4 | Argument to function "phase_2"         |
|-------------------------------------------------------------------------------------|

I hope this answer made it clear how the code works. Again, I'm sorry I kind of forced you to work with a different syntax (Intel) here, but AT&T syntax just makes me cringe and I can't work with it.

Upvotes: 3

Related Questions