Cracking a Mac OS X screensaver ------------------------------- (c) 2009 Fractal Guru (reverse AT put.as , http://reverse.put.as) Target: 3D Desktop Aquarium Screensaver (http://www.uselesscreations.com) Tools used: OTX, GDB, 0xED Platform: Mac OS X Leopard 10.5.6 @ Intel x86 Document version: 1.0 (15/04/2009) Index: 0 - Introduction 1 - How to debug our target 2 - Attacking our target - Removing the nags 3 - Attacking our target - Adding more fishes 4 - Attacking our target - More than 3 fishes! 5 - Conclusion 6 - Appendix 0 - Introduction ---------------- This is my first attempt at cracking a screensaver. My target is 3D Desktop Aquarium, a very good OpenGL screensaver for Mac OS X and Windows. The trial has the following limitations: - Nags asking to register that pop-up and interrupt the screensaver - Just two fishes - 3 fish limit The first thing we need to do is to understand how a screensaver works. Using 'ps' command we can find a process named ScreenSaver. This means the screensaver isn't a process itself. While web searching some info about screensavers I found the following tutorials: Cocoa Dev Central . Articles . Write a Screen Saver: Part I http://cocoadevcentral.com/articles/000088.php Cocoa Dev Central . Articles . Write a Screen Saver: Part II http://cocoadevcentral.com/articles/000089.php Save Our Screens 101 http://www.mactech.com/articles/mactech/Vol.21/21.06/SaveOurScreens/ With these three tutorials you can learn the basics and expected code layout. It's not explicit how the screensaver is loaded. From Apple documentation (http://developer.apple.com/documentation/UserExperience/Reference/ScreenSaver/Classes/ScreenSaverView_Class/Reference/Reference.html) we can understand how it's done: "ScreenSaverView is an abstract class that defines the interface for subclassers to interact with the screen saver infrastructure. Screen savers are subclasses of ScreenSaverView, packaged up in bundles and loaded by the screen saver application. (These bundles have a suffix of .saver and are located in the Library/Screen Savers directories of the various file system domains. See File System Overview for information about domains.) ScreenSaverView defines an interface for animating screen savers, instantiating small preview versions of the screen saver view (for display in the system preferences, for example), and for providing a configuration sheet to set various properties of the screen saver. In addition, subclasses can set the animation interval, the backing store of their window, and how the screen transitions to the animation. There are two main ways to do drawing in a screen saver. You can either do your drawing in the normal NSView drawRect: method, or you can do your drawing in ScreenSaverView’s animateOneFrame method. If you do drawing in drawRect:, you should call setNeedsDisplay: with an argument of YES in animateOneFrame." Basically a screensaver is a subclass from ScreenSaverView. The screensaver is loaded as a library into the ScreenSaver main program (I have read this somewhere but didn't took note of the url :( ). So let's go to our first reversing problem. 1 - How to debug our target ---------------------------- The first problem with debugging a screensaver is we can't touch the keyboard else the screensaver ends. With two computers we could use a ssh session to our Mac and debug from ssh. Does the job but it's boring... Screensaver coders must have a different way to debug their code else it would be a pain to build the screensavers. I found two solutions to this problem: 1st) Use the ScreenSaverEngine with some special options. The following text is a quote from some webpage (again, sorry for not having the original source url :( ) " ScreenSaverEngine is the Cocoa application that displays screen saver modules at the appropriate times (after so many minutes, when the cursor is in a hot-corner, etc.). It is part of the ScreenSaver system. If necessary you can run the ScreenSaverEngine from Terminal: /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine This is useful for running the engine under GDB for easy debugging. The ScreenSaverEngine supports some special command line flags to aid in debugging: * -background -- All screen saver windows appear behind all other windows (behind Finder icons but in front of the desktop image). The password and gamma fade features are disabled. It will not exit on mouse or keyboard activity. * -debug -- Like -background, except mouse movement over the desktop causes it to exit. Very handy when running the ScreenSaverEngine in GDB. (Trying to get around my computer in this mode without causing it to exit reminds me of playing Don't Touch The Floor as a kid) * -module -- Loads the specified module rather than the module specified by the user defaults. o accepts the same values as the moduleName node in ~/Library/Preferences/ByHost/com.apple.screensaver.*.plist o For example, Flurry, Abstract, or Spectrum. o It doesn't appear to accept full paths or URLs to .saver, .slideSaver, or .qtz files, however, so it's necessary to place the desired module in one of the typical ScreenSaver folders and reference it without the filename extension. " I had some issues with the -module option. It wasn't launching the right module or I wasn't giving it the correct information. I didn't bother to further check because I just configured the screensaver to use the module I wanted to debug. 2nd) Use SaverLab application. SaverLab (http://www.dozingcatsoftware.com/) is an application that runs screensavers modules in regular windows. Some people use it to debug. I gave it a try but haven't explored it further. I went for option #1, for no special reason... Working with first option. We want to debug /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine but there's still an additional problem. The screensaver module is loaded as a library so it will have a base address. We can easily find it after the module is running by using the "info shared" command from gdb. But if we want to break at the beginning of the screensaver then we need to find first the base address so we can define our adress breakpoint. There is a neat gdb option for this, "set stop-on-solib-events 1". From gdb documentation: " Sometimes you may wish that GDB stops and gives you control when any of shared library events happen. Use the set stop-on-solib-events command for this: set stop-on-solib-events This command controls whether GDB should give you control when the dynamic linker notifies it about some shared library event. The most common event of interest is loading or unloading of a new shared library. " Before starting the screensaver we set this option and find the module base address. It's easier to understand with a practical example: $ gdb GNU gdb 6.3.50-20050815 (Apple version gdb-768) (Sun Mar 22 01:47:54 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$ exec-file /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine Reading symbols for shared libraries .......... done gdb$ set args -background gdb$ set stop-on-solib-events 1 gdb$ r Reading symbols for shared libraries .+++++++++................................................................................................ done Stopped due to shared library event --------------------------------------------------------------------------[regs] EAX: 0000006A EBX: 8FE0CC4B ECX: 0000013E EDX: 8FE373E8 O d I t s z A p C ESI: BFFFDDB8 EDI: 0000006A EBP: BFFFD888 ESP: BFFFD84C EIP: 8FE0CB70 CS: 0017 DS: 001F ES: 001F FS: 0000 GS: 0037 SS: 001F [0017:8FE0CB70]-----------------------------------------------------------[code] 0x8fe0cb70 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info>: push ebp 0x8fe0cb71 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info+1>: mov ebp,esp 0x8fe0cb73 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info+3>: leave 0x8fe0cb74 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info+4>: ret 0x8fe0cb75 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info+5>: nop 0x8fe0cb76 <__dyld__Z18gdb_image_notifier15dyld_image_modejPK15dyld_image_info+6>: nop WORD PTR cs:[eax+eax+0x0] 0x8fe0cb80 <__dyld__Z24removeImageFromAllImagesPK11mach_header>: push ebp 0x8fe0cb81 <__dyld__Z24removeImageFromAllImagesPK11mach_header+1>: mov ebp,esp -------------------------------------------------------------------------------- gdb$ Gdb stopped as soon as a shared library event happened. Let's see if the module is already loaded. gdb$ info shared You will get a long list of loaded library/modules. Try to find for 3D Desktop Aquarium but it's still not there. Let gdb continue. gdb$ c Again, gdb stops. Issue again info shared command. Still not there. Let gdb continue... gdb$ c Gdb stops. Info shared command once again and voila, the module is loaded. gdb$ info shared 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 | | | | | | | | (...) 110 3D Desktop Aquarium - 0x176000 dyld Y Y /Users/xxxx/Library/Screen Savers/3D Desktop Aquarium.saver/Contents/MacOS/3D Desktop Aquarium at 0x176000 (offset 0x176000) (...) The base address for this module is 0x176000. To issue a memory breakpoint you need to add this value to the address provided by OTX. Example: If the address given from OTX disassembly is 00002531 then to set a memory breakpoint the address will be 0x00002531 + 0x176000 = 0x178531 . This address 0x176000 is rather stable between different runs. There should be another way to find this address but I don't feel like exploring it for now. To resume, we have a method to use gdb to debug the target screensaver module and a method to setup memory breakpoints. 2 - Attacking our target - Removing the nags -------------------------------------------- As usual, the first attempt is to try to find interesting strings. The nags have "register now" in their text, but no strings like that appear in the disassembly. Since there is a logo involved, I suspect all the nags are images and not text. The Resources dir usually holds such kind of stuff so I give a quick look there. There is a bunch of files there but nothing looks like images. A good technique is to explore everything by loading the files into an hex editor and see if anything familiar pops up. I did this and found something at "useless.creations" file. Two strings, "Paint Shop Pro" and "TRUEVISION-XFILE". Googling for TRUEVISION-XFILE and I find out that it's a TGA file. There are a few hits for this string so my theory is that "useless.creations" is a "container" for different image files. I have used "fs_usage" command (must be run as root) to check for this theory. This file was loaded first and then the .fish files (which I assumed that contained the data for each fish type). Since "useless.creations" holds different tga images, we need to find a way to extract them. TGA has a 18bytes header (http://en.wikipedia.org/wiki/Truevision_TGA) and an optional footer (TRUEVISION-XFILE string is part of the footer!). Reading TGA file specification, I found out that each image in that file was starting with the following bytes: 0x00 0x00 0x0a 0x00 Now I needed a way to automatically extract them out. I had the idea that there was already some software to do this, but I couldn't find it. I really hate to read tons of crap, save it, and then have it lost somewhere in the harddisk and not be able to find it when I really need it... Google to the rescue... I found a nice Forensics Wiki at http://www.forensicswiki.org/ with some tools. I tried Scalpel. Used the following line into scalpel.conf: tga y 15000000 \x00\x00\x0a\x00 Gave an arbitrary long size just to test it and configured the header I was looking for. Launched scalpel against "useless.creations" file and a few secs later I had a few .tga files extracted. Opened those files and voila, there were the images I was looking for. The first image isn't valid because "useless.creations" has those exact bytes before the first real image, so scalpel extracted bogus data. No big problem. Next task is to find how the images are displayed. My first attempt was to find where they were loaded. For that I did a string search for "useless.creations", "useless" and "creations". A good hit was at 0x000052ce. Scrolling back a few lines to see the method name, it's "-(bool)[AquariumSSView mainSetup]". To test if this is the right place, we can set a breakpoint and check with "fs_usage" if the file was loaded before this breakpoint. Breakpoint can be set at memory address 0x00005184 (don't forget to add 0x176000) or at the method [AquariumSSView mainSetup]. Example: gdb$ b [AquariumSSView mainSetup] Breakpoint 1 at 0x17b18a The difference in address is due to breakpoint on method sets the breakpoint after the prologue, a few bytes ahead. We can verify that before the breakpoint, "useless.creations" file doesn't show up in "fs_usage". This means file is loaded here or somewhere else ahead. If you check this method code (it's always a good idea to give a brief look at the whole disassembly for the method you are working on) there's an interesting call to SSMAIN::Setup. (...) +484 00005368 890424 movl %eax,(%esp) +487 0000536b e85e9f0000 calll SSMAIN::Setup(bool, int, int, SETTINGS*, char*) +492 00005370 84c0 testb %al,%al (...) Use the same trick as before with breakpoint and fs_usage. First, set a breakpoint at 0x00005368 and see if "useless.creations" was already loaded. If not then set a breakpoint at 0x00005370. You will verify that at the 2nd breakpoint the file was loaded so SSMAIN::Setup is loading it. Again, give a quick browse to SSMAIN::Setup code. One lines calls for attention: (...) +420 0000f472 e8250c0000 calll _LoadTexturesFromDataFile (...) Using the previous technique, we can see that "useless.creations" is loaded here. Analysing LoadTexturesFromDataFile we have the following piece of interesting code: (...) +239 0001018b e81e100000 calll _LoadTGA2 +244 00010190 84c0 testb %al,%al +246 00010192 7458 je 0x000101ec return; +248 00010194 8b55f0 movl 0xf0(%ebp),%edx +251 00010197 8b82b4060000 movl 0x000006b4(%edx),%eax +257 0001019d 8b44380c movl 0x0c(%eax,%edi),%eax +261 000101a1 893424 movl %esi,(%esp) +264 000101a4 89442404 movl %eax,0x04(%esp) +268 000101a8 e8c7f8ffff calll _BuildTexture (...) LoadTGA2 is a very suspicious name and it should be the one loading the images from the file. You can quickly confirm this. The next function, BuildTexture has too a suspicious name. BuildTexture will create an OpenGL texture (not sure if it's the most correct term). I already knew this screensaver was using OpenGL after reading those three tutorials and finding some OpenGL functions. I don't know anything about OpenGL so I had to read some stuff to understand how it works (it's incredible the amount of different stuff you have to read!). The page that was most useful for me was http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=06 , an example very well documented that allowed me to understand this. The idea is to load a image, create a texture, bind it, and generate the texture. Then you can select and apply the texture to an object. With all this info, we can formulate a theory for the nags. They are using some kind of OpenGL object (a polygon or something like that) and a texture with the nag image is applied to it. Now we just need to find where this is done. I can't explain everything I did to find the correct place, but it wasn't easy and I have traced a few functions without results. I will try to resume but excuse me if it's not very clear the path I followed. Some steps might be missing :). For example, one idea was to find where a specific texture was being used... Dead end! From the screensaver tutorials I knew that screensaver animation can be done using two methods, drawRect and animateOneFrame. animateOneFrame can be found at the disassembly. This was a rather big method to analyse but it had a few suspicious calls. I started setting some breakpoints and found out that SSMAIN::Draw was the one displaying the fishes and the letters. Then I tried to analyse SSMAIN::Draw and there I got some hints. SSMAIN::Draw more interesting calls. To understand what were they doing, I started nop'ing those calls and watching it's effects. This one has a funny effect: (...) +845 0000ed6b e852490000 calll AQUARIUM::Draw(bool) (...) If you nop this call, you can see "ghost" fishes without any textures :) I wasn't going anywhere so I got back to understand how OpenGL works. When a texture is to be applied/used, two things happen, first there is a glBindTexture and then a glBegin. Before this I was trying to understand how the transition effect was done with OpenGL but this wasn't a good path. Back to glBindTexture and glBegin, I did a grep in the disassembly for those two and found a few hits. glBegin had less hits and it was a better target because glBindTexture is used when textures are being loaded. These are the hits for glBegin: MS3D::Render() dump.txt: +41 00007093 e8c0c00100 calll 0x00023158 _glBegin MS3D::Render(Texture*) dump.txt: +99 000071c7 e88cbf0100 calll 0x00023158 _glBegin MS3D::DrawGroup(int) dump.txt: +56 00007634 e81fbb0100 calll 0x00023158 _glBegin MS3D::DrawGroup(int, Texture*) dump.txt: +118 0000776e e8e5b90100 calll 0x00023158 _glBegin SSMAIN::DoUnderlay() dump.txt: +589 0000da7b e8d8560100 calll 0x00023158 _glBegin SSMAIN::DoOverlay() dump.txt: +681 0000decb e888520100 calll 0x00023158 _glBegin SSMAIN::DoOverlay() dump.txt: +979 0000dff5 e85e510100 calll 0x00023158 _glBegin SSMAIN::DoOverlay() dump.txt: +1587 0000e255 e8fe4e0100 calll 0x00023158 _glBegin SSMAIN::DoIntro() dump.txt: +367 0000e4bb e8984c0100 calll 0x00023158 _glBegin SSMAIN::DoIntro() dump.txt: +738 0000e62e e8254b0100 calll 0x00023158 _glBegin SSMAIN::Draw() dump.txt: +1178 0000eeb8 e89b420100 calll 0x00023158 _glBegin BACKGROUND::Draw(int, int, float, double) dump.txt: +123 00012f71 e8e2010100 calll 0x00023158 _glBegin AQUARIUM::DrawBubbles() dump.txt: +169 000135b7 e89cfb0000 calll 0x00023158 _glBegin EndShadowsSTENCIL(float) dump.txt: +177 0001b38b e8c87d0000 calll 0x00023158 _glBegin There were a few candidated I wasn't interested in, AQUARIUM::DrawBubbles(), BACKGROUND::Draw(int, int, float, double), SSMAIN::Draw(). The remaining ones, I used a "brute force" approach. I started nop'ing the calls to each and saw it's effects. I finally found out that SSMAIN::DoOverlay() was the one I was looking for. There was a single call and it was from SSMAIN::Draw(). Piece of code: (...) +1355 0000ef69 80781800 cmpb $0x00,0x18(%eax) +1359 0000ef6d 7508 jne 0x0000ef77 <- doesn't jump with nags active +1361 0000ef6f 890424 movl %eax,(%esp) +1364 0000ef72 e8abecffff calll SSMAIN::DoOverlay() <- CRACKME! +1369 0000ef77 c70424e10d0000 movl $0x00000de1,(%esp) +1376 0000ef7e e825420100 calll 0x000231a8 _glDisable (...) To be sure of this, I set a breakpoint at 0x0000ef69 (I only activated this breakpoint as soon as the nag was active, else I would have too many hits because each animation frame calls SSMAIN::Draw()). With nag active, the jump at 0xef6d doesn't happen and SSMAIN::DoOverlay is called. I patched the jne to a jmp inside gdb and waited to see the results. No more nags, just fishes floating around !!! Hurray ! First limitation is done! 3 - Attacking our target - Adding more fishes --------------------------------------------- My first attempt to analyse this target was as soon as I saw in preferences that more fishes could be downloaded from the Internet. The trial version didn't allowed this but that's not something that can stop us :) Since our attacks should start by simple things, I used "strings" command to extract all the strings from the 3D Desktop Aquarium binary. One called for attention: http://www.uselesscreations.com/mac/zips/fish/fishTypes14.plist Hum...Let's download this file and see what do we get... Hum... We get a file with a list of fishes and an url to download them ! Example: angel = "Angel Fish;http://www.uselesscreations.com/mac/zips/fish/Angel.fish.gz"; blackmoor = "Black Moor (Black Goldfish) v1.1a;http://www.uselesscreations.com/mac/zips/fish/BlackMoor.fish.gz"; bluetang = "Blue Tang (Palette Surgeonfish);http://www.uselesscreations.com/mac/zips/fish/BlueTang.fish.gz"; I tried to download one, then unpacked it into Resources dir and tried to use it. The program wouldn't recognize the new fish :( There must be some kind of check. Doing a grep for fish at the disassembly, there is one interesting hit: AQUARIUM::BuildFishList(), called from AQUARIUM::Setup(SETTINGS*). Code: AQUARIUM::BuildFishList() +0 00017530 55 pushl %ebp +1 00017531 89e5 movl %esp,%ebp +3 00017533 57 pushl %edi +4 00017534 56 pushl %esi +5 00017535 53 pushl %ebx +6 00017536 81ec5c830000 subl $0x0000835c,%esp +12 0001753c 8b4508 movl 0x08(%ebp),%eax +15 0001753f e800000000 calll 0x00017544 +20 00017544 5b popl %ebx +21 00017545 8b5508 movl 0x08(%ebp),%edx +24 00017548 0518040000 addl $0x00000418,%eax +29 0001754d 8985b87cffff movl %eax,0xffff7cb8(%ebp) +35 00017553 8b8db87cffff movl 0xffff7cb8(%ebp),%ecx +41 00017559 83c218 addl $0x18,%edx +44 0001755c 8995bc7cffff movl %edx,0xffff7cbc(%ebp) +50 00017562 8d8378560000 leal 0x00005678(%ebx),%eax .fish +56 00017568 89442408 movl %eax,0x08(%esp) +60 0001756c 894c2404 movl %ecx,0x04(%esp) +64 00017570 891424 movl %edx,(%esp) +67 00017573 e818f3ffff calll CountFiles(char*, char*, char*) <- CRACKME: gives only 2 files with 3 there ? +72 00017578 89c6 movl %eax,%esi (...) I had 3 .fish files (two original and one I downloaded) and CountFiles was only returning 2. So this routine had some check inside it. Be careful, there are two functions named CountFiles with different parameters. We are looking for CountFiles(char*, char*, char*). I started tracing CountFiles and found out there was a difference between original and downloaded files. It was at this piece of code: (...) +154 0001692a 89442404 movl %eax,0x04(%esp) <- Resources directory path +158 0001692e e8b5c90000 calll 0x000232e8 _strcpy +163 00016933 8b85e47cffff movl 0xffff7ce4(%ebp),%eax +169 00016939 89742404 movl %esi,0x04(%esp) <- FishName.fish +173 0001693d 890424 movl %eax,(%esp) <- Resources directory path +176 00016940 e899c90000 calll 0x000232de _strcat +181 00016945 8b8de47cffff movl 0xffff7ce4(%ebp),%ecx +187 0001694b 893c24 movl %edi,(%esp) +190 0001694e 894c2404 movl %ecx,0x04(%esp) +194 00016952 e88b140000 calll FISHFILE::Open(char*) <- CRACKME: different results for original and downloaded fishes +199 00016957 84c0 testb %al,%al <- +201 00016959 740e je 0x00016969 <- jump for downloaded, no jump for original fishes (...) This clearly indicated there was some check inside FISHFILE::Open. This is a big routine and I will just paste the interesting piece of code. Code: (...) +334 00017f30 89fe movl %edi,%esi <- string from our file +336 00017f32 89c7 movl %eax,%edi <- expected string to compare with +338 00017f34 c78538feffff04000000 movl $0x00000004,0xfffffe38(%ebp) +348 00017f3e f3a6 repz/cmpsb (%esi),(%edi) <- verifies if it's a FISH file or not. +350 00017f40 b800000000 movl $0x00000000,%eax <- set EAX to zero, meaning it's ok +355 00017f45 740a je 0x00017f51 <- jump if it's a FISH file +357 00017f47 0fb646ff movzbl 0xff(%esi),%eax +361 00017f4b 0fb64fff movzbl 0xff(%edi),%ecx +365 00017f4f 29c8 subl %ecx,%eax <- EAX will be different from zero, meaning it's not ok +367 00017f51 85c0 testl %eax,%eax <- check if EAX was zero'ed (meaning it's ok!) +369 00017f53 0f8544010000 jnel 0x0001809d <- jump if EAX != 0 +375 00017f59 8b4d08 movl 0x08(%ebp),%ecx +378 00017f5c 8b8100400000 movl 0x00004000(%ecx),%eax +384 00017f62 f7d0 notl %eax +386 00017f64 3b81d4420000 cmpl 0x000042d4(%ecx),%eax +392 00017f6a 0f852d010000 jnel 0x0001809d <- doesn't jump +398 00017f70 8b7d08 movl 0x08(%ebp),%edi +401 00017f73 80bfc942000001 cmpb $0x01,0x000042c9(%edi) <- verifies if NA exists (right after FISH) +408 00017f7a 0f872b010000 jal 0x000180ab <- doesn't jump +414 00017f80 750d jne 0x00017f8f <- doesn't jump +416 00017f82 80bfc842000000 cmpb $0x00,0x000042c8(%edi) <- checks if byte before NA is 0x00 (Format is 0x00 0x01 NA) +423 00017f89 0f851c010000 jnel 0x000180ab <- doesn't jump +429 00017f8f f68544feffff01 testb $0x01,0xfffffe44(%ebp) <- byte after NA (0x00 0x01 NA 0xXX) identifies file +436 00017f96 0f850f010000 jnel 0x000180ab <- CRACKME! +442 00017f9c 8b4508 movl 0x08(%ebp),%eax (...) Open an original and a downloaded fish file into an hex editor. The first bytes for an original are: 436C6F776E20466973682076312E310067735C526F625C4170706C69636174696F6E2044617461004649534800014E411C00000003000000E5E7FAFFEA170500000C01062872616D90010000 Corresponding to this Ascii: Clown Fish v1.1gs\Rob\Application DataFISHNAÂÁ˙ˇÍ (ramê The first lines of code verify is the file is a FISH file. A few bytes are read from the file and it's checked if it's contents are equal to FISH. We can see that at the previous ascii dump. Follow the rest of the commented code until 0x00017f8f. There, the byte is tested against 0x01. From Intel manual we have the following for the test instruction: "Computes the bit-wise logical AND of first operand (source 1 operand) and the second operand (source 2 operand) and sets the SF, ZF, and PF status flags according to the result. The result is then discarded. " For Clown.fish that byte is 0x1C. For Angel.fish is 0x01. For BlackMoor.fish it's 0x1B. Load Calculator in Hex mode and try the following: 0x1C AND 0x1 = 0x0 0x01 AND 0x1 = 0x1 0x1B AND 0x1 = 0x1 Original files return 0, downloaded files return 1. This is the correct spot. The best solution is to NOP that jnel at 0x00017f96. Other solution is to modify each downloaded file and change that byte to a good one. I have patched the main binary, went to ScreenSaver preferences and voila, our downloaded fish appears there. Select it, save preferences and try the screensaver. Weeee a new fish appears :) One less limit ! We can download and install all the fishes. One less limit. 4 - Attacking our target - More than 3 fishes! ---------------------------------------------- This is a fantastic screensaver and I don't want to publish the whole crack without any effort from you, the reader. Good software deserves some respect :) I will give you some hints about this limit. Check the preferences file located at /Users/youruser/Library/Preferences/ByHost/ucFishSS.YOURMACADDRESS.plist You will see a MaxFish property. If you test with a single fish you will see it's respected, meaning there is a limit check somewhere. So search for MaxFish string at the disassembly listing. There are four hits, three of them you can discard by their name. One is left with lots of lines to analyse. I found it just by browsing the lines, looking for the 3 fishes limit (it's amazing how simple things can be efficient ;) ). 5 - Conclusion -------------- And that's it ! All limits are removed and we have a full working program. I love this screensaver and I'm going to buy it, 13 bucks isn't that expensive :) It wasn't easy to write this tutorial. I traced too much stuff, tried a few different ideas and had no notes for most of them. It's not a step by step tutorial like the previous two, but you should be able to understand it. Nevertheless it should be an useful tutorial to introduce you into reversing screensavers. Have fun! fG! 6 - Appendix ------------ This is the small crappy perl code I used to add the correct address to the disassembly dump, so I didn't need to calculate the correct address everytime I needed. First argument is the original dump, second the destination file for modified dump. ------------ START HERE ------------ #!/usr/bin/perl open(FILE,"<$ARGV[0]"); open(FILEW,">$ARGV[1]"); while() { if ($_ =~ /^[ \t\n\r\f]+\+[0-9]+[ \t\n\r\f]+([0-9a-z]+).*/) { $crap = $1; $crap = hex($crap); $crap = $crap + 0x176000; printf FILEW ("0x%x %s",$crap, $_); } else { print FILEW $_; } } ------------ END HERE ------------