
:::::::::::::::::::::  m E X / c 4 N  T U T O R I A L  D I V I S I O N  :::::::::::::::::::::

Cracker    : Vizion
Editor     : Notepad (wordwrap on)
Audience   : intermediate, advanced
Greets     : all people from mEX/c4N, Revolt, #cracking, #PST, and all I know...
Target     : Password Tracker Deluxe v3.20
Protection : name/serial
Solution   : key generator

Remark     : bug reported by Corn2 (11/03/1997), bug fixed (11/04/1997)

I'm working on a general key generator tutorial where I will try to explain and learn people how to build up all information they need to create there own key generators. This isn't finished! But because a lot of people wanted a solution for this AOTW I released this short tutorial on this target. So watch out for my keygen-tute in the near future!!

Ok, put a breakpoint on GetWindowTextA, youll break twice, and you should be able after some tracing, get a the next code,

* Possible Reference to Dialog: DialogID_008B, CONTROL_ID:03E8, ""
                                  |
:00415CDC 68E8030000              push 000003E8
:00415CE1 C644241C01              mov [esp+1C], 01
:00415CE6 E80FA80100              call 004304FA
:00415CEB 8BC8                    mov ecx, eax
:00415CED E897840100              call 0042E189                   << read name
:00415CF2 8D4C2404                lea ecx, dword ptr [esp+04]
:00415CF6 51                      push ecx

* Possible Reference to Dialog: DialogID_0092, CONTROL_ID:040B, ""
                                  |
:00415CF7 680B040000              push 0000040B
:00415CFC 8BCE                    mov ecx, esi
:00415CFE E8F7A70100              call 004304FA
:00415D03 8BC8                    mov ecx, eax
:00415D05 E87F840100              call 0042E189                   << read serial
:00415D0A 8B542404                mov edx, dword ptr [esp+04]
:00415D0E 8B442408                mov eax, dword ptr [esp+08]
:00415D12 52                      push edx                        << store serial
:00415D13 50                      push eax                        << store name
:00415D14 E807FFFFFF              call 00415C20                   << validate information
:00415D19 83C408                  add esp, 00000008
:00415D1C 83F840                  cmp eax, 00000040               << information correct?
:00415D1F 7509                    jne 00415D2A                    << jump_bad_cracker
:00415D21 8BCE                    mov ecx, esi
:00415D23 E856740100              call 0042D17E
:00415D28 EB21                    jmp 00415D4B                    << jump_good_buyer

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415D1F(C)
|
:00415D2A 6A00                    push 00000000
:00415D2C 6A30                    push 00000030

* Possible StringData Ref from Data Obj ->"Invalid registration number."
                                  |
:00415D2E 68043D4600              push 00463D04                   << string on stack
:00415D33 E8CD2E0200              call 00438C05                   << show nag screen

Ok, the above code should be easy to follow, now take a look at the call just before the critical compare (415D1C),

* Referenced by a CALL at Addresses:
|:00407A61   , :00415D14   
|
:00415C20 6AFF                    push FFFFFFFF
:00415C22 6858DB4400              push 0044DB58
:00415C27 64A100000000            mov eax, dword ptr fs:[00000000]
:00415C2D 50                      push eax
:00415C2E 64892500000000          mov dword ptr fs:[00000000], esp
:00415C35 83EC08                  sub esp, 00000008
:00415C38 8B442418                mov eax, dword ptr [esp+18]
:00415C3C 56                      push esi
:00415C3D 51                      push ecx
:00415C3E 8BCC                    mov ecx, esp
:00415C40 89642408                mov dword ptr [esp+08], esp
:00415C44 50                      push eax
:00415C45 E878B30100              call 00430FC2                  << make copy of name
:00415C4A 8D4C2408                lea ecx, dword ptr [esp+08]
:00415C4E E83D010000              call 00415D90                  << ! Call (1)
:00415C53 8B542420                mov edx, dword ptr [esp+20]
:00415C57 51                      push ecx
:00415C58 8BCC                    mov ecx, esp
:00415C5A 89642420                mov dword ptr [esp+20], esp
:00415C5E 52                      push edx
:00415C5F C744241C00000000        mov [esp+1C], 00000000
:00415C67 E856B30100              call 00430FC2                  << make copy of serial
:00415C6C 8D4C2408                lea ecx, dword ptr [esp+08]
:00415C70 E8CB010000              call 00415E40                  << ! Call (2)
:00415C75 8D4C2404                lea ecx, dword ptr [esp+04]
:00415C79 8BF0                    mov esi, eax
:00415C7B C7442414FFFFFFFF        mov [esp+14], FFFFFFFF
:00415C83 E878370000              call 00419400                  << do nothing
:00415C88 8B4C240C                mov ecx, dword ptr [esp+0C]
:00415C8C 8BC6                    mov eax, esi                   <<
:00415C8E 64890D00000000          mov dword ptr fs:[00000000], ecx
:00415C95 5E                      pop esi
:00415C96 83C414                  add esp, 00000014
:00415C99 C3                      ret

