欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

给篇教程,虽然是针对老版本的,但基本的scheme没太大变化 (85字)

给篇教程,虽然是针对老版本的,但基本的scheme没太大变化 (85字)



* Chapter 3 : BrainsBreaker v2.1

Tools used: Softice, Wdasm (or IDA)

Output: Brains Breaker v2.1 crack

Indeed a fun game to play! In fact I spent about 2-3 hours just playing the game before I started to crack it. It seems like some puzzles are only demos? Hmm, must be a nasty bug...but mr. Juan Trujillo Tarradas dont have to worry, we'll fix that for him! We'll just go to the "enter registraion name/serial" window and crack it like any other program... yeah...right...eh..only one problem... where is it?! At times like this I always go to the helpfile to try to find some usefull information, and look what I found:


Once your payment is processed, you'll receive an e-mail message containing your

registration code with the instructions to enter it in the program that makes

fully playable all the puzzles (if registered the Full pack) or the puzzles in the Entry set.

Ah...The registration window is hidden! How to find it then?

Step 1 - finding the registration window!
I choosed to disasemble the program, and searched after some well- choosen strings like "registration". I found this hit (after disasming with Wdasm, no need to use IDA if we dont have to):


* Possible StringData Ref from Data Obj ->"Registration info. Program version "

Interesting indeed! I browsed down a bit through the code and found alot of even more interesting strings there, including "Pack", "Name", "Your ID" and "Key:". Looks like this could be the place where we enter our registration details. Now we want to know how we execute this code, so we browse upwards through the code. We notice that this code is called from another location:

* Referenced by a CALL at Address:

|:0044C881   

Now we know where this code was executed from. Go up to that location and have a look:

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0044B8CF(C)

|

:0044C852 6A06                    push 00000006

:0044C854 FFB381010000            push dword ptr [ebx+00000181]

:0044C85A E8CF49FDFF              call 0042122E

:0044C85F E9EB0C0000              jmp 0044D54F

:0044C864 6A1C                    push 0000001C

:0044C866 E8723FFDFF              call 004207DD

:0044C86B 898590FEFFFF            mov dword ptr [ebp+FFFFFE90], eax

:0044C871 85C0                    test eax, eax

:0044C873 0F84D60C0000            je 0044D54F

:0044C879 6A00                    push 00000000

:0044C87B FFB590FEFFFF            push dword ptr [ebp+FFFFFE90]

:0044C881 E85FE40100              call 0046ACE5		<-- here is the intersting call

:0044C886 E9C40C0000              jmp 0044D54F

Hmm, judging from this code, we'd say that the call to the interesting routine will never be executed because the program will jump at offset 44C85F, and we dont see any other jump references...Here is a good example of how bad wdasm disassembles sometimes. There ARE references to the interesting call, if you use IDA you'll see them. The problem is that it referenced through the instruction "jmp dword ptr [4*eax+0044B96B]" and that's why Wdasm doesnt find it. Go to offset 44B964, you'll find this offset using IDA.
(It is possible to find it using Wdasm as well but it takes some more work. As you can see from the code snippet above, we'd like to jump to either offset 44C864 or 44C879 to execute our "enter registraion information" call, so try a search on "44C864" and you'll land in a very intersting place!)

:0044B964 FF24856BB94400          jmp dword ptr [4*eax+0044B96B]

:0044B96B C7C34400                DWORD 0044C3C7

:0044B96F 5CC44400                DWORD 0044C45C

:0044B973 CEC44400                DWORD 0044C4CE

:0044B977 C0C84400                DWORD 0044C8C0

:0044B97B 64C84400                DWORD 0044C864   <-- You'll land here if you're using Wdasm

:0044B97F C0C84400                DWORD 0044C8C0

:0044B983 8BC84400                DWORD 0044C88B

:0044B987 C0C84400                DWORD 0044C8C0

:0044B98B C0C84400                DWORD 0044C8C0

:0044B98F C0C84400                DWORD 0044C8C0

:0044B993 C0C84400                DWORD 0044C8C0

:0044B997 C0C84400                DWORD 0044C8C0

:0044B99B C0C84400                DWORD 0044C8C0

:0044B99F C0C84400                DWORD 0044C8C0

:0044B9A3 C0C84400                DWORD 0044C8C0

