THE

SPRAWL

  •  
  •  
  •  
  • Open Security Training's Introduction to Reverse Engineering class contains a detailed walkthrough of the Bomb Lab challenge, a crackme used for beginner reverse engineers. The course covers six phases necessary to defuse the bomb; however, there is also a secret phase hidden inside the original challenge. In this guide you will learn detailed steps necessary to locate and solve the hidden challenge.

    Spoiler Alert: Try to solve this challenge on your own first and refer back to this guide for tips and tricks in case you get stuck.

    Bomb Lab Refresher

    Before we get into the secret phase of the bomb lab, let's revisit some key parts of the program that are relevant to the solution below.

    Within the main function there are several repeated blocks of calls used to obtain user input strings and perform appropriate checks for each of the bomb's defusion phases.

    Here is a small snippet corresponding to the first phase:

    .text:00031085 loc_31085:
    .text:00031085 call    sub_317C0
    .text:0003108A push    offset aWelcomeToMyFie ; "Welcome to my fiendish little bomb"
    .text:0003108F call    ds:printf
    .text:00031095 add     esp, 4
    .text:00031098 push    offset aWhichToBlowYou ; "which to blow yourself up. Have a"
    .text:0003109D call    ds:printf
    .text:000310A3 add     esp, 4
    .text:000310A6 call    funGetSolution         ; Obtain user input for Phase 1
    .text:000310AB mov     [ebp+solution], eax
    .text:000310AE mov     edx, [ebp+solution]
    .text:000310B1 push    edx
    .text:000310B2 call    funPhase1              ; Check user input for Phase 1
    .text:000310B7 add     esp, 4
    .text:000310BA call    funDefuseBomb
    .text:000310BF push    offset aPhase1Defused_ ; "Phase 1 defused. How about the "
    .text:000310C4 call    ds:printf
    .text:000310CA add     esp, 4
    

    NOTE: I have renamed most of the default function names for convenience (e.g. sub_31870 is now called funGetSolution).

    The first call to funGetSolution is used to obtain user input. After obtaining a possible solution, the program calls the appropriate bomb phase checker (in this case funPhase1). Assuming the bomb does not "blow up", funDefuseBomb is called which in turn checks whether the bomb is completely defused and prints out the "Congratulations! You've defused the bomb!" message.

    Below is an illustration of the above pattern for all six phases:

             +----------------+     +-----------+     +---------------+
        +--> | funGetSolution | +-> | funPhase# | +-> | funDefuseBomb | +---+
        |    +----------------+     +-----------+     +---------------+     |
        |                                                                   |
        +------------------------[ goto next phase ]------------------------+
    

    As a quick reminder of how solutions are stored in memory let's take a look at the following snippet from funReadLine (originally sub_31810) that is called by funGetSolution. The funReadLine function is responsible for obtaining the actual solution string from STDIN or a file:

    .text:0003181D mov     ecx, File
    .text:00031823 push    ecx             ; File
    .text:00031824 push    80              ; MaxCount
    .text:00031826 mov     edx, phase_level; Calculate offset in the solutions array
    .text:0003182C imul    edx, 80         ; based on phase_level index
    .text:0003182F add     edx, offset byte_34F80   ; Get absolute address
    .text:00031835 push    edx             ; Buf
    .text:00031836 call    ds:fgets        ; Read and store string at the above offset
    

    The take away from the above is that all of the solutions are stored for the duration of the program in an array of strings located at byte_34F80.

    If you are using IDA, you could create a custom data type called Solutions and apply it to the byte_34F80 buffer to make things easier to recognize in the future:

    struct Solutions {
        char phase1[80];
        char phase2[80];
        char phase3[80];
        char phase4[80];
        char phase5[80];
        char phase6[80];
    }
    

    Please see Chris Eagle's excellent The IDA Pro Book, Chapter 8 on how to easily parse and apply C structure declarations.

    Finding the Secret Phase

    In order to locate the secret phase, let's take a look at how funDefuseBomb checks whether the bomb is defused or not:

    .text:00031990
    .text:00031990
    .text:00031990 ; Attributes: bp-based frame
    .text:00031990
    .text:00031990 funDefuseBomb proc near
    .text:00031990
    .text:00031990 num_items= dword ptr -5Ch
    .text:00031990 solution= byte ptr -58h
    .text:00031990 digit= byte ptr -4
    .text:00031990
    .text:00031990 push    ebp
    .text:00031991 mov     ebp, esp
    .text:00031993 sub     esp, 92
    .text:00031996 cmp     phase_level, 6   ; Check current bomb phase
    .text:0003199D jnz     short loc_31A07  ; return if not at six
    ...
    .text:00031A07
    .text:00031A07 loc_31A07:
    .text:00031A07 mov     esp, ebp
    .text:00031A09 pop     ebp
    .text:00031A0A retn
    .text:00031A0A funDefuseBomb endp
    .text:00031A0A
    

    Until you defuse all six phases of the bomb, the conditional jump at .text:0003199D will simply return from the function so you could progress to the next phase. However, a different branch is taken once all six phases are complete and the jump does not take place:

    .text:0003199F lea     eax, [ebp+solution]
    .text:000319A2 push    eax
    .text:000319A3 lea     ecx, [ebp+digit]
    .text:000319A6 push    ecx
    .text:000319A7 push    offset aDS      ; "%d %s"
    .text:000319AC push    offset Solutions.phase4 ; Corresponds to 35070h
    .text:000319B1 call    ds:sscanf       ; Read a format string
    .text:000319B7 add     esp, 10h
    .text:000319BA mov     [ebp+num_items], eax
    .text:000319BD cmp     [ebp+num_items], 2
    .text:000319C1 jnz     short loc_319F9
    ...
    .text:000319F9
    .text:000319F9 loc_319F9:              ; "Congratulations! You've defused the bom"...
    .text:000319F9 push    offset aCongratulation
    .text:000319FE call    ds:printf
    .text:00031A04 add     esp, 4
    

    Once again we are looking at the solutions buffer at the offset corresponding to the Phase 4 solution. If you recall, the solution for that phase consisted of a single digit. In this case we are still scanning for the same single digit; however, we are now looking for an extra string! The expected content of this extra string (called solution above) is revealed further down:

    .text:000319C3 push    offset aAustinpowers ; "austinpowers"
    .text:000319C8 lea     edx, [ebp+solution]
    .text:000319CB push    edx
    .text:000319CC call    funCheckStrEqual     ; Corresponds to sub_31740
    .text:000319D1 add     esp, 8
    .text:000319D4 test    eax, eax
    .text:000319D6 jnz     short loc_319F9
    .text:000319D8 push    offset aCurses ; "Curses, you've found the secret phase!\"...
    .text:000319DD call    ds:printf
    .text:000319E3 add     esp, 4
    .text:000319E6 push    offset aButFin ; "But finding it and solving it are quite"...
    .text:000319EB call    ds:printf
    .text:000319F1 add     esp, 4
    .text:000319F4 call    funPhaseSecret       ; Corresponds to sub_31620
    

    Aha! So if an extra string, austinpowers, is appended after the normal solution to Phase 4 we are redirected to the secret phase. Let's proceed.

    Talking to myself

    If you have followed this guide so far, you should be inside sub_31620 (called funPhaseSecret from now on) which performs the checks necessary to defuse the secret bomb phase.

    The secret phase begins with a typical call to funGetSolution to obtain another line of user input and immediately performs some preliminary checks:

    .text:00031620
    .text:00031620
    .text:00031620 ; Attributes: bp-based frame
    .text:00031620
    .text:00031620 funPhaseSecret proc near
    .text:00031620
    .text:00031620 result= dword ptr -0Ch
    .text:00031620 solution= dword ptr -8
    .text:00031620 Str= dword ptr -4
    .text:00031620
    .text:00031620 push    ebp
    .text:00031621 mov     ebp, esp
    .text:00031623 sub     esp, 0Ch
    .text:00031626 call    funGetSolution   ; Obtain user input
    .text:0003162B mov     [ebp+Str], eax
    .text:0003162E mov     eax, [ebp+Str]
    .text:00031631 push    eax              ; Str
    .text:00031632 call    ds:atoi          ; Convert input string to integer
    .text:00031638 add     esp, 4
    .text:0003163B mov     [ebp+solution], eax
    .text:0003163E cmp     [ebp+solution], 1    ; solution >= 1
    .text:00031642 jl      short loc_3164D      ; BOOM!!!
    .text:00031644 cmp     [ebp+solution], 1001 ; solution <= 1001
    .text:0003164B jle     short loc_31652
    .text:0003164D
    .text:0003164D loc_3164D:
    .text:0003164D call    funExplode           ; BOOM!!!
    

    So far we know that the input must be an integer between 1 and 1001. Let's proceed further down:

    .text:00031652
    .text:00031652 loc_31652:
    .text:00031652 mov     ecx, [ebp+solution]
    .text:00031655 push    ecx
    .text:00031656 push    offset dword_34688
    .text:0003165B call    funPhaseSecretCheck ; Call another check
    .text:00031660 add     esp, 8
    .text:00031663 mov     [ebp+result], eax
    .text:00031666 cmp     [ebp+result], 7
    .text:0003166A jz      short loc_31671     ; Confirm the output is 7
    .text:0003166C call    funExplode
    

    Interesting! In the snippet above funPhaseSecretCheck is called with two parameters: an address of some data structure (dword_4688) and the user provided number. The result of this function is compared against number 7. If the result does not match then the bomb explodes.

    Just out of curiosity let's take a peak at the mysterious data structure at dword_34688:

    .data:00034688 dword_34688     dd 24h         ; DATA XREF: funPhaseSecret+36o
    .data:0003468C                 dd offset unk_3467C
    .data:00034690                 dd offset unk_34670
    

    Looks like some kind of a linked data structure with an integer value at offset +0 and pointers to other similar looking structs at offsets +4 and +8.

    Let's push the above information on our mental stack as we dive into funPhaseSecretCheck:

    .text:000315C0
    .text:000315C0
    .text:000315C0 ; Attributes: bp-based frame
    .text:000315C0
    .text:000315C0 funPhaseSecretCheck proc near
    .text:000315C0
    .text:000315C0 pnode= dword ptr  8
    .text:000315C0 solution= dword ptr  0Ch
    .text:000315C0
    .text:000315C0 push    ebp
    .text:000315C1 mov     ebp, esp
    .text:000315C3 cmp     [ebp+pnode], 0   ; Compare node->value == 0
    .text:000315C7 jnz     short loc_315CE
    .text:000315C9 or      eax, 0FFFFFFFFh  ; Return -1
    .text:000315CC jmp     short loc_31618
    ...
    .text:00031618
    .text:00031618 loc_31618:
    .text:00031618 pop     ebp
    .text:00031619 retn
    .text:00031619 funPhaseSecretCheck endp
    .text:00031619
    

    The first check makes sure the current node value (offset +0) is not null. If the value is null, this function will simply return -1.

    The case where the initial node value is non-zero is more interesting:

    .text:000315CE
    .text:000315CE loc_315CE:
    .text:000315CE mov     eax, [ebp+pnode]     ; Get current node
    .text:000315D1 mov     ecx, [ebp+solution]  ; Get solution
    .text:000315D4 cmp     ecx, [eax]
    .text:000315D6 jge     short loc_315F1      ; If (node->value > solution)
    .text:000315D8 mov     edx, [ebp+solution]
    .text:000315DB push    edx
    .text:000315DC mov     eax, [ebp+pnode]
    .text:000315DF mov     ecx, [eax+4]         ; Get another node (offset +4)
    .text:000315E2 push    ecx
    .text:000315E3 call    funPhaseSecretCheck  ; Recursive call
    .text:000315E8 add     esp, 8
    .text:000315EB shl     eax, 1               ; Return 2 * funPhaseSecretCheck()
    .text:000315ED jmp     short loc_31618
    

    The first thing you should notice is the recursive call to funPhaseSecretCheck. Here an address stored at an offset +4 is passed to funPhaseSecretCheck along with the user provided solution integer. Next the result of this recursive call is multiplied by two using left shift and returned to the original caller.

    So far so good, let's look at another branch:

    .text:000315F1
    .text:000315F1 loc_315F1:
    .text:000315F1 mov     edx, [ebp+pnode]     ; Get current node
    .text:000315F4 mov     eax, [ebp+solution]  ; Get solution
    .text:000315F7 cmp     eax, [edx]           
    .text:000315F9 jnz     short loc_31601      ; If (node->value != solution)
    ...                                         ; or simply "node->value < solution"
    .text:00031601
    .text:00031601 loc_31601:
    .text:00031601 mov     ecx, [ebp+solution]
    .text:00031604 push    ecx
    .text:00031605 mov     edx, [ebp+pnode]
    .text:00031608 mov     eax, [edx+8]         ; Get another node (offset +8)
    .text:0003160B push    eax
    .text:0003160C call    funPhaseSecretCheck  ; Recursive call
    .text:00031611 add     esp, 8
    .text:00031614 lea     eax, [eax+eax+1]     ; Return 2 * funPhaseSecretCheck() + 1
    

    Another check and recursive call to funPhaseSecretCheck. In this case the current node value is checked not to be equal to the solution. Based on the initial comparison at 000315D6 this could also be interpreted as the current node value being less than the solution. Once again the next node at an offset +8 is passed to itself. The result of the recursive call is both doubled and incremented by one before being returned to the calling function.

    The last scenario in funPhaseSecretCheck is simply the case where both node->value and solution are equal:

    .text:000315FB xor     eax, eax
    .text:000315FD jmp     short loc_31618      ; Return 0
    

    Nothing exciting here, simply returning 0.

    With all of the above information in mind, it is now possible to write some pseudo-code to clear up the algorithm:

    struct Node {
        unsigned int value;
        struct Node *node_4;
        struct Node *node_8;
    }
    
    int funPhaseSecretCheck(struct Node *pnode, unsigned int solution)
    {
        if (pnode->value == 0) { return -1; }
    
        if (pnode->value > i) {
            return 2 * funPhaseSecretCheck(pnode->node_4, solution);
        }
        else if(pnode->value < i) {
            return 2 * funPhaseSecretCheck(pnode->node_8, solution) + 1;
        }
        else {
            return 0;
        }
    }
    

    Thinking back to the original call from funPhaseSecret this series of recursive calls must return the number 7 by navigating the linked structure based on the user provided number.

    Climbing the tree

    At this point, we must understand exactly how the linked structure looks like and see how it can be traversed to produce the desired result.

    If you are using IDA it is convenient to define a simple data structure based on the information learned above:

    00000000 Node            struc ; (sizeof=0xC, align=0x2) ; XREF: .data:000345E0r
    00000000                                         ; .data:000345ECr ...
    00000000 value           dd ?  
    00000004 node_4          dd ?
    00000008 node_8          dd ?
    0000000C Node            ends
    

    Let's apply the structure to the initial node from funPhaseSecret and continue applying to any linked nodes from there:

    .data:000345E0                 Node <3E9h, 0, 0>
    .data:000345EC                 Node <2Fh, 0, 0>
    .data:000345F8                 Node <14h, 0, 0>
    .data:00034604                 Node <7, 0, 0>
    .data:00034610                 Node <23h, 0, 0>
    .data:0003461C                 Node <63h, 0, 0>
    .data:00034628                 Node <1, 0, 0>
    .data:00034634                 Node <28h, 0, 0>
    .data:00034640                 Node <6Bh, 3461Ch, 345E0h>
    .data:0003464C                 Node <6, 34628h, 34604h>
    .data:00034658                 Node <2Dh, 34634h, 345ECh>
    .data:00034664                 Node <16h, 345F8h, 34610h>
    .data:00034670                 Node <32h, 34658h, 34640h>
    .data:0003467C                 Node <8, 3464Ch, 34664h>
    .data:00034688 stru_34688      Node <24h, 3467Ch, 34670h> ; DATA XREF: funPhaseSecret+36o
    

    There were a total of 15 nodes in this structure; however, in order illustrate the pattern it is useful to graph the relationship between nodes:

                                         +------+
                                         | 0x24 |
                        +----------------+------+---------------+
                        |                                       |
                        v                                       v
                   +------+                                  +------+
                   | 0x8  |                                  | 0x32 |
              +----+------+-----+                       +-----+------+-----+
              |                 |                       |                  |
              v                 v                       v                  v
         +------+             +------+             +------+             +------+
         | 0x6  |             | 0x16 |             | 0x2D |             | 0x6B |
         +------+             +------+             +------+             +------+
         |      |             |      |             |      |             |      |
         v      v             v      v             v      v             v      v
    +------+  +------+   +------+  +------+   +------+  +------+   +------+  +------+
    | 0x1  |  | 0x7  |   | 0x14 |  | 0x23 |   | 0x28 |  | 0x2F |   | 0x63 |  | 0x3e9|
    +------+  +------+   +------+  +------+   +------+  +------+   +------+  +------+
    

    The above structure corresponds to a sorted full binary tree.

    It is now possible to figure out how to navigate the recursive calls to produce the desired pattern. The solution to the problem is the number 1001 or 0x3e9. Below is the series of recursive calls resulting from the number 1001 being passed to funPhaseSecretCheck:

        +------+ if (pnode->value < solution)
        | 0x24 |    Return 2*(3) + 1 = 7
        +------+
               |
               +----------------+ if (pnode->value < solution)
                                |   Return 2*(1) + 1 = 3
                                v
                              +------+
                              | 0x32 |
                        +-----+------+-----+ if (pnode->value < solution)
                        |                  |    Return 2*(0) + 1 = 1
                        v                  v
                   +------+              +------+
                   | 0x2D |              | 0x6B |
                   +------+              +------+ if (pnode->value == solution)
                   |      |              |      |   Return 0
                   v      v              v      v
              +------+  +------+    +------+  +------+
              | 0x28 |  | 0x2F |    | 0x63 |  | 0x3e9|
              +------+  +------+    +------+  +------+
    

    At this point you can append the solution to the input text file or simply answer the bomb prompt to solve the secret stage:

    C:\reclass_2013\labs>Bomb.ex_ bomb.txt
    Welcome to my fiendish little bomb. You have 6 phases with
    which to blow yourself up. Have a nice day!
    Phase 1 defused. How about the next one?
    That's number 2.  Keep going!
    Halfway there!
    So you got that one.  Try this one.
    Good work!  On to the next...
    Curses, you've found the secret phase!
    But finding it and solving it are quite different...
    Wow! You've defused the secret stage!
    Congratulations! You've defused the bomb!
    

    External Links and References

    Special Note

    Thank you Frank Poz, Matt Briggs, and Open Security Training for another excellent class in the series. You rock!

    Published on March 1st, 2014 by iphelix

    sprawlsimilar

    open security training - introduction to software exploits - off-by-one

    A walkthrough for the Off-by-One exploit in the Open Security Training - Introduction to Software Exploits class. Read more.

    open security training - introduction to software exploits - uninitialized variable overflow

    Open Security Training's Introduction to Software Exploits course has a number of vulnerability examples designed to illustrate unconventional exploitation techniques. One such example is an uninitialized variable condition which may be exploitable under certain conditions. The following walkthrough goes into the exact exploitation steps for this class of vulnerabilities. Read more.

    nlxx crackme solution

    Crackmes.de has a nice of collection crackmes, fun and educational challenges useful for honing your reversing skills. Looking at the latest submissions section there was a recently published Crackme by nlxx rated at difficulty 2. In this guide I will go over the static analysis based solution to this crackme and explain how to write a key generator. Read more.

    open security training - introductory x86 - buffer overflow mystery box

    A walkthrough for the Mystery Box Buffer Overflow challenge in the Open Security Training - Introductory x86 class. Read more.


    sprawlcomments

    All original content on this site is copyright protected and licensed under Creative Commons - Attribution, NonCommercial, ShareAlike 4.0 International.

    π
    ///\oo/\\\