ARTeam Tutorial

by Shub-Nigurrath & Hosiminh

Visit: http://cracking.accessroot.com | http://forum.accessroot.com

Fishing serials from PerfectKeylogger and writing its Oraculum v.1.0


Information A tutorial explaining how Perfect Keylogger generates it's serials, and a final part on how to write an oraculum for this program (also included)
Target Perfect Keylogger 1.62
Available http://www.blazingtools.com/downloads.html
Tools OllyDbg 1.10 (HideDebug and OllyDump plugin), Import Reconstructor 1.6, Diablo2oo2 Universal Patcher
Protection not packed
level Beginner
Category patching
Author(s) Shub-Nigurrath & Hosiminh Feb 2005
Requirements Windows XP, Firefox 1.0 and above for best viewing & printing


1. Introduction


This tutorial is splitted into two parts, the first one explain how the serials are generated into the program and will find the point where the final real serial is ready to be taken from within the program.

The second part will explain how to write an oraculum using the framework Shub released into the Oraculum's tutorial (see http://tutorials.accessroot.com).

So, fasten your seatbelts ;-)



2. Fishing the serial


First of all, as usual, let's check the main executable program (BPK.EXE) using PEiD, well this time it's not packed so our work will be easier, but also give a look at the CRC signature which are present inside the program.
Scanning the file "bpk.exe" with PeId 0.93: Microsoft Visual C++ 6.0 PeID Krpyto plugin (kanal.dll) shows: BASE64 table at 0044A340 , MD5 at 00406A4B. But don't worry we will work around this problem indeed without modifying a single bit of the target application.

Load file "bpk.exe" into Ollydbg. Press F9 to run it.

Running bring some ugly Nag. Enter some fake details into registration boxes (click on: "Enter Registration Code") :

user_name: Halle Berry
fake_serial: 1111 2222 3333 4444

Click on OK.
Another Nag popups: "Registration code or user name is invalid. Please check all fields and try again!"..this is the right point where to start using OllyDbg and stopping the program.

Go back to Ollydbg and press F12 (Pause) and then Alt+K (Call stack).

Call stack of main thread
Address Stack Procedure / arguments Called from
0069BC34 00415139 ? <JMP.&MFC42.#4224> BPK.00415134
0069BC38 0044B0DC Arg1 = 0044B0DC ASCII "Registration code or u
0069BC3C 0044B130 Arg2 = 0044B130 ASCII "Registration error"
0069BC40 00000040 Arg3 = 00000040
0069BD38 0041548D ? BPK.00414F89 BPK.00415488
0069BD40 5F40228D Includes BPK.0041548D MFC42.5F40228A
0069BD50 5F4021E6 MFC42.5F4021F1 MFC42.5F4021E1
0069BD80 5F406506 MFC42.#4424 MFC42.5F406501
0069BDA4 5F402AE5 Includes MFC42.5F406506 MFC42.5F402AE2
0069BDF4 5F401BE1 Includes MFC42.5F402AE5 MFC42.5F401BDB
0069BE74 5F401AFF Includes MFC42.5F401BE1 MFC42.5F401AF9
0069BE94 5F401A88 Includes MFC42.5F401AFF MFC42.5F401A82
0069BEF4 5F401A10 MFC42.#1109 MFC42.5F401A0B
0069BF10 5F4019CF MFC42.#1578 MFC42.5F4019CA
0069BF3C BFF7363B Includes MFC42.5F4019CF KERNEL32.BFF73638
0069BF5C BFF94407 ? KERNEL32.BFF735FD KERNEL32.BFF94402


Right click on: BPK.00415134 -> Show Call (your offset might be different due to relocation of the program but note that the right point is the only call into the BPK program, all the other are into external libraries).