Ok the above code is pretty obvious again, no difficulties here, just couple of important calls. Let's take a look at Call (1) and see what happens with the name we entered,

* Referenced by a CALL at Address:
|:00415C4E   
|
:00415D90 64A100000000            mov eax, dword ptr fs:[00000000]
:00415D96 6AFF                    push FFFFFFFF
:00415D98 6898DB4400              push 0044DB98
:00415D9D 50                      push eax
:00415D9E 64892500000000          mov dword ptr fs:[00000000], esp
:00415DA5 57                      push edi
:00415DA6 8BF9                    mov edi, ecx
:00415DA8 8D4C2414                lea ecx, dword ptr [esp+14]
:00415DAC C744240C00000000        mov [esp+0C], 00000000
:00415DB4 E820500100              call 0042ADD9                   << ?
:00415DB9 8D4C2414                lea ecx, dword ptr [esp+14]
:00415DBD E860500100              call 0042AE22                   << ?
:00415DC2 8B442414                mov eax, dword ptr [esp+14]
:00415DC6 C74704FFFFFFFF          mov [edi+04], FFFFFFFF
:00415DCD C707FFFFFFFF            mov dword ptr [edi], FFFFFFFF
:00415DD3 8B50F8                  mov edx, dword ptr [eax-08]     << length of string
:00415DD6 85D2                    test edx, edx                   << zero? 
:00415DD8 743D                    je 00415E17                     << yes! get out'a here
:00415DDA 56                      push esi
:00415DDB BE0D000000              mov esi, 0000000D               << !
:00415DE0 85D2                    test edx, edx                   
:00415DE2 7E1A                    jle 00415DFE
:00415DE4 B901000000              mov ecx, 00000001
:00415DE9 55                      push ebp
:00415DEA 53                      push ebx
:00415DEB 2BC8                    sub ecx, eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415DFA(C)
|
:00415DED 0FBE28                  movsx ebp, byte ptr [eax]       << !
:00415DF0 8D1C01                  lea ebx, dword ptr [ecx+eax]    << !
:00415DF3 0FAFDD                  imul ebx, ebp                   << !
:00415DF6 03F3                    add esi, ebx                    << !
:00415DF8 40                      inc eax                         << !
:00415DF9 4A                      dec edx                         << !
:00415DFA 75F1                    jne 00415DED                    << !
:00415DFC 5B                      pop ebx
:00415DFD 5D                      pop ebp

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415DE2(C)
|
:00415DFE 56                      push esi
:00415DFF E8EC2C0000              call 00418AF0                   << ?
:00415E04 83C404                  add esp, 00000004
:00415E07 E8F42C0000              call 00418B00                   << ! Call (1.1)
:00415E0C 8907                    mov dword ptr [edi], eax        << store value
:00415E0E E8ED2C0000              call 00418B00                   << ! Call (1.1)
:00415E13 894704                  mov dword ptr [edi+04], eax     << store value
:00415E16 5E                      pop esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415DD8(C)
|
:00415E17 8D4C2414                lea ecx, dword ptr [esp+14]
:00415E1B C744240CFFFFFFFF        mov [esp+0C], FFFFFFFF
:00415E23 E82CB10100              call 00430F54                   << ?
:00415E28 8B4C2404                mov ecx, dword ptr [esp+04]
:00415E2C 8BC7                    mov eax, edi
:00415E2E 64890D00000000          mov dword ptr fs:[00000000], ecx
:00415E35 5F                      pop edi
:00415E36 83C40C                  add esp, 0000000C
:00415E39 C20400                  ret 0004

Call (1.1) in detail,