:0044B9A7 C0C84400                DWORD 0044C8C0

:0044B9AB C0C84400                DWORD 0044C8C0

:0044B9AF C0C84400                DWORD 0044C8C0

:0044B9B3 C0C84400                DWORD 0044C8C0

:0044B9B7 FEC54400                DWORD 0044C5FE

Ah - great! At offset 44B964, based on the value in eax, a jump is taken. So in order to execute our interesting call, eax should hold a value of 4 (4h*4h = 10h. 44B96Bh+10h = 44B97Bh and that's the offset where the interesting jump address is stored).
Now we need to know where anything gets saved in eax, so trace upwards through the dead-listing and you'll find that offset 0044B8A2 is the one we're searching for.

:0044B8A2 8B4607                  mov eax, dword ptr [esi+07]   

:0044B8A5 3D50040000              cmp eax, 00000450

:0044B8AA 0F8F99000000            jg 0044B949

:0044B8B0 0F849A0E0000            je 0044C750

...

:0044B949 3D68040000              cmp eax, 00000468

:0044B94E 7F6B                    jg 0044B9BB

:0044B950 0F84EB080000            je 0044C241

:0044B956 05AEFBFFFF              add eax, FFFFFBAE	;Same as "sub eax,452"

:0044B95B 83F813                  cmp eax, 00000013

:0044B95E 0F875C0F0000            ja 0044C8C0

:0044B964 FF24856BB94400          jmp dword ptr [4*eax+0044B96B]

There, now it's pretty easy to figure out what value we need to execute our "enter registation information" call. First of all it has to be over 450h, becuase we want to take the jump at offset 44B8AA. It must be lower than 468h because we DONT want to take the jump at offset 44B94E or at 44B950. Substract 452 from our value, and if the result is below or equal to 13h execute the jump at 44B964. We already know that we want eax to be 4 when the jump is beeing executed so therefor we want eax to be 452+4=456h at offset 44B8A2 in order to bring up the registraion window. To see if this theory is correct, lets test it in practice! Boot up the game, and put a breakpoint on cs:44B8A2 (make sure that brainsbreaker has control when you set the breakpoint, otherwise CS wont point to brainsbreakers code). Now choose a menu item, and softice will break due to our breakpoint. You will see that a value is moved into eax (the value depends on what menu-item we choosed). Step over the instruction and edit the eax register using the r command. Change to value to 456 instaed and let it run...YES! The "enter registraion information" windows pops up!

Step 2 - What pack do we want?
Ok, we got four edit fields that we must fill in, where the first one is "Pack:". Remember what the help file said? There are two different registraion "packs", one called Entry pack and another one called Full pack so we can easily guess that we shall enter either "full pack", "full" or something. To find out if we are right, let's enter "full pack" in the first field, and just some bogus info in the other fields. Now we go on as we normally do when cracking a name/serial protection, set a breakpoint on a string fetching function (GetDlgItemTextA, GetWindowTextA, hmemcpy or whatever, you know them :)) or try the breakpoint on windows message (bmsg) way...do whatever you want, just as long as you break in, I choosed to break on the windows message wm_gettext.
You all problaby know how to proceed from now on. Find our entered string (Full pack), and put a breakpoing on memory range (bpr) on it and then feel the code. You'll see ALOT of "repnz scasb" function (used alot for length checking if al=0), and a few "repz movsb functions. Just step through them, and put a new bpr everytime the string is copied. Remove an old breakpoint only when you see that it's overwritten with something else.
If you're having trouble telling the interesting code from the uninteresting I'll give you the offset where the interesting part begins, it's at offset 4012BD:


:004012BD 0FBE06                  movsx eax, byte ptr [esi]

:004012C0 50                      push eax

:004012C1 E8EE560000              call 004069B4

:004012C6 59                      pop ecx

:004012C7 8BD8                    mov ebx, eax

:004012C9 50                      push eax

:004012CA 0FBE17                  movsx edx, byte ptr [edi]

We start out by moving the first letter in our entered string into eax (in our case it's 46h, 'F'). What the next few lines do is that it copies that value into other registers (not really interesting), but the last instruction is very interesting. It takes a byte from what edi is pointing to and moves it into edx. I wonder what edi points to, dont you? Let's find out! "d edi" in softice, and now look in the datawindow. "Entry Full Upgrade..." and so on. Weee! We now know (ok, not exactly know, but it's a VERY good guess) that we shall enter "Full" in the first edit field, because we do want the fully registered copy, dont we?

Step 3 - Examining the protection scheme Time for the real pain, the real protection scheme. So, start out by entering something in the edit fields, I entered "Full", "Cruehead", "111", "1230123". Now we do like we did last time, but this time we're only interested in when the program is doing anything with our entered serial, so we choose to breakpoing on that string (1230123 in my case). Same story as before, a lot of length checking and string copying...I know it's pretty boring, but just hang in there. After some time you'll get to offset 401440. This is where its finally gets somewhat first interesting. What the program does is that it compares all characters in the serial with "-", but as our serial doesnt have a "-", we continue without any change. After some more uninteresting length checkings we get to the same offset again, but this time the program compares every character in our serial with "0". If it finds a match, the zero is removed and instead an O is put there. Our new code will be "123O123". We find ourself at this offset once again after some more time in softice. This time the program checks if any of the characters in the serial is '1'. If they are, replace them with a "L" instead. Our new code will be "L23OL23". After this, each of our characters in the serial will be compared to (in order and in hex): 00,3B,0D and 0A. We dont have to care about this because these bytes wont be found in our serial.
Geez, so much debuging and yet nothing really interesting, but fear not - we are getting there. After a while we'll land at offset 46AC55 and here is where the fun part begins.


* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0046ACB3(C)

|

:0046AC4C FF35BC554800            push dword ptr [004855BC]

:0046AC52 8B45E8                  mov eax, dword ptr [ebp-18]

:0046AC55 0FBE10                  movsx edx, byte ptr [eax]	<-- We'll land here

:0046AC58 52                      push edx

:0046AC59 E856BDF9FF              call 004069B4

:0046AC5E 59                      pop ecx

:0046AC5F 50                      push eax

:0046AC60 E8BDBAFAFF              call 00416722

:0046AC65 8845EF                  mov byte ptr [ebp-11], al

:0046AC68 33C0                    xor eax, eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0046ACA5(C)

|

:0046AC6A 8BC8                    mov ecx, eax

:0046AC6C BA01000000              mov edx, 00000001

:0046AC71 D3E2                    shl edx, cl

:0046AC73 33C9                    xor ecx, ecx

:0046AC75 8A4DEF                  mov cl, byte ptr [ebp-11]

:0046AC78 23D1                    and edx, ecx

:0046AC7A 740E                    je 0046AC8A

:0046AC7C 8BCB                    mov ecx, ebx

:0046AC7E B201                    mov dl, 01

:0046AC80 D2E2                    shl dl, cl

:0046AC82 8B4DF6                  mov ecx, dword ptr [ebp-0A]

:0046AC85 081431                  or byte ptr [ecx+esi], dl

:0046AC88 EB0E                    jmp 0046AC98

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0046AC7A(C)

|

:0046AC8A 8BCB                    mov ecx, ebx

:0046AC8C B201                    mov dl, 01

:0046AC8E D2E2                    shl dl, cl

:0046AC90 F6D2                    not dl

:0046AC92 8B4DF6                  mov ecx, dword ptr [ebp-0A]

:0046AC95 201431                  and byte ptr [ecx+esi], dl

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0046AC88(U)

|

:0046AC98 43                      inc ebx

:0046AC99 83FB08                  cmp ebx, 00000008

:0046AC9C 7503                    jne 0046ACA1

:0046AC9E 46                      inc esi

:0046AC9F 33DB                    xor ebx, ebx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:0046AC9C(C)

|

:0046ACA1 40                      inc eax

:0046ACA2 83F805                  cmp eax, 00000005

:0046ACA5 7CC3                    jl 0046AC6A

:0046ACA7 FF45F0                  inc [ebp-10]

:0046ACAA FF45E8                  inc [ebp-18]

:0046ACAD 8B45F0                  mov eax, dword ptr [ebp-10]

:0046ACB0 3B45FC                  cmp eax, dword ptr [ebp-04]

:0046ACB3 7C97                    jl 0046AC4C

Whoah, quite some code there! But dont worry, we really dont have to understand it (as you will see later), we just need to know where the output is stored. The code here isnt very hard to understand either, but we'll deal with it later. To save some time, I'll tell you now that output will be created at offset 46AC85 and 46AC95. You will see that after the loop, a few bytes (how many depends on the length of the serial entered) have been generated from our serial and saved at the address that ecx+esi points to at offset 46AC85. You can now remove all breakpoints and put one (breakpoint on memory range - bpr) on our nely created bytes (in our case it will be 5 of them). Just to make sure you got the correct spot, the bytes for "L23OL23" is 4B,6F,B7,F4,06. As you will see this string will be copied around a bit, but just follow the same procedure as last time (same procedure as every year, james). After a little while you'll land in the next interesting spot:

:00459B50 8BC1                    mov eax, ecx

:00459B52 99                      cdq

:00459B53 F7FB                    idiv ebx

:00459B55 8B45E4                  mov eax, dword ptr [ebp-1C]

:00459B58 03C2                    add eax, edx

:00459B5A 8A16                    mov dl, byte ptr [esi]	<-- You'll be here

:00459B5C 3010                    xor byte ptr [eax], dl

:00459B5E 41                      inc ecx

:00459B5F 46                      inc esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00459B4E(U)

|

:00459B60 8B45FC                  mov eax, dword ptr [ebp-04]

:00459B63 2BC3                    sub eax, ebx

:00459B65 3BC8                    cmp ecx, eax

:00459B67 7CE7                    jl 00459B50

Not that hard to understand. One byte will be XOR'd with each of our new bytes, and a few others as well (these are constants). This final byte will have a value of 0E in our case. You should always be very suspicious when you see an instruction that uses XOR. The only exception is when it's followed by two registers of the same kind, because then (as you all problaby already know) it just sets the register to zero. Now after this loop we'll get to the first check. If you start stepping through the code you'll soon see that the byte which were XOR'd with our "registraion bytes" will be compared to zero. If it matches, go on, otherwise it's a fake code. Finally we're getting somewhere! We now know that after quite a lot of different manipulations of our serial (remember that we had two different manipulations, the first created a couple of bytes based on our serial, and the other XOR'ed them together along with a few other constant bytes) we shall have a final value of zero! Remember this as we'll use it later, but for now, just change the program so it continues, preferable by changing the final byte from 0E to 00 (you should always try to patch as little as possible, atleast while investigating a protection scheme). One can almost guess that somewhere nearby there will be another (or more) checks, and therefor we start stepping through the next piece of code. This is pretty hard, you have to feel where something interesting happends, but I'll tell you that at offset 459d01 something is happening:

:00459D01 3B4DEC                  cmp ecx, dword ptr [ebp-14]

:00459D04 7D4F                    jge 00459D55

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00459D53(C)

|

:00459D06 8BC1                    mov eax, ecx

:00459D08 99                      cdq

:00459D09 F7FF                    idiv edi

:00459D0B 8B45E0                  mov eax, dword ptr [ebp-20]

:00459D0E 8A1C10                  mov bl, byte ptr [eax+edx]

:00459D11 0FB745F0                movzx eax, word ptr [ebp-10]

:00459D15 3BC8                    cmp ecx, eax

:00459D17 7D09                    jge 00459D22

:00459D19 0FB7D1                  movzx edx, cx

:00459D1C 8B45F4                  mov eax, dword ptr [ebp-0C]

:00459D1F 321C10                  xor bl, byte ptr [eax+edx]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00459D17(C)

|

:00459D22 0FB7560C                movzx edx, word ptr [esi+0C]

:00459D26 3BCA                    cmp ecx, edx

:00459D28 7D09                    jge 00459D33

:00459D2A 0FB7C1                  movzx eax, cx

:00459D2D 8B5610                  mov edx, dword ptr [esi+10]

:00459D30 321C02                  xor bl, byte ptr [edx+eax]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00459D28(C)

|

:00459D33 0FB74614                movzx eax, word ptr [esi+14]

:00459D37 3BC8                    cmp ecx, eax

:00459D39 7D09                    jge 00459D44

:00459D3B 0FB7D1                  movzx edx, cx

:00459D3E 8B4618                  mov eax, dword ptr [esi+18]

:00459D41 321C10                  xor bl, byte ptr [eax+edx]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:00459D39(C)

|

:00459D44 8BC1                    mov eax, ecx

:00459D46 99                      cdq

:00459D47 F7FF                    idiv edi

:00459D49 8B45E0                  mov eax, dword ptr [ebp-20]

:00459D4C 881C10                  mov byte ptr [eax+edx], bl

:00459D4F 41                      inc ecx

:00459D50 3B4DEC                  cmp ecx, dword ptr [ebp-14]

:00459D53 7CB1                    jl 00459D06

A pretty easy to understand protection here. Take the first char in the "pack" field, xor it with the first char in the "Username" field and finally xor this with the first char in the "ID" field. Save this byte and then proceed with the second character, the third and so on. I suppose a table will describe it better:

Pack string: 46 75 6C 6C
Username String: 43 72 75 65 68 65
ID string: 31 32 33
Result after XOR: 34 35 2A 09 68 65

"Hey! You only got 6 characters in the table, but the strings can be longer (and one of them in fact is)!" Calm down, That's because something is happening after 6 bytes has been processed. Now the first byte in our "result string" (34 in our case) will also be included the scheme. So, 34 will be xor'ed to every 7'th char in the other strings, and in our case the "username" string will be the only one effected, as the others are too small. The result is written in the first position in the "result string", thus removing the old value. After 6 more bytes, it does the same again , thus keeping the size of the result string to 6 bytes. When 20h (=32 dec) characters has been processed the routine is done.
Something has to be happening with these 6 bytes (why write that routine otherwise), so lets put a bpr on them. You will break in a string comparing routine. So...this string is compared to another string! Very interesting indeed... I wonder what happends if they matches??? let's see! Change one of the strings so it matches the other and let the program run...

YEEEEES! Program registered!

So, now all we have to do is to make the strings match...just one BIG problem...One would have thought that the string that our "pack, username, ID" string was compared with should be constant...but this is not true! It changes everytime, so there is not a chance to make these strings match eachother! First I thought this was some anti-cracking feature, but that is not true either. I changed the program so it would display a little messagebox with the first bytes of the string, and I ran it without softice (for the first time in months, softice wasnt loaded on my machine :)). No luck, it still changed! One alternative still remains...patching!

Step 4 - Cracking it! First of all I'd like to say that I dont really like patches because one never knows what comes with a patch. It could be a nasty checksum, or even worse - a delayed checksum. That's why I always like to find/calculate a valid serial when cracking name/serial protections, but when you're out of ideas, I guess patching can be justified. But even if we are going to patch, let's patch as little as we have too. We dont have to patch the first check (remember? one byte is compared to zero...) as we can pretty easily find a serial that justifies that. As most crackers are lazy by nature (well, atleast I am :)), lets just write a quick and dirty bruteforcer. You can find my source and the executable for it here. Run it, and we'll get the serial 125000. If our bruteforcer does what it's suppose to do we would now get by the first check by using this seriall, so let's test it! Go back to the register window, and enter the same information as before, but change the serial to 125000. Wow! It worked! Of course we didnt get the "thanks for registering" message, but we didnt expect that either, because the last check is still there. For reasons that I told you about earlier we have to patch that (or spend the next couple of weeks trying to figure out a way how to crack it without patching, but as I said - we're all lazy).

