Cracking TimeLock32 v2.0 without patching
("Universal keygenerator for a commercial protection")

by Riz la+

(07 July 1997, slightly edited by Fravia)
Courtesy of Fravia's page of reverse engineering

Well... Riz la+ is a very very cautious man, his essay came through a couple of remailers, with some reconstruction problems, now cleared.
If you ever wanted to know in detail how a mathematical protection algorithm works, and you could not follow +ORC's strainer about Instant Access in the C.1, C.2 and C.3 lessons (a very difficult protection scheme, btw), here you will get pretty deeep knowledge about the workings of such protections. This essay has a very important annex: Riz la+'s C++ source code for a keygenerator for this protection scheme. Needless to say this example will be very helpful for anyone of you interested in building his own "crack_probes" and his own tools (in order to reverse engineer applications you do not happen to have the source code of :-)
Studying this essay (after having read the one by Xoanon on the same subject, of course) you'll be able to reverse engineer every program that uses this weak "commercial" protection, used by many programs

Cracking TimeLock32 v2.0 without patching
by Riz la+ [6 July 1997]

Preface:
I recently read Xoanon's article about patching [tl32v20.dll] on my favourite
+HCU's site. 
Nice work, but I hate patching. You always have to store a copy of that patched 
prog, because other progs might overwrite your local copy. Or a new subversion 
is published and you'll have to verify your patch. (I found two versions of 
[tl32v20.dll]).I prefer a non-invasive technique in general. Let's make 
keygenerators. Or much better,only  one "super" keygenerator that will unlock 
all  programs that use as protection [tl32v20.dll], without any need to debug 
or patch them anymore. 

This lesson is for advanced reverse engineer. You should really know how to 
handle your SoftIce and your disassembler. It goes without saying that
You need good knowledge of assembler. Knowledge of C++ will be very helpful, 
because I would like to speak in "C++ terms" during his lesson.
At last (DO NOT UNDERESTIMATE IT!) you'll need a lot of time, if you really 
want to understand it all. (It took me ~12 hours to to grasp the scheme, and 
even more time to write this lesson.)

Target:
[tl32v20.dll] 
Version a): size: 91,648 bytes, internal date: 01/11/97
or
Version b): size: 86,528 bytes, internal date: 03/25/97 
(on their web site http://www.timelock.com they date it 03/20/97)
(It's quite rare nowadays that a newer version of a program has a smaller 
size than the older one!)

Preparations:
0) Read Xoanon's article.
1) Get Geoboy v1.2.  (that's what I used), but v1.3.1 will work definitely the
   same. Look at the target. Look inside the target. Write down your registration
   number, and everything that seems to be remarkable.
2) Disassemble [tl32v20.dll]

And now... Let's start!

Step 1: A normal keygenerator:
At first we'll try to make a normal keygenerator for Geoboy v1.2.

Install Geoboy, and start it. The typical TimeLock Nag window will pop up.
Choose purchase.Enter anything as unlock code, e.g.: "12345678", enter anything 
as name, but it must be 3 chars long (God knows why), enter anything as company. 
Before you press [OK], enter your SIce and "bpx getwindowtexta". (That's the 
way I usually start)
Press [OK] and debugger will pop. Press [F12] = "p ret", and smack, you are 
in [tl32v20.dll]. Now search for your fake unlock code and "bpm" on that. 
The next access to our input will let SIce pop again:

[All listings from tl32v20.dll; size: 86,528 bytes]

:10005A70 mov edx, dword ptr [esp + 04]  ; [esp+04] is moved to edx
                                    ;...
:10005A80 mov eax, dword ptr [edx]  ; [edx] is moved to eax
:10005A82 cmp al, byte ptr [ecx]    ; [ecx] is our input, and is compared to al
:10005A84 jne 10005AB4              ; (SIce will pop here)
:10005A86 or al, al                 ; ....

