--------------------------------------------------------------------------------------------
Date : 05-september-2001
File : Preview 4.0 (http://lib-sys.com)
Tools: Softice, Borland C Compiler
(optional)
Author: j!m (zejim@netcourrier.com)
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