给篇教程,虽然是针对老版本的,但基本的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: |:0044C881Now 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 0044D54FHmm, 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 0044C5FEAh - 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 0046AC4CWhoah, 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 00459B50Not 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 00459D06A 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, 00000006Can 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 functionOh 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!MessageBoxAYou 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 eaxNow, 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 encryptedOf 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.