* Referenced by a CALL at Addresses:
|:00403EC2   , :00403EDC   , :00403F47   , :00403F63   , :00403F76   
|:00403FE0   , :00404096   , :0040C26A   , :0040C271   , :00411276   
|:00415E07   , :00415E0E   
|
:00418B00 E84B450000              call 0041D050                           << ?
:00418B05 8B4814                  mov ecx, dword ptr [eax+14]             << 
:00418B08 8D1449                  lea edx, dword ptr [ecx+2*ecx]          
:00418B0B 8D1491                  lea edx, dword ptr [ecx+4*edx]          
:00418B0E C1E204                  shl edx, 04                             
:00418B11 03D1                    add edx, ecx                            
:00418B13 C1E208                  shl edx, 08                             
:00418B16 2BD1                    sub edx, ecx                            
:00418B18 8D8C91C39E2600          lea ecx, dword ptr [ecx+4*edx+00269EC3] 
:00418B1F 894814                  mov dword ptr [eax+14], ecx             << !
:00418B22 8BC1                    mov eax, ecx                            
:00418B24 C1E810                  shr eax, 10                             
:00418B27 25FF7F0000              and eax, 00007FFF                       
:00418B2C C3                      ret

Well, what happens here with the name that we entered? First, the target checks if we entered a name at all (415DD6, 415DD8 and 415DE0, 415DE2). Then there follows a very important part, in fact this will be the first part of code that we will need to create the key generator! The entered name is used to calculate a temporary value (415DED to 415DFA) - call it value_0, this value is then used in Call (1.1) - the code of Call (1.1) will also be important for our key generator - and we finally got two different values (call them value_1 and value_2) that are stored for later use (415E0C and 415E0E). The line 418B1F is important when Call (1.1) is used the second time.

Ok, it's time for the serial we entered, lets take a look at Call (2), I wish to warn you for the listing that follows, it's long - so read it several times if you can't follow it from the first time and due to its length I will add comments in between the code listing,

* Referenced by a CALL at Address:
|:00415C70   
|
:00415E40 6AFF                    push FFFFFFFF
:00415E42 68B8DB4400              push 0044DBB8
:00415E47 64A100000000            mov eax, dword ptr fs:[00000000]
:00415E4D 50                      push eax
:00415E4E 64892500000000          mov dword ptr fs:[00000000], esp
:00415E55 51                      push ecx
:00415E56 53                      push ebx
:00415E57 56                      push esi
:00415E58 57                      push edi
:00415E59 8BF1                    mov esi, ecx
:00415E5B 8B06                    mov eax, dword ptr [esi]        << 
:00415E5D 83CFFF                  or edi, FFFFFFFF                << 
:00415E60 3BC7                    cmp eax, edi                    <<
:00415E62 C744241800000000        mov [esp+18], 00000000
:00415E6A 7509                    jne 00415E75                    <<
:00415E6C 397E04                  cmp dword ptr [esi+04], edi     <<
:00415E6F 0F848E000000            je 00415F03                     <<

Uhm, here the target checks if value_1 and value_2 are not -1.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415E6A(C)
|
:00415E75 8D4C2420                lea ecx, dword ptr [esp+20]
:00415E79 E8A44F0100              call 0042AE22                    << ?
:00415E7E 8D4C2420                lea ecx, dword ptr [esp+20] 
:00415E82 E8524F0100              call 0042ADD9                    << ?
:00415E87 8B442420                mov eax, dword ptr [esp+20]
:00415E8B 8378F80D                cmp dword ptr [eax-08], 0000000D << 
:00415E8F 7572                    jne 00415F03                     << 
:00415E91 8D4C240C                lea ecx, dword ptr [esp+0C]
:00415E95 6A02                    push 00000002
:00415E97 51                      push ecx
:00415E98 8D4C2428                lea ecx, dword ptr [esp+28]
:00415E9C E8C84B0100              call 0042AA69
:00415EA1 8B10                    mov edx, dword ptr [eax]

Ok, first you should now what '<< ?' means. Well, I just wanna say by this that the call is not important to us or that I don't know or remember what it does... here it probably calculates the string length and stores it... Anyway in line 415E8B the target checks if the length of the serial is 13 digits long. If the serial that you entered isn't 13 digits long, reenter a new one that is.

* Possible StringData Ref from Data Obj ->"PT"                      << !
                                  |
:00415EA3 68243D4600              push 00463D24
:00415EA8 52                      push edx
:00415EA9 E8B22D0000              call 00418C60                     << Call (2.1)
:00415EAE 8BD8                    mov ebx, eax                      << eax = result of (2.1)
:00415EB0 83C408                  add esp, 00000008
:00415EB3 F7DB                    neg ebx                           <<
:00415EB5 1BDB                    sbb ebx, ebx                      <<
:00415EB7 8D4C240C                lea ecx, dword ptr [esp+0C]
:00415EBB F7DB                    neg ebx                           <<
:00415EBD E892B00100              call 00430F54
:00415EC2 84DB                    test bl, bl                       << everything ok?
:00415EC4 753D                    jne 00415F03                      << no! get out'a here

