Reversing Snood v2.4.2



Target: Snood - http://www.snood.com

Contents:

Introduction:

Snood is a very fun puzzle game based on the popular arcade game "Bust-a-Move". The game is easy to learn but very tough to master and has kept me busy for hours since I first cracked it. If you like puzzle games, you'll probably enjoy this game.

Anyway - we have four goals in this project. Goal number one is to make it registered and although this is a trivial program to crack, I'll cover the procedure for the newbies who might be reading this essay. This will be discussed in - you guessed it - "Part 1 - A Simple Crack". In "Part 2 - Snoods HATE Bugs!" we will be doing a simple bug fix of a page fault that occurs on occasion after you finish games - it can be very irritating. In "Part 3 - Cheaters Always Prosper" we will discuss allowing the use of the "mulligan" and "aimer" features without putting a piece of "cheese" near your score (which doesn't allow you to submit your scores to the "World High Score" list). In "Part 4 - Closing" we will recap what we have learned and say a few closing words.

Tools Needed:

You can actually use any diassembler, hex editor, and debugger of your choice, but these are my first choices for tools.

Part 1 - A Simple Crack

Load up the game and you will get a nag with a few options. Click "Enter Code" and then enter whatever you want. You will get "Sorry, that code is not valid..." (unless you somehow managed to enter a valid serial randomly, in which case you should quit reversing and start up a "psychic friends" hotline :). Find that message in IDA and you will find the following code referencing it:

0040159C                 call    CheckRegistration
004015A1                 add     esp, 0Ch
004015A4                 cmp     al, bl
004015A6                 mov     byte_42D581, al
004015AB                 jnz     short loc_4015DA
004015AD                 lea     edx, [esp+120h+var_100]
004015B1                 push    edx
004015B2                 push    offset unk_44CB47
004015B7                 call    sub_40E510
004015BC                 push    offset unk_44CB47
004015C1                 call    CheckRegistration
004015C6                 push    offset aSorryThatCodeI ; "Sorry, that code is not valid as entere"...
004015CB                 mov     byte_42D581, al
004015D0                 call    sub_4034D0
004015D5                 add     esp, 10h
004015D8                 jmp     short loc_4015EC
004015DA ; ---------------------------------------------------------------------------
004015DA 
004015DA loc_4015DA:                             ; CODE XREF: sub_4013E0+1CBj
004015DA                 push    offset aThanksForRegis ; "Thanks for registering!  Your code entr"...
004015DF                 call    sub_4034D0
004015E4                 add     esp, 4
004015E7                 call    sub_40E500

We know that the function called at 40159C and 4015C1 is "CheckRegistration" because the return value is checked to set the flag for "JNZ GOOD_GUY" at 4015AB. What does the return need to NOT be? There are two ways to find out. One is to set a breakpoint on 4015A4 and see what "BL" is at the time of the compare. The other way is to trace back quite a long ways to find out what was put into EBX/BL before this piece of code. I did it the hard way the first time by tracing back in the disassembler, so lets explain both methods.

Method #1: SoftICE

Load up SNOOD.EXE in the SoftICE loader and place a breakpoint on 4015A4. It will look like this in SoftICE:

:bpx 4015a4

When the nag shows up, follow the same method as before - press the "Enter Code" button and enter anything. SoftICE should break and you will find that EBX contains 0:

:? ebx
00000000  0000000000  ""

You can also find this out through the register window at the top, but I thought I'd include this for something visual. We now know that the CheckRegistration function must not contain a value of 0, or FALSE (my guess is that the programmer used a boolean function). Now you can clear all breakpoints:

:bc *

Method #2 - Backtracking

Look up from the address 4015a4 until you find something that would modify EBX such as "MOV EBX..." or "SUB EBX..." or "XOR EBX..." etc. Anything that would modify EBX in any way. I can tell you now that I even traced through the calls to see if EBX was ever modified. You will see this at the third line from the top of the procedure:

004013E7                 xor     ebx, ebx

This clears ebx (EBX is now 0). Just as we saw in the SoftICE method.

Patching:

To patch this quickly and easily, load SNOOD.EXE into HIEW and press enter twice to go to "Decode" mode. Press F5 and enter .403CF0 (the address of CheckRegistration [if you prefix the goto address with a . in HIEW it will go to the virtual address. Pretty useful, I think :]). Press F3 to edit and F2 to assemble instructions and enter the following:

