Universe's best and legal Mac OS X reversing tutorial for newbies (or maybe not!) ------------------------------------------------------------------------------ (c) 2011 Fractal Guru (reverse AT put.as , http://reverse.put.as) Target: Macserialjunkie.com Cracking Challenge 09 #1 Tools used: OTX, GDB, 0xED, gcc Platform: Mac OS X Leopard 10.6.5 @ Intel x86 Document version: 0.1 (12/02/2011) Index: 0 - Introduction 1 - Building our toolkit 2 - How to use our tools 2.1 - OTX 2.2 - GDB 2.3 - Putting otx and gdb together 3 - Reversing and cracking Challenge #1 3.0 - Introduction and workflow 3.1 - Patching the binary 3.2 - Fishing a valid serial number 3.3 - Keygen 4 - Conclusion 0 - Introduction ---------------- Update from the original version: Reversing and breaking protections is a great hobby and fantastic knowledge to possess. The problem is that many abuse this and want to profit from it. I really don't like not sharing knowledge because sharing also allows me to progress, seeking new challenges and learning new things. I really hope that you make good use of this information and do not share your cracks with the world, especially in MSJ that is full of idiots just wanting to rip off others work. Don't do that please. Don't make me regret once again releasing knowledge that may ease piracy! Enjoy the process, learn, get frustrated, and buy the apps if you really use them in your day to day. This tutorial is still based on 32bit binaries. Have fun, fG! ---- One of the most difficult tasks is to write a tutorial for beginners. It's not an easy task so here's an attempt to create one that can launch people with some basic knowledge into the world of reverse engineering (I consider cracking a subset of reverse engineering, and a very useful one as a learning platform). It's assumed you have basic x86 assembly knowledge (already too many good tutorials about this!). Some URLs: http://www.woodmann.com/crackz/Getstart.htm http://www.uc-forum.com/forum/programming-beginners/63947-reverse-engineering-beginners-guide-x86-assembly-and-debugging-windows-apps.html http://en.wikipedia.org/wiki/Assembly_language The term "function" will be used alot. If you know Objective-C or C++, you know it's not entirely correct to use it. Method would be more correct in this context. But some parts of this tutorial can be used to reverse other languages where the term function is correct. It shouldn't be a big deal for you to handle. A word of caution: reversing/cracking is about exploring and thinking. You should get used to think and explore problems and find solutions for them. These days, Google and other search engines are your main friend and they can make your task much easier ! Get used to search, think and explore ! That's the beauty of Reverse Engineering, diving into the unknown ! And now, let's start the fun ! fG! 1 - Building our toolkit -------------------------- The first step is to build our reversing toolkit. For me, two tools are essential, a disassembler and a debugger (especially this one!). There are three available disassemblers and two debuggers. In disassemblers we have IDA Pro, Otool and OTX. IDA is the most famous and powerful but it's paid (there is a demo version available (HexRays released a native OS X demo version!), and a warez version is around of course) and it's expensive. If you are serious to RE field and can buy it, do it ! If your company can buy it, ask them to buy it. It's worth the money! An excellent book about IDA is "The IDA Pro Book: The Unofficial Guide to the World's Most Popular Disassembler" by Chris Eagle. Buy it if you can (it's not that expensive and author deserves it!). The other two options are technically just one, since OTX is a frontend for Otool. OTX is available at: http://otx.osxninja.com/ Otool is part of XCode, available at: http://developer.apple.com/ (open an account, it's free!) GDB is part of XCode, so you should download both. The available debuggers are GDB and IDA (the debugger is integrated with the disassembler). GDB is free and part of XCode. This tutorial will use GDB since it's faster to use (because IDA uses remote debugging, meaning you will need two machines to debug) and it's capable to do everything we need for this tutorial and any future uses you may have. To make GDB even more easier to use, you should grab gdbinit. This is a script for GDB that will enhance it's output and has macros to make our work easier and faster. Grab my modified version here: http://reverse.put.as/wp-content/uploads/2010/04/gdbinit73 To install gdbinit, you will need to copy it into your home folder with the name ".gdbinit". For example, if you have downloaded the file gdbinit73 into your download folders, you can install it using Terminal.app with the following command: cp ~/Downloads/gdbinit73 ~/.gdbinit ~ in Unix means your home folder. There is a bug in Apple GDB version. You can read about it here: http://reverse.put.as/2008/11/28/apples-gdb-bug/ It's annoying and not a big obstacle to our work, and it's useful to fix it. You might also want to give a look at http://reverse.put.as/2009/08/26/gdb-patches/ , which features other patches. The next tool is an Hex Editor. I use 0xEd, available at http://www.suavetech.com/0xed/0xed.html. Hex-Fiend is another good alternative (http://ridiculousfish.com/hexfiend/) You should be able to install everything without any problem. To resume, our basic reversing toolkit is composed of gdb, OTX/otool/IDA and 0xED/Hex-Fiend. 2 - How to use our tools ------------------------ 2.0 - Updating OTX ------------------ The binary version of OTX doesn't support 64bit binaries, so you should download the version from the SVN repository. The information is available here: http://otx.osxninja.com/subinfo.html You will need XCode to compile the project. 2.1 - OTX --------- Run OTX and you will get the program window. We need to open the binary file we want to disassemble. Open a Terminal.app windows (yes I really love Terminal, some things are done faster and better thru the command line) and go to the folder where you have the Cracking Challenge #1 application. List all available files with "ls" command. You should see a folder named Challenge #1.app. This is our target. Mac OS X programs have a nice program structure, where everything (almost) is contained into a single folder. Using Challenge #1.app as an example, we have the following structure inside it: Challenge\ #1.app/Contents/ Then we have the following folders: Info.plist MacOS PkgInfo Resources You can find the main binary inside the MacOS folder. This is where we should start. Frameworks folder (not present in this binary) might have interesting binaries to disassemble because some protections can reside there instead in the main binary. Listing the MacOS folder gives us: $ ls MacOS/ Challenge #1 Challenge #1 is the binary we want to disassemble. The full path is: Challenge\ #1.app/Contents/MacOS/Challenge #1 Some information from the binary can be extracted with the "file" command or otool. To see if this is a fat binary (contains more than 1 architecture), you can use the following command: $ file Challenge\ #1.app/Contents/MacOS/Challenge\ #1 Challenge #1.app/Contents/MacOS/Challenge #1: Mach-O universal binary with 2 architectures Challenge #1.app/Contents/MacOS/Challenge #1 (for architecture i386): Mach-O executable i386 Challenge #1.app/Contents/MacOS/Challenge #1 (for architecture ppc): Mach-O executable ppc The equivalent otool command is: $ otool -h Challenge\ #1.app/Contents/MacOS/Challenge\ #1 Challenge #1.app/Contents/MacOS/Challenge #1 (architecture i386): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 0xfeedface 7 3 0x00 2 19 2356 0x00000085 Challenge #1.app/Contents/MacOS/Challenge #1 (architecture ppc): Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 0xfeedface 18 0 0x00 2 17 2412 0x00000085 So this binary contains two architectures, x86 32 bits and PowerPC. Let's try to disassemble the x86 version. Select Open File in OTX and select that binary. You should select x86 as processor (it's the default). You might change the output name or just leave the default. Click Save and select where to save (usually Desktop or select one folder dedicated to your reversing project to have things organized). If you can also use the otx command line version (I have installed mine at /usr/local/bin). I usually use the following command "otx Challenge #1 >dump.txt". And voila, you have disassembled your first binary. Very simple ! The output file is the disassembled listing of the selected binary, and it will be our main guide into reversing the target. 2.2 - GDB --------- Gdb is a very powerful debugger although not easy and not intuitive as Windows equivalents like OllyDbg or Softice (well, Softice as also text only). Nevertheless you can master it and do everything you should need for your RE projects. Let's give it a shot and introduce you the world of GDB ! Just a little note on the commands to be used: 1) Commands issued inside gdb will always use the following prompt: gdb$ 2) Commands issued in a Terminal.app shell will always use the following prompt: shell$ To learn gdb we are going to use a simpler target so we can understand the basic commands. You will need to compile the following program example.c: ------------------- CUT HERE ----------------- #include main(int argc, char *argv[]) { printf("Hello GDB!\n"); printf("Argument is: %s\n", argv[1]); } ------------------- CUT HERE ----------------- Save this source code somewhere and compile it with (if you have called it example.c, else modify the name): $ gcc -arch i386 -o example example.c Note: The -arch i386 option is required to compile the binary in 32bits instead of the default 64bits in Snow Leopard. This small program will print 2 lines, where the second prints the argument from the command line. Example: $ ./example Test Hello GDB! Argument is: Test GDB runs from the command line, so you will need to open a Terminal.App (at this moment you should already have a Terminal.app shortcut into your Dock hehehe). To start gdb, just type "gdb" at the prompt and press enter. You should get something like this (date should be different since this one was compiled by me on January): GNU gdb 6.3.50-20050815 (Apple version gdb-768) (Fri Jan 23 17:22:29 UTC 2009) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-apple-darwin". gdb$ To make sure gdbinit is installed correctly, type "help user". You should get a list of available commands, the ones created by gdbinit script. There are two different ways to debug a program, one is to attach to a version already running and the other one to start the program from gdb. To attach you will need the PID (process ID) for your target. You can find it by issuing a "ps aux" command (or in Activity Monitor). The PID is the number in the second column. After you have the PID, you use the "attach " gdb command. To start the program from gdb, you can use the "exec-file " (this is the best way to overcome the gdb bug described earlier, if you don't have a patched version). If you need to set parameters to the executable (usually not needed for our targets), you can use "set args" command or set the arguments when you start the program with "run" command. Practical example: To start debugging our example code, open a command prompt and then type the following commands: $gdb (gdb is loaded) gdb$ exec-file "PATH/example" (substitute PATH for the full path where our example binary is) or $cd "PATH" (substitute PATH for the full path where our example binary is) $gdb (gdb is loaded) gdb$ exec-file ./example In the first example we are using the full path to our binary, in the second example we change into the correct directory and just point to the binary. It's a matter of personal taste (you can use TAB completation inside gdb!). The basic commands we need are related to breakpoints, stepping, change flags or memory, dump/evaluate memory locations. What is a breakpoint ? From Wikipedia (http://en.wikipedia.org/wiki/Breakpoint): "A breakpoint, in software development, is an intentional stopping or pausing place in a program, put in place for debugging purposes. More generally, a breakpoint is a means of acquiring knowledge about a program during its execution. During the interruption, the programmer inspects the test environment (logs, memory, files, etc.) to find out whether the program functions as expected. In practice, a breakpoint consists of one or more conditions that determine when a program's execution should be interrupted." The breakpoint related commands interesting to us are: 1) bp/b (set a breakpoint) You can set a breakpoint on a memory location (most used) or in a symbol (if GDB knows about it). For a memory breakpoint you just need to use the memory location where you want the program to stop. Example: gdb$ bp *0x1234 This will set a breakpoint on memory location 0x1234 (you need to use the * before the address). You should have guessed that 0x is the format for hexadecimal number. If program execution reaches that memory location, program execution will be interrupted and you will get back to gdb prompt! Setting a breakpoint on a symbol is equivalent to use a name instead a memory location. Usually it's a function from a library or some other symbol that GDB can solve. An example is: gdb$bp [NSControl stringValue] This means whenever the program calls the stringValue function gdb will halt it's execution and return control to us. This function can allow you to break on text input routines, so you can for example fish a valid serial. 2) bpl (list all breakpoints) This command will list all breakpoints active or inactive. Example: gdb$ bpl Num Type Disp Enb Address What 1 breakpoint keep y 0x00001f44 2 breakpoint keep n 0x00001f46 3) bpd/bpe (disable/enable a breakpoint) This will enable or disable a breakpoint. You should use the Num column from bpl output to select which one to enable or disable. Example: gdb$ bpd 1 gdb$ bpl Num Type Disp Enb Address What 1 breakpoint keep n 0x00001f44 <- we have disabled this one as you can see 2 breakpoint keep n 0x00001f46 4) bpc (clear breakpoint) This will clear a breakpoint. Unlike bpe/bpd, you should use the memory location to remove from breakpoint list (same syntax as bp command). Example: gdb$ bpc *0x1f44 gdb$ bpl Num Type Disp Enb Address What 2 breakpoint keep n 0x00001f46 You can also use the delete command to clear breakpoints by number (bpc is a gdbinit command, which uses clear instead delete [maybe to be modified in the future]). Example: gdb$ delete 2 gdb$ bpl No breakpoints or watchpoints. Breakpoints are done, let's go to the step commands. Step commands will allow you to "browse" and run the assembly code for your target. All step commands allow you to advance to the next address pointed by EIP (meaning, executing the code pointed by EIP). But there is a distinction to be made. Some commands allow you to go into functions or subroutines, usually "calls". Making it easier to understand, if you find a call to another function and want to see what happens inside that function, you can step into that function and trace it, and then you get back to where you were (in reality you get back to the address after the call you traced). Or you maybe want to skip that call and just execute it without getting into it's details. The functions I regularly use are nexti (or it's shortcut ni) and stepo (if I want to skip over calls or subroutines). These should be enough for your reversing efforts. Step commands interesting to us are (this is just a dump of help from gdb, should be enough): 1) next gdb$ help next Step program, proceeding through subroutine calls. Like the "step" command as long as subroutine calls do not happen; when they do, the call is treated as one instruction. Argument N means do this N times (or till program stops for another reason). 2) nexti gdb$ help nexti Step one instruction, but proceed through subroutine calls. Argument N means do this N times (or till program stops for another reason). 3) step gdb$ help step Step program until it reaches a different source line. Argument N means do this N times (or till program stops for another reason). 4) stepi gdb$ help stepi Step one instruction exactly. Argument N means do this N times (or till program stops for another reason). 5) stepo gdb$ help stepo Step over calls (interesting to bypass the ones to msgSend) This function will set a temporary breakpoint on next instruction after the call so the call will be bypassed You can safely use it instead nexti or n since it will single step code if it's not a call instruction (unless you want to go into the call function) Try to play with these step commands and see what are the differences between them. Like I said, nexti and stepo should be enough! The dump/evaluate commands will allow you to dump the contents of memory, cpu registers, variables, pointers, etc. Dump/evaluate commands are: 1) x gdb$ help x Examine memory: x/FMT ADDRESS. ADDRESS is an expression for the memory address to examine. FMT is a repeat count followed by a format letter and a size letter. Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char) and s(string), T(OSType). Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes). The specified number of objects of the specified size are printed according to the format. Defaults for format and size letters are those previously used. Default count is 1. Default address is following last thing printed with this command or "print". "x" will allow you to examine memory addresses and registers. It allows you to use formats for the output (something like printf). Usually you will want to use the "x" format (hexadecimal) and "s" format (string). For example, to dump EAX register contents in hexadecimal you would use gdb$ x/x $eax Check this live example, using our example.c code: gdb$ 0x00001fb9 in main () --------------------------------------------------------------------------[regs] EAX: 00001FE1 EBX: 00001FB2 ECX: BFFFF848 EDX: 00000000 o d I t S z a p c ESI: 00000000 EDI: 00000000 EBP: BFFFF828 ESP: BFFFF810 EIP: 00001FB9 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFF810]----------------------------------------------------------[stack] BFFFF860 : D2 F9 FF BF F1 F9 FF BF - 01 FA FF BF 3B FA FF BF ............;... BFFFF850 : 33 F9 FF BF 6C F9 FF BF - 88 F9 FF BF C1 F9 FF BF 3...l........... BFFFF840 : 00 00 00 00 01 00 00 00 - FC F8 FF BF 00 00 00 00 ................ BFFFF830 : 01 00 00 00 48 F8 FF BF - 50 F8 FF BF BC F8 FF BF ....H...P....... BFFFF820 : 00 10 00 00 BC F8 FF BF - 40 F8 FF BF 7A 1F 00 00 ........@...z... BFFFF810 : 00 00 00 00 00 00 00 00 - 3C F8 FF BF 37 10 E0 8F ........<...7... [0017:00001FB9]-----------------------------------------------------------[code] 0x1fb9 : mov DWORD PTR [esp],eax 0x1fbc : call 0x300a 0x1fc1 : mov eax,DWORD PTR [ebp+0xc] 0x1fc4 : add eax,0x4 0x1fc7 : mov eax,DWORD PTR [eax] 0x1fc9 : mov DWORD PTR [esp+0x4],eax 0x1fcd : lea eax,[ebx+0x3a] 0x1fd3 : mov DWORD PTR [esp],eax -------------------------------------------------------------------------------- gdb$ x/s $eax 0x1fe1 : "Hello GDB!" We just printed the string that was pointed by EAX register. Still using the same example, if you increase count to 2 (x/2s), the next string is printed too. gdb$ x/2s $eax 0x1fe1 : "Hello GDB!" 0x1fec : "Argument is: %s\n" 2) print This one is used more or less like "x". Usually used when you have the source, so you can print the program variables. Check it's help on gdb. 3) po gdb$ help po Ask an Objective-C object to print itself. This command will allow you to print Objective-C objects. This command is very handy when you are debugging Objective-C and you want to print the object. You can't simply see an object by using the "x" command. Little example: Breakpoint in 0x30001acf +548 30001acf 890424 movl %eax,(%esp,1) <- NSBundle (loaded) +551 30001ad2 e82eb50300 calll 0x3003d005 _objc_msgSend In gdb: gdb $ x/s $eax 0x30a9c0: "?c??" gdb $ po $eax NSBundle (loaded) You can see the object where eax is pointing too. Much better than the first x/s $eax Change flags or memory commands are: 1) cfX, where X can be a,c,d,i,o,p,s,t,z,s All the cfX commands are from gdbinit. These will allow you to change the cpu register flags (they will invert the current flag state). For example, the JE (jump if equal) assembler instruction, will be followed (meaning the code will jump to the new location) only if the Zero flag (ZF or Z) is equal to 1. If the next instruction to be executed is a JE and the Zero flag is equal to 0, there will be no jump. But if you want to easily force the jump, then you just need to modify the Zero Flag and make it equal to one. You can simply do this by using the cfz command, like gdb$ cfz This would change the value for ZF from 0 to 1. Or maybe you don't want the jump to be followed (ZF=1) and so you issue cfz to reverse ZF to 0. The different cfX commands are: cfa -- Change Auxiliary Carry Flag cfc -- Change Carry Flag cfd -- Change Direction Flag cfi -- Change Interrupt Flag cfo -- Change Overflow Flag cfp -- Change Parity Flag cfs -- Change Sign Flag cft -- Change Trap Flag cfz -- Change Zero Flag 2) set The set command has many available subcommands. We are interested in changing memory contents and/or registers contents. To change memory location the syntax is: gdb$ set *address = newvalue , where address is in the usual hexadecimal format. The memory location can be program code (if you want for example to live patch the program) or any other memory location (for example an address holding a variable). If you want to change a register content, for example EAX, the syntax is: gdb$ set $eax = newvalue You can use complex expressions with set, for example type casting. For example if you want to write a single byte you could use: gdb$ set $eax = (char) 0x12345 gdb$ print $eax $3 = 0x45 gdb$ set $eax = (int) 0x12345 gdb$ print $eax $4 = 0x12345 Do you understand what happened with this example ? Think about it (should be easy to understand!). 2.3 - Putting otx and gdb together ---------------------------------- Let's play a little bit with gdb so you can watch a debugging session. Disassemble the example binary with otx. You should have an output more or like this (I have added line numbers for easy reference here!): md5: 27cc7b61ed3322057d52b6b014d589e6 (__TEXT,__text) section (Uninteresting code here for our purposes, we are just interested in the main function) _main: 1: +0 00001fa6 55 pushl %ebp 2: +1 00001fa7 89e5 movl %esp,%ebp 3: +3 00001fa9 53 pushl %ebx 4: +4 00001faa 83ec14 subl $0x14,%esp 5: +7 00001fad e800000000 calll 0x00001fb2 6: +12 00001fb2 5b popl %ebx 7: +13 00001fb3 8d832f000000 leal 0x0000002f(%ebx),%eax Hello GDB! 8: +19 00001fb9 890424 movl %eax,(%esp) 9: +22 00001fbc e849100000 calll 0x0000300a _puts 10: +27 00001fc1 8b450c movl 0x0c(%ebp),%eax 11: +30 00001fc4 83c004 addl $0x04,%eax 12: +33 00001fc7 8b00 movl (%eax),%eax 13: +35 00001fc9 89442404 movl %eax,0x04(%esp) 14: +39 00001fcd 8d833a000000 leal 0x0000003a(%ebx),%eax Argument is: %s\n 15: +45 00001fd3 890424 movl %eax,(%esp) 16: +48 00001fd6 e82a100000 calll 0x00003005 _printf 17: +53 00001fdb 83c414 addl $0x14,%esp 18: +56 00001fde 5b popl %ebx 19: +57 00001fdf c9 leave 20: +58 00001fe0 c3 ret Remember that our source version looks like: main(int argc, char *argv[]) { printf("Hello GDB!\n"); printf("Argument is: %s\n", argv[1]); } You can easily see that the compiler used "puts" for our first printf and used printf for our second. This was a compiler optimization. Compilers usually optimized your source code and use the best options available to make your code shorter and faster. Since our first printf doesn't have any format string being used, it can be replaced by a shorter and faster function at compiler level, puts, and still do what we wanted in our source code, to print a simple "Hello GDB!" message. Let's start debugging our little program. Open gdb and start our example program. shell$ gdb GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-apple-darwin". gdb$ exec-file ./example Reading symbols for shared libraries ... done gdb$ If you type the run command, our program will run and exit, since we haven't set any breakpoint yet. Let's give it a try: gdb$ run Reading symbols for shared libraries .++. done Hello GDB! Argument is: (null) Program exited with code 024. --------------------------------------------------------------------------[regs] EAX:Error while running hook_stop: No registers. gdb$ You can easily see our messages printed. Let's try running our program with an argument: gdb$ run TESTING Hello GDB! Argument is: TESTING Program exited with code 025. --------------------------------------------------------------------------[regs] EAX:Error while running hook_stop: No registers. gdb$ Now let's set our first breakpoint. We want to breakpoint the program before the first message "Hello GDB!" is printed. We already know that the function responsible to print that first message is "puts", and you can find the call to this function at line 9. Let's explain this line contents: 9: +22 : local offset, meaning the offset inside this function (it's not interesting to us and you can configure otx to remove this) 00001fbc : code address (hexadecimal format), this is the address we are going to use for our breakpoint(s) e849100000 : opcodes, these are the bytes your cpu will read and form your instructions (and the ones we can modify to patch the code) calll 0x0000300a : this is the assembly mnemonic correspondent to the previous opcode bytes _puts : this is additional information that otx was able to identify, in this case it identified we are going to call the "puts" function Hopefully you have understood otx output. So if we want to break on line 9, we should use memory address 0x1fbc. To be honest, since we have the source code, we could use source file line reference to create the breakpoint but since we usually don't have source for our targets, this will not be explored. Set the breakpoint: gdb$ bp *0x1fbc Breakpoint 1 at 0x1fbc gdb$ And list: gdb$ bpl Num Type Disp Enb Address What 1 breakpoint keep y 0x00001fbc gdb$ Our first breakpoint is set, we can run again our program (you should already have noted that we can run again and again the program after we have loaded it into gdb). Let's go... gdb$ run Breakpoint 1, 0x00001fbc in main () --------------------------------------------------------------------------[regs] EAX: 00001FE1 EBX: 00001FB2 ECX: BFFFF848 EDX: 00000000 o d I t S z a p c ESI: 00000000 EDI: 00000000 EBP: BFFFF828 ESP: BFFFF810 EIP: 00001FBC CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFF810]----------------------------------------------------------[stack] BFFFF860 : D2 F9 FF BF F1 F9 FF BF - 01 FA FF BF 3B FA FF BF ............;... BFFFF850 : 33 F9 FF BF 6C F9 FF BF - 88 F9 FF BF C1 F9 FF BF 3...l........... BFFFF840 : 00 00 00 00 01 00 00 00 - FC F8 FF BF 00 00 00 00 ................ BFFFF830 : 01 00 00 00 48 F8 FF BF - 50 F8 FF BF BC F8 FF BF ....H...P....... BFFFF820 : 00 10 00 00 BC F8 FF BF - 40 F8 FF BF 7A 1F 00 00 ........@...z... BFFFF810 : E1 1F 00 00 00 00 00 00 - 3C F8 FF BF 37 10 E0 8F ........<...7... [0017:00001FBC]-----------------------------------------------------------[code] 0x1fbc : call 0x300a 0x1fc1 : mov eax,DWORD PTR [ebp+0xc] 0x1fc4 : add eax,0x4 0x1fc7 : mov eax,DWORD PTR [eax] 0x1fc9 : mov DWORD PTR [esp+0x4],eax 0x1fcd : lea eax,[ebx+0x3a] 0x1fd3 : mov DWORD PTR [esp],eax 0x1fd6 : call 0x3005 -------------------------------------------------------------------------------- gdb$ This is the output you will get. GDB did it's job correctly and program stopped at address 0x1fbc as we wanted. There are two areas of output interesting to us. The first one is [regs] and the seconde [code]. In [regs] you can find the current state of cpu registers and flags at breakpoint moment. You should know the meaning of each register ;) In [code] you have disassembly output for the current memory address. The first line is always the next line code to be executed. If you compare otx disassemble output and gdb output, you will see there are cosmetic differences. These are due to otx using AT&T syntax and gdb is configured for Intel syntax. You can modify gdb to use AT&T syntax if you aren't comfortable with different syntaxes. Refer to http://www.redhat.com/docs/manuals/enterprise/RHEL-3-Manual/gnu-assembler/i386-syntax.html if you are interested in understanding differences between the two. At this moment, you can do various operations. For example, you can dump the contents of EAX register or any other memory location. Or you can step the code to understand it, or simply let the program continue running without any further interference. If you use "c" or "continue" commands, program will continue running and eventually end. Let's try. gdb$ c Hello GDB! Argument is: (null) Program exited with code 024. --------------------------------------------------------------------------[regs] EAX:Error while running hook_stop: No registers. gdb$ Since our breakpoint is still set, we can run the program again and it will stop again at the same place. Try it... Assuming you did tried, we are back to our breakpoint. We want to follow and understand the code, so we are going to step each line of code. Since the next instruction to be executed is a call, you know we can step over it (meaning we don't want to analyse what "puts" function will do) or we can step into it (meaning we want to analyse what "puts" is going to do). For this example, we are not interested in understanding how "puts" work (since we already know it will just print characters) and just want it to be executed. We can use either "n", "ni", "nexti", "stepo" gdb commands. If you use "stepi" you will get inside "puts" function. The safest bet is to use "stepo" if you want to skip over calls. Let's try... gdb$ stepo Breakpoint 3 at 0x1fc1 <----------- you can ignore this, it's a temporary breakpoint used by stepo Hello GDB! <----------- OUR MESSAGE WAS PRINTED Breakpoint 3, 0x00001fc1 in main () --------------------------------------------------------------------------[regs] EAX: 0000000A EBX: 00001FB2 ECX: 0000000B EDX: 00000000 o d I t S z a P c ESI: 00000000 EDI: 00000000 EBP: BFFFF828 ESP: BFFFF810 EIP: 00001FC1 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFF810]----------------------------------------------------------[stack] BFFFF860 : D2 F9 FF BF F1 F9 FF BF - 01 FA FF BF 3B FA FF BF ............;... BFFFF850 : 33 F9 FF BF 6C F9 FF BF - 88 F9 FF BF C1 F9 FF BF 3...l........... BFFFF840 : 00 00 00 00 01 00 00 00 - FC F8 FF BF 00 00 00 00 ................ BFFFF830 : 01 00 00 00 48 F8 FF BF - 50 F8 FF BF BC F8 FF BF ....H...P....... BFFFF820 : 00 10 00 00 BC F8 FF BF - 40 F8 FF BF 7A 1F 00 00 ........@...z... BFFFF810 : E1 1F 00 00 00 00 00 00 - 3C F8 FF BF 37 10 E0 8F ........<...7... [0017:00001FC1]-----------------------------------------------------------[code] 0x1fc1 : mov eax,DWORD PTR [ebp+0xc] 0x1fc4 : add eax,0x4 0x1fc7 : mov eax,DWORD PTR [eax] 0x1fc9 : mov DWORD PTR [esp+0x4],eax 0x1fcd : lea eax,[ebx+0x3a] 0x1fd3 : mov DWORD PTR [esp],eax 0x1fd6 : call 0x3005 0x1fdb : add esp,0x14 -------------------------------------------------------------------------------- gdb$ What we have here ? Next code to be executed is line 10: and we can clearly see that "puts" function was executed (Hello GDB! message is displayed!). As before, you can do whatever operations you want now. You can continue stepping thru the code, set more breakpoints, dump memory/registers, continue the program, etc... As an exercise, now we want to stop at line 13. How many alternatives do you have to do that ? At least 2 ! Breakpoint on address correspondent to line 13 or step thru the code until you reach line 13. You should reach something like this: gdb$ 0x00001fc9 in main () --------------------------------------------------------------------------[regs] EAX: 00000000 EBX: 00001FB2 ECX: 0000000B EDX: 00000000 o d I t S z a p c ESI: 00000000 EDI: 00000000 EBP: BFFFF828 ESP: BFFFF810 EIP: 00001FC9 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFF810]----------------------------------------------------------[stack] BFFFF860 : D2 F9 FF BF F1 F9 FF BF - 01 FA FF BF 3B FA FF BF ............;... BFFFF850 : 33 F9 FF BF 6C F9 FF BF - 88 F9 FF BF C1 F9 FF BF 3...l........... BFFFF840 : 00 00 00 00 01 00 00 00 - FC F8 FF BF 00 00 00 00 ................ BFFFF830 : 01 00 00 00 48 F8 FF BF - 50 F8 FF BF BC F8 FF BF ....H...P....... BFFFF820 : 00 10 00 00 BC F8 FF BF - 40 F8 FF BF 7A 1F 00 00 ........@...z... BFFFF810 : E1 1F 00 00 00 00 00 00 - 3C F8 FF BF 37 10 E0 8F ........<...7... --------------------------------------------------------------------[ObjectiveC] 0x0:
[0017:00001FC9]-----------------------------------------------------------[code] 0x1fc9 : mov DWORD PTR [esp+0x4],eax <---- first mov instruction 0x1fcd : lea eax,[ebx+0x3a] 0x1fd3 : mov DWORD PTR [esp],eax <---- 2nd mov instruction 0x1fd6 : call 0x3005 0x1fdb : add esp,0x14 0x1fde : pop ebx 0x1fdf : leave 0x1fe0 : ret -------------------------------------------------------------------------------- gdb$ For now you can ignore the ObjectiveC part. The code for our printf function is: printf("Argument is: %s\n", argv[1]); You can easily see that we have two arguments passed onto printf function, "Argument is: %s\n" and argv[1]. You should already know that arguments are passed into the stack (ESP) in reverse order, from last to first. So this first mov instruction is moving the second argument into the stack and the second mov is moving the first argument. You should be able to identify that in this case the was no argument to "run" command since EAX is empty. If you have used a parameter (or set args command) EAX would hold it's contents. Try it ! When you stop there, use the "x" command to dump EAX contents, like this: (I used run TESTING) gdb$ x/s $eax 0xbffff923: "TESTING" You can step to line 15 (0x1fd3 address) and dump again the contents of EAX. You should see the first argument to printf function. And that's it ! The basics are covered. You should now know how to set breakpoints, step code and dump memory/register contents. 3 - Reversing and cracking Challenge #1 --------------------------------------- 3.0 - Introduction and workflow ------------------------------- The first thing to do is reconnaissance. We need to understand what are the program limits and what messages (if any!) are being displayed about those limits. If we have an error message like "Bad serial", "Trial is expired" or something like this, the next step is to check if this message is present in the program binaries in plain text, which can give us fast clues where we should start our work. The best tool for this job is "grep". If you have Unix experience you should already know it, else you are about to be introduced. We want to grep all files belonging to our application. Best way is like this: shell$ cd Challenge\ #1.app/Contents/ shell$ grep -r -i "message" * (-r means recursive and -i case insensitive) When you start Challen #1 for the first time you have a message telling you about the goals of this crackme. Continue and insert a random name and a random serial. You get an error message: "The name and serial number combination you entered is incorrect." Try to search for that one... There is one hit at "Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib". shell$ grep -r -i "you entered is incorrect" * Binary file Challenge #1.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib matches This means that there are no direct references in the main binary to the string, so we need to use other methods. Another classic way is to open the disassembly listing and search for methods with interesting names. If you browse the disassembly for this challenge, you will find an interesting string "isRegistered". Unfortunately, many developers for OS X use names like this for their registration/protection code, so it's very easy to track them. The interesting place where you find this is here: -(void)[Level1 applicationDidFinishLaunching:] +0 0000251e 55 pushl %ebp +1 0000251f 89e5 movl %esp,%ebp +3 00002521 53 pushl %ebx +4 00002522 83ec24 subl $0x24,%esp +7 00002525 8b5d08 movl 0x08(%ebp),%ebx +10 00002528 a110400000 movl 0x00004010,%eax isRegistered +15 0000252d 891c24 movl %ebx,(%esp) +18 00002530 89442404 movl %eax,0x04(%esp) +22 00002534 e8252b0000 calll 0x0000505e -[(%esp,1) isRegistered] +27 00002539 84c0 testb %al,%al +29 0000253b 742b je 0x00002568 From Apple's documentation available at http://developer.apple.com/library/mac/#documentation/cocoa/reference/NSApplicationDelegate_Protocol/Reference/Reference.html you get the following about applicationDidFinishLaunching: Delegates can implement this method to perform further initialization. This method is called after the applicationŐs main run loop has been started but before it has processed any events. If the application was launched by the user opening a file, the delegateŐs application:openFile: method is called before this method. If you want to perform initialization before any files are opened, implement the applicationWillFinishLaunching: method in your delegate, which is called before application:openFile:.) This is a good place to start in Cocoa apps, because many developers start checking here if trials are ok, if serial numbers are ok, etc. This is more or less the behaviour we have experienced with the crackme, because we got that initial message telling us about what we need to do (we can speculate that the message will not appear if the crackme is registered successfully). Getting back to that piece of code: -(void)[Level1 applicationDidFinishLaunching:] +0 0000251e 55 pushl %ebp +1 0000251f 89e5 movl %esp,%ebp +3 00002521 53 pushl %ebx +4 00002522 83ec24 subl $0x24,%esp +7 00002525 8b5d08 movl 0x08(%ebp),%ebx +10 00002528 a110400000 movl 0x00004010,%eax isRegistered +15 0000252d 891c24 movl %ebx,(%esp) +18 00002530 89442404 movl %eax,0x04(%esp) +22 00002534 e8252b0000 calll 0x0000505e -[(%esp,1) isRegistered] <- call the routine +27 00002539 84c0 testb %al,%al +29 0000253b 742b je 0x00002568 <- if it's not registered, then it will show the bad message +31 0000253d a10c500000 movl 0x0000500c,%eax +36 00002542 8b10 movl (%eax),%edx +38 00002544 c744241800000000 movl $0x00000000,0x18(%esp) +46 0000254c c744241400000000 movl $0x00000000,0x14(%esp) +54 00002554 c744241000000000 movl $0x00000000,0x10(%esp) +62 0000255c 8b430c movl 0x0c(%ebx),%eax (id)mainWindow +65 0000255f 8944240c movl %eax,0x0c(%esp) +69 00002563 8b4314 movl 0x14(%ebx),%eax (id)registeredSheet <- the good message +72 00002566 eb29 jmp 0x00002591 What we got here is a classical (and very weak) test for available registration or not. If the function isRegistered returns true, then the good message will be shown, else the bad one will be shown since the program isn't registered. Since that je is conditional we have two possible solutions for a crack: 1) Nop the conditional jump: JE to NOP 2) Patch the isRegistered routine to return always true The first alternative is very easy to test with gdb, even before patching anything. Load the crackme into gdb and set a breakpoint at 0x0000253b (the address of the JE). Sample session: $ gdb Challenge\ #1 GNU gdb 6.3.50-20050815 (Apple version gdb-1344) (Mon Dec 28 15:21:35 UTC 2009) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries ......b .. done gdb$ b *0x0000253b Breakpoint 1 at 0x253b gdb$ r Reading symbols for shared libraries .+++++++.................................................................................. done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Breakpoint 1, 0x0000253b in -[Level1 applicationDidFinishLaunching:] () --------------------------------------------------------------------------[regs] EAX: 0x00000000 EBX: 0x00415CC0 ECX: 0x00000001 EDX: 0x00000000 o d I t s Z a P c ESI: 0xBFFFEA50 EDI: 0x00000002 EBP: 0xBFFFEA18 ESP: 0xBFFFE9F0 EIP: 0x0000253B CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F Jump is taken (z=1) --------------------------------------------------------------------------[code] 0x253b: 74 2b je 0x2568 0x253d: a1 0c 50 00 00 mov eax,ds:0x500c 0x2542: 8b 10 mov edx,DWORD PTR [eax] 0x2544: c7 44 24 18 00 00 00 00 mov DWORD PTR [esp+0x18],0x0 0x254c: c7 44 24 14 00 00 00 00 mov DWORD PTR [esp+0x14],0x0 0x2554: c7 44 24 10 00 00 00 00 mov DWORD PTR [esp+0x10],0x0 0x255c: 8b 43 0c mov eax,DWORD PTR [ebx+0xc] 0x255f: 89 44 24 0c mov DWORD PTR [esp+0xc],eax -------------------------------------------------------------------------------- gdb$ Gdb stopped before executing the JE and we can observe that the jump will be taken, so the not registered message will appear. To test our theory, you should use the "cfz" command. This will change the zero flag, from 1 to 0 (you know it's the zero flag because you have that information in the Jump is taken msg, z=1). Change the flag and then issue the command "context". This will redraw gdb output without advancing any instruction. gdb$ cfz gdb$ context --------------------------------------------------------------------------[regs] EAX: 0x00000000 EBX: 0x00416FC0 ECX: 0x00000001 EDX: 0x00000000 o d I t s z a P c ESI: 0xBFFFEA50 EDI: 0x00000002 EBP: 0xBFFFEA18 ESP: 0xBFFFE9F0 EIP: 0x0000253B CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F Jump is NOT taken (z!=1) --------------------------------------------------------------------------[code] 0x253b: 74 2b je 0x2568 0x253d: a1 0c 50 00 00 mov eax,ds:0x500c 0x2542: 8b 10 mov edx,DWORD PTR [eax] 0x2544: c7 44 24 18 00 00 00 00 mov DWORD PTR [esp+0x18],0x0 0x254c: c7 44 24 14 00 00 00 00 mov DWORD PTR [esp+0x14],0x0 0x2554: c7 44 24 10 00 00 00 00 mov DWORD PTR [esp+0x10],0x0 0x255c: 8b 43 0c mov eax,DWORD PTR [ebx+0xc] 0x255f: 89 44 24 0c mov DWORD PTR [esp+0xc],eax -------------------------------------------------------------------------------- You can observe that the message changed and the jump will not be executed. Issue the continue command and check the program... Voila, you have just bypassed the protection! So the first method is to change the JE into a NOP. A nop mean no-operation, a harmless instruction will be executed. Since the instruction JE is two bytes (74 2b), and NOPs are a single byte (90), you will need two use two nops to replace 74 2b. More on patching later... The alternative, is to modify the isRegistered method. The disassembly is: -(BOOL)[Level1 isRegistered] +0 00002a88 55 pushl %ebp +1 00002a89 89e5 movl %esp,%ebp +3 00002a8b 8b4508 movl 0x08(%ebp),%eax +6 00002a8e 0fb64020 movzbl 0x20(%eax),%eax +10 00002a92 c9 leave +11 00002a93 c3 ret You can modify this method to always return 1 (true). The most common way to do this is to replace the beginning of this method with the following code: xor eax, eax inc eax ret Using the assemble command from gdb: gdb$ assemble Instructions will be written to stdout. Type instructions, one per line. Do not forget to use NASM assembler syntax! End with a line saying just "end". >xor eax,eax >inc eax >ret >end 00000000 31C0 xor eax,eax 00000002 40 inc eax 00000003 C3 ret Those are the bytes you need to use and replace in the original method, 31 C0 40 c3. You need to patch 4 bytes, starting at 0x00002a88. The two first instructions total 3 bytes, so you will "eat" space in the third instruction, which is 3 bytes long. Since you will patch a single byte in the third instruction, the rest of the code there will be most probably invalid. This isn't a problem because we will not execute any code after the ret (return). To make this cleaner you could NOP the two remaining bytes from the old third instruction. A variation of patching isRegistered, is to patch the code before the method is called. The original code is: +7 00002525 8b5d08 movl 0x08(%ebp),%ebx +10 00002528 a110400000 movl 0x00004010,%eax isRegistered +15 0000252d 891c24 movl %ebx,(%esp) +18 00002530 89442404 movl %eax,0x04(%esp) +22 00002534 e8252b0000 calll 0x0000505e -[(%esp,1) isRegistered] <- call the routine +27 00002539 84c0 testb %al,%al +29 0000253b 742b je 0x00002568 <- if it's not registered, then it will show the bad message For example, you could replace the code at address 0x00002534 by: xor eax, eax inc eax and NOP the remaining bytes. So there are different ways to do this, based on available space, conditions and your lazyness ;-) As with the first alternative, you can try this in gdb before patching. Breakpoint the isRegistered method at 0x00002a88 and run the crackme. $ gdb Challenge\ #1 GNU gdb 6.3.50-20050815 (Apple version gdb-1344) (Mon Dec 28 15:21:35 UTC 2009) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries ........ done gdb$ b *0x00002a88 Breakpoint 1 at 0x2a88 gdb$ r Reading symbols for shared libraries .+++++++.................................................................................. done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries . done Breakpoint 1, 0x00002a88 in -[Level1 isRegistered] () --------------------------------------------------------------------------[regs] EAX: 0x00002A88 EBX: 0x00418820 ECX: 0x00000001 EDX: 0x00000000 o d I t s Z a P c ESI: 0xBFFFEA50 EDI: 0x00000002 EBP: 0xBFFFEA18 ESP: 0xBFFFE9EC EIP: 0x00002A88 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F --------------------------------------------------------------------------[code] 0x2a88: 55 push ebp 0x2a89: 89 e5 mov ebp,esp 0x2a8b: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 0x2a8e: 0f b6 40 20 movzx eax,BYTE PTR [eax+0x20] 0x2a92: c9 leave 0x2a93: c3 ret 0x2a94: 55 push ebp 0x2a95: b8 01 00 00 00 mov eax,0x1 -------------------------------------------------------------------------------- gdb$ set *0x2a88 = 0xc340c031 gdb$ context --------------------------------------------------------------------------[regs] EAX: 0x00002A88 EBX: 0x00418820 ECX: 0x00000001 EDX: 0x00000000 o d I t s Z a P c ESI: 0xBFFFEA50 EDI: 0x00000002 EBP: 0xBFFFEA18 ESP: 0xBFFFE9EC EIP: 0x00002A88 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F --------------------------------------------------------------------------[code] 0x2a88: 31 c0 xor eax,eax 0x2a8a: 40 inc eax 0x2a8b: c3 ret 0x2a8c: 45 inc ebp 0x2a8d: 08 0f or BYTE PTR [edi],cl 0x2a8f: b6 40 mov dh,0x40 0x2a91: 20 c9 and cl,cl 0x2a93: c3 ret -------------------------------------------------------------------------------- gdb$ c Voila, it's cracked. The set command will modify memory. As explained, we don't need to patch the remaining bytes, and since our new bytes are 4 bytes (32bits) we don't need cast any values in the set command. For example, if you just wanted to patch a single byte you would use the following: set *(char *) address = 0x90 3.1 - Patching the binary ------------------------- To patch the binary, you will need to open it in an hex editor. Then you will need an offset. The offset is where the program is located. Mac OS X has what is called fat binaries (or universal binaries), meaning that a PPC and x86 version will be present in the same file. This obliges an extra step because you need to calculate the offset for the x86 part (this tutorial is for x86!). I'm going to show you how to patch with the first alternative, the NOP. To verify if the binary is fat or universal, we can use otool command. $ otool -f Challenge\ #1 Fat headers fat_magic 0xcafebabe nfat_arch 2 architecture 0 cputype 7 cpusubtype 3 capabilities 0x0 offset 4096 <- HERE ! size 22548 align 2^12 (4096) architecture 1 cputype 18 cpusubtype 0 capabilities 0x0 offset 28672 size 18560 align 2^12 (4096) nfat_arch is equal to 2, meaning there are two architectures present in this binary. We are interested in cputype 7, which is Intel x86. Cputype 18 is PPC. You can see that x86 binary starts at offset 4096 (this is in decimal!), 0x1000 in hexadecimal. To calculate the correct offset for address 0x0000253b (the one we want to patch), we just need to add 0x1000 + 0x0000253b = 0x353B. So the formula is offset + offset_to_patch, where offset is the one reported in otool. Load the binary into an hex editor and go to offset 0x353B. If you use 0xED if you an input box at upper right corner. Just insert 353B there and press return. You should land at the correct offset. Check if the bytes match those we want to patch (742b). It should. Now you just need to replace 74 by 90 and 2B by 90 and save. Start Challenge #1 and voila, no more bad message! Our work is done... A word of caution while trying to calculate the offset. The previous paragraph about the formula to calculate correct offset is true if Intel x86 binary is the first one inside the fat binary. If PPC is first, then the formula is (offset - 0x1000 + offset_to_patch). You need to subtract 0x1000 because that's the header size of PPC part. Not that hard to remember (I'm coding a small utility to do this math :) ). The easiest way to calculate the offset is to use the utility I created, offset.pl. You can download the latest version at http://reverse.put.as/wp-content/uploads/2011/02/offset1.3.pl_.gz or Ghalen's C version at http://reverse.put.as/wp-content/uploads/2009/06/ocalc.c To practice your patching skills, try to patch the isRegistered method :-) 3.2 - Fishing a valid serial number ----------------------------------- The routine that verifies if the serial is valid can be easily found in the disassembly listing. It's called -(BOOL)[Level1 validateSerial:forName:]. The name is very suggestive (another common mistake in OS X developers). You should study that routine to understand what's happening there. A basic scheme for a serial verification routine is: 1) Verify if user serial number length is ok. If ok continue, else give an error. 2) Compute the good serial number. 3) Compare the user serial number with the good serial number. All these elements are present here. I will just dump my notes on what the code is doing. Keep in mind that valid serial number should be different in your case. Serial should be 8 chars in length, as this piece of code shows: +29 00002abb 83f808 cmpl $0x08,%eax A quick look at the whole method and we find the piece of code we are interested in: +369 00002c0f 891c24 movl %ebx,(%esp) <- our serial first 4 chars +372 00002c12 89c6 movl %eax,%esi <- esi with our serial next 4 chars +374 00002c14 8b45d8 movl 0xd8(%ebp),%eax <- eax with 5680 (first part of good serial) +377 00002c17 89442408 movl %eax,0x08(%esp) +381 00002c1b a108400000 movl 0x00004008,%eax isEqual: +386 00002c20 89442404 movl %eax,0x04(%esp) +390 00002c24 e835240000 calll 0x0000505e -[(%esp,1) isEqual:] +395 00002c29 84c0 testb %al,%al <- are they equal ? +397 00002c2b 7421 je 0x00002c4e <- jump if our serial part is different from good serial part +399 00002c2d 8b45dc movl 0xdc(%ebp),%eax <- eax holding second part of good serial +402 00002c30 893424 movl %esi,(%esp) <- our second serial part +405 00002c33 89442408 movl %eax,0x08(%esp) +409 00002c37 a108400000 movl 0x00004008,%eax isEqual: +414 00002c3c 89442404 movl %eax,0x04(%esp) +418 00002c40 e819240000 calll 0x0000505e -[(%esp,1) isEqual:] +423 00002c45 ba01000000 movl $0x00000001,%edx <- used to return that serial is GOOD +428 00002c4a 84c0 testb %al,%al <- test if 2nd serial parts match +430 00002c4c 7502 jne 0x00002c50 <- jump if they match +432 00002c4e 31d2 xorl %edx,%edx <- this will clean edx, thus telling that serial is BAD +434 00002c50 83c43c addl $0x3c,%esp +437 00002c53 89d0 movl %edx,%eax <- if serial is ok, edx is equal to 1, so eax will return 1 meaning an ok serial! (...) Instead of a comparison with a single serial number, two comparisons are made, the first half and then the second half. You just need to fish the two good pieces and you get your valid serial number. 3.3 - Keygen ------------ To create a keygen, you will need to study the previous routine and understand how it's created. The algorithm is very simple and it contains a bug. The source for my dirty keygen follows. The code is commented so it should be easy for you to follow. ----- CUT HERE --------- /* Keygen for Macserialjunkies Challenge '09 The serial algorithm has a bug because the format string has no zero padding. For example with the following name "zeparreco" the valid serial is 39940081 but since there is lack of padding, the algorithm generates 399481. Serial length must be equal to 8 so this username is impossible to keygen due to this small bug. This code sucks, it was made in a hurry :/ It works but it's damn ugly hehehehe */ #include #include #include int main(int argc, char *argv[]) { char name[256], *pname; printf("Macserialjunkies.com challenge #1 Keygen v0.1\n\n"); printf("Insert name:\n"); fflush(stdout); fgets(name, 256, stdin); if ((pname = strchr(name, '\n')) != NULL) { *pname = '\0'; } /* serial number is composed by 8 digits there are two algorithms, one for the first 4 digits and the other for the remaining */ // first block of four digits int i=0; int digit,multiplier=4; // mov eax,0x68db8bad int wtf = 0x68db8bad; int accumulator=0; int ecx=0; unsigned long long temp1; int temp2, temp3, temp4; int x=0; int stringsize = strlen(name); int firstblock,secondblock; for (x=0; x < stringsize ; x++) { // movsx eax,BYTE PTR [edi+ebx] digit = name[i]; // inc ebx i++; // imul eax,esi digit = digit * multiplier; // add esi,0x4 multiplier += 4; // shl edx,0x4 // sub edx,eax digit = (digit << 4 ) - digit; // lea ecx,[edx+ecx+0x29a] ecx = digit + ecx + 0x29a; // imul ecx temp1 = (unsigned long long) ecx * wtf; // this grabs the ecx value since long multiplication the result goes to EDX:EAX temp1 = temp1 >> 32; // mov eax,ecx // sar eax,0x1f temp2 = ecx >> 0x1f; // sar edx,0xc temp1 = temp1 >> 0xc; // sub edx,eax temp3 = temp1 - temp2; // imul edx,edx,0x2710 temp4 = temp1 * 0x2710; // sub eax,edx ecx = ecx - temp4; } // the last ecx is the good first serial part firstblock = ecx; // second block i=0; multiplier = 4; int edx; x=0; ecx=0; int firstsar, secondsar; for (x=0; x < stringsize ; x++) { //movsx eax,BYTE PTR [edi+ebx] digit = name[i]; // inc ebx i++; // imul eax,esi digit = digit * multiplier; // add esi,0x8 multiplier += 8; // lea edx,[eax+eax*4] edx = digit + digit * 4; // lea edx,[eax+edx*2+0x2d] edx = digit + edx*2 + 0x2d; ecx = ecx + edx; temp1 = (unsigned long long) ecx * wtf; edx = temp1 >> 32; firstsar = ecx >> 0x1f; secondsar = edx >> 0xc; temp2 = secondsar - firstsar; edx = secondsar * 0x2710; temp3 = ecx - edx; } // 2nd part of good serial secondblock = temp3; // convert to decimal and print printf("Serial number is: %04d%04d\n", firstblock, secondblock); printf("Byeeeeeee!\n"); } ----- CUT HERE --------- 4 - Conclusion -------------- And here we are, at the end of this long tutorial. I hope you have enjoyed it and learnt something with it. Now you should have a basic framework and ability to work with the basic tools. To advance further, you need to find more targets and practice with them. Breaking protections is a good way to learn reverse engineering, because you always have a goal, breaking the protection. You will need to think and research so you can advance, that is the really fun part about reversing. Sometimes you will not be able to advance a target, maybe you should postpone it and get back at it later when your skills improved. This was my contribution to introduce you to this world, the next step depends on you. If you have any suggestions, doubts or found any error, please feel free to leave a comment at my blog http://reverse.put.as or drop an email at reverse AT put.as Have fun! fG!