0041511C |. E8 211B0200 CALL <JMP.&MFC42.#860>
00415121 |. 6A 01 PUSH 1
00415123 |. 58 POP EAX
; BPK.0044B130
00415124 |. EB 20 JMP SHORT BPK.00415146
00415126 |> 6A 40 PUSH 40
00415128 |. 68 30B14400 PUSH BPK.0044B130  
; ASCII "Registration error"
0041512D |. 68 DCB04400 PUSH BPK.0044B0DC
; ASCII "Registration code or user name is invalid. Please check all fields and try again!"
00415132 |. 8BCE MOV ECX,ESI
00415134 |. E8 37200200 CALL <JMP.&MFC42.#4224>
; We are here after -> Right click on: BPK.00415134 <-
00415139 |. 68 2C010000 PUSH 12C
; /Timeout = 300. ms
0041513E |. FF15 4CC14300 CALL NEAR DWORD PTR DS:[<&KERNEL32.Sleep>
; \Sleep
00415144 |> 33C0 XOR EAX,EAX
; BPK.0044B130
00415146 |> 8B4D F4 MOV ECX,[LOCAL.3]
00415149 |. 5F POP EDI
0041514A |. 5E POP ESI
0041514B |. 64:890D 000000>MOV DWORD PTR FS:[0],ECX
00415152 |. C9 LEAVE
00415153 \. C3 RETN
00415154 /$ 55 PUSH EBP
00415155 |. 8BEC MOV EBP,ESP
00415157 |. 83EC 40 SUB ESP,40
0041515A |. 8D45 C0 LEA EAX,[LOCAL.16]
0041515D |. 56 PUSH ESI
0041515E |. 50 PUSH EAX
; BPK.0044B130
0041515F |. 68 50FA4300 PUSH BPK.0043FA50
; ASCII "_r <()<1-Z2[l5,^"

Scroll up , until we are at the beginning of this routine:

00414F89 /$ B8 1C9D4300 MOV EAX,BPK.00439D1C ; Put breakpoint here (with F2).
00414F8E |. E8 BD230200 CALL <JMP.&MSVCRT._EH_prolog>
00414F93 |. 81EC DC000000 SUB ESP,0DC
00414F99 |. 56 PUSH ESI
00414F9A |. 57 PUSH EDI
00414F9B |. 8D85 4CFFFFFF LEA EAX,[LOCAL.45]
00414FA1 |. 6A 32 PUSH 32
00414FA3 |. 50 PUSH EAX
; BPK.0044B130
00414FA4 |. 8BF1 MOV ESI,ECX
00414FA6 |. 68 D9D60000 PUSH 0D6D9
00414FAB |. E8 381F0200 CALL <JMP.&MFC42.#3098>
00414FB0 |. 80BD 4CFFFFFF >CMP BYTE PTR SS:[EBP-B4],0
00414FB7 75 53 JNZ SHORT BPK.0041500C
; Did the sucker enter his name ?

After you put breakpoint , click again on OK. Ollydbg should break.

Trace with F8 until here:

00415056 |. 50 PUSH EAX ; /String2 = "1111"
00415057 |. 8D45 80 LEA EAX,[LOCAL.32] ; |
0041505A |. 50 PUSH EAX
; |String1 = 0069BCE8
0041505B |. FF15 38C14300 CALL NEAR DWORD PTR DS:[<&KERNEL32.lstrc>
; \lstrcpyA
00415061 |. 8B3D 64C14300 MOV EDI,DWORD PTR DS:[<&KERNEL32.lstrcat>
00415067 |. 8D45 D8 LEA EAX,[LOCAL.10]
0041506A |. 50 PUSH EAX
; /StringToAdd = "1111"
0041506B |. 8D45 80 LEA EAX,[LOCAL.32] ; |
0041506E |. 50 PUSH EAX
; |ConcatString = "1111"
0041506F |. FFD7 CALL NEAR EDI
; \lstrcatA
00415071 |. 8D45 C0 LEA EAX,[LOCAL.16]
00415074 |. 50 PUSH EAX
; /StringToAdd = "1111"
00415075 |. 8D45 80 LEA EAX,[LOCAL.32] ; |
00415078 |. 50 PUSH EAX
; |ConcatString = "1111"
00415079 |. FFD7 CALL NEAR EDI
; \lstrcatA
0041507B |. 8D45 CC LEA EAX,[LOCAL.13]
0041507E |. 50 PUSH EAX
; /StringToAdd = "1111"
0041507F |. 8D45 80 LEA EAX,[LOCAL.32] ; |
00415082 |. 50 PUSH EAX
; |ConcatString = "1111"
00415083 |. FFD7 CALL NEAR EDI
; \lstrcatA
00415085 |. 8D85 18FFFFFF LEA EAX,[LOCAL.58]
0041508B |. 50 PUSH EAX


What's this ? Well, program takes first 4 char. from our fake_serial (from 1111 2222 3333 4444) and put them into EAX.
Then it takes another 4 chars, do some manipulations (add both strings), and the result is 11112222 .
Then after takes other 4 chars , so we get 111122223333.
Apply the same procedure for last 4 chars , result is 1111222233334444 .

Resume all using tracing, with F8 until here:

0041508C |. 8D85 4CFFFFFF LEA EAX,[LOCAL.45] ; load "Halle Berry" v EAX
00415092 |. 68 50FA4300 PUSH BPK.0043FA50
; ASCII "_r <()<1-Z2[l5,^" ; push magic string to stack
00415097 |. 50 PUSH EAX
00415098 |. E8 1FFEFFFF CALL BPK.00414EBC
; Interesting CALL , step into with F7

What's inside the call at 00414EBC:

1st iteration :

00414F08 |> /8BC6 /MOV EAX,ESI
00414F0A |. |6A 19 |PUSH 19
00414F0C |. |99 |CDQ
; Convert Double to Quad
00414F0D |. |F77D FC |IDIV [LOCAL.1]
; Signed Integer Division (division with remainder)
00414F10 |. |8BC6 |MOV EAX,ESI
00414F12 |. |5B |POP EBX
; Pop Word off Stack
00414F13 |. |8D0C3A |LEA ECX,DWORD PTR DS:[EDX+EDI]
; ECX = "_r <()<1-Z2[l5,^"
00414F16 |. |99 |CDQ
00414F17 |. |F77D F8 |IDIV [LOCAL.2]
00414F1A |. |8B45 08 |MOV EAX,[ARG.1]
; load string "Halle Berry" into EAX
00414F1D |. |0FB60402 |MOVZX EAX,BYTE PTR DS:[EDX+EAX]
; take char. "H" (48) from string "Halle Berry" into EAX
00414F21 |. |0FB611 |MOVZX EDX,BYTE PTR DS:[ECX]
; take char. "_" (5F) from magic string into EDX
00414F24 |. |33C2 |XOR EAX,EDX
; EAX = EAX xor EDX = 48 xor 5F = 17
00414F26 |. |99 |CDQ¸
; EBX = 19
00414F27 |. |F7FB |IDIV EBX
; EAX = EAX div EBX = 17 div 19 = 0 ; remainder into EDX = 17
00414F29 |. |80C2 41 |ADD DL,41
; DL = DL + 41 (char. A) = 17 + 41 = 58 (char. X)
00414F2C |. |46 |INC ESI
; ESI = ESI + 1
00414F2D |. |3B75 0C |CMP ESI,[ARG.2]
00414F30 |. |8811 |MOV BYTE PTR DS:[ECX],DL
; MOVes char. "X" v "_" ; changes ECX = "Xr <()<1-Z2[l5,^"
00414F32 |.^\7C D4 \JL SHORT BPK.00414F08
; Did we finish? If no , jump.
00414F34 |> \8BC7 MOV EAX,EDI
; MOVes EDI into EAX
00414F36 |. 5F POP EDI
; 82BA5FD8
00414F37 |> 5E POP ESI
; 82BA5FD8
00414F38 |. 5B POP EBX
; 82BA5FD8
00414F39 |. C9 LEAVE
00414F3A \. C3 RETN
; end of CALL

2nd iteration:

00414F16 |. 99 |CDQ ; ECX = "r <()<1-Z2[l5,^" ; EDI = "Xr <()<1-Z2[l5,^"
00414F17 |. F77D F8 |IDIV [LOCAL.2]
00414F1A |. 8B45 08 |MOV EAX,[ARG.1]
00414F1D |. 0FB60402 |MOVZX EAX,BYTE PTR DS:[EDX+EAX]
; take char. "a" (61) from string "Halle Berry" into EAX
00414F21 |. 0FB611 |MOVZX EDX,BYTE PTR DS:[ECX]
; take char. "r" (72) from magic string into EDX
00414F24 |. 33C2 |XOR EAX,EDX
; EAX = EAX xor EDX = 61 xor 72 = 13
00414F26 |. 99 |CDQ
; EBX = 19
00414F27 |. F7FB |IDIV EBX
; EAX = EAX div EBX = 13 div 19 = 0 ; remainder into EDX = 13
00414F29 |. 80C2 41 |ADD DL,41 ; DL = DL + 41 (char. A) = 13 + 41 = 54 (char. T) ; EDX = 13
00414F2C |. 46 |INC ESI
00414F2D |. 3B75 0C |CMP ESI,[ARG.2]
00414F30 |. 8811 |MOV BYTE PTR DS:[ECX],DL
00414F32 |.^ 7C D4 \JL SHORT BPK.00414F08
; ECX = "T <()<1-Z2[l5,^" ; EDI = "XT <()<1-Z2[l5,^"

3rd iteration:

00414F1D |. 0FB60402 |MOVZX EAX,BYTE PTR DS:[EDX+EAX] ; take char. "l" (6C) from string "Halle Berry" into EAX
00414F21 |. 0FB611 |MOVZX EDX,BYTE PTR DS:[ECX]
; take char. " " (20) from magic string into EDX
00414F24 |. 33C2 |XOR EAX,EDX
; EAX = EAX xor EDX = 6C xor 20 = 4C
00414F27 |. F7FB |IDIV EBX
; EAX = EAX div EBX = 4C div 19 = 3 ; remainder into EDX = 1 ; 4C = 3 * 19 + 1
00414F29 |. 80C2 41 |ADD DL,41
; DL = DL + 41 (char. A) = 1 + 41 = 42 (char. B)
00414F2C |. 46 |INC ESI
00414F2D |. 3B75 0C |CMP ESI,[ARG.2]
00414F30 |. 8811 |MOV BYTE PTR DS:[ECX],DL
00414F32 |.^\7C D4 \JL SHORT BPK.00414F08
; ECX = "B<()<1-Z2[l5,^" ; EDI = "XTB<()<1-Z2[l5,^"



After all chars from user_name are taken and manipulated by the code, the final result is into the EDI regiser: EDI = "XTBFCJBJUPATNOOJ"

Halle Berry
XTBF-CJBJ-UPAT-NOOJ

Reg. info are stored into the file "pk.bin" .



3. Writing the Oraculum

We now have the grips to create the oraculum for this program, as we told at the beginning there are several interesting pros for writing an Oraculum:

- first, the program has an integrity check inside so modifying it also would imply to find the place where the check is made,
- second, we already have a clear serial into the program, it's enough to extract it from its stomach,
- third, we want to distribute a patch for the program, easy to be used, without having to open Olly
- fourth, we don't have will to completely reverse the serial generation routine and transform it into a keygenerator

All these good reasons lead us to use an Oraculum (see the tutorial "Guide on How to play with processes memory, write loaders and Oraculums" by Shub-Nigurrath, available in the tutorials section of ARTeam) and specially the framework released. I'll assume you have already read it!

Following the instructions of that tutorial the things left to say are very few, because in this case the program is very simple.

There are two main steps you should do:

Step 1: take the standard project described into that tutorial and write new Callbacks as following:

void DoPatch_callback(COraculum *oraculum, DWORD dwMemoryPatchAddr) {

    oraculum->WriteProcessMemory(oraculum->GetPI()->hProcess,
    (LPVOID)dwMemoryPatchAddr,
    (void*)&COraculum::HALT_CODE, HALT_SIZE, NULL);
// places the EBFE loop
}

void DoActionPatch_callbackStop(COraculum *oraculum) {


    //EDI contains the pointer to real code!
    oraculum->ReadProcessMemory(oraculum->GetPI()->hProcess,
    (LPVOID)oraculum->GetProcessContext()->Edi,
    oraculum->GetProcessBuffer(),100,NULL);
}

void DoActionPatch_callbackResume(COraculum *oraculum) {


    //Writes the message, format it correctly
    char str[256];
    char tmpSerialStr[20], tmpSerialStrOut[20];

    strcpy(tmpSerialStr,oraculum->GetProcessBuffer());


    //each 4 characters inserts a -
    int idx=0, idxOut=0;
    while (tmpSerialStr[idx]!='\0') {
        if(idx%4==0 && idx!=0) {
            tmpSerialStrOut[idxOut]='-';
        idxOut++;
        }
        tmpSerialStrOut[idxOut]=tmpSerialStr[idx];
        idx++;
        idxOut++;
    }
    tmpSerialStrOut[idxOut]='\0';

    sprintf(str,"Shub-Nigurrath says that your correct serial is %s", tmpSerialStrOut);

    oraculum->pushMessage(str);

}


Step 2: insert the pattern to be searched in the victim's image. A proper pattern is a piece of code which have only local references, to lines of code not too far (not CALLs, not far JMPs). Why? Simple, when a new release comes out, the offsets and call address will surely change then also the exadecimal representations of the asm code. A pattern relative to such calls or jmps will probably not survive to a new release.

For this program one right pattern is:

00414F27 |. F7FB |IDIV EBX
00414F29 |. 80C2 41 |ADD DL,41
00414F2C |. 46 |INC ESI
00414F2D |. 3B75 0C |CMP ESI,[ARG.2]
00414F30 |. 8811 |MOV BYTE PTR DS:[ECX],DL
00414F32 |.^\7C
 
which is in bytes: F7FB80C241463B750C88117C
which becomes this pattern :xF7:xFB:x80:xC2:x41:x46:x3B:x75:x0C:x88:x11:x7C
The code we want to stop at is at address 414F34, so at an offset of 0xD forward.

This is then the final line you should add at the beginning of the main() part of the oraculum

Oraculum.AddPattern(":xF7:xFB:x80:xC2:x41:x46:x3B:x75:x0C:x88:x11:x7C", 0xD);

If your addresses are different, it's not a problem indeed, because the Oraculum class worries of all the magic using a search routine which searches the string inside the program and moves to 0x0D offset from it. Thus the oraculum will work also for relocated processes.

Compile everything and run the program as "BPK_oraculum bpk.exe". It launches the main program and stay underground waiting you to go into the registration dialog, enter any serial you like for any name and press OK. Then the oraculum terminates the victim's process and reports to you what you needed.

Messages Stack dump, read information in reverse order..
--------------------------------------------------------
3> The target process has been terminated! Restart it directly and enter the gathered information..
2> Shub-Nigurrath says that your correct serial is XTBF-CJBJ-UPAT-NOOJ
1> Location to patch: 14F79(offset), 414F79 (memory)

 

As you might have noticed the memory address that have been patched is different than the one I said before, but the oraculum worked as well.

 



4. Conclusions

Lesson Learnt

This time we learnt how simple could be to find a serial even for a program that apparently has some protections inside (the integrity check) and how to obtian what we want through a different way developers didn't think.

Moreover here's another example of an easy oraculum..

have phun!


 
5. Greetingz

[MAIN TEAM]
| Nilrem | Enforcer | Ferrari | Pompeyfan(ex-member) | MaDMAn_H3rCuL3s | EJ12N | Kruger |
Shub-Nigurrath | Jdog45 | R@adier | Teerayoot | ThunderPwr | Eggi | Bone Enterprise | Gabri3l |

*****************************

Exetools | Woodmann | VCT | TSRh | Sir JMI | | FEUERRADER | Britedream | cl0ud (Mephisto) | Zest | Hobgoblin | Nullz | Everyone I missed & you


(.|.)
 ).( (¯`·._.·[¯¨´*·~-.¸¸,.-~*´¨&8~) Ŝħůβ¬Ňïĝµŕřāŧħ ¨´*·~-.¸¸,.-~*´¨]·._.·´¯)
( v )
 \|/