Okay, echo of valid unlock code found in *edx. (By the way, that's the echo 
for a permanent unlock code. There is a second compare for an unlock code 
that will reset your trial time. You can go after that on your own, we won't 
do it here.
We could register Geoboy now, but we want to make a keygenerator. 
So what should we do now? The answer is quite simple: We'll have to trace 
that echo.
And some lines above you'll see that mov edx, dword ptr [esp + 04] I pointed 
out in the code snippet above. 
So you should press [F12] now and you'll land here:

:10003FA5 Call dword ptr [100163DC]   ; GetDlgItem()
:10003FAB push 00000031
:10003FAD push 10013B50
:10003FB2 push eax
:10003FB3 call ebx                    ; GetClassNameA()
:10003FB5 lea eax, dword ptr [ebp-28] ; in [ebp-28] our valid_code will be stored
:10003FB8 push eax
:10003FB9 call 10001D08               ; C++ prog)

Listing of :10001c29  (compare with my C++ code):
(Remark: "first" means "first push" and so on)

:10001C29 push ebp
:10001C2A mov eax, dword ptr [esp + 08]
:10001C2E mov ebp, esp
:10001C30 add eax, dword ptr [esp + 0C]  ; eax = fifth + fourth
:10001C34 cmp eax, 00000009              ; if (eax > 0x09) eax -= 0x0a 
:10001C37 jle 10001C3C
:10001C39 sub eax, 0000000A
:10001C3C sub eax, dword ptr [ebp+10]    ; eax -= third
:10001C3F jns 10001C44                   ; if (eax <0X00) eax +="0x0a" :10001C41 add eax, 0000000A :10001C44 add eax, dword ptr [ebp+14] ;eax +="second" :10001C47 cmp eax, 00000009 ;if (eax> 0x09) eax -= 0x0a			
:10001C4A jle 10001C4F
:10001C4C sub eax, 0000000A
:10001C4F sub eax, dword ptr [ebp+18]    ; eax -= first
:10001C52 jns 10001C57	         ; if (eax <0X00) eax +="0x0a" :10001C54 add eax, 0000000A :10001C57 pop ebp :10001C58 ret Okay, now we have done it. A keygenerator for Geoboy. Let's stop for a moment and think about it: The unlock code "was.class" tppabs="http://fravia.org/was.class" generated from the registration number, provided by [tl30v20.dll] itself and this mysterious "120000" This "120000" seems to be specific to Geoboy . 120000 looks like the version number (Remember I was working with v1.2) and this succeeding "NDGGBY", although it wasn't used, is the name of the company(="NDG)" that made Geoboy (="GBY)." It must be a Product ID. And this Product ID won't be stored in [tl32v20.dll], but in a [*.tsf file]. How do I know? First: Just try to delete [geoboytl.tsf]. Second: You did look around that data location of "120000NDGGBY"? Third: Xoanon told us so. Fourth: It cannot be stored in [tl32v20.dll] itself, because that ProductID must be different for every difefrent prog. Fifth: It cannot be found in geoboy.exe or anywhere else. Sixth: You can (and should be able to) feel it. If we could find a way to decrypt that [*.tsf files], so we could get that Product ID automatically... So we need to find [tl32v20.dll]'s decryption routine. Step 2: Decrypting a [*.tsf file]:
I told you above, you should look around that data locations of that Product 
ID of Geoboy. Remember?
Above and below seemed to be the content of the [geoboytl.tsf], but ...decrypted! 
That's nice, so we know what's in it. There is everything in
it: Your registration number and the ProductID. Everything we need.
Did you notice those blocks of hexadecimal digits, separated by 0x00 blocks 
in the [geoboytl.tsf] ?
Now I can imagine different approaches in order to locate the decryption routine.
Easiest one seems to be: "bpm" (insie winice) on that "120000", leave Geoboy 
and restart. First pop of SIce will be a "Bingo": 

:10004E01 mov dword ptr [edi], edx     ; [edi] is our location, edx is "1200"
:10004E03 add edi, 00000004            ; (SIce will pop here)

Now have a look at our data location. Above [edi] everything seems to be 
decrypted, but not below. We are in the middle of our decryption
routine now. Now press [F12] several times, until everthing is decrypted:

:1000412A push 10013A50           ; "geoboytl.tsf"
:1000412F call 10004190           ; travsacunt"
:1000470D add esp, 00000010
:10004710 lea ecx, dword ptr [ebp+F6FD]		
:10004716 lea edx, dword ptr [ebp-3F]		
:10004719 push 00000000
:1000471B push ecx         ; *destination_buffer( == source_buffer)
:1000471C push edx         ; "travsacunt"
:1000471D push ecx         ; source_buffer
                           ; *ecx == 0xc4,0xb7,0xb8,0x87,...,0x00
                           ; these hexes can be found in [geoboytl.tsf]
                           ; at Offset: 0x00
:1000471E call 10002FF1    ; <--- here ! (again) ; afterwards: *ecx="=" "pewb48n2DG" :10004723 add esp, 00000010 :10004726 lea edx, dword ptr [ebp+F6FD] :1000472C push [ebp+0C] ; "pewb48n2DG" from [geoboytl.tsf](tl32v20.data) :1000472F push edx ; "pewb48n2DG" from [geoboy.exe](geoboy.data) :10004730 call 10005A70 ; equal ? :10004735 add esp, 00000008 :10004738 test eax, eax :1000473A je 10004746 ; yes, equal: jump :1000473C mov eax, FFFFFFFE; else: bad password (return code: 2) :10004741 jmp 10004A78 Summary: Here it is ! That call 10002FF1 is [tl32v20.dll]'s decryption routine: Its prototype seems to be:
Decrypt(char* source_buffer, char* decode_key, char* destination_buffer, 0).
"travsacunt" is the decryption key. "pewb48n2DG" 
is a password to check, if [geoboytl.tsf] is valid.  And all these values 
are stored in 
[geoboytl.tsf]. Let's keep in mind: When decoding the decryption key itself, 
decode_key is 0x00.

And now the work begins:

:10004746 lea eax, dword ptr [ebp+F6FD] ; copying "pewb48n2DG" and "travsacunt"
:1000474C mov esi, 10014569				
:10004751 push eax
:10004752 push 10013CA5
:10004757 call 10004D70
:1000475C add esp, 00000008
:1000475F lea eax, dword ptr [ebp-3F]
:10004762 xor edi, edi                 ; edi=0
:10004764 push eax
:10004765 push esi
:10004766 call 10004D70
:1000476B add esp, 00000008            ; ... copying finished
:1000476E lea eax, dword ptr [ebp+F6F8]; repetitive code begins....
:10004774 push edi                     ; 0
:10004775 push 10013CA0                ; *destination_buffer
:1000477A push esi                     ; *decode_key = "travsacunt"
:1000477B push eax                     ; *source_buffer
:1000477C call 10002FF1                ; <-- here ! (decrypt) :10004781 add esp, 00000010 ;... ;...decrypt again, and again, and again... Summary: The source_buffers are "solid" Offsets of [geoboytl.tsf]. How did I know? I didn't. I just assumed it: It is the easiest way to store data (else you need to calculate the offsets) ...And I was right. And now: Let's have a look at :10002FF1 itself. :10002FF1 push ebp :10002FF2 mov ebp, esp :10002FF4 sub esp, 00000110 :10002FFA push ebx :10002FFB push esi :10002FFC push edi :10002FFD push [ebp+08] :10003000 call 10004F20 ;get length of source_buffer :10003005 add esp, 00000004 :10003008 xor esi, esi ;initialize counter/index2 with 0x00 :1000300A mov edi, 10011044 :1000300F mov dword ptr [ebp-08], eax ;store length of source_buffer :10003012 push [ebp+0C] :10003015 call 10004F20 ;get length of decode_key :1000301A add esp, 00000004 :1000301D mov ebx, eax ;store length of decode_key in ebx (!) :1000301F lea eax, dword ptr [ebp-10] :10003022 push edi :10003023 push eax :10003024 call 10004D70 :10003029 add esp, 00000008 :1000302C lea eax, dword ptr [ebp+FEF0] :10003032 push edi :10003033 push eax :10003034 call 10004D70 :10003039 mov [ebp-01], 2A ; "solid decryptor" (see below) ; [might be overwritten later] :1000303D add esp, 00000008 :10003040 cmp dword ptr [ebp+14], esi ; 4th parameter="=" 0 ? :10003043 je 100030A8 ; yes, decrypt encryption routine: :10003045 xor edi, edi ; else, encrypt... ;... Summary: :10002FF1 is [tl32v20.dll]'s encryption routine as well. How do I know? Just set a bpx at :10003045. Leave Geoboy, restart. SIce will pop. Press [F12] and snoop around. (TimeLock needs to encrypt your number of uses, if you are registered, and so on) So: :10002FF1's prototype is: 
Crypt(char* source_buffer, char* decode_key, char* destination_buffer, bool flag), 
where flag == 0 to decrypt and flag == 1 to encrypt. 
(I only mentioned it,  to make it complete)

decryption routine:
:100030A8 xor edi, edi                ; initialize counter/index1 with 0x00
:100030AA cmp dword ptr [ebp-08], edi ; length of source_buffer <= 0 ? :100030AD jle 10003106 ; yes, jump to end of function :__begin of decryption loop :100030AF mov eax, dword ptr [ebp+08] ; [ebp+08]="*source_buffer" :100030B2 test ebx, ebx ; length of decode_key="=" 0 ? :100030B4 mov al, byte ptr [eax + edi]; get source_buffer[index1] :100030B7 je 100030CE ; length of decode_key="=" 0, then jump ; (will be 0x00, when the real decryption_key ;="=" "travsacunt" needs to be decoded)
                               ; else:
:100030B9 mov ecx, dword ptr [ebp+0C] 
:100030BC lea edx, dword ptr [ebx-02]
:100030BF cmp edx, esi         ; compare length of decode_key with index2
:100030C1 mov cl, byte ptr [ecx + esi] ; get decode_key[index2]
:100030C4 mov byte ptr [ebp-01], cl	   ; store that in [ebp-01]
                               ; Remark: else [ebp-01] will be 0x2a (see above)
:100030C7 jge 100030CD         ; index2 >= length of decode key ? then jump
                                       ; else:
:100030C9 xor esi, esi         ;  reset index2 ...
:100030CB jmp 100030CE         ;  ...and don't increase it
:100030CD inc esi              ; increase index2
	
:100030CE movsx eax, al        ; transform signed byte to signed dword 
                               ; [char from source_buffer]
:100030D1 movsx ecx, byte ptr [ebp-01] ; transform signed byte to signed dword
                                       ; [char from decryption_key]
:100030D5 sub eax, ecx         ; substract them !!! (decryption operation no.1)
:100030D7 inc edi	             ; increase counter
:100030D8 add eax, 00000020    ; add 0x20 !!! (decryption operation no.2)

Summary: The decryption schema is that easy: 

result=source_buffer[index1] - decode_key[index2] + 0x20;

:100030DB lea ecx, dword ptr [ebp-10] ; store decrypted result as char ..
                                      ; ...transform result to char
:100030DE push eax                    ; decrypted result
:100030DF push 100111B4               ; "%c"
:100030E4 push ecx                    ; destination_buffer
:100030E5 Call dword ptr [100163AC]   ; wsprintfA(destination_buffer,"%c", eax)
:100030EB add esp, 0000000C				
:100030EE lea ecx, dword ptr [ebp-10] ; ... and copy it to destination_buffer
:100030F1 lea edx, dword ptr [ebp+FEF0]
:100030F7 push ecx
:100030F8 push edx
:100030F9 call 10004D80					
:100030FE add esp, 00000008           ; ...char is stored
:10003101 cmp edi, dword ptr [ebp-08] ; counter C++prog.)

Step 3: An Universal Keygenerator
Now we need to get the Offset of the decryption key in the [tsf-file], 
write our own little prog, that reads any [tsf-file] you like and
automatically create your own unlock codes. Enter that in the "Purchase 
Dialog" and have fun. (Or you can skip that step: Calculate the key,
and write it back to the [tsf-file]. Certainly you need to know the 
[tsf-file] format then, but that seems to be easy.)

Epilogue:
Hope you learned a lot.  What about testing your knowledge on [tlock32.dll], 
which seems to be TimeLock v1.0 ?  It is used by Numega's BoundsChecker and 
Caligari's Pioneer Pro. The unlock code generating routine is quite similiar, 
but even more ridiculous. The Product ID is stored in the specific prog's 
main exe-file itself [6 digits + 6 chars as well]. So, give it a try...

Hm,...TimeLock v3.0 is in beta phase. Does anyone know about some targets 
protected by it? ;)

And at last: Thanks to : +ORC and +his students for tutorials, essays and 
orcpacks!

(c) Riz la+ 1997


You are deep inside fravia's page of reverse engineering, choose your way out:

homepage links red anonymity +ORC students' essays tools cocktails
search_forms mailFraVia

Is reverse engineering legal?