Of course the next question will be Where shall we patch? Before we can answer that we have to make sure we understand what we want to do. In this case we want two strings to match eachother (or atleast making the program think they do). If we investigate the comparision routine we see that the routine compares the number of bytes specified in edi. In our case edi=6 and therefor 6 bytes are compared. If we look at the code right after the comparison routine we will also see that dl is set to 1 if the strings matches. If they dont dl is set to zero. Now we can make a small list of possible patches:


1)   Action: Set edi to zero so that zero bytes will be compared.

Consequence: The strings wont be compared.

2)   Action: Overwrite the two strings with the same 6 bytes.

Consequence: The strings will match.

3)   Action: Fake the return value so dl will be set to one either way.

Consequence: The program thinks that the strings matches.

4)   Action: Fake a conditional jump

Consequence: The program thinks that the strings matches.

There are even more possibilites but these are more than enough. We can choose any of these and the program would, in theory, think our entered serial is valid. Let's try for example to set edi to zero. First of all lets find where the program sets edi to 6:

:00459C43 8B7508                  mov esi, dword ptr [ebp+08]

:00459C46 807E2400                cmp byte ptr [esi+24], 00

:00459C4A 7415                    je 00459C61

:00459C4C BF06000000              mov edi, 00000006

Can we change this into "mov edi,0" instead? Yes we can but the program would crash later due to a couple of "IDIV" instructions that relies on edi not beeing zero. So where can we change edi to zero then? Look at this:

