Sandwich CrackMe - App icon Sandwich CrackMe - Tutorial

Task:

This crackme is a simple task and very well designed for the ones of us who are beginners in the world of reservse engineering. I'd give it a  2 / 10 in difficulty. Anyhow to solve it you need some basic knowledge in assembler language and how to use a disassembler / debugger like Hopper or IDA. I wrote this article about solving the crackme to show you an easy approach on how to proceed step-by-step.
 

Requirements:


Task to-do's:

  1. Launch the app or at least try to (don't worry if it won't run just read on)
  2. Decompile the App / Executable file
  3. Find the validation function
  4. Convert to psudo code (optional)
  5. Reconstruct with XCode / Objectiv-C
  6. Create serial generator


1. Launch the app

The first thing you always should do when attempting to reverse engineer, crack or patch an app is - Launch it and examine its functions! It's the only way to get a real feeling of the app and get to know how it works and how the workflow is. You can gather a lot of information just by clicking around the app and test it's functionalities. You also may get hints to texts, buttons, menus, labels etc. used and so you may find yourself comfortable a lot quicker in the disassebly of the app which you're going to examine later on in the reverse engineering process.

Check if it's running on your OS version - I didn't manage to get it to run on macOS BigSur - but on macOS High Sierra the app runs very well. Don't worry if it's not starting on your device - also if it has a blocked icon in the finder which says that the app is not compatible with your os version. Even if it's not running on your system, you can generate a keygen just fine by decompiling it and analyzing the machO (assembler) and / or pseudo code.

Here you can see the main (only) screen of the macOS / OSX Sandwich crackme app (1-Sandwich) from reverse.put.as/crackmes/ - with some numbers entered by me to test the serial validation function:


After clicking on the "Validate" button the app calls a function to check the entered serial. As excpected the validation of my test-serial fails and the crackme shows the following message:



So far so well ... It's time now to dig in and visit the code of this app. What we want to do now is to find the validation function for the serial check. So let's start!


2. Find the validation function

After trying to launch and examining the app is to disassemble the executable with a decompiler and try to find the serial validate function. We already have gathered some interessting informations by running the app and trying to validate a test serial. If you couldn't run the app - don't worry just carry on and continue reading!



Cranck up the disassembler of your choice (I use the recent Hopper demo version - but you can also use IDA free) and analyze the Sandwich crackme app. It's a fairly small executable and you won't have much troubles finding the validate:(void*)arg2 function. If you remember the message of the failed serial check "Error!" (Title), "The serial is not valid." (Message) and "Try again" (Button) you can also try to search the disassebly for this strings. Of course the more unique a string you search is, the more precise your results will be and you will get to the "juice" a lot quicker.

The following block is the disassembled code of the validate function. Analyze it to get an impression of what the function is doing.

00001d0e         push       ebp                                                 ; Objective C Implementation defined at 0x30ac (instance method)
00001d0f         mov        ebp, esp
00001d11         push       ebx
00001d12         sub        esp, 0x24
00001d15         mov        ebx, dword [ebp+self]
00001d18         mov        edx, dword [ebx+8]
00001d1b         mov        eax, dword [objc_msg_stringValue]                   ; @selector(stringValue)
00001d20         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001d24         mov        dword [esp+0x28+var_28], edx                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001d27         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001d2c         mov        dword [esp+0x28+var_20], eax
00001d30         mov        eax, dword [objc_msg_validateSerial_]               ; @selector(validateSerial:)
00001d35         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001d39         mov        dword [esp+0x28+var_28], ebx                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001d3c         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001d41         test       al, al
00001d43         jne        loc_1d6e <= THIS IS THE CHECK IF VALIDATION SUCCEEDED !!!

