Level : intermediate
Bustme1 solution by haggar
The goal of this crackme is to unpack it and then dig a serial for some name while keygen is optional. Solution consit from two parts - unpacking and reversing serial. Tools that I used are OllyDbg 1.10, ImpREC, LordPE, hex editor.
Dumped file: http://www.reversing.be/binaries/articles/20060827142108579.rar
Crackme is packed with custom protector. Protector is based on two-process protection (self debugging) which makes it more dificult for unpacking with Ring3 debuggers such as Olly. Protected file has some bad values in PE header (Number Of Rva And Sizes, for example) which Olly cannot dygest. We cannot change those values because crackme becomes corupted. But that doesn't prevent debugging in olly. However, if you would like to have nice analysis, try to find OllyAdvanced plugin that fixes these bugs in Olly.
Protector has few anti-debug tricks. It uses IsDebuggerPresent to detect debugger and BlockInput to block keyboard and mouse. Those are well known tricks (check Yoda Protectors tutorials on BIW reversing for more info about these tricks). But main protection is self-debugging.
Protector debugs it's own process to prevent debugging from Ring3 debuggers. Detaching processes will not give solution because import accessing requires both processes. Unpacking can be separated in two parts; decrypting and import repairing.
- Decrypting and dumping -
Since this is a self-debugging based protection, processes must know which one is debugger and which one is debuggee. When we start process by double-click, that process must check is he a debuggee. There are warious techniques to accomplish this; command line arguments, mutex, etc. But this protector will use very simple solution. It will check does some string exist in it, if it does - that process is debuggee. If not, then it will start a new process and write that string in it. Second process will perform same check. For writing is used WriteProcessMemory that writes next string to 400002 (in PE header) of "son" process:
So to avoid self-debugging, we just have to write this string in our process loaded in Olly. However, altough there will be no double processes, crackme will create debugg loop and try to debug something. Debugging loop is in separate thread while main one enters in infinite jump EB FE. Why it that happening , I didn't tried to find out. All sections of crackme are decrypted except import section at 406000. That is enough for dumping. Dump is OK, it just needs fixing some PE info (sections sizes needs to be rounded on 1000 alignment). OEP is easy to find since we have MingWin compiled executable. OEP is at 401220.
- Import repairing -
This is little harder thing to sove. For that I had to examne protector's debug loop. Secondary process, a debuggee, has redirected all import jumps to allocated block. For example, if we detach processes and follow first import call at 40122D:
0040122D FF15 5C614000 CALL DWORD PTR DS:[40615C]
at the call destination (I removed junk jumps from pasted code):
009B0268 50 PUSH EAX
009B0269 52 PUSH EDX
009B026A B8 860E9EEC MOV EAX,EC9E0E86 ; IMPORT_ID value.
009B026F BA 875A3E00 MOV EDX,3E5A87 ; Address of IMPORT_ALGO
009B0293 C3 RETN
EAX holds IMPORT_ID value which is maybe some hash. Then it jumps to IMPORT_ALGO where some small calculations are performed and then HLT opcode is triggered that couses exception C0000096 (PRIVILEGED INSTRUCTION):
003E5AD5 F4 HLT ; Privileged command
That exception is caugth by debugger, ei. first process which now takes controll. If we examne debug loop of first process, we will see how this exception is handled:
003E4C5D 813D 494D3E00 96>CMP DWORD PTR DS:[3E4D49],C0000096 ; Our exception code.
003E4C67 74 31 JE SHORT 003E4C9A
003E4C69 813D 494D3E00 04>CMP DWORD PTR DS:[3E4D49],80000004
003E4C73 74 58 JE SHORT 003E4CCD
003E4C75 813D 494D3E00 03>CMP DWORD PTR DS:[3E4D49],80000003
003E4C7F 74 05 JE SHORT 003E4C86
003E4C81 ^E9 DAFDFFFF JMP 003E4A60
Protector then retrieves resgisters state of debugged process (via GetThreadContext) and reads some data from debuggee (ReadProcessMemory). With that data it decrypts apropriate strings (API ordinals and DLL names) in 406000 section, loads libraries, retrieves API address and uses SetThreadContext as a jump directly to API. Then it continues debug event.
To retrieve imports, I wrote small script that plays with thread context buffer. After couple minutes and manually work, I placed all pointers in dumped file, then I loaded needed libraries in Olly (kernel32.dll, user32.dll, msvcrt.dll) and used ImpREC to rebuild IAT. Then I noticed that I still has some problems.
- Runtime decryption-encryption -
Protector has runtime decryption-encryption. For example, this part will decrypt 185 bytes starting from 00401C2D address:
00401C08 $ 55 PUSH EBP
00401C09 . 89E5 MOV EBP,ESP
00401C0B . 83EC 78 SUB ESP,78
00401C0E > 50 PUSH EAX
00401C0F . 68 85010000 PUSH 185 ; Number of bytes to decrypt.
00401C14 . 68 2D1C4000 PUSH bustme1.00401C2D ; Starting address.
00401C19 . B8 00009B00 MOV EAX,9B0000 : Block where decryptor loop is placed.
00401C1E . FFD0 CALL EAX
00401C20 . 58 POP EAX
There are about 20~30 this paterns. They doesn't have to be decrypted. We can place those decryptor loops in dump and change MOV EAX,009B0000 to new address , MOV EAX,0040xxxx. And dump will run fine. But it is better to decrypt all those parts and NOP all those opcodes. After decrypting, I removed last two sections and now I have nice and clean dump.
 REVERSING KEYCHECK