Sorry I break here, but here we got an important part of code. 
We got a string data reference to "PT" (maybe from Password Tracker ;). Call (2.1) checks if the first 2 digits are equal to "PT" and the result is returned in eax. The following lines are very typical (415EB3, 415EB5 and 415EBB) when it comes to testing if something went just like it should have gone... Ok, you probly will have to reenter you serial, now enter one that starts with "PT" and that's 13 digits long.

:00415EC6 8D44240C                lea eax, dword ptr [esp+0C]
:00415ECA 6A02                    push 00000002                     <<
:00415ECC 50                      push eax
:00415ECD 8D4C2428                lea ecx, dword ptr [esp+28]
:00415ED1 E8684A0100              call 0042A93E                     <<
:00415ED6 8B08                    mov ecx, dword ptr [eax]
:00415ED8 51                      push ecx
:00415ED9 E8F2340000              call 004193D0                     << Call (2.2)
:00415EDE 8BC8                    mov ecx, eax                      << 
:00415EE0 8B06                    mov eax, dword ptr [esi]          << ! value_1
:00415EE2 F7D8                    neg eax                           <<
:00415EE4 99                      cdq                               <<

* Possible Reference to Dialog: DialogID_008B, CONTROL_ID:03E8, ""
                                  |
:00415EE5 BBE8030000              mov ebx, 000003E8                 <<
:00415EEA 83C404                  add esp, 00000004
:00415EED F7FB                    idiv ebx                          <<
:00415EEF 33DB                    xor ebx, ebx                      <<
:00415EF1 3BCA                    cmp ecx, edx                      << !
:00415EF3 8D4C240C                lea ecx, dword ptr [esp+0C]
:00415EF7 0F95C3                  setne bl                          << !
:00415EFA E855B00100              call 00430F54
:00415EFF 84DB                    test bl, bl                       <<
:00415F01 7423                    je 00415F26                       << jump_good_buyer