00001d45         mov        dword [esp+0x28+var_18], 0x0
00001d4d         mov        dword [esp+0x28+var_1C], 0x0
00001d55         mov        dword [esp+0x28+var_20], cfstring_Try_again         ; @"Try again" <= THE BUTTON TITLE OF ALERT BOX
00001d5d         mov        dword [esp+0x28+var_24], cfstring_The_serial_is_not_valid_ ; @"The serial is not valid." <= ALERT BOX MESSAGE
00001d65         mov        dword [esp+0x28+var_28], cfstring_Error_            ; @"Error!" <= ALERT BOX TITLE
00001d6c         jmp        loc_1d95

             loc_1d6e:
00001d6e         mov        dword [esp+0x28+var_18], 0x0                        ; CODE XREF=-[SandwichAppDelegate validate:]+53
00001d76         mov        dword [esp+0x28+var_1C], 0x0
00001d7e         mov        dword [esp+0x28+var_20], cfstring_OK                ; @"OK" <= THE BUTTON TITLE
00001d86         mov        dword [esp+0x28+var_24], cfstring_The_serial_is_valid_ ; @"The serial is valid." <= ALERT BOX MESSAGE
00001d8e         mov        dword [esp+0x28+var_28], cfstring_Success_          ; @"Success!" <= ALERT BOX TITLE

             loc_1d95:
00001d95         call       imp___symbol_stub__NSRunAlertPanel                  ; NSRunAlertPanel, CODE XREF=-[SandwichAppDelegate validate:]+94
00001d9a         add        esp, 0x24
00001d9d         pop        ebx
00001d9e         leave
00001d9f         ret
                        ; endp
As you can see this where the app decides wether the entered serial was validated successfully or not (@ address 0x00001d43) and in return presents you with either an error or the success message.

If you use Hopper (I'd really recommend you to use Hopper even if its just the demo version!) there is a neat feature to convert the asm code into pseudo code. My conversion into pseudo code looks as follows:

/* @class SandwichAppDelegate */ -(void)validate:(void *)arg2 { if ([self validateSerial:[*(self + 0x8) stringValue]] == 0x0) { <= This is the check @ address 0x00001d43 var_20 = @"Try again"; var_24 = @"The serial is not valid."; var_28 = @"Error!"; } else { var_20 = @"OK"; var_24 = @"The serial is valid."; var_28 = @"Success!"; } NSRunAlertPanel(var_28, var_24, var_20, 0x0, 0x0); return; }

This representation of the function is obiously much shorter and quit easier to analyze. Not just that, but you also can most of the times take the pseudo code and copy it into a new XCode project (Objective-C) to rebuild the functionalities you need in a test app. So this powerfull feature should not be underestimated and missed - at least if you want to save lots of time and headscratching ;-).

As we see the validate function calls another function validateSerial:(void*)arg2 whichs return value is used to check wether the validation succeeded or not. So we continue by examining the validateSerial function. The disassembled code looks as follows:

00001b2d         push       ebp                                                 ; Objective C Implementation defined at 0x30a0 (instance method)
00001b2e         mov        ebp, esp
00001b30         sub        esp, 0x28
00001b33         mov        dword [ebp+var_C], ebx
00001b36         mov        dword [ebp+var_8], esi
00001b39         mov        dword [ebp+var_4], edi
00001b3c         mov        ebx, dword [ebp+arg_8]
00001b3f         mov        esi, dword [objc_msg_length]                        ; @selector(length)
00001b45         mov        dword [esp+0x28+var_24], esi                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001b49         mov        dword [esp+0x28+var_28], ebx                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001b4c         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001b51         cmp        eax, 0x13
00001b54         jne        loc_1cd2

00001b5a         mov        dword [esp+0x28+var_20], cfstring__                 ; @"-"
00001b62         mov        eax, dword [objc_msg_componentsSeparatedByString_]  ; @selector(componentsSeparatedByString:)
00001b67         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001b6b         mov        dword [esp+0x28+var_28], ebx                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001b6e         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001b73         mov        edi, eax
00001b75         mov        eax, dword [objc_msg_count]                         ; @selector(count)
00001b7a         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001b7e         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001b81         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001b86         cmp        eax, 0x4
00001b89         jne        loc_1cd2