Keycheck is not hard , but I'm totally rusty. Check is performed in separate thread. Also, crackme goes to Sleep for a 500 miliseconds every little. Don't know why, maybe just to annoy us. Keycheck consist from two main parts.
- First part -
00401A7C |. 83F8 11 |CMP EAX,11 ; Serial must be 11h bytes long.
00401A7F |. 75 25 |JNZ SHORT dump.00401AA6
00401A81 |. C70424 9050400>|MOV DWORD PTR SS:[ESP],dump.00405090
00401A88 |. E8 71F9FFFF |CALL dump.004013FE
00401A8D |. 83F8 03 |CMP EAX,3 ; Name must have min 4 chars.
00401A90 |. 76 14 |JBE SHORT dump.00401AA6
00401A92 |. 803D 14514000 >|CMP BYTE PTR DS:,2D ; Serial format is XXXX-XXX-XXXXXXXX
00401A99 |. 75 0B |JNZ SHORT dump.00401AA6
00401A9B |. 803D 18514000 >|CMP BYTE PTR DS:,2D
00401AA2 |. 75 02 |JNZ SHORT dump.00401AA6
00401AA4 |. EB 14 |JMP SHORT dump.00401ABA
Crackme will then take midlle part of serial (those 3 chars between "-") and INC/DEC them to create new three byte string "S1,S2,S3":
S1 = 6._char + 2
S2 = 7._char + 1
S3 = 8._char - 2
Then it creates 100h byte array and fills it with values 00,01,02,03,04,....,FD,FE,FF. This array is then changed with our new three byte string values. One string is taken:
00BCFF8C 62 75 73 74 20 6D 65 20 23 31 20 2D 20 62 70 78 bust me #1 - bpx
00BCFF9C 20 32 6B 36 FF EE CA 00 2k6....
And it's hashed:
00BCFF8C EC B7 72 DD B9 5A E9 90 B9 10 EA 3E D2 AD 60 28 ..r..Z.....>..`(
00BCFF9C D2 F6 8D 23 D5 B2 CF 00 ...#....
That hash is compared with hardcoded one:
0040407F 94 D8 BE 56 20 7F AA 07 74 B3 EC D4 96 11 35 84 ...V ...t.....5.
0040408F 11 CF 40 25 8F 59 15 00 ..@%.Y..
If they match - first check is passed. This check depends only on 3 bytes from our serial, and it is actually hardcode part same for every name. I tought that it was too big job to rip all that code, and all in order to find out 3 bytes from serial. So I patched algo to loop untill it finds right 3 bytes that give correct hash. Three bytes that gives correct hash are
which gives new serial shape
- Second part -
Second part of serial is harder to catch and this time I needed some brain usage. I'll start from end. We will see GoodBoy message if this loop gets EAX=3 as incoming parameter. In case of EAX=1, serial is bad, other values are possible bugs in crackme (judging by author comments):
004012C3 |. 837D FC 01 CMP DWORD PTR SS:[EBP-4],1 ; BAD BOY!
004012C7 |. 74 08 JE SHORT dump.004012D1
004012C9 |. 837D FC 03 CMP DWORD PTR SS:[EBP-4],3 ; GOOD BOY!
004012CD |. 74 2C JE SHORT dump.004012FB
004012CF |. EB 54 JMP SHORT dump.00401325
004012D1 |> C74424 0C 1000>MOV DWORD PTR SS:[ESP+C],10 ; |
004012D9 |. C74424 08 0040>MOV DWORD PTR SS:[ESP+8],dump.00404000 ; |ASCII "Error"
004012E1 |. C74424 04 0840>MOV DWORD PTR SS:[ESP+4],dump.00404008 ; |ASCII "Bad serial, you better fix this."
004012E9 |. A1 14504000 MOV EAX,DWORD PTR DS: ; |
004012EE |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |
004012F1 |. E8 7A170000 CALL <JMP.&user32.MessageBoxA> ; MessageBoxA
004012F6 |. 83EC 10 SUB ESP,10
004012F9 |. EB 5E JMP SHORT dump.00401359
004012FB |> C74424 0C 4000>MOV DWORD PTR SS:[ESP+C],40 ; |
00401303 |. C74424 08 2940>MOV DWORD PTR SS:[ESP+8],dump.00404029 ; |ASCII "Not an Error"
0040130B |. C74424 04 3840>MOV DWORD PTR SS:[ESP+4],dump.00404038 ; |ASCII "Good serial, pat yourself on the back."
00401313 |. A1 14504000 MOV EAX,DWORD PTR DS: ; |
00401318 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |
0040131B |. E8 50170000 CALL <JMP.&user32.MessageBoxA> ; MessageBoxA
00401320 |. 83EC 10 SUB ESP,10
00401323 |. EB 34 JMP SHORT dump.00401359
00401325 |> C74424 0C 1000>MOV DWORD PTR SS:[ESP+C],10 ; |
0040132D |. C74424 08 5F40>MOV DWORD PTR SS:[ESP+8],dump.0040405F ; |ASCII "Quitting"
00401335 |. C74424 04 6840>MOV DWORD PTR SS:[ESP+4],dump.00404068 ; |ASCII "Shit, somthin's broken"
0040133D |. A1 14504000 MOV EAX,DWORD PTR DS: ; |
00401342 |. 890424 MOV DWORD PTR SS:[ESP],EAX ; |
00401345 |. E8 26170000 CALL <JMP.&user32.MessageBoxA> ; MessageBoxA
0040134A |. 83EC 10 SUB ESP,10
0040134D |. C70424 0100000>MOV DWORD PTR SS:[ESP],1 ; |
00401354 |. E8 B7160000 CALL <JMP.&msvcrt.exit> ; exit
Let's see where EAX=3 is given:
00401B8B |. E8 18FBFFFF |CALL dump.004016A8
00401B90 |. 8945 D4 |MOV DWORD PTR SS:[EBP-2C],EAX
00401B93 |. E8 66FDFFFF |CALL dump.004018FE ; This proc must return EAX=0.
00401B98 |. 0105 10504000 |ADD DWORD PTR DS:,EAX
00401B9E |. 833D 10504000 >|CMP DWORD PTR DS:,0 ;  must be 0 to pass check.
00401BA5 |. 74 14 |JE SHORT dump.00401BBB ; GoodBoy jump is executed in that case.
00401BA7 |. C74424 04 0A00>|MOV DWORD PTR SS:[ESP+4],0A
00401BAF |. C70424 0100000>|MOV DWORD PTR SS:[ESP],1
00401BB6 |. E8 D5F6FFFF |CALL dump.00401290
00401BBB |> 833D 10504000 >|CMP DWORD PTR DS:,0
00401BC2 |. 75 16 |JNZ SHORT dump.00401BDA
00401BC4 |. C74424 04 0000>|MOV DWORD PTR SS:[ESP+4],0
00401BCC |. C70424 0300000>|MOV DWORD PTR SS:[ESP],3 ; EAX=3 will be passed here.
00401BD3 |. E8 B8F6FFFF |CALL dump.00401290
As it can be seen, EAX=3 if =0, and that will be if CALL at 00401B93 returns EAX=0. So we must start from the end of that procedure. I will not paste code, but just reversed pseudo code:
EAX = 0 <---- LAST CONDITION !
0 = 69696969 SUB ((reg XOR 37) XOR 37) <-> reg=69696969
70000390 = reg SUB 06969A27 <-> reg=70000390
0000A954 = reg IMUL reg <-> reg=0000A954
0000A955 = reg - 1 <-> reg=0000A955
0000A955 = _lrotr reg , 1 <-> reg=000152AA
000152AA = reg XOR 0B33F <-> reg=0001E195
0001E195 = _lrotl reg , 3 <-> reg=A0003C32
That was reversing from the end. _lrotl is ROL and reg represent DWORD value in register format (in memory bytes are swapped). Before that _lrotr is used on dword given from name hash and last part of serial. Procedure for name hash is at 004016A8. Procedure for serial hash is at 0040195C and it is not actually a hash. It is just characters formated to hex value (egxample "12345678" -> 12345678h). Those two hashes must give A0003C32 value to _lrorl.
AND shash1,FFFF0000 -> 
MOV EDX,shash1 -> 12340000
XOR nhash,EDX -> [00000F0D]
AND shash2,0000FFFF -> 
mov EDX,shash2 -> 00005678
ADD nhash,EDX -> [78560F0D]
[78560F0D] -> 0D0F5678 -> This value must be A0003C32 to pass last check.
And here is solution: We can take name hash as constant (nhash) and we need to find serial hash that will give needed DWORD. Since serial hash is nothing but last part of serial formated as hex DWORD, we can reverse it easily.
0D0F(5678) -> Last four serial chars from false serial -> "5678"
A000(3C32) -> True last four chars -> "3C32"
XOR 1F3B0000,12340000 = 0D0F0000
but it must be
XOR 1F3B0000,xxxxxxxx = A0000000
and serial for my name is
For the end I need to mention that name hash depends on firs four characters from serial too. I didn't botheredd to reverse that part too because keygen is optional, but it is not hard and generic keygen is easy to code.
 THE END
And would be it. Greets to bpx , this was nice crackme but not so hard as I tought it would be.
See you folks :)