Reverse engineering

Three Ways to Defeat a ReverseMe

Soufiane Tahiri
October 3, 2012 by
Soufiane Tahiri


What is a « ReverseMe »?

Disassembling or debugging commercial programs is usually prohibited by international laws. When practicing reversing and when we need to study a kind of software protection, reverse engineers usually make some stand alone applications which implement only the protection, in other words it simulates the behavior of a software protection, nothing more nothing less!

A "ReverseMe" as its name says, is a little piece of code compiled to produce one or more protections, and the whole is designed to be "reversed", which means designed to be a target for practicing reverse code engineering and studying software protections without any risk regarding laws and intellectual properties.

Tools needed

Practicing reverse engineering includes mastering lot of tools, but mainly we are talking about tools like debuggers, dissemblers and hex editors. In this article we will use only one tool, a famous and widely used analyzer / dissembler / debugger which is OllyDBG.

OllyDBG (or OllyDebugger) is free Microsoft Windows based 32 bits analyzing debugger used when we are in need of tracing registers, monitoring procedures and APIs calls or searching hard coded strings without having the source code of the target program which makes it one of the cracker's most preferred tools.

Links to download tools used in this article are in the bottom.


Our "ReverseMe.exe" was made to simulate a software that asks for a username and a serial key that is generated according to the username given. We will see how it tests if we filled in a correct serial number, how to force it into accepting an incorrect one, how it tests the correct and incorrect one, and how to use reverse engineering to make our ReverseMe calculate and show us the correct serial number corresponding to the user name entered.

Let's reverse it

The first thing a cracker does when it comes time to "crack" software is look if his target is packed or encrypted or protected against disassembling or debugging. There are several tools that help in finding out executable file signatures of compilers, file encryptors or packers, like PEiD that says that our ReverseMe is not encrypted nor packed and was coded using MASM32 / TASM32. We can go ahead and start analyzing our target.

First of all is running our target; as you can see, a simple one window program that prompts us to input a "Name" and a "Serial", I filled the name with "Soufiane Tahiri" and typed a random sequence of numbers "1298762573" then I pressed "Check" button. Of course I have no chance of filling a correct serial without knowing it and its normal that I got this message: "Wrong Serial !"

Figure 1. Typing a name and a random serial number.

Technically, our ReverseMe may have a lot of ways to GET what we typed, to analyze it and to prompt us a message box, and this has always something to do with Windows APIs (said Win32 APIs and stands for Application Programming Interfaces), at this point we can think about looking for APIs that prompt a message box or APIs used to get text filled into text or input boxes.

Usually when a program is brought to prompt a message box like the one we got with this ReverseMe, it makes a call to the MessageBox API which is a function belonging to the Microsoft Windows library user32.dll.