:00459CFF 33C9                    xor ecx, ecx

:00459D01 3B4DEC                  cmp ecx, dword ptr [ebp-14]

:00459D04 7D4F                    jge 00459D55

...

This is the begining of the code that creates the 6 bytes long string from our entered Name, ID and Pack. This code is completely useless if we change edi to zero (because zero bytes will be compared) so we can saftly patch here. Let's change the code so it looks like this instead:

:00459CFF 33FF                    xor edi,edi	;set edi to zero

:00459D01 EB52                    jmp 00459D55	;Jump over the rest

						;of the function

Oh yeah! This works very nicely! Patch it using your favourite hex-editor and run it.

Integrity check:
Program seems to be altered from his original contents

Of course...a checksum function as well. How can we crack this then?

In fact, the author has already done half the work for us. Notice how the message is presented - through a messagebox! Very good news for us crackers, we just have to put a bpx on MessageBoxA now in order to land right in the interesting code. So, do just that and trace upward and you'll see this:


:00442CC2 E870DBFFFF              call 00440837

:00442CC7 3D22334455              cmp eax, 55443322	;Whoah! What a nice hardcoded value!

:00442CCC 7472                    je 00442D40		;Take this jump if you want to jump

							;over the messagebox!

:00442CCE 6810200100              push 00012010

:00442CD3 FF35843B4800            push dword ptr [00483B84]

:00442CD9 83C4FC                  add esp, FFFFFFFC

:00442CDC 66C70424BE00            mov word ptr [esp], 00BE

:00442CE2 E8C16FFFFF              call 00439CA8

:00442CE7 50                      push eax

:00442CE8 6A00                    push 00000000

:00442CEA E80AEF0300		  call USER32!MessageBoxA

You can see directly that something is fuzzy about the "cmp eax, 55443322" line. That looks VERY suspicious if you ask me (why not say "Hey Cracker! I'm here!!!" instead). Now we could quickly crack this just by changing the "je 00442D40" instruction to "jmp 00442D40" but we wont do that. The program might save the value returned in eax and use it later on for more checksum-checking (this is infact true for this program) so instead we want the above call to return 55443322. So, let's trace the call! You'll soon find this piece of code:

:00440DAE E8B504FCFF              call 00401268

:00440DB3 83C40C                  add esp, 0000000C

:00440DB6 85C0                    test eax, eax

:00440DB8 752E                    jne 00440DE8

:00440DBA A1803B4800              mov eax, dword ptr [00483B80]

:00440DBF 8B10                    mov edx, dword ptr [eax]

:00440DC1 8B0D243B4800            mov ecx, dword ptr [00483B24]