00001b8f         mov        dword [esp+0x28+var_20], 0x0
00001b97         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001b9c         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001ba0         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001ba3         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001ba8         mov        dword [esp+0x28+var_24], esi                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001bac         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001baf         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001bb4         cmp        eax, 0x4
00001bb7         jne        loc_1cd2

00001bbd         mov        dword [esp+0x28+var_20], 0x1
00001bc5         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001bca         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001bce         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001bd1         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001bd6         mov        dword [esp+0x28+var_24], esi                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001bda         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001bdd         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001be2         cmp        eax, 0x4
00001be5         jne        loc_1cd2

00001beb         mov        dword [esp+0x28+var_20], 0x2
00001bf3         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001bf8         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001bfc         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001bff         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c04         mov        dword [esp+0x28+var_24], esi                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c08         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c0b         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c10         cmp        eax, 0x4
00001c13         jne        loc_1cd2

00001c19         mov        dword [esp+0x28+var_20], 0x3
00001c21         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001c26         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c2a         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c2d         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c32         mov        dword [esp+0x28+var_24], esi                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c36         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c39         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c3e         cmp        eax, 0x4
00001c41         jne        loc_1cd2

00001c47         mov        dword [esp+0x28+var_20], 0x0
00001c4f         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001c54         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c58         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c5b         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c60         mov        ebx, dword [objc_msg_intValue]                      ; @selector(intValue)
00001c66         mov        dword [esp+0x28+var_24], ebx                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c6a         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c6d         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c72         mov        esi, eax
00001c74         mov        dword [esp+0x28+var_20], 0x1
00001c7c         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001c81         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c85         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c88         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c8d         mov        dword [esp+0x28+var_24], ebx                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001c91         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001c94         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001c99         lea        esi, dword [eax+esi]
00001c9c         mov        dword [esp+0x28+var_20], 0x3
00001ca4         mov        eax, dword [objc_msg_objectAtIndex_]                ; @selector(objectAtIndex:)
00001ca9         mov        dword [esp+0x28+var_24], eax                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001cad         mov        dword [esp+0x28+var_28], edi                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001cb0         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001cb5         mov        dword [esp+0x28+var_24], ebx                        ; argument "selector" for method imp___symbol_stub__objc_msgSend
00001cb9         mov        dword [esp+0x28+var_28], eax                        ; argument "instance" for method imp___symbol_stub__objc_msgSend
00001cbc         call       imp___symbol_stub__objc_msgSend                     ; objc_msgSend
00001cc1         sar        esi, 0x2
00001cc4         mov        edx, 0x19c5
00001cc9         sub        edx, esi
00001ccb         cmp        edx, eax
00001ccd         sete       al
00001cd0         jmp        loc_1cd4

             loc_1cd2:
00001cd2         xor        eax, eax                                            ; CODE XREF=-[SandwichAppDelegate validateSerial:]+39, -[SandwichAppDelegate validateSerial:]+92, -[SandwichAppDelegate validateSerial:]+138, -[SandwichAppDelegate validateSerial:]+184, -[SandwichAppDelegate validateSerial:]+230, -[SandwichAppDelegate validateSerial:]+276

             loc_1cd4:
00001cd4         mov        ebx, dword [ebp+var_C]                              ; CODE XREF=-[SandwichAppDelegate validateSerial:]+419
00001cd7         mov        esi, dword [ebp+var_8]
00001cda         mov        edi, dword [ebp+var_4]
00001cdd         leave
00001cde         ret
                        ; endp

 

Again we get the help of the pseudo code feature of Hopper to simplify the code and turn it into a bit more easy to read form of code. After turning on the psudo code helper the code looks as follows:


validateSerial function pseudo code