Notes about MessageBox API:

  1. The MessageBox function creates, displays, and makes a message box work! MessageBox contains a main message (in this case "Wrong Serial !") and a title (in this case "Error"), in addition to any combination of predefined icons and buttons (for instance the "OK" button that we have).
  2. A MessageBox function is structured this way: int MessageBox( HWND hWnd, LPCTSTR lpText,LPCTSTR lpCaption, UINT uType );

  • hWnd = Handle to the parent window.
  • lpText = The main text of the message box.
  • lpCaption= Title of the message box.
  • uType = Determines the style of the message box.
    1. The MessageBox function has two versions depending on string arguments that it takes, either narrow (ANSI) or wide (Unicode), and that is the reason why we can find MessageBoxW that provides 2 bytes character Unicode formatting and MessageBoxA which gives 1 byte character ANSI formatting.

    Using OllyDBG we will set a breakpoint on every call to the API function MessageBoxA, so after disassembling our target we have two different ways to set breakpoints on API calls, the simplest one is by opening the command line using Alt+F1 and typing "BPX MessageBoxA" then "Enter". Please note that the name of every WIN32 API is case sensitive.

    Figure 2. Setting Breakpoints to all MessageBoxA win32 API calls. To be sure that we correctly set breakpoint(s) we can use Alt+B to see a breakpoints list.

    Our ReverseMe makes two calls of the MessageBoxA API, first at address 00404411 and second at 00404427:

    Intermodular calls, item 28


    Disassembly=CALL <JMP.&USER32.MessageBoxA>


    Intermodular calls, item 29


    Disassembly=CALL <JMP.&USER32.MessageBoxA>


    After following the first address by simply double-clicking on its line we can clearly see that the fist call is used when the user fills an invalid serial number, and the second when he gives the correct one:

    00404402 |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL

    00404404 |. 68 BE604000 PUSH ReverseM.004060BE ; |Title = "Error"

    00404409 |. 68 AF604000 PUSH ReverseM.004060AF ; |Text = "Wrong Serial !"

    0040440E |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner

    00404411 |. E8 0A010000 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA

    00404416 |. EB 14 JMP SHORT ReverseM.0040442C

    00404418 |> 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL

    0040441A |. 68 A7604000 PUSH ReverseM.004060A7 ; |Title = "orial !"

    0040441F |. 68 7C604000 PUSH ReverseM.0040607C ; |Text = "Good

    Job, Serial found . Write a little tutorial !"

    00404424 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner

    00404427 |. E8 F4000000 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA

    To be sure that we are not wrong, let's keep these breakpoints set, and start the ReverseMe by pressing F9, filling in some random information, then pressing "Check":

    Figure 3. Breakpoint set on the first Call triggered when pressing Check.

    As expected, OllyDBG stops at the first call of MessageBoxA (Address 00404411) by pressing F8 (which is step over, in other words let the debugger execute the current line and pass to the second without passing the subroutine line by line). The message box with "wrong serial" will be prompted, and the unconditional JUMP at the address 00404416 will go to the address 0040442C, avoiding it by this way and showing the message box saying, "Good job…"

    We are here in front of a very basic protection type; just before the address 00404402 (before the beginning of passing parameters to the first message box) we can see curious CALL, TEST and JNZ (You can find more information about this in my first article here

    004043F9 |. E8 AB000000 CALL ReverseM.004044A9

    004043FE |. 85C0 TEST EAX,EAX

    00404400 |. 75 16 JNZ SHORT ReverseM.00404418

    The conditional jump JNZ goes to the address 00404418 and this way it jumps the first MessageBoxA's call so it avoids showing "Wrong Serial". Interesting!

    Set a breakpoint on the CALL at address 004043F9 (by pressing F2) and re press "Check" on the ReverseMe window. OllyDBG breaks on the line 004043F9 |. E8 AB000000 CALL ReverseM.004044A9, step into it by pressing F7 and let's see what is inside the routine:

    Figure 4. Serial verification routine.

    We can see a call to the WIN32 API GetWindowTextA at address 004044B6.

    Notes about GetWindowText API:

    1. This API is structured this way : int GetWindowText(hwnd, lpString, nMaxCount)

    • hwnd = Identifies the window or control whose title or text will be copied.
    • lpString = Points to the buffer that will receive the string to copy.
    • nMaxCount = Specifies the maximum number of characters to be copied in the buffer. If the string is longer than the number of characters specified by the parameter nMaxCount, it will be automatically truncated.
      1. This function copies the text of the control identified by the hWnd parameter.

      Refer to Figure 4 and notice that the ReverseMe:

      1. Sets the nMaxCount parameter to 50 characters at 004044A9 /$ 6A 32 PUSH 32.
      2. Points to the buffer that will receive the serial number entered at 004044AB |. 68 6C634000 PUSH ReverseM.0040636C.
      3. Identifies the control it will copy text from at 004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4]. 

      Forcing ReverseMe to accept all given serial numbers

      Our first task is only to force ReverseMe to tell us "Good Job", regardless of the serial number we type; we will see later what the routine shown in Figure 3 does exactly.

      The Reverse Me expects two "ends" of the routine mentioned above, either EAX = 1 or EAX = 0, which depends on executing either 004044DF |. B8 01000000 MOV EAX,1 or 004044E6 |> 33C0
      XOR EAX,EAX and this will -with absolutely no doubt - lead to show either "Wrong Serial" or "Good Job" (refer to figure 3).

      To know which value EAX must get to ensure that our target accepts every serial number typed, let's put a breakpoint on the RET at address 004044E8, and resume execution of our stopped program by pressing F9 and keep on having an eye on EAX values (which is right now equal to 9, the length of the serial number I entered "123456789").

      Figure 5. EAX = 0 and OllyDBG breaks on the RET.

      At this stage continuing debugging will be just a waste of time! Logically an incorrect given serial will lead to an EAX = 0. Let's force EAX to get 1 at the beginning of the verification routine (Figure 5) and see what this will produce.

      Restart the ReverseMe loaded inside OllyDBG using CTRL+F2, and by going directly to the first instruction on the concerned routine, which is at address 004044A9 (Ctrl+G > 004044A9. Press "space" and start changing this:

      004044A9 /$ 6A 32 PUSH 32 ; /Count = 32 (50.)

      004044AB |. 68 6C634000 PUSH ReverseM.0040636C ; |Buffer = ReverseM.0040636C

      004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4] ; |hWnd = NULL

      Into this, by simply typing mov eax,1 and ret :

      004044A9 B8 01000000 MOV EAX,1

      004044AE C3 RETN

      004044AF 90 NOP

      004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4] ; |hWnd = NULL

      You can remove already set breakpoints by going to the window list of breakpoints (Alt+B) and pressing "Del" on your keyboard. Now we have to test our changes, run the ReverseMe by pressing F9 and type on concerned fields whatever you like, then press "Check".

      Figure 6.

      As you can see, our ReverseMe now accepts every combination of names / serials. But note that modifications made until now are just in memory; to make these changes physically, you can right click on the disassembly code > Copy to executable > All modifications, in the little window prompted, click "Copy All", right click on the window shown, then select "Save File". Figure below:

      Figure 7. Steps to save a modified copy of a file.

      Understanding the routine of verification and fishing a valid serial number

      Now we know that our serial number is checked somewhere in this routine:

      004044A9 6A 32 PUSH 32

      004044AB 68 6C634000 PUSH ReverseM.0040636C

      004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4] ; |hWnd = NULL

      004044B6 |. E8 4D000000 CALL <JMP.&USER32.GetWindowTextA> ; GetWindowTextA

      004044BB |. 0BC0 OR EAX,EAX

      004044BD |. 74 29 JE SHORT ReverseM.004044E8

      004044BF |. 33C9 XOR ECX,ECX

      004044C1 |. 33D2 XOR EDX,EDX

      004044C3 |. 33DB XOR EBX,EBX

      004044C5 |. 33FF XOR EDI,EDI

      004044C7 |> 8B91 6C634000 /MOV EDX,DWORD PTR DS:[ECX+40636C]

      004044CD |. 8B99 6C674000 |MOV EBX,DWORD PTR DS:[ECX+40676C]

      004044D3 |. 33D3 |XOR EDX,EBX

      004044D5 |. 75 0F |JNZ SHORT ReverseM.004044E6

      004044D7 |. 83C1 04 |ADD ECX,4

      004044DA |. 47 |INC EDI

      004044DB |. 3BF8 |CMP EDI,EAX

      004044DD |.^7C E8 JL SHORT ReverseM.004044C7

      004044DF |. B8 01000000 MOV EAX,1

      004044E4 |. EB 02 JMP SHORT ReverseM.004044E8

      004044E6 |> 33C0 XOR EAX,EAX

      004044E8 > C3 RETN

      Once we've set a breakpoint at the address 004044A9, we can start a debugging session by pressing F9, (I typed in the fields Name and Serial respectively "Soufiane Tahiri" and "12344321"). After clicking on the button Check, OllyDB stops on the line with the breakpoint set at, at this point we will keep on analyzing what the ReverseMe does exactly by "Tracing or Stepping Over" each instruction, and notice that there is a kind of sub-routine inside our "main" routine:

      Figure 8. Serial number comparison sub-routine.

      Arriving at the line: 004044C7 |> 8B91 6C634000 /MOV EDX,DWORD PTR DS:[ECX+40636C] our ReverseMe puts the value of the address ECX+40636C INTO EDX. We can follow this value in dump to see its content:

      Figure 9. Following in dump address 40636C.

      (To follow it in dump, on OllyDBG "window stat" right click on DS:[0040636C]=34333231 > Follow address in Dump). We can clearly see the serial number we typed, so ReverseMe puts the address that contains the serial typed in EDX.

      The instruction that comes after is: 004044CD |. 8B99 6C674000 |MOV EBX,DWORD PTR DS:[ECX+40676C], by redoing the same thing we will see that ReverseMe puts the address that contains "some numbers" in ECX.

      Figure 10.

      This seems to be our valid serial number, but what make this probable are the upcoming two instructions:

      004044D3 |. 33D3 |XOR EDX,EBX
      004044D5 |. 75 0F |JNZ SHORT ReverseM.004044E6

      We saw that ReverseMe puts any serial number typed in the register EDX and some numbers (which represent certainly the correct serial number) in the register ECX; after these operations comes a XOR between values of both registers.

      Notes about XOR

      XOR is a binary operation like all others and represents an exclusive OR between destination and source. XOR is used in cryptography as a type of additive cipher which works according to this principle:

      A XOR B

      0 0 0

      0 1 1

      1 0 1

      1 1 0

      We have:

      004044C7 |> 8B91 6C634000 /MOV EDX,DWORD PTR DS:[ECX+40636C]

      004044CD |. 8B99 6C674000 |MOV EBX,DWORD PTR DS:[ECX+40676C]

      004044D3 |. 33D3 |XOR EDX,EBX

      With EDX=34333231 and EBX=39393834 in decimal which is equal to 110100001100110011001000110001 and 111001001110010011100000110100 in binary.

      004044D3 |. 33D3|XOR EDX,EBX will do the exclusive OR and put the result into EDX, that means it will do : 110100001100110011001000110001 XOR 111001001110010011100000110100 which is equal to 1101000010100000101000000101 (and D0A0A05 in hexadecimal).

      Figure 11.

      Since we are "Xoring" two different values the result will always be different from zero and the conditional jump to come at the line 004044D5|. 75 0F|JNZ SHORT ReverseM.004044E6 will be taken and will go to 004044E6 |> 33C0 XOR EAX,EAX for resetting the value of EAX to zero (XOR same values)

      This routine continues:

      1. 004044D7 |. 83C1 04|ADD ECX,4 : Adds 4 to the value of ECX.
      2. 004044DA |. 47|INC EDI : Increments by 1 the value of EDI.
      3. 004044DB |. 3BF8|CMP EDI,EAX : Compares if values of EAX and EDI are identical by making a logical subtraction.
      4. 004044DD |.^7C E8 JL SHORT ReverseM.004044C7 : Jumps to 4044C7 if the value of EDI stills less than the EAX's one.

      After trying the serial number fished (refer to Figure 10), we can see that it really works:

      Figure 12.

      Making a serial number self-generator

      This step is considered very classical when it comes to practicing reverse code engineering and may be useless but still is funny! Instead of showing us "Wrong Serial" we will change some bytes to let ReverseME show us the correct serial number.

      Now we know two important things, the first one is that ReverseMe stocks the correct serial number at the address 40676C (refer to Figure 10) and loads the text "Wrong Serial !" from the address 4060AF as seen here:

      Figure 13.

      Note about the instruction PUSH

      As its name indicates, this instruction is used to place a 16 or 32 bits value at the top of the stack. Example:

      • Push AX
      • Push BX
      • Push 1000
      • First AX is pushed onto the stack, then BX, but it is 1000 which will come out first.

        We will change the address pushed at the line: 00404409 |. 68 AF604000 PUSH ReverseM.004060AF ; |Text = "Wrong Serial !" by the address that contains the correct serial number:

        By changing this PUSH ReverseM.004060AF into this PUSH ReverseM.0040676C

        Figure 13. Address push changed

        The same way we saved first changes (refer to Figure 6) I saved these changes and as expected here is the result:

        Figure 14. ReverseMe serial self-generator

        Hope you learned something new from this article!

        Tools download links:


        Soufiane Tahiri
        Soufiane Tahiri

        Soufiane Tahiri is is an InfoSec Institute contributor and computer security researcher, specializing in reverse code engineering and software security. He is also founder of and practiced reversing for more then 8 years. Dynamic and very involved, Soufiane is ready to catch any serious opportunity to be part of a workgroup.

        Contact Soufiane in whatever way works for you: