World's best Mac OS X reversing tutorial for newbies (or maybe not!) -------------------------------------------------------------------- (c) 2009 Fractal Guru (reverse AT put.as , http://reverse.put.as) Target: SlidePad @ http://slidepad-mac.com/ Tools used: OTX, GDB, 0xED Platform: Mac OS X Leopard 10.5.6 @ Intel x86 Document version: 1.0 (23/02/2008) 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 (or cracking) Slidepad 3.0 - Introduction and workflow 3.1 - Patching the binary 4 - Conclusion 0 - Introduction ---------------- One of the most difficults 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!). 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 -------------------------- First step is to build our reversing toolkit. Two tools are essential, a disassembler and a debugger. There are 3 available disassemblers and two debuggers. For 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, and 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 in reality 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!) Download both since GDB is part of XCode too. Available debuggers are GDB and IDA (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 your future uses. 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/2009/01/gdbinit2 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 gdbinit2 into your download folders, you can install it using Terminal.app with the following command: cp ~/Downloads/gdbinit2 ~/.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 not a big obstacle to our work. 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 and 0xED/Hex-Fiend. 2 - How to use our tools ------------------------ 2.1 - OTX --------- Run OTX and you will get the program window. We need to open the binary file we want to disassemble. If you have installed SlidePad normally, then you can find it on Applications folder. Open a Terminal.app windows (yes I really love Terminal, some things are done faster and better thru the command line) and go to /Applications/ "cd /Applications" List all available files with ls command "ls" You should see a folder named SlidePad.app . This is our target. Mac OS X programs have a nice program structure, where everything (almost) is contained into a single folder. Using SlidePad.app as an example, we have the following structure inside it: /Application/SlidePad.app/Contents Then we have the following folders: Frameworks Info.plist MacOS PkgInfo Resources The main binary is inside MacOS folder. This is where we should start. Frameworks dir might have interesting binaries to disassemble because protections can reside there instead the main binary. Listing the MacOS folder gives us: $ ls MacOS/ SlidePad SlidePad is the binary we want to disassemble. Full to it is: /Application/SlidePad.app/Contents/MacOS/SlidePad 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). And voila, you have disassembled your first binary. Very simple ! The output file is the disassembled dead 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. Nevertheless you can master it and do everything you should need for your RE projects. Let's give it a shot and introduce the world of GDB ! Commands issued inside gdb will always use the following prompt: gdb$ 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 -o example example.c 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. 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 the gdb bug described earlier). 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 can know 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" 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 just want 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 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 responsable 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 (or cracking) SlidePad ------------------------------------ 3.0 - Introduction and workflow ------------------------------- The first thing to do is reconnaissance. We need to understand what are the program limits and what messages 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 /Applications/SlidePad.app/Contents/ shell$ grep -r -i "message" * (-r means recursive and -i case insensitive) When you start SlidePad for the first time you have this message "Your trial will expire in 15 days" Try to search for that one...No hits ! Hum... try to search for the string "expire". You get a few more hits ! shell$ grep -r -i "expire" * Binary file Frameworks/DTCRegistration-eSellerate.framework/DTCRegistration-eSellerate matches Frameworks/DTCRegistration-eSellerate.framework/Headers/RegistrationController.h: * Has the license expired? Frameworks/DTCRegistration-eSellerate.framework/Headers/RegistrationController.h:- (BOOL) licenseHasExpired; Frameworks/DTCRegistration-eSellerate.framework/Headers/RegistrationController.h: * Shows the trial-period-expired options Frameworks/DTCRegistration-eSellerate.framework/Headers/RegistrationController.h:- (void) showExpiredOptions; Binary file Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate matches Frameworks/DTCRegistration-eSellerate.framework/Versions/A/Headers/RegistrationController.h: * Has the license expired? Frameworks/DTCRegistration-eSellerate.framework/Versions/A/Headers/RegistrationController.h:- (BOOL) licenseHasExpired; Frameworks/DTCRegistration-eSellerate.framework/Versions/A/Headers/RegistrationController.h: * Shows the trial-period-expired options Frameworks/DTCRegistration-eSellerate.framework/Versions/A/Headers/RegistrationController.h:- (void) showExpiredOptions; Binary file Frameworks/DTCRegistration-eSellerate.framework/Versions/Current/DTCRegistration-eSellerate matches Frameworks/DTCRegistration-eSellerate.framework/Versions/Current/Headers/RegistrationController.h: * Has the license expired? Frameworks/DTCRegistration-eSellerate.framework/Versions/Current/Headers/RegistrationController.h:- (BOOL) licenseHasExpired; Frameworks/DTCRegistration-eSellerate.framework/Versions/Current/Headers/RegistrationController.h: * Shows the trial-period-expired options Frameworks/DTCRegistration-eSellerate.framework/Versions/Current/Headers/RegistrationController.h:- (void) showExpiredOptions; All these hits correspond to this binary: Frameworks/DTCRegistration-eSellerate.framework/DTCRegistration-eSellerate And it has a very interesting thing which is the source code header files with class/method definitions. This is a big help for our reversing efforts. If you open this file, Frameworks/DTCRegistration-eSellerate.framework/Headers/RegistrationController.h, you can see interesting function names, like: trialDaysLeft licenseHasExpired hasValidKey showTrialPeriodOptions showExpiredOptions This is a big hint that protection is implemented into this framework and not into the main binary. So instead disassembling the main binary, we will start by this one. Load it into otx and disassemble. Open the output disassembly file into a text editor. We will start by trying to find the trialDaysLeft function. Do a text search for string "trialDaysLeft". You will have a few hits (some calls to this function) and then the real code for it, when you find this string "-(int)[RegistrationController trialDaysLeft]" The code for this starts like: -(int)[RegistrationController trialDaysLeft] 000025e4 55 pushl %ebp 000025e5 89e5 movl %esp,%ebp 000025e7 53 pushl %ebx (...) Time to learn a new trick ! So you are thinking about setting a breakpoint at the beginning of this routine. You see it starts at address 0x25e4 and you will use this address. Well this would be correct if we were talking about the main binary ! But this is a framework (or library to be more easier to understand), so it's located in a different address space. The library will be located at some base address which we need to find ! This base address is computed by the dynamic linker, but only when the library is loaded (consult chapter 2 from Mac OS X Internals book or a Mach-O reference document). So we can only have the address after the dynamic linker computed it. Mac OS X Leopard has ASLR for library but it doesn't seem to be affecting the frameworks (something to verify in the future). After we have this base address, we can compute this specific breakpoint with the formula $base_address + $library_address. In this case, it would be $base_address + 0x25e4. To find the base address we can use the "vmmap" shell utility or we can use gdb. The workflow for vmmap is: run target program, find it's PID, execute vmmap, grab base address. Example: shell$ open /Application/SlidePad.app (you can use open command to open applications from a Terminal.app window) shell$ ps ax | grep -i slide 303 ?? S 0:00.70 /Applications/SlidePad.app/Contents/MacOS/SlidePad -psn_0_192559 (the first column is the PID as we have seen before, unless you are lucky it should be different than 501) (if you have Unix experience [well Windows has it too], you certainly know pipe |, if not, then you should know that pipe allows you to redirect output from one command to the other) shell$ vmmap -allSplitLibs 303 (vmmap -allSplitLibs PID) (you should get a long long output from this command!!!) (...) shell$ vmmap -allSplitLibs 303 | grep DTCRegistration (let's use grep to reduce the output, we know that framework name is DTCRegistration-eSellerate.framework) __TEXT 00036000-00051000 [ 108K] r-x/rwx SM=COW ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate __LINKEDIT 00055000-0005a000 [ 20K] r--/rwx SM=COW ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate __DATA 00051000-00052000 [ 4K] rw-/rwx SM=COW ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate __DATA 00052000-00053000 [ 4K] rw-/rwx SM=NUL ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate __OBJC 00053000-00054000 [ 4K] rw-/rwx SM=COW ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate __IMPORT 00054000-00055000 [ 4K] rwx/rwx SM=COW ...tents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate The code we want to breakpoint is at __TEXT section. You can easily see it's mapped onto address 0x36000 till 0x51000. Our base address for this specific framework is 0x36000. To correctly set the breakpoint for trialDaysLeft function, you compute it to 0x36000 + 0x25e4 = 0x385E4 ! Let's try to set that breakpoint... 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 /Applications/SlidePad.app/Contents/MacOS/SlidePad warning: Unable to read symbols for "@loader_path/../Frameworks/ShortcutRecorder.framework/Versions/Current/ShortcutRecorder" (file not found). warning: Unable to read symbols from "ShortcutRecorder" (not yet mapped into memory). Reading symbols for shared libraries .............. done gdb$ b *0x385e4 Breakpoint 1 at 0x385e4 gdb$ r Reading symbols for shared libraries .............................................................................................. done Re-enabling shared library breakpoint 1 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 Reading symbols for shared libraries . done Reading symbols for shared libraries .. done Breakpoint 1, 0x000385e4 in -[RegistrationController trialDaysLeft] () --------------------------------------------------------------------------[regs] EAX: 000385E4 EBX: 000385C0 ECX: 00000000 EDX: 00000001 o d I t S z a P c ESI: 00178280 EDI: 00000008 EBP: BFFFEAE8 ESP: BFFFEACC EIP: 000385E4 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFEACC]----------------------------------------------------------[stack] BFFFEB1C : BB DD D8 96 D0 AA 16 00 - 08 00 00 00 E8 EB FF BF ................ BFFFEB0C : E9 26 00 00 80 82 17 00 - 48 0D C2 96 F3 21 B9 96 .&......H....!.. BFFFEAFC : D6 26 BA 96 F0 A2 15 00 - D0 AA 16 00 38 EB FF BF .&..........8... BFFFEAEC : 83 7C 03 00 80 82 17 00 - 90 C8 04 00 38 EB FF BF .|..........8... BFFFEADC : D6 26 BA 96 C0 85 03 00 - 42 7C 03 00 08 EB FF BF .&......B|...... BFFFEACC : D9 85 03 00 80 82 17 00 - 82 C8 04 00 08 EB FF BF ................ [0017:000385E4]-----------------------------------------------------------[code] 0x385e4 <-[RegistrationController trialDaysLeft]>: push ebp 0x385e5 <-[RegistrationController trialDaysLeft]+1>: mov ebp,esp 0x385e7 <-[RegistrationController trialDaysLeft]+3>: push ebx 0x385e8 <-[RegistrationController trialDaysLeft]+4>: call 0x385ed <-[RegistrationController trialDaysLeft]+9> 0x385ed <-[RegistrationController trialDaysLeft]+9>: pop ebx 0x385ee <-[RegistrationController trialDaysLeft]+10>: sub esp,0x34 0x385f1 <-[RegistrationController trialDaysLeft]+13>: mov eax,DWORD PTR [ebp+0x8] 0x385f4 <-[RegistrationController trialDaysLeft]+16>: mov edx,DWORD PTR [eax+0x30] -------------------------------------------------------------------------------- gdb$ If you compare gdb output with otx output you will be sure you are at the correct place ! Voila, you have just learned a new trick :) Now you can compute and breakpoint inside any framework address ! To use gdb, you will need the command "info sharedLibrary". The workflow is: open gdb, run the program, all libraries should be loaded (if not, try to do the operation that will load it), press Ctrl-C in gdb to interrupt the process, issue "info sharedLibrary" command, grab the address for the library. Example: 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 SlidePad warning: Unable to read symbols for "@loader_path/../Frameworks/ShortcutRecorder.framework/Versions/Current/ShortcutRecorder" (file not found). warning: Unable to read symbols from "ShortcutRecorder" (not yet mapped into memory). Reading symbols for shared libraries .............. done 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 Reading symbols for shared libraries . done Reading symbols for shared libraries . done Reading symbols for shared libraries .. done ^C Program received signal SIGINT, Interrupt. 0x91ff71c6 in mach_msg_trap () --------------------------------------------------------------------------[regs] EAX: 10004005 EBX: 970F89C7 ECX: BFFFDB2C EDX: 91FF71C6 o d I t s z a P c ESI: 00000000 EDI: 00000000 EBP: BFFFDB68 ESP: BFFFDB2C EIP: 91FF71C6 CS: 0007 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFDB2C]----------------------------------------------------------[stack] BFFFDB7C : 50 04 00 00 03 14 00 00 - 00 00 00 00 00 00 00 00 P............... BFFFDB6C : AE 90 0F 97 50 DC FF BF - 06 00 00 03 00 00 00 00 ....P........... BFFFDB5C : 06 00 00 03 68 BB 10 00 - 50 DC FF BF 28 E1 FF BF ....h...P...(... BFFFDB4C : 01 00 00 00 03 51 00 00 - D6 94 E0 89 DB 77 0F 97 .....Q.......w.. BFFFDB3C : 50 04 00 00 03 14 00 00 - 00 00 00 00 00 00 00 00 P............... BFFFDB2C : BC E9 FF 91 50 DC FF BF - 06 00 00 03 00 00 00 00 ....P........... [0007:91FF71C6]-----------------------------------------------------------[code] 0x91ff71c6 : ret 0x91ff71c7 : nop 0x91ff71c8 : mov eax,0xffffffe0 0x91ff71cd : call 0x91ff7a14 <_sysenter_trap> 0x91ff71d2 : ret 0x91ff71d3 : nop 0x91ff71d4 : mov eax,0xffffffdf 0x91ff71d9 : call 0x91ff7a14 <_sysenter_trap> -------------------------------------------------------------------------------- gdb$ info sharedLibrary The DYLD shared library state has been initialized from the executable's shared library information. All symbols should be present, but the addresses of some symbols may move when the program is executed, as DYLD may relocate library load addresses if necessary. Requested State Current State Num Basename Type Address Reason | | Source | | | | | | | | 1 dyld - 0x8fe00000 dyld Y Y /usr/lib/dyld at 0x8fe00000 (offset 0x0) with prefix "__dyld_" 2 Cocoa F - c-init Y Y /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa at 0xa3c000 (offset 0xa3c000) 3 Sparkle F - c-init Y Y @executable_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle at 0x146d000 (offset 0x146d000) (objfile is) /Applications/SlidePad.app/Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle 4 DTCFoundation F - c-init Y Y @executable_path/../Frameworks/DTCFoundation.framework/Versions/A/DTCFoundation at 0x1eb4000 (offset 0x1eb4000) (objfile is) /Applications/SlidePad.app/Contents/Frameworks/DTCFoundation.framework/Versions/A/DTCFoundation 5 Carbon F - c-init Y Y /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon at 0x28f3000 (offset 0x28f3000) 6 DTCRegistration-eSellerate F - c-init Y Y @executable_path/../Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate at 0x3316000 (offset 0x3316000) (objfile is) /Applications/SlidePad.app/Contents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate 7 libgcc_s.1.dylib - - c-init Y Y /usr/lib/libgcc_s.1.dylib at 0x3d66000 (offset 0x3d66000) 8 libSystem.B.dylib - - c-init Y Y /usr/lib/libSystem.B.dylib at 0x4643000 (offset 0x4643000) (commpage objfile is) /usr/lib/libSystem.B.dylib[LC_SEGMENT.__DATA.__commpage] 9 libobjc.A.dylib - - c-init Y Y /usr/lib/libobjc.A.dylib at 0x5107000 (offset 0x5107000) (commpage objfile is) /usr/lib/libobjc.A.dylib[LC_SEGMENT.__DATA.__commpage] 10 CoreFoundation F - c-init Y Y /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation at 0x5af1000 (offset 0x5af1000) 11 AppKit F - c-init Y Y /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit at 0x5e63000 (offset 0x5e63000) 12 ApplicationServices F - c-init Y Y /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices at 0x709e000 (offset 0x709e000) 13 Foundation F - c-init Y Y /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation at 0x7860000 (offset 0x7860000) 14 SlidePad - 0x1000 exec Y Y /Applications/SlidePad.app/Contents/MacOS/SlidePad at 0x1000 (offset 0x0) 15 Cocoa F 0x910c3000 dyld Y Y /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa at 0x910c3000 (offset -0x6ef3d000) 16 Sparkle F 0x17000 dyld Y Y /Applications/SlidePad.app/Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle at 0x17000 (offset 0x17000) 17 DTCFoundation F 0x2e000 dyld Y Y /Applications/SlidePad.app/Contents/Frameworks/DTCFoundation.framework/Versions/A/DTCFoundation at 0x2e000 (offset 0x2e000) 18 Carbon F 0x945c1000 dyld Y Y /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon at 0x945c1000 (offset -0x6ba3f000) 19 DTCRegistration-eSellerate F 0x36000 dyld Y Y /Applications/SlidePad.app/Contents/Frameworks/DTCRegistration-eSellerate.framework/Versions/A/DTCRegistration-eSellerate at 0x36000 (offset 0x36000) 20 SSCrypto F 0x20000000 dyld Y Y /Applications/SlidePad.app/Contents/Frameworks/SSCrypto.framework/Versions/A/SSCrypto at 0x20000000 (offset 0x0) (... continues ...) Check the num 19 ! See the address ? It's the same we retrieved using vmmap :) Dyld (the dynamic linker) already resolved the library/frameworks addresses so we can grab it and compute our breakpoint using the algorithm described in vmmap. We can proceed with our reversing effort ! One easy way to verify is trialDaysLeft is any good for us, is to force it returning a value of 0 aka expiring the program. If this program expires, then we are at the right place. An even easier and faster approach would be to trace where trialDaysLeft is called. This way we maybe could find that initial nag and remove it ! Let's go for the first one since it allows us for more gdb practice. The code for this function is (numbered lines added on purpose): -(int)[RegistrationController trialDaysLeft] 1: 000025e4 55 pushl %ebp 2: 000025e5 89e5 movl %esp,%ebp 3: 000025e7 53 pushl %ebx 4: 000025e8 e800000000 calll 0x000025ed 5: 000025ed 5b popl %ebx 6: 000025ee 83ec34 subl $0x34,%esp 7: 000025f1 8b4508 movl 0x08(%ebp),%eax 8: 000025f4 8b5030 movl 0x30(%eax),%edx (NSDate)expiryDate 9: 000025f7 8b83dbaa0100 movl 0x0001aadb(%ebx),%eax 10: 000025fd 891424 movl %edx,(%esp) 11: 00002600 89442404 movl %eax,0x04(%esp) 12: 00002604 e88fbb0100 calll 0x0001e198 -(double)[(%esp,1) timeIntervalSinceNow] 13: 00002609 dd5de0 fstpl 0xe0(%ebp) 14: 0000260c f20f1045e0 movsd 0xe0(%ebp),%xmm0 15: 00002611 f20f5e8353550100 divsd 0x00015553(%ebx),%xmm0 16: 00002619 f20f110424 movsd %xmm0,(%esp) 17: 0000261e e8b7ba0100 calll 0x0001e0da _ceil 18: 00002623 dd5df0 fstpl 0xf0(%ebp) 19: 00002626 f20f2c45f0 cvttsd2si 0xf0(%ebp),%eax 20: 0000262b 83c434 addl $0x34,%esp 21: 0000262e 5b popl %ebx 22: 0000262f c9 leave 23: 00002630 c3 ret You don't need to be a super elite reverser to understand the big picture for this piece of code. It's retrieving how many days are left until the trial expiration and then rounding the value using "ceil". Go to line 19 either by breakpointing it and issue continue or by stepping until you reach there. Step one instruction and you should be at line 20. Verify the value of EAX register. It should hold the number of trial days left, in hexadecimal. Mine for example is holding E, which in decimal is 14. My trial will expire in 14 days ! :) Btw, what is cvttsd2si instruction doing ? CVTTSD2SI--Convert Scalar Double-Precision Floating-Point Value to Signed Doubleword Integer with Truncation Now you can play with this value. To expire the trial right now, change EAX to 0 (gdb$ set $eax=0) and continue (Important: register name must be lower case!). You should get a message telling trial is expired and you can only Quit, Register or Buy ! This is a good sign ! Press quit and start the program again. This time change EAX to hold a big value, like 255 (gdb$ set $eax=0xFF) and continue. This time you will hit again the first breakpoint. You need to go to line 20, set again EAX to 0xFF and continue. Now you have a 255 days trial (0xFF = 255 decimal) !!! Enough play and let's get the job done :) We know that trialDaysLeft is an interesting function and we can play with it. Our next step should list where is it being called from. Do a search for trialDaysLeft in the program disassembly. You should have 3 hits, two calls and the function itself. This is the first hit: 00001e5f e82ac30100 calll 0x0001e18e -[(%esp,1) trialDaysLeft] The second at: 000025d4 e8b5bb0100 calll 0x0001e18e -[(%esp,1) trialDaysLeft] And the third is the function we saw before. At the first hit, if you scroll back a few lines until the beginning of the function, you will see the function name: -(void)[RegistrationController showTrialPeriodOptions] At the second hit, you can see immediately the function name, -(BOOL)[RegistrationController licenseHasExpired] The second one looks more interesting. Let's see if this is correct... Do a text search for licenseHasExpired and you should get 2 hits, one call and the function itself. Go to the call hit, and scroll back to see the function name, -(void)[RegistrationController launch]. A few lines before licenseHasExpired, you have another call with a very suggestive name of hasValidKey. Let's briefly analyse that piece of code: (...) 00001c68 e821c50100 calll 0x0001e18e -[(%esp,1) hasValidKey] <--- check if we have a valid key ? 00001c6d 84c0 testb %al,%al <--- test the result 00001c6f 7536 jne 0x00001ca7 <--- if eax different from 0, jump 00001c71 8b832eb40100 movl 0x0001b42e(%ebx),%eax 00001c77 893424 movl %esi,(%esp) 00001c7a 89442404 movl %eax,0x04(%esp) 00001c7e e80bc50100 calll 0x0001e18e -[(%esp,1) licenseHasExpired] 00001c83 84c0 testb %al,%al 00001c85 7408 je 0x00001c8f 00001c87 8b8332b40100 movl 0x0001b432(%ebx),%eax (...) This should be easy to understand. A routine to verify is there is a valid key is called and the result is verified. If AL (low 8 bits of EAX) is equal to 0, the jump will not be made and then licenseHasExpired call will be executed. Algorithm is something like, check if valid key exists, if not then verify if trial days are available. We need to test this with gdb, either changing EAX to 1 after the call to hasValidKey and before the testb is executed, or by forcing the JNE to be executed. Set up a breakpoint on 0x1c6d (don't forget you need to add the base address!) and start again SlidePad. Let me show you the output: Breakpoint 3, 0x00037c6d in -[RegistrationController launch] () --------------------------------------------------------------------------[regs] EAX: 00000000 EBX: 00037C42 ECX: BFFFE9C0 EDX: BFFFE9C0 o d I t s Z a P c ESI: 0013DAB0 EDI: 00000008 EBP: BFFFEB18 ESP: BFFFEB00 EIP: 00037C6D CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [001F:BFFFEB00]----------------------------------------------------------[stack] BFFFEB50 : 30 69 16 00 44 49 C1 96 - 00 35 11 00 4C C1 0F C8 0i..DI...5..L... BFFFEB40 : E0 61 00 00 BB DD D8 96 - F8 EB FF BF 1A DE D8 96 .a.............. BFFFEB30 : 50 AA 16 00 08 00 00 00 - F8 EB FF BF D6 26 BA 96 P............&.. BFFFEB20 : B0 DA 13 00 48 0D C2 96 - F3 21 B9 96 BB DD D8 96 ....H....!...... BFFFEB10 : 30 69 16 00 50 AA 16 00 - 48 EB FF BF E9 26 00 00 0i..P...H....&.. BFFFEB00 : B0 DA 13 00 24 C8 04 00 - 48 EB FF BF D6 26 BA 96 ....$...H....&.. [0017:00037C6D]-----------------------------------------------------------[code] 0x37c6d <-[RegistrationController launch]+57>: test al,al 0x37c6f <-[RegistrationController launch]+59>: jne 0x37ca7 <-[RegistrationController launch]+115> 0x37c71 <-[RegistrationController launch]+61>: mov eax,DWORD PTR [ebx+0x1b42e] 0x37c77 <-[RegistrationController launch]+67>: mov DWORD PTR [esp],esi 0x37c7a <-[RegistrationController launch]+70>: mov DWORD PTR [esp+0x4],eax 0x37c7e <-[RegistrationController launch]+74>: call 0x5418e 0x37c83 <-[RegistrationController launch]+79>: test al,al 0x37c85 <-[RegistrationController launch]+81>: je 0x37c8f <-[RegistrationController launch]+91> -------------------------------------------------------------------------------- gdb$ nexti ( You can see that EAX is 0 at this moment, so let's step to the next instruction) 0x00037c6f in -[RegistrationController launch] () --------------------------------------------------------------------------[regs] EAX: 00000000 EBX: 00037C42 ECX: BFFFE9C0 EDX: BFFFE9C0 o d I t s Z a P c ESI: 0013DAB0 EDI: 00000008 EBP: BFFFEB18 ESP: BFFFEB00 EIP: 00037C6F CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F Jump is NOT taken <- LOOK HERE! [001F:BFFFEB00]----------------------------------------------------------[stack] BFFFEB50 : 30 69 16 00 44 49 C1 96 - 00 35 11 00 4C C1 0F C8 0i..DI...5..L... BFFFEB40 : E0 61 00 00 BB DD D8 96 - F8 EB FF BF 1A DE D8 96 .a.............. BFFFEB30 : 50 AA 16 00 08 00 00 00 - F8 EB FF BF D6 26 BA 96 P............&.. BFFFEB20 : B0 DA 13 00 48 0D C2 96 - F3 21 B9 96 BB DD D8 96 ....H....!...... BFFFEB10 : 30 69 16 00 50 AA 16 00 - 48 EB FF BF E9 26 00 00 0i..P...H....&.. BFFFEB00 : B0 DA 13 00 24 C8 04 00 - 48 EB FF BF D6 26 BA 96 ....$...H....&.. [0017:00037C6F]-----------------------------------------------------------[code] 0x37c6f <-[RegistrationController launch]+59>: jne 0x37ca7 <-[RegistrationController launch]+115> 0x37c71 <-[RegistrationController launch]+61>: mov eax,DWORD PTR [ebx+0x1b42e] 0x37c77 <-[RegistrationController launch]+67>: mov DWORD PTR [esp],esi 0x37c7a <-[RegistrationController launch]+70>: mov DWORD PTR [esp+0x4],eax 0x37c7e <-[RegistrationController launch]+74>: call 0x5418e 0x37c83 <-[RegistrationController launch]+79>: test al,al 0x37c85 <-[RegistrationController launch]+81>: je 0x37c8f <-[RegistrationController launch]+91> 0x37c87 <-[RegistrationController launch]+83>: mov eax,DWORD PTR [ebx+0x1b432] -------------------------------------------------------------------------------- gdb$ You can see that jump will not be taken, as we expected. We want to force the jump so you can use the "cfz" command. This command will invert the Zero flag. If you consult Intel jump reference, you will see that JNE will be executed if ZF=0. That uppercase Z means at this moment ZF=1, that's why JNE will not be executed. gdb$ cfz gdb$ context --------------------------------------------------------------------------[regs] EAX: 00000000 EBX: 00037C42 ECX: BFFFE9C0 EDX: BFFFE9C0 o d I t s z a P c ESI: 0013DAB0 EDI: 00000008 EBP: BFFFEB18 ESP: BFFFEB00 EIP: 00037C6F CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F Jump is taken <-- SEE WE JUST MODIFIED THE FLOW [001F:BFFFEB00]----------------------------------------------------------[stack] BFFFEB50 : 30 69 16 00 44 49 C1 96 - 00 35 11 00 4C C1 0F C8 0i..DI...5..L... BFFFEB40 : E0 61 00 00 BB DD D8 96 - F8 EB FF BF 1A DE D8 96 .a.............. BFFFEB30 : 50 AA 16 00 08 00 00 00 - F8 EB FF BF D6 26 BA 96 P............&.. BFFFEB20 : B0 DA 13 00 48 0D C2 96 - F3 21 B9 96 BB DD D8 96 ....H....!...... BFFFEB10 : 30 69 16 00 50 AA 16 00 - 48 EB FF BF E9 26 00 00 0i..P...H....&.. BFFFEB00 : B0 DA 13 00 24 C8 04 00 - 48 EB FF BF D6 26 BA 96 ....$...H....&.. [0017:00037C6F]-----------------------------------------------------------[code] 0x37c6f <-[RegistrationController launch]+59>: jne 0x37ca7 <-[RegistrationController launch]+115> 0x37c71 <-[RegistrationController launch]+61>: mov eax,DWORD PTR [ebx+0x1b42e] 0x37c77 <-[RegistrationController launch]+67>: mov DWORD PTR [esp],esi 0x37c7a <-[RegistrationController launch]+70>: mov DWORD PTR [esp+0x4],eax 0x37c7e <-[RegistrationController launch]+74>: call 0x5418e 0x37c83 <-[RegistrationController launch]+79>: test al,al 0x37c85 <-[RegistrationController launch]+81>: je 0x37c8f <-[RegistrationController launch]+91> 0x37c87 <-[RegistrationController launch]+83>: mov eax,DWORD PTR [ebx+0x1b432] -------------------------------------------------------------------------------- gdb$ At this moment you can do two things, keep stepping the code and verify that the jump will be taken or trust me and use "continue" command. Well, just trust me ! :) Voila... No more trial message and program seems to be running fine :) That's it, I'm happy with the result. Maybe there are some more checks or something, but for this tutorial purposes I'm done with it! The next step is to patch the program. Remember that our magic line is: 00001c6f 7536 jne 0x00001ca7 <--- if eax different from 0, jump ^--- opcodes! We have seen that we want the JNE to be transformed into a JUMP ALWAYS. 75 is the opcode for short JNE. A short JUMP (always) has an opcode of EB. So what we need is to hexedit the framework binary (remember the protection is located inside it) and replace 75 for EB. 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!). To verify if the binary is fat or universal, we can use otool command. $ otool -f DTCRegistration-eSellerate Fat headers fat_magic 0xcafebabe nfat_arch 2 architecture 0 cputype 7 cpusubtype 3 capabilities 0x0 offset 4096 <--- HERE! size 142128 align 2^12 (4096) architecture 1 cputype 18 cpusubtype 0 capabilities 0x0 offset 147456 size 140324 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 0x1c6f (the one we want to patch), we just need to add 0x1000 + 0x1c6f = 0x2c6f. 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 0x2c6f. If you use 0xED if you an input box at upper right corner. Just insert 2c6f there and press return. You should land at the correct offset. Check if the bytes match those we want to patch (7536). It should. Now you just need to replace 75 by EB and save. Start SlidePad.app and voila, no more trial 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 :) ). 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!