/* @class SandwichAppDelegate */
-(bool)validateSerial:(void *)arg2 {
    var_C = ebx;
    var_8 = esi;
    var_4 = edi;
    ebx = arg2;
    if ([ebx length] == 0x13) {
        edi = [ebx componentsSeparatedByString:@"-"];
        if ((([edi count] == 0x4) && 
([[edi objectAtIndex:0x0] length] == 0x4)) &&
([[edi objectAtIndex:0x1] length] == 0x4)) {
if ([[edi objectAtIndex:0x2] length] == 0x4) { if ([[edi objectAtIndex:0x3] length] == 0x4) { esi = [[edi objectAtIndex:0x0] intValue]; esi = [[edi objectAtIndex:0x1] intValue] + esi; eax = [edi objectAtIndex:0x3]; eax = [eax intValue]; eax = 0x19c5 - (SAR(esi, 0x2)) == eax ? 0x1 : 0x0; }else { eax = 0x0; } }else { eax = 0x0; } }else { eax = 0x0; } } else { eax = 0x0; } return eax; }

Now that we have the pseudo code version of the serial validation function it look lots less fightning and we can use it to create a helper app with XCode wich rebuilds the validation function. Then after we have successfully rebuilt the validation function we can turn that in to another function which is able to generate a valid serial - a key generator.

Understanding the validateSerial function

  1. The first interessting line is ebx = arg2 => This is where the serial to check is put into the ebx register. So from now on ebx holds the NSString value of our entered serial.
  2. Right after this comes a check for the length of the entered serial. It has to be exactly 0x13 chars long. 0x13 is hexadecimal and 19 in decimal land. Always remember to know in which "number-land" you are hexadecimal or decimal ( further  octal binary etc.) this may lead to confusion and errors real quick an cause you lot of headaches.
    We can see that if the entered serial is not exactly 19 chars long the function jumps to the end and sets the return register eax to 0x0 / 0. So if we try to debug the app and want the function to succeed we have to supply a serial with this length.
  3. The next interessting line comes right after the length check and is the call to componentsSeparatedByString (a member function of NSString) => Go ahead and search the internet for the documentation of [NSString componentsSeparatedByString]. With the help of the documentation you will find out, that this call returns an array with substrings of the entered which are devided by the provided separator "-". The returned array is assigned to edi
  4. The next line is 3 checks in one if statement. First we check that we have 4 elements in the result array of componentsSeparatedByString. Then we check that the string length of element 0 and 1 is exactly 4 chars. Below that are the checks for the other 2 elements: element number 2 and 3 (why they're seperated / coded this way I don't know) => This string elements of the array also have to be 4 chars long otherways the function exits with return value 0x0 / 0.

    PS: You can now double check the algorithmus by computing the total length of the necessary serial string / length. 4 x 4 chars = 16 + 3 x "-" => 19 chars total => Yes, we are on track!!!
  5. If all this conditions are met we are going to get the int value of the first element (0x0 / 0) and store it into esi. Now we also know that the element has to be an int value - characters are thus not allowed!
  6. Next we are doing the same with the second element of the array (0x1 / 1) - add it to esi and reassign it to esi
  7. Now we get the int value of the last (0x3 / 3) element of the array and do some math with it. Let's deconstruct the statement eax = 0x19c5 - (SAR(esi, 0x2)) == eax ? 0x1 : 0x0;
    1. First the SAR command this is a arithmetic shift of the binary value to the right. 

      "The SAR command corresponds to the SAL command, only the shift direction is reversed. Zeros are added from the left, the bits falling out on the right are lost. Each individual shift corresponds to a division by the number 2, with any remainder of the division being omitted."

      Example:
      MOV AL, 00010000b; load value 16 to AL
      MOVCL, 3; load 3 to CL
      SAR AL, CL; slide AL right three times. The result is 2 (000000l0b) and corresponds to division by the number 2^3 (8)

      => So what SAR does is a division of the first param by 2^second param and then reassign the result to the first param

      => esi = esi / 2^2
    2. We the substract this value from 0x19c5 (6597) and check if this result is equal to eax which is the last part of the serial or the last element of the array. If it is so we assign 0x1 to eax and return from the function indicating the validation function succeeded.
  8. AND VOILA!!! => Now we have everything to rebuild this function in our test project.

 