:00440DC7 8911                    mov dword ptr [ecx], edx

:00440DC9 B822334455              mov eax, 55443322	;We want this!

:00440DCE 50                      push eax

Now, this isnt very hard either. As the program is already patched the call at line 440DAE will return eax<>0 and thus the jump will be taken. Btw that call is our good old "comparision" call. As you see it's used quite a lot in the program so it was a good idea not to patch there! Now just nop away the jump. 55443322 will be stored in eax and the checksum routine thinks that everything is alright and we have a fully functional registered copy of BrainsBreaker!

Step 5 - Making a nice crack!
You can skip this part if you want to. This step is not really necessary, but if we were to release a crack for this program we had to make a nice looking patcher. The problem is that we dont know how to bring up the "enter registraion info" window, and thus the user wouldnt have anywhere to enter his name. We can solve this by making our own little "registraion window" that asks for a username in our patcher. If you look where BrainsBreaker saves the registraion information you will see that all of the reg-info is saved in the "BBRK.INI" file. It will save both plaintext and encrypted values. It will look something like this:


[PackFull]			;What pack we're using

0=Cruehead			;Username plain text

1=123				;ID plain text

2=7E15AB3D1516D6		;Serial encrypted

3=2100				;Not sure. I believe it's version number 

4=192BEC6D323CF86C		;Username encrypted

