I finally got around to trying to solve a crackme for OS X this morning and it was a blast! For those who are interested the crackme can be found on this site (MSJ2009#1.zip). The goal of the crackme (not surprisingly) is to successfully register the application using either a hex edit, serial sniff, or keygen.
Step 1: Recon!
Before I even ran the application I went and dug around int he application package contents. I didn’t find anything all that interesting there so it was time to dig a little deeper. To do this I decided to see what symbols were in the binary using the nm tool.
dean@Atlantis:~/Dropbox/Crackmes/MSJ 2009/Challenge #1.app/Contents/MacOS $ nm Challenge\ #1 Challenge #1 (for architecture i386): 0000251e t -[Level1 applicationDidFinishLaunching:] 00002a94 t -[Level1 applicationShouldTerminateAfterLastWindowClosed:] 00002433 t -[Level1 awakeFromNib] 000026d6 t -[Level1 cancelButton:] 000025ac t -[Level1 continueWelcomeButton:] 000027e3 t -[Level1 emailResults:] 00002a88 t -[Level1 isRegistered] 0000264e t -[Level1 okErrorSheetButton:] 00002692 t -[Level1 okIncorrectSerialButton:] 000025f0 t -[Level1 quitCorrectSerialButton:] 000026f7 t -[Level1 unregisterButton:] 00002a9e t -[Level1 validateSerial:forName:] 0000288e t -[Level1 verifyRegistration:] ...
Great! There is a function just for validation, now all we need to do is to get that to say we’ve passed in valid credentials.
Step 2: Validation
The first step I took in manipulating the validation function is to see what types the function expected. You can get this information using otool as follows.
dean@Atlantis:~/Dropbox/Crackmes/MSJ 2009/Challenge #1.app/Contents/MacOS $ otool -Vo Challenge\ #1... method_name 0x00002d7d validateSerial:forName: method_types 0x00002cb6 c16@0:4@8@12 method_imp 0x00002a9e -[Level1 validateSerial:forName:]...
From this we know that the function expects two objects as arguments and it returns a char indicating whether or not the credentials have been successfully verified. The next step is to see what the heck this function is doing. Once again we use otool to disassemble the binary. In order to save space I’ve only included the parts relevant to this crack.
dean@Atlantis:~/Dropbox/Crackmes/MSJ 2009/Challenge #1.app/Contents/MacOS $ otool -Vt Challenge\ #1...-[Level1 validateSerial:forName:]: 00002a9e pushl %ebp 00002a9f movl %esp,%ebp 00002aa1 pushl %edi 00002aa2 pushl %esi 00002aa3 pushl %ebx 00002aa4 subl $0x3c,%esp 00002aa7 movl 0x00004040,%eax 00002aac movl %eax,0x04(%esp) 00002ab0 movl 0x10(%ebp),%eax 00002ab3 movl %eax,(%esp) 00002ab6 calll 0x0000505e ; symbol stub for: _objc_msgSend 00002abb cmpl $0x08,%eax 00002abe jne 0x00002c4e...00002c4e xorl %edx,%edx 00002c50 addl $0x3c,%esp 00002c53 movl %edx,%eax 00002c55 popl %ebx 00002c56 popl %esi 00002c57 popl %edi 00002c58 leave 00002c59 ret
We see that this function begins as expected with setting up the stack, saving registers it will trash, and allocating space for local variables. The next point of interest is the call to objc_msgSend. This functions takes a variable number of arguments the first of which is the object to send a message too and the second of which is the message to be sent. All remaining arguments to objc_msgSend are passed arguments for the message to the receiver. If you were to set a breakpoint at the beginning of the validate function and run the program in gdb you can find out (by inspecting the memory) that the message being sent is the length message. We can also see that the receiving object is the first parameter (serial number) to the validation function. The next thing to notice is that if the length of the serial number is not eight characters then the function would return. We can leverage the reliance on the serial number string length and the immediate return to cause the function to always return true regardless which serial number is entered.
Step 3: Hex Edit
For the sake of simplicity I decided to solve this crackme using a simple hex edit. In order to do this successfully all we need to know is (1) where to make the change and (2) what to change the code too. The standard calling convention on x86 (32-bit) mandates that return values from a function be placed in %eax, therefore our goal is to modify the value of %eax just before the validate function exits. To do this I chose to change the xorl at address 0x2c4e to a movl instruction that placed a 0×1 in %edx. You can make this change by opening the binary in your favourite hex editor and then changing the bytes at 0x2c4e to ba01000000. Be sure to shift the remaining bytes in the function over to make space for the additional bytes. Assuming the changes were made properly the next time you run the program it should register your serial number without a problem.