Rebuilt validateSerial function in Objective-C

- (void)validateSerial{
    
    NSString *serial = _txtSerial.stringValue;
    
    int eax = 0;
    NSString *ebx = serial;
    if ([ebx length] == 0x13) {
        NSArray *edi = [ebx componentsSeparatedByString:@"-"];
        if ((([edi count] == 0x4) && ([[edi objectAtIndex:0x0] length] == 0x4)) && ([[edi objectAtIndex:0x1] length] == 0x4)) {
            if ([[edi objectAtIndex:0x2] length] == 0x4) {
                if ([[edi objectAtIndex:0x3] length] == 0x4) {
                    int esi = [[edi objectAtIndex:0x0] intValue];
                    esi = [[edi objectAtIndex:0x1] intValue] + esi;
                    NSString *eax_2 = [edi objectAtIndex:0x3];
                    eax = [eax_2 intValue];
                    int tmp = (esi / pow(2, 2));
                    int tmp2 = (0x19c5 - tmp);
                    eax = (tmp2 == eax ? 0x1 : 0x0);
                }else {
                    eax = 0x0;
                }
           } else {
               eax = 0x0;
           }
       }else {
           eax = 0x0;
       }
    } else {
        eax = 0x0;
    }
    if(eax == 0x1){
        [self logMsg2Output:[NSString stringWithFormat:@"Serial \"%@\" IS VALID", serial]];
    }else{
        [self logMsg2Output:[NSString stringWithFormat:@"Serial \"%@\" IS NOT VALID", serial]];
    }
}
As you can see in the above Objective-C code I had to adjust the extracted pseudo code slight to get a fully functional source code that runs. The most important thing you always have to do is to Declare the variables for the register values that are moved, assigned and so on. I.e. "NSString *ebx = lic;" => This is where (in the original code) the parameter of the function gets assigned to the ebx register. For ease of use I directly take the stringValue of the NSTextBox and assign it to a new NSString variable which I also name ebx. I used the original register and variable names from the pseudo code in my rebuilt function to make it easier to compare the sources. But somtimes a register in the disassembly gets reassigned with a new value which belongs to another variable. In this case I again use the original register / variable name but append a number (i.e. eax_2) to make it easier to read and still preserve the "original syntax".

New Key-Gen function

- (IBAction)buttonGenerateLicensePressed:(id)sender { 
NSString *license = @"";
int lowerBound = 0;
int upperBound = 9;
int checkSum = 0;

for (int i = 0; i < 4; i++) {
if(i < 3){
NSString *licPart = @"";
for (int j = 0; j < 4; j++) {
int rndValue = lowerBound + arc4random() % (upperBound - lowerBound);
licPart = [NSString stringWithFormat:@"%@%d", licPart, rndValue];
}
if(i == 0){
license = [NSString stringWithFormat:@"%@-", licPart];
}else{
license = [NSString stringWithFormat:@"%@%@-", license, licPart];
}
if(i < 2){
checkSum += [licPart intValue];
}
}else{
checkSum = (checkSum / pow(2, 2));
checkSum = (0x19c5 - checkSum);
license = [NSString stringWithFormat:@"%@%d", license, checkSum];
}
}
[self logMsg2Output:license];
}

 


The Objective-C solution (GUI)


The solution project - in Objective-C - features the rebuilt serial validate function as well as a keygen function to generate a valid key fo every name you want to register and create a valid serial for. Check it out and download the source code or visit the project on my GitHub website.



Interesting Links

  • Other CrackMe Articles
  • ...