Date: 2007-08-07 00:29
Checksums and how to defeat them
A tutorial by StingRay written for www.flashtro.com
Ok, so here it finally is, my first tutorial for Flashtro. Lately while doing some HD fixes I found a nice protection which involved checksum checks and that's what I'm gonna write about in this tutorial. Without further ado let's have a look at the target:
"The Clue!" (c)1994 Neo
The things you'll need to follow this tutorial are:
- The game of course (CAPS/SPS 2134)
- An assembler of your choice
- An Amiga or emulator
Ok, let's start the game to see what happens. We are greeted by a nice "Enter word xx from the manual" window. Now that's gonna be an easy crack, right? Yes indeed but only if you do it right :) (or your name is Codetapper, then you already know everything about any 68k related things anyway and can stop reading here as this tutorial is not for such a super experienced cracker like you!)
Ok, let's load the file in ReSource to have a closer look: Load file "TheClue!" in ReSource, press "Disassemble" in the "Project" menu and scroll down a bit. At offset $58 (check Resource's menu bar), we'll see a routine that loads the texts we saw in the lovely copy protection window into some registers and well, what to say, that's of course the protection routine.
Let's check from where it's called and what happens after this call. Move back to the beginning of the file (i.e. offset 0) and press the "Search" button you can see in the lower part of the screen. Now press the "Forwards" button and ReSource returns location $3e, we see a "bsr lbC000058" at this very position. Now press "Forwards" again to find any other occurrences. There are none.
Most probably the game calls the protection routine and as long as the correct word is not entered, it won't return from the protection routine. So, let's check what the next routine after the call to the protection routine does. Et voila, as expected the next routine (lbC000628) starts the game. It loads the intro and then the game. So, to disable the protection, we only have to remove the call to the protection routine. Well, not quite. For some reason I always find such utterly simple protections suspicious so let's look around a bit more to see if there's something fishy going on.
Let's go back to the beginning of the file. Why the heck does the game store the contents of $3fc.w and initializes that very address with a pointer to the routine at offset $A5C? That deserves further investigation!
Ok, let's quickly do a search for $3fc, we'll find 3 occurrences, 1. save ptr, 2. load it with a new address, 3. restore old ptr. So it's not really used within this file, so let's load the file that is loaded after the intro ("TheClouDisk1:Tc.Base"). Once loaded and disassembled, we enter the "Search" requester again and search for $3fc. And what a surprise, ReSource finds the first occurrence at offset $875c and a bit below that at offset $8784 we see a compare with some weird looking number. Why is the game doing that? Let's find out how the routine works.
move.w #$3FC,a3 ; should not need any comment :) moveq #0,d4 ; initialize checksum value move.l (a3),d0 ; d0: ptr to offset $a5c in TheClue! sub.l #$A04,d0 ; $A5C-$A04=$58 = start of protection routine move.l d0,a2 moveq #0,d5 ; loop counter lbC00876E moveq #$53,d0 add.l d0,d0 ; d0=$53*2=166 = number of longs to check cmp.l d0,d5 ; i.e. check until offset $2A0 which bhs.b lbC008784 ; is the end of the protection routine move.l d5,d0 asl.l #2,d0 ; multiply loop counter by 4 to get correct offset move.l (a2,d0.l),d1 ; get current long eor.l d1,d4 ; xor with current checksum value addq.l #1,d5 ; increase loop counter bra.b lbC00876E lbC008784 cmp.l #$42483A01,d4 ; check the checksum :)
Does that need any more explaining? I hope not. :) What we have here is a classic checksum routine. It either was obfuscated a bit on purpose or it is the product of a C compiler. Here's a simplified version done by yours truely which might be a bit easier to understand:
move.l $3fc.w,a2 sub.w #$a04,a2 moveq #0,d4 move.w #166-1,d5 .loop move.l (a2)+,d1 eor.l d1,d4 dbf d5,.loop
It checks 166 longwords from offset $58 in the "TheClue!" file. And what a wonder, at offset $2A0 the protection routine ends. If you want to check the routine you can do the following: Go to offset $875c in ReSource and select "Partial save -> set start" in the "SV" menu, then scroll down to offset $878a and select "Partial save -> set end". Now select "Save .asm -> partial" in the same menu and ReSource will save the routine then. If you thought I typed the routine to get it into this document you now know better. :)
Anyway, now load this routine into your favourite assembler, change it so that it would accept a ptr to an area to check and give it a fitting name like "CalcChecksum" or whatever you prefer. Then you just "incbin" the "TheClue!" file and call the routine with a pointer that points to the start of the protection routine. Adapting my version from above, it would look like:
START lea data+32+$58,a0 ; not pc relative to give Codetapper bsr CalcChecksum ; something he can complain about rts data incbin RAM:TheClue! ; a0: ptr to area to check ; -- ; d4: 32bit checksum value CalcChecksum moveq #0,d4 move.w #166-1,d5 .loop move.l (a2)+,d1 eor.l d1,d4 dbf d5,.loop rts
I think this is easy enough to understand, the only thing you might wonder about is the "+32", this is just to skip the hunk header. There are many ways to get the size of the hunk header, usually I just do a "d data" in Asm1/Pro and just check where the code starts, in this case it starts at the 8. longword so the size of the hunk header is 8*4=32 bytes.
If you execute this routine, d4 will contain the value $42483A01 and somehow that value does look familiar, doesn't it? :) As soon as we mess with the protection routine the checksum will be altered which would mean the game might do some undesirable things. :) I actually didn't check what happens if the checksums are not valid, if you feel like it you can try it yourself.
Now, how to deal with this problem? An obvious way would be to check for all occurrences of $42483a01 and $3fc and adapt all the checks accordingly. This would probably be the approach someone super experienced like Codetapper would use. But we are better than that! :) As a rule of thumb, do your "dirty work" with as less modifications as possible. I just had a quick look and could spot 7 checksum routines, that would mean we would have to change at least 7 checks but what if there are checksum checks in other files as well? So, this approach, even though it would work, is not elegant at all because it has too many disadvantages.
- we need to find EVERY checksum check in each file
- we would have to alter EVERY file that contains a checksum check
As you can see, this is not only time consuming but also pretty unsafe. What if we miss some checksum? The game could have "hidden checksums", i.e. it could use obfuscated code to check/calculate the checksum without any direct use of the $3fc pointer and/or the original checksum value! You would have to fully analyze the code in each file to be 100% sure you found all checksums as there are endless ways to obfuscate such code. Like f.e. take this one, invented in 2 minutes by yours truely:
move.l #$ffffc040,d0 ; move.l #-$3fc<<4,d0 lea $00002054,a0 ; lea $0815<<2,a0 lea (a0,d0.l),a0 ; do a lot of fake code that uses a0 without altering the value ; ... ; sub.l #$ffffdc98,a0 ; sub.l #(-$3fc<<4)+($0815<<2)-$3fc,a0 ; call a slightly different checksum routine which does ; "add.w #$58,a0" at the beginning bsr CalcCheckSum2 ; d4: checksum lsr.l #1,d4 ; do a lot of fake code here without altering checksum value ; ... move.l #CORRECT_CHECKSUM>>1,d0 sub.l d4,d0 beq.b .checksum_correct
As you can see, no direct $3fc access or using the original checksum value, so the standard approach of searching for the checksum value would return nothing! As said, possibilities are endless! :) Or would you have guessed that a0=$3fc without looking at the comments? If so, your name is probably Codetapper and I told you already that this tutorial is not for super experienced crackers like you!
Ok, so how can we crack the game then? Easy, we remove the protection call and put it back before the game is started! :) Now, if that sounds confusing, just think a bit about it. The game expects to have an unaltered protection routine and exactly that is what we will feed the game with! You probably guessed it already, we need to code a loader for the game which does the following:
- load file "TheClue!" and get a ptr to the first hunk (LoadSeg)
- remove the call to the protection routine
- restore the original code before the game is started so the checksum tests won't fail
Now, that doesn't sound too complicated, does it? Except for the fact that we don't know in which area of the game we can safely put our patch as there could be more checksum checks. So even with this approach we would have to check the whole game for checksums? That's pretty useless then, isn't it? NO! :) Luckily the code is NOT 100% pc relative, that means there are areas that will always change depending on the memory location the game has been loaded to as AmigaDOS relocates these absolute addresses. Thus, they CAN'T be checked by a simple checksum routine! Make sure you understood the last paragraph, it's essential knowledge! Now you might think "but what if it will just be loaded without LoadSeg within the game?". As we are NOT touching the original gamecode in any way, even that check would always return the correct checksum, our approach simply can't fail as we don't touch the original executable!
Ok, let's code our cute little loader then. The only thing we need to do is to find a location where we can place our patch. Let's have a look. Too bad, the code that follows directly after the protection call is fully pc relative and as it is the code that starts the game, we are doomed? Of course not! :) As the game uses the "Execute" function in dos.library to load files, we will simply patch that very function! :) We just have to compare the filename, if it matches with the one the game loads, we "patch back" the area we have altered within "TheClue!" and simply remove our dos "Execute" patch. Patching a library can be done using a "forbid/store ptr to old routine/set ptr to our new routine/calc new checksum (oh the irony :D)/ permit" combination or by using exec's "SetFunction" routine. As we are lazy, we could use SetFunction but the disadvantage is that it won't work with all libraries on Kickstart 1.3 and guess what, dos.library is one of them. And as we don't want to have a patch that works on OS2.0+ machines only, we need to find a solution for this problem. So let's examine what happens when we just use a normal approach, i.e we will do the following:
- get a ptr to the original "Execute" routine
- forbid task switching, i.e. turn multi-tasking off
- replace it with our patched version
- call exec's "SumLib" function to calculate the new library checksum
- enable task switching, i.e. turn multi-tasking on
move.l $4.w,a6 lea DOSname(pc),a1 jsr -408(a6) ; OldOpenLibrary() (hello Mr.Spiv :D) move.l d0,a1 lea -222+2(a1),a0 ; ptr to execute routine lea Patch(pc),a1 ; ptr to our patched routine move.l (a0),OldExecute-Patch(a1) ; store ptr to old routine move.l a1,(a0) ; install it jsr -132(a6) ; Forbid() jsr -426(a6) ; SumLibrary() jmp -138(a6) ; Permit()
If you wonder about the -222+2, the +2 is just to skip the "jmp" instruction, i.e. the $4ef9 opcode which is 2 bytes long. That was what I tried first. Apparently it didn't work very well, the Amiga crashes with a "Library checksum failure" alert. It puzzled me for a while but after some trial and error and checking the "exec/libraries.i" include file I found out that the "LIB_CHANGED" bit had to be set in the "LIB_FLAGS" before calling SumLibrary, as usual that wasn't mentioned anywhere. Once I added that, patching the library worked fine, as long as the patch wasn't started on OS versions below 2.0. And at this point I have to thank Mr.Spiv for giving the crucial hint. It turned out the 1.3 version of dos.libray doesn't use a standard jumptable to call the functions. Normally, when you call a library function, the code inside the library would look like:
jmp routine ; if you didn't understand why I did the +2 above jmp routine2 ; you may get the idea now jmp routine3 ...
So if we take the "Execute" function as example, in library versions greater than v34 (i.e. OS2.0+) the system would do the following after your _LVOExecute(a6) call:
DOS_Execute jmp DOS_REALEXECUTE
so our approach described above works perfectly. Now, for pre-OS 2.0 versions of the dos.library things look totally different. This is what the 1.3 version does when you call Execute:
DOS13_Execute moveq #-27,d0 bra.w SomeGenericRoutine
As you can see, our approach will totally fail here because the function is called in a totally different way, no jmp opcode used at all. The only way to deal with that is to add some special code which checks if it's an old library and use different code then. What I did to get it working was the following: I just coded a very small program which did nothing more than opening dos.library and giving me the ptr to this generic routine. Then I just added a mousewait and ran this program on an 1.3 machine equipped with an Action Replay. Yes, this is actually one of the very few occasions where using the "magic button" is justified in my opinion. So once my little program entered the "wait for left mouse" loop, I pressed the magic button to have a look at the code. What I saw was the following:
movem.l d2-d7/a2-a6,-(a7) sub.l a0,a0 move.l a7,d5 sub.l #$5dc,d5 ...
Now, this last instruction looked promising because it occupied 6 bytes. A perfect place for our "jsr patch" instruction as that has exactly the same size. I think you can figure out the rest yourself, in the patch I just executed the original code (the "sub.l #$5dc,d5" instruction) and called the real patch then. In case you have troubles understanding it, just check the accompanying source code. You'll find the complete source for the "TheClue!" loader, examine it and try to understand the "how and why". You'll notice the mess in the "PatchLib" routine, I originally made it as a generic library patching routine that could be used similar to "SetFunction" but due to the stupidity of the 1.3 dos.lib it turned into quite a messy routine. You'll also notice that I didn't add any support to remove the patch for 1.3 machines, if you feel like hacking the source a bit, you can add it yourself. :) But you'll find both methods in the source, SetFunction and the old-styled method of patching a library as I thought it could be interesting to see them both. Anyway, it's all for you to explore! :) But now, back to our actual task, what does the patch actually have to do? We need to:
- compare given filename (which is given in register d1)
- if we found the correct one, we will "repair" the code, i.e. we put back the "bsr.b protection" opcode
- lastly we simply remove our "Execute" patch as it's not needed anymore.
; d1: filename Patch movem.l d1/a0,-(a7) move.l VARS+OldExecute(pc),a0 jsr (a0) ; execute original "execute" :) movem.l (a7)+,d1/a0 ; check filename movem.l d0-a6,-(a7) lea Introname(pc),a1 .loop cmp.b #" ",(a0) beq.b .end tst.b (a0) beq.b .end cmpm.b (a0)+,(a1)+ bne.b .wrong bra.b .loop .end tst.b (a1) bne.b .wrong ; file found, let's do the magic :) move.l VARS+Hunkstart(pc),a0 move.w #$6118,$3e(a0) ; restore original code ; remove our patch move.l VARS+DOSbase(pc),a1 move.l VARS+OldExecute(pc),d0 lea -222.w,a0 ; Execute offset move.l $4.w,a6 jsr -420(a6) ; SetFunction .wrong movem.l (a7)+,d0-a6 rts
So what exactly does it do? First we execute the original "Execute" routine, in case you wonder why we don't simply call -222(a6), i.e. _LVOExecute, then just think a bit about it and you'll notice we would enter an endless loop if we would do so! :) Once that routine returns we just check the given filename. If it was the introfile ("TheClouDisk1:TCIntro"), we just get a pointer to the first hunk in the "TheClue!" file and simply put back the "bsr.b Protection" opcode at offset $3e. Once we have done that we don't need our patch any longer and thus simply remove it. And that's actually all there is to do! :) Again, if you have problems understanding it, check out the included source code, it uses exactly the approaches I've described in this tutorial.
So, I guess that's all I can think of for the moment, I hope you found the tutorial interesting and could learn something new. It was interesting to write for me as even I did learn something new (the 1.3 dos.library patching stuff). I tried to have something different than the usual "mfm/rnc copylock/novella protection" because I think there are already enough tutorials covering these topics. If you want to test yourself, you can try to use the things you (hopefully) learned in this tutorial to do a 100% crack of Whales Voyage. Even a very talented cracker like N.O.M.A.D. makes mistakes sometimes, i.e. he missed ALL the checksums thus rendering the FLT crack totally useless! I can't remember that I've ever seen a 100% version of that game so you might try to do one yourself! I actually did it already for my own entertainment, you'll have to "reverse" the FLT crack first, i.e. you have to undo the changes N.O.M.A.D. did to the original file! If you read this tutorial carefully, you should know how you could check if you successfully reverted the messed up FLT exe to a clean original executable. :) Another game using checksums is "The Settlers", you could try your luck with that one too.
Now, as the very last thing, I'll reveal how you could have cracked this game without doing all this dos.library patching and stuff. Actually, there is a bug/backdoor in the protection routine, if you place "STR!" (or any other random 4 character word) at offset $52a in the "TheClue!" file, you'll have a fully cracked game! I won't explain why and how that works, it's for you to find out! :) Also, I am pretty sure that just "noping" the call to the protection routine would also yield in a 100% cracked game as I couldn't find anywhere that this area is checked. Yet, both of the things I mentioned wouldn't work for Whales Voyage f.e. so I still think and hope this tutorial was useful for you.
Lastly, I want to send out a special thank to Mr.Spiv for giving the crucial hint why the patch didn't work on 1.3, I would have never looked at the library jumptable! :) Also greetings to all on flashtro, I hope to see some more tutorials soon. :) I'll try to do another one as well, I just don't know in which year that will be. ;)
Have fun, StingRay in August 2007
Filesize: 0KB, downloaded 81 times