--------------------------------------------------------------------------------------------     

Date : 05-september-2001

File : Preview 4.0 (http://lib-sys.com)

Tools: Softice, Borland C Compiler (optional)

Author: j!m (zejim@netcourrier.com) published by +Tsehp September 2001

Skill level: Beginner[x] Aware[] Advanced[] Master[]

--------------------------------------------------------------------------------------------

 

Begin

 

Hi folks, here i am with my very first tutorial. Before starting playing with this stuff i must say that i'm an absolute beginner with Intel ASM and Windows API (but i was quite at ease with 68000 and the good old Atari ST when i was young in the late 80's!!).

Moreover the protection scheme reversed here is totally lame, so be indulgent with me!

 

Today we are going to make a full registered version of preview 4.0 which is a nice and light postscript viewer/printer edited by lib-sys.com.

 

Ok, go and get a copy of this program, install it, launch preview.exe and Ctrl+D to call the wonderfull softice debugger, put a breakpoint: Bpx MessageBoxA, Ctrl+D to go back to Preview.

 

open the registration box, type what you want (12345 for example) and press "Thank You for Registering!" (you're welcome...)

 

you are in back softice, F12, you see a messagebox called "BAD SERIAL NUMBER" (we are not lucky, we have to change this later!!), click "OK", you are in softice pointing on Preview asm code.

 

move backward to the beginnig of the subfunction until you reach a RET instruction, you should see this sequence:

 

 PUSH 41A148 ; the serial you entered (d 41A148 to verify)

 CALL 404950 ; the serialcheck routine

 ADD  ESP,04

 TEST EAX,EAX ; good serial ?

 JNZ  ok

ko:

  messagebox "BAD SERIAL"

  ...

 

At this point maybe you think all we need is to change the JNZ by an unconditionnal JUMP.

this way doesn't work well, because the program makes a check at launch to see if your serial stored (encrypted) in the file PROFILE.DAT is valid and you should encounter an error.

We could inspect the code to remove all these checks to force the program to run whatever serial entered but i prefer another approach...in a way we won't have to patch even a byte of code, we are going to generate a valid key!

 

so let's go deeper in the serial check rout:

 

:00404950 53                      push ebx                 

:00404951 8B5C2408                mov ebx, dword ptr [esp+08] ; ebx points to the serial you entered

:00404955 56                      push esi

:00404956 57                      push edi

:00404957 8BFB                    mov edi, ebx

:00404959 83C9FF                  or ecx, FFFFFFFF

:0040495C 33C0                    xor eax, eax

:0040495E F2                      repnz

:0040495F AE                      scasb                 ; test input bytes until 00

:00404960 F7D1                    not ecx

:00404962 49                      dec ecx               ; ecx = length of your serial

:00404963 83F90F                  cmp ecx, 0000000F   ; these lines calculates the length of the serial 

:00404966 7404                    je 0040496C         ; it must be 15 digits long

:00404968 5F                      pop edi

:00404969 5E                      pop esi

:0040496A 5B                      pop ebx

:0040496B C3                      ret

 

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

|:00404966(C)

|

:0040496C B902000000              mov ecx, 00000002   ; here is a strange check

:00404971 8BFB                    mov edi, ebx

 

* Possible StringData Ref from Data Obj ->"21"

                                  |

:00404973 BE44674100              mov esi, 00416744   

:00404978 33C0                    xor eax, eax

:0040497A F3                      repz

:0040497B A6                      cmpsb

:0040497C 7404                    je 00404982         ; the serial must begin with "21", why not..

:0040497E 5F                      pop edi      

:0040497F 5E                      pop esi

:00404980 5B                      pop ebx

:00404981 C3                      ret

 

the check routine follows,

 

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

|:0040497C(C)

|

:00404982 33D2                    xor edx, edx        ; edx = 0

:00404984 33F6                    xor esi, esi        ; esi = 0

 

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

|:004049B3(C)

|

:00404986 8A4C1A05                mov cl, byte ptr [edx+ebx+05] ; we begin with digit at index=ebx+5+edx

:0040498A 80F930                  cmp cl, 30                ; testing range (0<=x<=9)

:0040498D 7C2F                    jl 004049BE

:0040498F 80F939                  cmp cl, 39

:00404992 7F2A                    jg 004049BE

:00404994 8A441A0A                mov al, byte ptr [edx+ebx+0A]   ; get digit at index=ebx+0xA+edx

:00404998 3C30                    cmp al, 30                ; testing range

:0040499A 7C22                    jl 004049BE

:0040499C 3C39                    cmp al, 39

:0040499E 7F1E                    jg 004049BE

:004049A0 0FBEC9                  movsx ecx, cl

--------------------------------------------------------------------------------------------------

:004049A3 388431B8654100          cmp byte ptr [ecx+esi+004165B8], al   ; interesting!!(see below)

---------------------------------------------------------------------------------------------------

:004049AA 7512                    jne 004049BE              ; you are WRONG

:004049AC 83C60C                  add esi, 0000000C         ; esi + 12

:004049AF 42                      inc edx             ; edi + 1

:004049B0 83FE3C                  cmp esi, 0000003C        

:004049B3 7CD1                    jl 00404986               ; looping five times

 

if each digit maps well you execute the next code:

 

:004049B5 B801000000              mov eax, 00000001         ; you are GOOD!!!

:004049BA 5F                      pop edi

:004049BB 5E                      pop esi

:004049BC 5B                      pop ebx

:004049BD C3                      ret

 

else...

 

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

|:0040498D(C), :00404992(C), :0040499A(C), :0040499E(C), :004049AA(C)

|

:004049BE 33C0                    xor eax, eax              ; you are WRONG!!!

:004049C0 5F                      pop edi

:004049C1 5E                      pop esi

:004049C2 5B                      pop ebx

:004049C3 C3                      ret

 

 

the instruction "cmp byte ptr [ecx+esi+004165B8], al" shows us that the digit located at index ebx+0xA+edx (AL) depends on the digit located at ebx+0x5+edx (CL)

 

here is a dump of memory at 0x4165B8:

 

:004165B8 50 72 6F 67 72 65 73 73  Progress

:004165C0 00 00 00 00 31 30 30 25  ....100%

:004165C8 00 00 00 00 30 25 00 00  ....0%..

:004165D0 25 64 25 25 00 00 00 00  %d%%....

:004165D8 25 73 20 50 61 67 65 20  %s Page

:004165E0 25 64 00 00 00 00 00 00  %d......     ;yeahh, you can see five key tables used to compute the last five digits!!!

:004165E8 33 34 38 39 36 31 37 30  34896170     ;of the serial code

:004165F0 32 35 00 00 34 31 32 30  25..4120     ;

:004165F8 39 38 35 37 33 36 00 00  985736..

:00416600 36 32 34 33 31 39 35 38  62431958

:00416608 37 30 00 00 32 30 39 38  70..2098

:00416610 37 34 36 31 35 33 00 00  746153..

:00416618 37 33 30 36 34 35 31 39  73064519

:00416620 38 32 00 00 4C 69 63 65  82..Lice

:00416628 6E 73 65 20 63 6F 6E 66  nse conf

:00416630 69 67 75 72 61 74 69 6F  iguratio

:00416638 6E 20 65 72 72 6F 72 20  n error

 

do you understand what happens?

 

first round in details:

----------------------_

remember : "cmp byte ptr [ecx+esi+004165B8], al"

                       ^   ^      ^       ^

                           |   |      |       |__ digit at index 0xA in the serial

                           |   |      |________ points to the key tables

                           |   |_______________ to address the right table, each table is 12 bytes long (10 digits + 00 00)

                           |                    so esi takes values 0, 0xC, ...

                           |___________________digit at index 0x5 (takes values in 0x30, 0x31 ... 0x39)

 

 

so the serial code format is :

------------------------------------

index|0 1 2 3 4 5 6 7 8 9 A B C D E|

------------------------------------

digit|2 1 x x x a b c d e f g h i j|

------------------------------------

 

digits at indexes 0,1 are "2" and "1"

digits at indexes 2,3,4 are not significants choose what you want even ascii characters if you want

only digits at indexes 5,6,7,8,9 are important

 

if a= 0 1 2 3 4 5 6 7 8 9

f=    3 4 8 9 6 1 7 0 2 5

 

if b= 0 1 2 3 4 5 6 7 8 9

g=    4 1 2 0 9 8 5 7 3 6

 

if c= 0 1 2 3 4 5 6 7 8 9

h=    6 2 4 3 1 9 5 8 7 0

 

if d= 0 1 2 3 4 5 6 7 8 9

i=    2 0 9 8 7 4 6 1 5 3

 

if e= 0 1 2 3 4 5 6 7 8 9

j=    7 3 0 6 4 5 1 9 8 2

 

To compute a valid serial, all you have to do is to choose 5 digits and determine the last five digits with the tables above.

for example i choose 67894, a=6 so f=7, b=7 so g=7 ...

 

My serial is 21xxx6789477734 where xxx is what i want!!

 

To achieve this tutorial let's write a simple keygen program in C:

 

#include <stdio.h>

#include <conio.h>

 

int main() {

 int digit, i;

 int serial[10] = {0,0,0,0,0,0,0,0,0,0};

 int keymap[5][10] = {{3,4,8,9,6,1,7,0,2,5},

                      {4,1,2,0,9,8,5,7,3,6},

                      {6,2,4,3,1,9,5,8,7,0},

                      {2,0,9,8,7,4,6,1,5,3},

                      {7,3,0,6,4,5,1,9,8,2}};

 printf("Preview 4.0 Key Generator\n");

 printf("-------------------------\n");

 printf("Enter a five digit code : ");

 

 for ( i=0; i<5; i++) {

  digit=getch();

  putchar(digit);

  serial[i] = digit-0x30;

  serial[i+5] = keymap[i][digit-0x30];

 }

 printf("\nYour serial number is : ");

 printf("21j!m");

 for ( i=0; i<10; i++) {

  putchar(serial[i]+0x30);

 }

 printf("\npress any key to quit");

 digit=getch();

 return 0;

}

 

that's all folks

although this protection is very simple, i think this tutorial may be usefull for absolute beginners.

see you soon!

 

end