Hey, here I'm again. The call in line 415ED1 will 'cut' of the first two digits from the serial, so we got a serial from 11 (13-2 ;) digits left. What's next? Well Call (2.2) is some kind of split_and_convert function, it gets the first four digits and convert it to a number and the value is returned in eax. Then something great happens, value_1 is loaded (don't remember it, get back to some of the code above and read it again). With value_1, the target does some more math and then in line 415EF1 a we got a critical compare. If you want to get the good value check out ecx (you probly need again reenter the serial).

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00415E6F(C), :00415E8F(C), :00415EC4(C)
|
:00415F03 8D4C2420                lea ecx, dword ptr [esp+20]
:00415F07 897C2418                mov dword ptr [esp+18], edi
:00415F0B E844B00100              call 00430F54
:00415F10 8BC7                    mov eax, edi
:00415F12 8B4C2410                mov ecx, dword ptr [esp+10]
:00415F16 64890D00000000          mov dword ptr fs:[00000000], ecx
:00415F1D 5F                      pop edi
:00415F1E 5E                      pop esi
:00415F1F 5B                      pop ebx
:00415F20 83C410                  add esp, 00000010
:00415F23 C20400                  ret 0004

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415F01(C)
|
:00415F26 8D54240C                lea edx, dword ptr [esp+0C]
:00415F2A 6A06                    push 00000006                         <<
:00415F2C 52                      push edx
:00415F2D 8D4C2428                lea ecx, dword ptr [esp+28]
:00415F31 E8084A0100              call 0042A93E                         <<
:00415F36 8B00                    mov eax, dword ptr [eax]
:00415F38 50                      push eax
:00415F39 E892340000              call 004193D0                         << Call (2.3)
:00415F3E 8B4E04                  mov ecx, dword ptr [esi+04]           << ! value_2
:00415F41 83C404                  add esp, 00000004
:00415F44 8BD1                    mov edx, ecx                          <<
:00415F46 33DB                    xor ebx, ebx                          <<
:00415F48 C1E203                  shl edx, 03                           <<
:00415F4B 2BD1                    sub edx, ecx                          <<
:00415F4D D1E2                    shl edx, 1                            <<
:00415F4F 2BD1                    sub edx, ecx                          <<
:00415F51 8D4C240C                lea ecx, dword ptr [esp+0C]           <<
:00415F55 F7DA                    neg edx                               <<
:00415F57 3BC2                    cmp eax, edx                          << !
:00415F59 0F95C3                  setne bl                              << !
:00415F5C E8F3AF0100              call 00430F54          
:00415F61 84DB                    test bl, bl                           <<
:00415F63 897C2418                mov dword ptr [esp+18], edi
:00415F67 8D4C2420                lea ecx, dword ptr [esp+20]
:00415F6B 741B                    je 00415F88                           << jump_good_buyer
:00415F6D E8E2AF0100              call 00430F54
:00415F72 8BC7                    mov eax, edi
:00415F74 8B4C2410                mov ecx, dword ptr [esp+10]
:00415F78 64890D00000000          mov dword ptr fs:[00000000], ecx
:00415F7F 5F                      pop edi
:00415F80 5E                      pop esi
:00415F81 5B                      pop ebx
:00415F82 83C410                  add esp, 00000010
:00415F85 C20400                  ret 0004

Uhm, again the same shit (euhh code ;). Now the last 7 digits are again used in the same and previous mentioned split_and_convert function. Again, a value from our name is loaded, value_2 this time and some more math again and the critical compare at line 415F57. Now if you like to see the good third part of the serial it's in eax.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00415F6B(C)
|
:00415F88 E8C7AF0100              call 00430F54
:00415F8D 8B4C2410                mov ecx, dword ptr [esp+10]
:00415F91 5F                      pop edi
:00415F92 5E                      pop esi
:00415F93 B840000000              mov eax, 00000040                     << !!
:00415F98 64890D00000000          mov dword ptr fs:[00000000], ecx
:00415F9F 5B                      pop ebx
:00415FA0 83C410                  add esp, 00000010
:00415FA3 C20400                  ret 0004

This last part is quite obvious if you remember that eax will be compared to 40h. So that's all we need to know about the target for our key generator. Now let me quickly show you how I translated all that code to a working pascal program,

program PTD320-K;

var
 sPart1, sPart2, sUName      : string;
 bCount                      : byte;
 lPart1, lPart2,lTemp,lDummy : longint;
 bUNLen                      : byte absolute sUName;
 bP1Len                      : byte absolute sPart1;
 bP2Len                      : byte absolute sPart2;

function Calculate(lNumber : longint) : longint;        { Call (1.1) }
var
 lPart : longint;
begin
 lPart   := 3 * lNumber;
 lPart   := lNumber + (4 * lPart);
 lPart   := lPart shl 4;
 lPart   := lPart + lNumber;
 lPart   := lPart shl 8;
 lPart   := lPart - lNumber;
 lNumber := lNumber + (4 * lPart) + $269EC3;

 lTemp   := lNumber;

 lNumber := lNumber shr 16;
 lPart   := lNumber and 32767; { $7FFFF, bug! reported by Corn2 }

 Calculate := lPart
end;

function Complete(sNumber : string; bTotal : byte) : string;
var
 bCount, bDummy : byte;
 bNuLen         : byte absolute sNumber;
 sDummy         : string;
begin
 if bTotal = 7 then Dec(bNuLen);

 bDummy    := 2;
 sDummy[1] := sNumber[1];

 for bCount := 2 to bTotal do
  if bCount < bNuLen
   then sDummy[bCount] := '0'
   else begin
         sDummy[bCount] := sNumber[bDummy];
         Inc(bDummy)
        end;

 if bTotal = 7
  then sDummy[0] := #7
  else sDummy[0] := #4;

 Complete  := sDummy
end;

begin
 writeln('Password Tracker Deluxe v3.20 - Key Generator by Vizion');
 write('Enter your name : ');
 readln(sUName);

 lTemp := $0D;
 for bCount := 1 to bUNLen do
  begin
   lDummy := byte(sUName[bCount]);
   lDummy := lDummy * bCount;
   lTemp  := lTemp + lDummy
  end;

 lPart1 := Calculate(lTemp);

 lPart1 := (0 - lPart1);
 lPart1 := lPart1 mod $3E8;

 Str(lPart1, sPart1);
 if bP1Len < 4 then sPart1 := Complete(sPart1, 4);

 lDummy := Calculate(lTemp);

 lPart2 := lDummy shl $3;
 lPart2 := lPart2 - lDummy;
 lPart2 := lPart2 shl $1;
 lPart2 := lPart2 - lDummy;
 lPart2 := (0 - lPart2);

 Str(lPart2, sPart2);
 if bP2Len < 7 then sPart2 := Complete(sPart2, 7);

 write('Registration Number : ', 'PT' + sPart1 + sPart2)
end.

If you like to get more information on this pascal code just contact me and ask...
So that's all folks, hope you enjoyed it just like I did,

Vizion.

:::::::::::::::::::::  m E X / c 4 N  T U T O R I A L  D I V I S I O N  :::::::::::::::::::::