5=6B6BAA			;ID encrypted

Of course I didnt know all of this before I fully examined the program. I started out by putting a breakpoint on GetPrivateProfileStringA and when the encrypted serial line was read I put a bpr on that so I would see what was happening to that string. I wont describe every step on how I did it because it's fairly easy. Instead I'll try to describe how it works:

Every two letters in '7E15AB3D1516D6' is in fact one byte. So first of all the string is transformed so it looks like this : 7E 15 AB 3D 15 16 D6 . 7 bytes that is. Now this string is Xor'ed against a four byte long string that was "5A 59 99 08". This string caught my curiosity because it didnt look like a hardcoded value and neither could I find it in the .EXE file but I continued nevertheless, not worring aout it. The strings was xor'ed like this:

String from INI file: 7E 15 AB 3D 15 16 D6
Four byte string: 5A 59 99 08 5A 59 99
Result after XOR: 24 4C 32 35 4F 4F 4F

You see? The result is '$L25OOO'. Our entered serial string (somewhat transformed). The same procedure is done with the two other encrypted strings. If the decrypted username matches the username in plain text and if the decrypted ID matches the ID in plain text, then the program executes the registration check with the decrypted serial. The program will gladly accept it because we have patched the last check!

Ok, I sat down one night and coded this crack. It worked great on my computer but when I tried it on another computer it simply didnt work! Back to the drawing board again. It turned out that I was right when thinking that the 4 bytes xor string was strange. After some more examination I found out that this value was created from a special ID which is stored in the registry. The ID is created the first time brainsbreaker is installed and there is'nt much point in reversing the algorigthm for creating a BrainsBreaker ID becuase it uses the "TlsGetValue" function as source. The complete ID is about 50 bytes long but only 8 of these bytes are used to create the xor string (if you're really interested byte number 5,10,8,13,17,19,22,38 of the ID is used to make the xorstring). That's why my crack didnt work on the other machine - when I installed BrainsBreaker for the first time a different ID was created and thus also a different xorstring. So I sat down another night and re-coded the crack, this time for windows as we need to access the registry. This time the crack worked like a sharm on both machines! Oh joy oh joy!

Thoughts about BrainsBreaker
This is not important for the crack, but it's never the less very interesting. I'm not sure about this but it looks like every puzzle is a stand-alone part of the program. Every puzzle stores a number of things including the name of the author of the puzzle, if the main program has been altered, wether this puzzle is free/demo/registered and a number of other things. My guess is that these values (or flags) are stored directly after the mainprogram checks if it's registered or not. This way every puzzle can check if the main program has been cracked. But we dont have to care about all of this because the crack I presented above takes care of everything :). None of these flags are stored directly in the .exe file, instead they are stored in encrypted state. At startup these puzzles are decrypted using different keysources but the most common seems to be "(c)J. Trujillo T." I managed to get this info by stumbeling across a "xor [edi],al" function when the program was loading the startup screen with all the puzzles. That of course cought my interest and I started to examine it a bit more closer.

A small hint : It's a good idea to search the deadlisting for "MessageBoxA" functions as these can give you alot of usefull information sometimes. Fake a flag or something to make the program show the messagebox. For example I found out that every puzzle stores if the main program has been altered or not (if it has, a value of 12345 is stored in each of the puzzles) using this method.

But there are some things that remains a mystery for me still. I havnt been able to figure out how to bring up the registration-window yet and you can find a few very interesting functions if you look at the dead-listing:

Addr:00487ABC Ord:  14 (000Eh) Name: __DebuggerHookData

Addr:004035BC Ord:  15 (000Fh) Name: __lockDebuggerData(void)

Addr:004035E4 Ord:  16 (0010h) Name: __unlockDebuggerData(void)

Antidebugging functions problaby but I havnt seen any marks of them. Pretty strange...

Conclusion - BrainsBreaker:
A somewhat complicated protection scheme, but nothing that we havnt seen before. The hidden registraion window is a good idea but it's far to easy to find it using a dead-listing approach.


注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功