MOV EAX,1
RETN

Press F9 to update the file.

If you are using another hex editor, insert the bytes "B8 01 00 00 00 C3" at 3CF0. Run Snood once again and you will be registered.

Part 2 - Snoods HATE Bugs!

Now that you are a registered user, you can use a couple new features. One of these is the aimer feature, this tells you where exactly the snood will land when you shoot it. To enable it, one must press the "X" key during a game. The downfall is that it puts a piece of cheese next to your score and it will not let you submit to the high score table. Anyway, there is a bug when you try to use the aimer. Start a game and press X to enable the aimer. Lose the game (or play it :) by shooting the same place until the snoods hit the bottom of the screen. You will get a page fault:

SNOOD caused an invalid page fault in module SNOOD.EXE at 0167:004088cd.

Well, look at the address 4088CD in your disassembler:

004088C0 sub_4088C0      proc near               ; CODE XREF: sub_40D510+15p
004088C0 
004088C0 arg_0           = dword ptr  8
004088C0 
004088C0                 push    ebx
004088C1                 mov     ebx, [esp+arg_0]
004088C5                 test    ebx, ebx
004088C7                 jnz     short loc_4088CD
004088C9                 xor     eax, eax
004088CB                 pop     ebx
004088CC                 retn
004088CD ; ---------------------------------------------------------------------------
004088CD 
004088CD loc_4088CD:                             ; CODE XREF: sub_4088C0+7j
004088CD                 mov     eax, [ebx+8]

EBX will be some number like 101 (it is always 101 for me). Well, that is trying to access some memory that it can't, so why not just patch that procedure to return. Open the program up in HIEW and go to 4088C0. Enter C3 (the opcode for retn) and update. It will now work just fine. I haven't noticed any problems from this little fix so I'm assuming it always returns 0 for a successful call anyway. It appears so from a bit of backtracking.

Part 3 - Cheaters Always Prosper

Here comes the most interesting part (IMO) of this essay. Allowing use of mulligans and the aimer without the "cheesy" score. Load Snood and enable the aimer by pressing X during a game (in a level greater than child mode). Score at least a few points and then either lose or continue the level. At the end you will get a message saying "You used the aimer...". Go to the location that referenes that screen:

0040DFC8                 cmp     cl, 1
0040DFCB                 jz      short loc_40DFEA
0040DFCD                 cmp     aimer_used, 1
0040DFD4                 jnz     short loc_40DFEA
0040DFD6                 push    offset aYouUsedTheAime ; "You used the aimer, so you'll have a ch"...
0040DFDB                 call    sub_4034D0
0040DFE0                 add     esp, 4
0040DFE3                 mov     [esp+30h+var_29], 1
0040DFE8                 jmp     short loc_40E046
0040DFEA ; ---------------------------------------------------------------------------
0040DFEA 
0040DFEA loc_40DFEA:                             ; CODE XREF: sub_40DFA0+2Bj
0040DFEA                                         ; sub_40DFA0+34j
0040DFEA                 mov     al, byte ptr mulligan_count
0040DFEF                 test    al, al
0040DFF1                 jle     short loc_40E007
0040DFF3                 push    offset aYouTookAMullig ; "You took a mulligan, so you'll have a c"...
0040DFF8                 call    sub_4034D0
0040DFFD                 add     esp, 4
0040E000                 mov     [esp+30h+var_29], 1
0040E005                 jmp     short loc_40E046
0040E007 ; ---------------------------------------------------------------------------
0040E007 
0040E007 loc_40E007:                             ; CODE XREF: sub_40DFA0+51j
0040E007                 cmp     byte_444AA0, 1
0040E00E                 jnz     short loc_40E024
0040E010                 push    offset aYourSavedPuzzl ; "Your saved puzzle file indicated that y"...
0040E015                 call    sub_4034D0
0040E01A                 add     esp, 4
0040E01D                 mov     [esp+30h+var_29], 1
0040E022                 jmp     short loc_40E046
0040E024 ; ---------------------------------------------------------------------------
0040E024 
0040E024 loc_40E024:                             ; CODE XREF: sub_40DFA0+6Ej
0040E024                 mov     al, byte_42F520
0040E029                 test    al, al
0040E02B                 jz      short loc_40E046
0040E02D                 push    offset aThereSeemsToBe ; "There seems to be some problem with how"...
0040E032                 call    sub_4034D0
0040E037                 add     esp, 4
0040E03A                 mov     [esp+30h+var_29], 1
0040E03F                 mov     byte_42F595, 1
0040E046 loc_40E046:                             ; CODE XREF: sub_40DFA0+48j
0040E046                                         ; sub_40DFA0+65j ...
0040E046                 call    sub_414B20

It appears that when anything is wrong, it sets var_29 to 1. In order to bypass all of the notices and get on with the scoring, we can change 40DFC8 to the following:

mov [esp+30+var_29], 0
jmp 40E046

First off, the opcodes for "mov [esp+30+var_29], 1" is:

.0040DFE3: C644240701                   mov         b,[esp][00007],001 ;""

So we should change it to C644240700. If you are using HIEW, you can just input this opcode directly and let HIEW calculate the jump on the next line. If you are using another hex editor without the assemble feature, the full code to enter at DFC8 is C644240701E974000000. This little patch gets rid of the cheese.

Continue on and then when you get back to the main screen click Game->Get Score Verification Code. It will say "You used a cheat (Mulligan or Aimer)...". Find this string and the following code will reference it:
004169BE loc_4169BE:                             ; CODE XREF: sub_4169A0+Bj
004169BE                 mov     al, byte_42F595
004169C3                 test    al, al
004169C5                 jz      short loc_4169D8
004169C7                 push    offset aYouUsedACheatM ; "You used a cheat (Mulligan or Aimer) so"...
004169CC                 call    sub_4034D0
004169D1                 add     esp, 4
004169D4                 add     esp, 48h
004169D7                 retn

I'm confident that byte_42F595 is "cheat_used". We could put 0 into "cheat_used" at each reference, but that is a number of references. We'll rather make the JZ after the TEST into a JMP. Go to 169BE in your hex editor and replace the 74 with EB. It will now generate a code for you!

Part 4 - Closing

Overall, this was very simplistic but took a bit of digging in the code. I was planning on making a keygen for whatever score you choose, but the algorithm is very long so it would just be a waste of time. If someone wants to do this, I would like to see an essay on it :) Also, it is up to you to remove the annoying messages when you try to use the aimer or mulligan. I would also like to see an essay if someone besides myself completes this task, I would also like to see an essay on that. If you need to contact me, you can E-Mail me at muaddib(at)immortaldescendants(dot)org or catch me on IRC in #cracking4newbies and #pravus (slightly more friendly at times ;), among other channels.

Greetings go out to Carpathia, Zen, WarezPup, noptical, Nitrus, Dead-Mike, vman_, Volatility, amante4, FatBoyJoe, NeOXQuiCK (nhoe), Dawai, C_DKnight, f0dder, FLeBBHuE, gaHn, jroger, PoiznFree, SeiZe_M, NoodleSpa, Hutch, Iczelion, JosephCo, radical, sortof, Quantico, barcode_, g0dmonkey, Xorolc, Kwai_Lo, Crudd, BMonkey, _Bonkers_, and anyone who I forgot. Hmm...a greetz list...haven't written one of those in a while ;)

More seriously (and sadly)...

RIP Unixfu 1983-2001 - you will be remembered.

Unixfu died of a drug overdose early in the month of July, 2001. He overdosed on DXM (dextromethorphan hydrobromide) which he had been taking for some time. I strongly advocate the use of drugs for personal exploration, but only with research and responsibility, which sadly, I didn't stress enough to Unixfu. Unixfu, we will all remember you. Sincere condolences to your family and friends in this tough time.

Written by Muad'Dib on Thursday, July 19, 2001
muaddib(at)immortaldescendants(dot)org

Links:

http://crackmes.cjb.net - Practice cracking!
http://muaddib.immortaldescendants.org - Random Crap
http://protools.cjb.net - Get some tools!
http://www.erowid.org - Do your research.
http://tsehp.cjb.net - Great essays!
http://fraviamb.cjb.net - A forum (I got the idea for this essay from the "Mini-Project" board.