Solution for Part I of +HCU Strainer 99.
Solved by iNT_03h.
TARGET: Terminate 5.00
terminat.exe - 1,475,650 bytes long.
Where to get target: www.terminate.com
TOOLS: SoftIce 3.22 for W95, IDA PRO 3.75 Full, Turbo Pascal 6.0,
Asm Edit 1.82a, Borland C++ 4.5.
SOLUTION:
First, English is not my motherlanguage, so please excuse my mistakes...
I've spent a lot of time cracking Terminate. So now, when I write this
solution I can't remember all steps and details.
I've used SoftIce for understand how key file should looks like and IDA
for comment all I found. Also without IDA it would be almost impossible
(at least for me) crack target, because IDA 3.75 gave names for library subs,
it was great help for understanding those math subs, like Real2Long, etc,
coz without knowing sub name it was almost imposible to understand what
this sub does. Also I had to learn some Pascal (I didn't know it before)
in order to understand what the hell is going on in key validating sub
(I even rewrote it in pascal , it was great help to understand that almost
all of those floating point operations were fakes :))
Ok, I won't write step by step how I crack target, just say, that I
began with bpint 21 if ah==3d in SoftIce, then I saw that handle for
keyfile is always =6 so I begun to use bpint 21 if bx==6...
I begin my solution in back order, i.e. first I explain protection
scheme and after that I'll explain what kind of keyfile T wants.
Very important info stored at offset 1614FEh-1615F9h in terminat.exe.
First seven bytes 06, ad, 5d, 47, 17, 20, 77 used by T to determine is
it already registered or not. Or, if be more exact, only last byte is
really usefull for us: 77 means registered, BD - unregistered.
161505h-1615D0h - contains randomly encrypted reginfo, i.e. name, city,
address, country (if we are already registered).
1615D1h-1615D9h - CRC of reginfo, converted to ASCII form(first byte = length of ASCII string)
1615DAh-1615E2h - 32 bit value, xored and converted to ASCII form,
used as mask for decrypt reginfo (first byte = length of ASCII string)
1615E3h-1615E6h - 4 bytes 01,x,01,y. Where x,y are bytes set by key
validating sub. These bytes depends of some values
from keyfile. I'll explain this later.
1615E7h-1615EEh - only if we are already registered this offset contains
ASCII form some date, 1/08/98.
(first byte = length of ASCII string)
1615F1h-1615F9h - only if we are already registered this offset contains
32 bit value, xored and converted to ASCII form,
this value taken from offsets 152h, 154h of block 4
of key file. I'll explain this later.
(first byte = length of ASCII string)
Protection scheme in two words:
T reads seven bytes from offset 1614FEh of terminat.exe, and if last
of seven bytes = 77, it "thinks" that its registered and set byte_192f_12d8
to BB (by default it = 8B). This is core of protection, because in all further
checks T refers to that byte_192f_12d8 ( I named it "_pro_or_not_byte") and
check it for 8B (unregistered) or BB (registered).
Full explantation:
Everything begin from 1000:0099 (I use IDA PRO 3.75 for create dead listing)
from CALL SUB_B_FCC -> call far ptr sub_813_1797 -> call sub_2A8_115 ->
call sub_29C_43 -> call sub_7A91_1EE4 ->call sub_298_20( I named it "Check_reginfo").
Last sub decides if BB already registered by reading 1614FEh-1615F9h from
terminat.exe and compare first 7 bytes from 1614FEh-1615F9h with 06, ad,
5d, 47, 17, 20, 77. If last byte doesnt =77 sub_77A8_3FD( I named it
"Validate_key") called to read and validate keyfile (if it exist in
terminate directory). So:
87A8:2309 Check_reginfo proc far ; CODE XREF: j_Check_reginfo
.... vars
.... args
87A8:2309
87A8:2309 push bp
87A8:230A mov bp, sp
87A8:230C sub sp, 544h
87A8:2310 cmp byte_192F_50, 0 ; we always have 0 here
87A8:2315 jz loc_77A8_231A ; jump
87A8:2317 jmp loc_77A8_280D
87A8:231A ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:231A
87A8:231A loc_77A8_231A: ; CODE XREF: Check_reginfo+C
87A8:231A mov di, offset unk_192F_5798 €€€€€€À
87A8:231D push ds Ò
87A8:231E push di Ò
87A8:231F lea di, [bp+var_240] Ò
87A8:2323 push ss Ò
87A8:2324 push di Ò
87A8:2325 lea di, [bp+var_140] Ò
87A8:2329 push ss Ò
87A8:232A push di Ò
87A8:232B mov di, offset aEu ; offset of encrypted string Ò
87A8:232E push cs Ì€€€À
87A8:232F push di Ò Ò
87A8:2330 call @$basg$qm6Stringt1 ; Load string Ò Ò
87A8:2335 mov ax, 0B3ACh Ò Ò
87A8:2338 xor dx, dx Ò Ò
87A8:233A push dx Ò Ò
87A8:233B push ax Ò Ò
87A8:233C mov ax, 25D1h Ò Ò
87A8:233F mov dx, 0BABEh Ò Ò
87A8:2342 push dx Ò Ò
87A8:2343 push ax Ò Ò
87A8:2344 call decrypt_string_and_verify_it €ô Ò
87A8:2349 call @$bsub$qm6Stringt1 ; compare two magic strings Ò
87A8:234E jz loc_77A8_2353 06, ad, 5d, 47, 17, 20, 77 Ò
87A8:2350 jmp loc_77A8_2801 this is common code sequence in Terminate
it used for decrypt strings, those paranoid developers encrypted all
protection related strings. call decrypt_string_and_verify_it was originally
call sub_10F1_1A72.
So, after decrypting T compares those strings and if byte sequence from
terminat.exe = 06, ad, 5d, 47, 17, 20, 77 ,i.e. if Term registered ,
then we go here:
87A8:2353 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:2353
87A8:2353 loc_77A8_2353: ; CODE XREF: Check_reginfo+45j
87A8:2353 mov [bp+var_AX_MAGIC], 0FFFFh ; some kind of "seed"
87A8:2359 mov [bp+var_DX_MAGIC], 0FFFFh ; for calc CRC
87A8:235F xor ax, ax
87A8:2361 mov some_counter_for_CRC_calc_word, ax
87A8:2364 jmp short loc_77A8_236A
87A8:2366 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:2366
87A8:2366 loc_77A8_2366: ; CODE XREF: Check_reginfo+180j €À
87A8:2366 inc some_counter_for_CRC_calc_word? Ò
87A8:236A Ò
87A8:236A loc_77A8_236A: ; CODE XREF: Check_reginfo+5Bj Ò
87A8:236A mov di, some_counter_for_CRC_calc_word? Ò
87A8:236E mov al, [di+57A0h] Ò
87A8:2372 push ax Ò
87A8:2373 push [bp+var_DX_MAGIC] Ò
87A8:2377 push [bp+var_AX_MAGIC] Ò
87A8:237B pop bx Ò
87A8:237C pop dx Ò
87A8:237D pop cx Ò
87A8:237E push dx Ò
87A8:237F push bx Ò
87A8:2380 xor bx, cx Ì€€À
87A8:2382 xor bh, bh Ò Ò
87A8:2384 shl bx, 1 Ò Ò
87A8:2386 shl bx, 1 Ò Ò
87A8:2388 add bx, word_192F_E320 Ò Ò
87A8:238C mov ax, [bx] Ò Ò
87A8:238E mov cx, [bx+2] Ò Ò
87A8:2391 pop bx Ò Ò
87A8:2392 pop dx Ò Ò
87A8:2393 push cx Ò Ò
87A8:2394 mov cx, 8 Ò Ò
87A8:2397 Ò Ò
87A8:2397 loc_77A8_2397: ; CODE XREF: Check_reginfo+9 Ò Ò
87A8:2397 shr dx, 1 Ò Ò
87A8:2399 rcr bx, 1 Ò Ò
87A8:239B loop loc_77A8_2397 Ò Ò
87A8:239D and dx, 0FFh Ò Ò
87A8:23A1 pop cx Ò Ò
87A8:23A2 xor ax, bx Ò Ò
87A8:23A4 mov bx, cx Ò Ò
87A8:23A6 xor dx, bx Ò Ò
87A8:23A8 mov [bp+var_AX_MAGIC], ax Ò Ò
87A8:23AC mov [bp+var_DX_MAGIC], dx Ò Ò
€€€€ô Ò
ò€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ô
this code block is very often in Terminate. It checks CRC.
...
...
So T calcs CRC for reginfo, which was read from terminate.exe and then
compare it with "ASCII" CRC written at offset 1615D1h-1615D9h ( it convert just
calculated CRC into ASCII):
87A8:248C loc_77A8_248C: ; CODE XREF: Check_reginfo+17Ej
87A8:248C mov ax, [bp+var_AX_MAGIC]
87A8:2490 mov dx, [bp+var_DX_MAGIC]
87A8:2494 xor ax, 34FFh
87A8:2497 xor dx, 12FFh
87A8:249B mov [bp+var_AX_MAGIC], ax ; save just calculated CRC
87A8:249F mov [bp+var_DX_MAGIC], dx
87A8:24A3 lea di, [bp+var_340]
87A8:24A7 push ss
87A8:24A8 push di
87A8:24A9 push [bp+var_DX_MAGIC]
87A8:24AD push [bp+var_AX_MAGIC]
87A8:24B1 call j_convert_32bit_to_ASCII ; originally: sub_139A_20
87A8:24B6 mov di, offset unk_192F_5870
87A8:24B9 push ds
87A8:24BA push di
87A8:24BB call @$bsub$qm6Stringt1 ; compare "ascii" crc from reginfo with just calculated
87A8:24C0 jz loc_77A8_24C5 ; =
87A8:24C2 jmp loc_77A8_27F5 ; if we go there - we have unreg. T
87A8:24C5 ;€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:24C5 loc_77A8_24C5: ; CODE XREF: Check_reginfo+1B7j
87A8:24C5 cmp byte_192F_587A, 0 ;if length of next 32bit "ASCII" value
87A8:24CA jnz loc_77A8_24CF ;from reginfo doesnt = 0, then it exist
87A8:24CC jmp loc_77A8_25D4 ; and we go to loc_77A8_24CF
87A8:24CF ;€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:24CF loc_77A8_24CF: ; CODE XREF: Check_reginfo+1C1j
87A8:24CF mov di, offset byte_192F_587A
87A8:24D2 push ds
87A8:24D3 push di
87A8:24D4 call j_Ascii_to_Hex
87A8:24D9 mov [bp+var_AX_MAGIC], ax
87A8:24DD mov [bp+var_DX_MAGIC], dx
87A8:24E1 mov ax, [bp+var_AX_MAGIC]
87A8:24E5 mov dx, [bp+var_DX_MAGIC]
87A8:24E9 xor ax, 0FFFFh
87A8:24EC xor dx, 0D941h
87A8:24F0 mov [bp+var_AX_MAGIC], ax
87A8:24F4 mov [bp+var_DX_MAGIC], dx
87A8:24F8 mov ax, [bp+var_AX_MAGIC]
87A8:24FC mov dx, [bp+var_DX_MAGIC]
87A8:2500 mov magic1_for_calc_xor_mask_1_word, ax
87A8:2503 mov magic2_for_calc_xor_mask_1_word, dx
87A8:2507 mov some_counter_for_CRC_calc_word?, 1
87A8:250D jmp short loc_77A8_2513
87A8:250F ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:250F loc_77A8_250F: ; CODE XREF: Check_reginfo+22Ej
87A8:250F inc some_counter_for_CRC_calc_word?
87A8:2513
87A8:2513 loc_77A8_2513: ; CODE XREF: Check_reginfo+2
87A8:2513 mov ax, 100h
87A8:2516 push ax
87A8:2517 call calc_xor_mask_1 ; part of encryption system
; this sub calculates mask for next xor operation. Originally its sub_10F1_2053
87A8:251C mov dx, ax
87A8:251E mov di, some_counter_for_CRC_calc_word
87A8:2522 mov al, [di+57A0h] ; take encrypted byte
87A8:2526 xor ah, ah
87A8:2528 xor ax, dx ; decrypt with just calced mask
87A8:252A mov di, some_counter_for_CRC_calc_word
87A8:252E mov [di+57A0h], al ; save decrypted byte instead encrypted one
87A8:2532 cmp some_counter_for_CRC_calc_word?, 32h ; 32h - length of one field of
87A8:2537 jnz loc_77A8_250F ; reginfo, e.g. name.
87A8:2539 mov some_counter_for_CRC_calc_word?, 1
87A8:253F jmp short loc_77A8_2545
87A8:2541 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:2541 loc_77A8_2541: ; CODE XREF: Check_reginfo+
87A8:2541 inc some_counter_for_CRC_calc_word?
....
and so on
....
so, T decrypts four parts of reginfo, i.e. name,addres,city,country.
After that T "thinks" that its registered:
87A8:25D4 loc_77A8_25D4: ; CODE XREF: Check_reginfo+1C3j
87A8:25D4 mov _pro_or_not_byte, 0BBh ; BB means registered Terminate
But then Terminate looks for terminat.key:
87A8:26CA loc_77A8_26CA: ; CODE XREF: Check_reginfo+3B1j
87A8:26CA mov di, offset unk_192F_D1CC
87A8:26CD push ds
87A8:26CE push di
87A8:26CF mov di, offset byte_192F_1806
87A8:26D2 push ds
87A8:26D3 push di
87A8:26D4 call @Assign$qm4Filem6String ;
87A8:26D9 mov di, offset unk_192F_D1CC
87A8:26DC push ds
87A8:26DD push di
87A8:26DE mov ax, 1
87A8:26E1 push ax
87A8:26E2 call @Reset$qm4File4Word ; open key file
87A8:26E7 call @IOResult$qv ; IOResult: Word{AX}
87A8:26EC or ax, ax
87A8:26EE jnz loc_77A8_271D
87A8:26F0 mov di, offset unk_192F_D1CC
87A8:26F3 push ds
87A8:26F4 push di
87A8:26F5 les di, dword_192F_46
87A8:26F9 push es
87A8:26FA push di
87A8:26FB mov ax, 0F41h ; read 3905 bytes of key
87A8:26FE push ax
87A8:26FF xor ax, ax
87A8:2701 push ax
87A8:2702 push ax
87A8:2703 call @BlockRead$qm4Filem3Any4Wordm4Word ; BlockRead
87A8:2708 mov di, offset unk_192F_D1CC
87A8:270B push ds
87A8:270C push di
87A8:270D call @Close$qm4File ; Close(var f: File)
87A8:2712 call @IOResult$qv ; IOResult: Word{AX}
87A8:2717 mov file_pointer_word, ax
87A8:271A jmp loc_77A8_27F3
Here Terminate doesnt check key, it simply opens it and tries to read 3905
bytes, you can put file of any content and length to Terminate directory
and rename it to "terminat.key" and Terminate won't reject it.
Then Terminate returns from "Check_reginfo".
Everything go to very different way when we have unregistered Terminate:
...
87A8:2344 call decrypt_string_and_verify_it
87A8:2349 call @$bsub$qm6Stringt1 ; compare two magic strings: 06, ad, 5d, 47, 17, 20, 77
87A8:234E jz loc_77A8_2353 ; with one which was read from terminat.exe
87A8:2350 jmp loc_77A8_2801 ; WE GO HERE, BECAUSE TERM UNREGISTERED
; and therefore last byte =BD, not 77
...
87A8:2801 loc_77A8_2801: ; CODE XREF: Check_reginfo+47j
87A8:2801 cmp byte_192F_52, 0 ; this byte =0 by default
87A8:2806 jnz loc_77A8_280D ; it sets to 1 only in "validate_key"
87A8:2808 push bp
87A8:2809 push cs
87A8:280A call near ptr validate_key ; originaly sub_77A8_3FD
; This is key validating sub.
I'll explain how this sub works.
It decrypts "key" string, appends it to "terminat." string antd tries to
open file "terminat.key":
87A8:03FD validate_key proc far ; CODE XREF: Check_reginfo+501p
.... vars
87A8:03FD
87A8:03FD push bp
87A8:03FE mov bp, sp
87A8:0400 sub sp, 366h
87A8:0404 mov [bp+var_FLAG_1_byte], 0
87A8:0408 lea di, [bp+var_206]
87A8:040C push ss
87A8:040D push di
87A8:040E mov di, offset byte_192F_153A
87A8:0411 push ds
87A8:0412 push di
87A8:0413 call @$basg$qm6Stringt1 ; Load string
87A8:0418 lea di, [bp+var_106]
87A8:041C push ss
87A8:041D push di
87A8:041E lea di, [bp+var_6]
87A8:0421 push ss
87A8:0422 push di
87A8:0423 mov di, offset aZ_2 ; offset of encrypted "key"
87A8:0426 push cs
87A8:0427 push di
87A8:0428 call @$basg$qm6Stringt1 ; Load string
87A8:042D mov ax, 79C1h
87A8:0430 xor dx, dx
87A8:0432 push dx
87A8:0433 push ax
87A8:0434 mov ax, 2D79h
87A8:0437 mov dx, 0BABEh
87A8:043A push dx
87A8:043B push ax
87A8:043C call decrypt_string_and_verify_it ; decrypt string "key"
87A8:0441 call @Concat$qm6Stringt1 ; Concat(dst, src: String): String
87A8:0446 mov di, offset unk_192F_58EA
87A8:0449 push ds
87A8:044A push di
87A8:044B mov ax, 45h ; 'E'
87A8:044E push ax
87A8:044F call @$basg$qm6Stringt14Byte ; Store string
87A8:0454 mov byte_192F_52, 1
87A8:0459 mov di, offset unk_192F_58EA
87A8:0454 mov byte_192F_52, 1
87A8:0459 mov di, offset unk_192F_58EA
87A8:045C push ds
87A8:045D push di
87A8:045E mov ax, 20h ; ' '
87A8:0461 push ax
87A8:0462 mov di, offset unk_192F_BF20
87A8:0465 push ds
87A8:0466 push di
87A8:0467 call @FindFirst$q7PathStr4Wordm9SearchRec ; FindFirst
87A8:046C cmp word_192F_E482, 0
87A8:0471 jnz loc_77A8_481
87A8:0473 mov ax, word_192F_BF3A
87A8:0476 mov dx, word_192F_BF3C
87A8:047A mov word_192F_54, ax
87A8:047D mov word_192F_56, dx
87A8:0481 loc_77A8_481: ; CODE XREF: validate_key+74j
87A8:0481 mov di, [bp+arg_0]
87A8:0484 add di, 0FEFEh
87A8:0488 push ss
87A8:0489 push di
87A8:048A mov di, offset unk_192F_58EA
87A8:048D push ds
87A8:048E push di
87A8:048F call @Assign$qm4Filem6String ; Assign(var f: File; name: String)
87A8:0494 mov di, [bp+arg_0]
87A8:0497 add di, 0FEFEh
87A8:049B push ss
87A8:049C push di
87A8:049D mov ax, 20h ; ' '
87A8:04A0 push ax
87A8:04A1 call @SetFAttr$qm3Any4Word ; SetFAttr(Any &,Word)
87A8:04A6 call @IOResult$qv ; IOResult: Word{AX}
87A8:04AB mov file_pointer_word, ax
87A8:04AE mov di, [bp+arg_0]
87A8:04B1 add di, 0FEFEh
87A8:04B5 push ss
87A8:04B6 push di
87A8:04B7 mov ax, 162h
87A8:04BA push ax
87A8:04BB call @Reset$qm4File4Word ; open "terminat.key"
87A8:04C0 call @IOResult$qv ; IOResult: Word{AX}
87A8:04C5 mov file_pointer_word, ax ; save filepointer
87A8:04C8 cmp file_pointer_word, 0
87A8:04CD jz loc_77A8_4D2
Then it begins to read blocks of keyfile each 162h bytes long. Before that
sub clears place for keyfile's block:
87A8:04D2 loc_77A8_4D2: ; CODE XREF: validate_key+D0j
87A8:04D2 mov ax, 162h
87A8:04D5 push ax
87A8:04D6 call @GetMem$q4Word ; Alloc 162h bytes
87A8:04DB mov di, [bp+arg_0]
87A8:04DE mov word ptr ss:[di+var_106], ax
87A8:04E3 mov word ptr ss:[di+var_106+2], dx
87A8:04E8 mov ss:[di+var_AX_CRC32_MAGIC], 0FFFFh ; init CRC "seed"
87A8:04EF mov ss:[di+var_DX_CRC32_MAGIC], 0FFFFh
87A8:04F6 xor ax, ax
87A8:04F8 mov ss:[di+var_12C], ax
87A8:04FD mov ss:[di+var_12A], ax
87A8:0502 mov keyfile_loaded_blocks_counter_word, 1 ; counter of loaded
87A8:0508 jmp short loc_77A8_50E ; keyfile's blocks
87A8:050A ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:050A
87A8:050A loc_77A8_50A: ; CODE XREF: validate_key+503j
87A8:050A inc keyfile_loaded_blocks_counter_word
87A8:050E loc_77A8_50E: ; CODE XREF: validate_key+10Bj
87A8:050E mov di, [bp+arg_0]
87A8:0511 les di, ss:[di+var_106]
87A8:0516 push es
87A8:0517 push di
87A8:0518 mov ax, 162h
87A8:051B push ax
87A8:051C mov al, 0 ; fill memory with 00h
87A8:051E push ax
87A8:051F call @FillChar$qm3Any4Word4Byte ; prepare place for key
87A8:0524 mov di, [bp+arg_0]
87A8:0527 add di, 0FEFEh
87A8:052B push ss
87A8:052C push di
87A8:052D mov di, [bp+arg_0]
87A8:0530 les di, ss:[di+var_106]
87A8:0535 push es
87A8:0536 push di
87A8:0537 call @Read$qm4Filem3Any ;read curent block of keyfile
87A8:053C add sp, 4
87A8:053F call @IOResult$qv ; check for error
87A8:0544 mov word_192F_17D6, ax
87A8:0547 mov di, [bp+arg_0]
It reads 11 blocks. It calcs CRC for every block. When CRC for
all 11 block calculated sub compares calculated CRC with one stored in last
block of keyfile. Also there are some "special" block of keyfile: block 1,
block 2, block 4,block 5 and block 11.
87A8:054A les di, ss:[di+var_106]
87A8:054F seges
87A8:054F lea ax, [di+15Dh] ; end offset in kf's block to calc CRC for
87A8:0554 mov [bp+var_end_offset_of_CRC_calc?], ax ; so, CRC calculated
87A8:0557 mov di, [bp+arg_0] ; for 0-15dh in each block
87A8:055A les di, ss:[di+var_106]
87A8:055F mov ax, di
87A8:0561 cmp ax, [bp+var_end_offset_of_CRC_calc?]
87A8:0564 ja loc_77A8_5D2
87A8:0566 mov some_counter_for_CRC_calc_word?, ax
87A8:0569 jmp short loc_77A8_56F
87A8:056B ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:056B loc_77A8_56B: ; CODE XREF: validate_key+1D3j
87A8:056B inc some_counter_for_CRC_calc_word?
87A8:056F loc_77A8_56F: ; CODE XREF: validate_key+16Cj
87A8:056F mov di, [bp+arg_0] €À
87A8:0572 les di, ss:[di+var_106] Ò
87A8:0577 mov ax, es Ò
87A8:0579 push ax Ò
87A8:057A mov di, some_counter_for_CRC_calc_word? Ò
87A8:057E pop es Ò
87A8:057F mov al, es:[di] Ò
87A8:0582 push ax Ò
87A8:0583 mov di, [bp+arg_0] Ò
87A8:0586 push ss:[di+var_DX_CRC32_MAGIC] Ò
87A8:058B push ss:[di+var_AX_CRC32_MAGIC] Ò
87A8:0590 pop bx Ò
87A8:0591 pop dx Ò
87A8:0592 pop cx Ò
87A8:0593 push dx Ò
87A8:0594 push bx Ò
87A8:0595 xor bx, cx Ò
87A8:0597 xor bh, bh Ò
87A8:0599 shl bx, 1 Ò
87A8:059B shl bx, 1 Ò
87A8:059D add bx, word_192F_E320 Ò
87A8:05A1 mov ax, [bx] Ò
87A8:05A3 mov cx, [bx+2] Ò
87A8:05A6 pop bx Ò
87A8:05A7 pop dx Ò
87A8:05A8 push cx Ò
87A8:05A9 mov cx, 8 Ì€€ calc CRC
87A8:05AC loc_77A8_5AC: ; CODE XREF: validate_key+1B3j Ò
87A8:05AC shr dx, 1 Ò
87A8:05AE rcr bx, 1 Ò
87A8:05B0 loop loc_77A8_5AC Ò
87A8:05B2 and dx, 0FFh Ò
87A8:05B6 pop cx Ò
87A8:05B7 xor ax, bx Ò
87A8:05B9 mov bx, cx Ò
87A8:05BB xor dx, bx Ò
87A8:05BB xor dx, bx Ò
87A8:05BD mov di, [bp+arg_0] Ò
87A8:05C0 mov ss:[di+var_AX_CRC32_MAGIC], ax ; store CRC Ò
87A8:05C5 mov ss:[di+var_DX_CRC32_MAGIC], dx Ò
87A8:05CA mov ax, some_counter_for_CRC_calc_word? Ò
87A8:05CD cmp ax, [bp+var_end_offset_of_CRC_calc?] ; enough? Ò
87A8:05D0 jnz loc_77A8_56B ; if no, again Ò
87A8:05D2 €€ô
Block 1 checked for bytes 46h,2Fh,2Eh at offsets 0bh, 1bh , 14h because
there is old keygenerator for Terminate version 1 or even earlier (I dont
remember now, I saw this keygen one day) and this keygen creates key
with string at block 1 like "Terminate HAKE-KEY for all outlaws all
around the world". So now Terminate checks for this header.
87A8:05D2 loc_77A8_5D2: ; CODE XREF: validate_key+167j
87A8:05D2 cmp keyfile_loaded_blocks_counter_word, 1
87A8:05D7 jnz loc_77A8_63B
87A8:05D9 mov di, [bp+arg_0]
87A8:05DC les di, ss:[di+var_106]
87A8:05E1 mov ax, es
87A8:05E3 push ax
87A8:05E4 mov di, [bp+arg_0]
87A8:05E7 les di, ss:[di+var_106]
87A8:05EC seges
87A8:05EC lea di, [di+0Bh] ; here it begins to check first block of keyfile
87A8:05F0 pop es
87A8:05F1 cmp byte ptr es:[di], 46h ; 'F'
87A8:05F5 jnz loc_77A8_638 ;
87A8:05F7 mov di, [bp+arg_0]
87A8:05FA les di, ss:[di+var_106]
87A8:05FF mov ax, es
87A8:0601 push ax
87A8:0602 mov di, [bp+arg_0]
87A8:0605 les di, ss:[di+var_106]
87A8:060A seges
87A8:060A lea di, [di+1Bh]
87A8:060E pop es
87A8:060F cmp byte ptr es:[di], 2Fh ; '/'
87A8:0613 jnz loc_77A8_638
87A8:0615 mov di, [bp+arg_0]
87A8:0618 les di, ss:[di+var_106]
87A8:0613 jnz loc_77A8_638
87A8:0615 mov di, [bp+arg_0]
87A8:0618 les di, ss:[di+var_106]
87A8:061D mov ax, es
87A8:061F push ax
87A8:0620 mov di, [bp+arg_0]
87A8:0623 les di, ss:[di+var_106]
87A8:0628 seges
87A8:0628 lea di, [di+14h]
87A8:062C pop es
87A8:062D cmp byte ptr es:[di], 2Eh ; '.'
87A8:0631 jnz loc_77A8_638
87A8:0633 call sub_2A8_4D ; here T display error msg, like "Runtime error..."
; and gives choice: reboot or return to dos
87A8:0638 loc_77A8_638: ; CODE XREF: validate_key+1F8j validate_key+216
87A8:0638 jmp loc_77A8_8F9
Block 2: if first 5 bytes of this block = each other , then Terminate
adjusts current CRC values:
87A8:06E5 mov di, [bp+arg_0]
87A8:06E8 add ss:[di+var_AX_CRC32_MAGIC], 329h
87A8:06EF adc ss:[di+var_DX_CRC32_MAGIC], 0
Block 4 is a core of keyfile. It contains encrypted reginfo and several
very important values. I'll explain this later.
Block 5 is also very important for us. It contains values which used
to verify if key valid:
87A8:06F8 cmp keyfile_loaded_blocks_counter_word, 5 ; block 5 ?
87A8:06FD jz loc_77A8_702 ; yes
87A8:06FF jmp loc_77A8_79F
87A8:0702 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:0702 loc_77A8_702: ; CODE XREF: validate_key+300j
87A8:0702 mov di, [bp+arg_0]
87A8:0705 les di, ss:[di+var_106]
87A8:070A mov ax, es:[di+15Eh] ; in AX - 16 bit value from offset 15e of block 5
87A8:070F mov dx, es:[di+160h] ; in DX - 16 bit value from offset 160 of block 5
87A8:0714 call @Real$q7Longint ; Real(x: Longint{DX:AX}): Real
87A8:0719 mov di, [bp+arg_0] ; call above convert 32bit value from ax:dx
87A8:071C mov ss:[di+var_temp_AX], ax ; to 48 bit real value in ax:bx:dx
87A8:0721 mov ss:[di+var_temp_BX], bx
87A8:0726 mov ss:[di+var_temp_DX], dx
It was very hard for me to understand what the hell is that, because I didn't
know Pascal and that kind of floating point format. So I found good old ;-)
Turbo Pascal 6.0 for DOS, took book from friend and learned some Pascal.
I wrote several smalls programms in Pascal which use floating points numbers,
perform some operations with them, and I look at these programms in IDA.
It was great help. I began to understand whats going on and how to handle
those ax:bx:dx stuffs. I wrote little programm on Pascal which convert hex
values from ax,bx,dx into floating point number, and several small programs
to perform math operations with values from ax,bx,dx, i.e. I enter values1
from ax,bx,dx, then value2 from ax,bx,dx and my program returns me result
as floating point number. So I converted all such numbers from Terminate
and commented them in IDA.
87A8:072B mov ax, ss:[di+var_temp_AX] ; converted to real value
87A8:0730 mov bx, ss:[di+var_temp_BX] ; "magic" from block5
87A8:0735 mov dx, ss:[di+var_temp_DX]; will be compare
87A8:073A xor cx, cx ; with 0
87A8:073C xor si, si
87A8:073E xor di, di
87A8:0740 call @__Cmp$q4Realt1 ; So terminate checks if "magic" from block 5 is below zero
87A8:0745 jnb loc_77A8_778 ; jump here if "magic" is above zero
87A8:0747 mov di, [bp+arg_0]
87A8:074A mov ax, ss:[di+var_temp_AX] ; else T gonna change sign
87A8:074F mov bx, ss:[di+var_temp_BX] ; of that "magic"
87A8:0754 mov dx, ss:[di+var_temp_DX]
87A8:0759 mov cx, 81h ; this means -1
87A8:075C xor si, si ;
87A8:075E mov di, 8000h
87A8:0761 call @$brmul$q4Realt1 ; magic*=-1;
87A8:0766 mov di, [bp+arg_0]
87A8:0769 mov ss:[di+var_temp_AX], ax ; store "magic"
87A8:076E mov ss:[di+var_temp_BX], bx
87A8:0773 mov ss:[di+var_temp_DX], dx
87A8:0778 loc_77A8_778: ; CODE XREF: validate_key+348j
87A8:0778 mov di, [bp+arg_0]
87A8:077B mov ax, ss:[di+var_temp_AX]
87A8:0780 mov bx, ss:[di+var_temp_BX]
87A8:0785 mov dx, ss:[di+var_temp_DX]
87A8:078A mov di, [bp+arg_0]
87A8:078D mov ss:[di+var_real_AX_bl5], ax ; move "magic" from temp vars
87A8:0792 mov ss:[di+var_real_BX_bl5], bx ; into their permanet loc
87A8:0797 mov ss:[di+var_real_DX_bl5], dx
87A8:079C jmp loc_77A8_8F9 ; go ot check if all blocks of keyfile loaded?
Block 11: if 5 bytes at offsets 12c,12d,12e,12f,130 = each other
then Term. adjusts current CRC values:
87A8:0850 mov di, [bp+arg_0]
87A8:0853 add ss:[di+var_AX_CRC32_MAGIC], 192h
87A8:085A adc ss:[di+var_DX_CRC32_MAGIC], 0
Also this block checked for bytes DC,64,D9,E9 at offsets 15a,15b,15c,15d
because that old "HAKE-KEY" keygen writes these bytes in every key
it produces.
Also this block contains global CRC of keyfile at offsets 15eh,160h and
T compares calculated CRC with one stored in block 11:
87A8:08E1 ; validate_key+4BEj validate_key+4DDj
87A8:08E1 mov di, [bp+arg_0]
87A8:08E4 mov ss:[di+var_11C], 4890h ; 57544.23
87A8:08EB mov ss:[di+var_11A], 3AE1h ; this value used furhter
87A8:08F2 mov ss:[di+var_118], 60C8h ; I'll explain it later
87A8:08F9
87A8:08F9 loc_77A8_8F9: ; CODE XREF: validate_key+23Bj validate_key+2F
87A8:08F9 ; validate_key+39Fj validate_key+3A9j
87A8:08F9 cmp keyfile_loaded_blocks_counter_word, 0Bh ; last block ?
87A8:08FE jz loc_77A8_903 ; yes
87A8:0900 jmp loc_77A8_50A ; else again
87A8:0903 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:0903
87A8:0903 loc_77A8_903: ; CODE XREF: validate_key+501j
87A8:0903 mov di, [bp+arg_0]
87A8:0906 mov ax, ss:[di+var_AX_CRC32_MAGIC] ; take just calced CRC
87A8:090B mov dx, ss:[di+var_DX_CRC32_MAGIC]
87A8:0910 les di, ss:[di+var_106] ; di=0
87A8:0915 cmp dx, es:[di+160h] ; here is compares calculated CRC with one
; stored in block 11 of kf
87A8:091A jnz loc_77A8_92A ; this jump means "damaged key"
87A8:091C cmp ax, es:[di+15Eh]
87A8:0921 jnz loc_77A8_92A ; this jump means "damaged key"
87A8:0923 cmp word_192F_17D6, 0 ; this flag =1 when kf opened successfuly
87A8:0928 jz loc_77A8_92D
87A8:092A loc_77A8_92A: ; CODE XREF: validate_key+51Dj validate_key+52
87A8:092A jmp loc_77A8_22A7 ; this jump means "damaged key"
After T completed first check of keyfile, it begins to decrypt main block -
block 4:
87A8:092D loc_77A8_92D: ; CODE XREF: validate_key+52Bj
87A8:092D mov di, [bp+arg_0]
87A8:0930 mov ax, ss:[di+var_AX_CRC32_MAGIC]
87A8:0935 mov dx, ss:[di+var_DX_CRC32_MAGIC]
87A8:093A mov CRC_AX_WORD, ax
87A8:093D mov CRC_DX_WORD, dx
87A8:0941 add di, 0FEFEh
87A8:0945 push ss
87A8:0946 push di ; close keyfile
87A8:0947 call @Close$qm4File ; Close(var f: File)
87A8:094C call @__IOCheck$qv ; Exit if error
87A8:0951 mov di, [bp+arg_0]
87A8:0954 add di, 0FEFEh
87A8:0958 push ss
87A8:0959 push di
87A8:095A mov ax, 162h
87A8:095D push ax
87A8:095E call @Reset$qm4File4Word ; open it again
87A8:0963 call @__IOCheck$qv ; Exit if error
87A8:0968 mov some_counter_for_CRC_calc_word?, 1
87A8:096E jmp short loc_77A8_974
87A8:0970 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:0970 loc_77A8_970: ; CODE XREF: validate_key+59Cj
87A8:0970 inc some_counter_for_CRC_calc_word?
87A8:0974 loc_77A8_974: ; CODE XREF: validate_key+571j
87A8:0974 mov di, [bp+arg_0]
87A8:0977 add di, 0FEFEh
87A8:097B push ss
87A8:097C push di
87A8:097D mov di, [bp+arg_0]
87A8:0980 les di, ss:[di+var_106]
87A8:0985 push es
87A8:0986 push di
87A8:0987 call @Read$qm4Filem3Any ; Read block
87A8:098C add sp, 4
87A8:098F call @__IOCheck$qv ; Exit if error
87A8:0994 cmp some_counter_for_CRC_calc_word?, 4 ; we need block 4
87A8:0999 jnz loc_77A8_970 ; rather stupid way to get block 4
87A8:099B mov di, [bp+arg_0] ; T doesnt wanna use Seek function
87A8:099E add di, 0FEFEh ; it prefer to read blocks of keyfile
87A8:09A2 push ss ; until block 4 reached
87A8:09A3 push di
87A8:09A4 call @Close$qm4File ; Close(var f: File)
87A8:09A9 call @__IOCheck$qv ; Exit if error
87A8:09AE mov di, [bp+arg_0]
87A8:09B1 les di, ss:[di+var_106]
87A8:09B6 seges
87A8:09B6 lea ax, [di+161h]
87A8:09BB mov [bp+var_end_offset_of_CRC_calc?], ax
87A8:09BE mov di, [bp+arg_0]
87A8:09C1 les di, ss:[di+var_106]
87A8:09C6 seges
87A8:09C6 lea ax, [di+5Bh]
87A8:09CA cmp ax, [bp+var_end_offset_of_CRC_calc?]
87A8:09CD ja loc_77A8_A0A
87A8:09CF mov some_counter_for_CRC_calc_word?, ax
87A8:09D2 jmp short loc_77A8_9D8
87A8:09D4 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
Then T begins to decrypt block 4 ( not whole block, only 5b-161h range).
It encrypted 4 times (nice, isn't it ;-) First, simple xor with FF:
87A8:09D8 loc_77A8_9D8: ; CODE XREF: validate_key+5D5j
87A8:09D8 mov di, [bp+arg_0]
87A8:09DB les di, ss:[di+var_106]
87A8:09E0 mov ax, es
87A8:09E2 push ax
87A8:09E3 mov di, some_counter_for_CRC_calc_word?
87A8:09E7 pop es
87A8:09E8 mov al, es:[di]
87A8:09EB xor al, 0FFh ; decrypt byte
87A8:09ED mov dl, al
87A8:09EF mov di, [bp+arg_0]
87A8:09F2 les di, ss:[di+var_106]
87A8:09F7 mov ax, es
87A8:09F9 push ax
87A8:09FA mov di, some_counter_for_CRC_calc_word?
87A8:09FE pop es
87A8:09FF mov es:[di], dl
87A8:0A02 mov ax, some_counter_for_CRC_calc_word?
87A8:0A05 cmp ax, [bp+var_end_offset_of_CRC_calc?]
87A8:0A08 jnz loc_77A8_9D4
Then goes more complex decryption which calculates dexor mask for
every byte. For this it uses sub "calc_xor_mask_1":
20F1:2053 calc_xor_mask_1 proc far
20F1:2053 arg_0= word ptr 6
20F1:2053 push bp
20F1:2054 mov bp, sp
20F1:2056 mov ax, magic1_for_calc_xor_mask_1_word ; some kind of "seed"
20F1:2059 mov bx, magic2_for_calc_xor_mask_1_word
20F1:205D mov cx, ax
20F1:205F mul word_192F_12BC
20F1:2063 shl cx, 1
20F1:2065 shl cx, 1
20F1:2067 shl cx, 1
20F1:2069 add ch, cl
20F1:206B add dx, cx
20F1:206D add dx, bx
20F1:206F shl bx, 1
20F1:2071 shl bx, 1
20F1:2073 add dx, bx
20F1:2075 add dh, bl
20F1:2077 mov cl, 5
20F1:2079 shl bx, cl
20F1:207B add dh, bl
20F1:207D add ax, 1
20F1:2080 adc dx, 0
20F1:2083 mov magic1_for_calc_xor_mask_1_word, ax
20F1:2086 mov magic2_for_calc_xor_mask_1_word, dx
20F1:208A xor ax, ax
20F1:208C mov bx, [bp+arg_0]
20F1:208F or bx, bx
20F1:2091 jz loc_10F1_2097
20F1:2093 xchg ax, dx
20F1:2094 div bx
20F1:2096 xchg ax, dx
20F1:2097 loc_10F1_2097: ; CODE XREF: calc_xor_mask_1+3Ej
20F1:2097 pop bp
20F1:2098 retf 2
20F1:2098 calc_xor_mask_1 endp
So, for second decryption T uses values 7,0 as seed for "calc_xor_mask1":
87A8:0A0A loc_77A8_A0A: ; CODE XREF: validate_key+5D0j
87A8:0A0A mov magic1_for_calc_xor_mask_1_word, 7 ; init mask "seed"
87A8:0A10 mov magic2_for_calc_xor_mask_1_word, 0
87A8:0A16 mov di, [bp+arg_0]
87A8:0A19 les di, ss:[di+var_106]
87A8:0A1E seges
87A8:0A1E lea ax, [di+161h] ; set end offset for decrypting - 161h
87A8:0A23 mov [bp+var_end_offset_of_CRC_calc?], ax
87A8:0A26 mov di, [bp+arg_0]
87A8:0A29 les di, ss:[di+var_106]
87A8:0A2E seges
87A8:0A2E lea ax, [di+5Bh] ; set begining offset for decrypting - 5bh
87A8:0A32 cmp ax, [bp+var_end_offset_of_CRC_calc?]
87A8:0A35 ja loc_77A8_A7F
87A8:0A37 mov some_counter_for_CRC_calc_word?, ax
87A8:0A3A jmp short loc_77A8_A40
87A8:0A3C ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:0A3C loc_77A8_A3C: ; CODE XREF: validate_key+680j
87A8:0A3C inc some_counter_for_CRC_calc_word?
87A8:0A40 loc_77A8_A40: ; CODE XREF: validate_key+63Dj
87A8:0A40 mov ax, 100h ; arg for sub below
87A8:0A43 push ax
87A8:0A44 call calc_xor_mask_1 ; calculates xor mask
87A8:0A49 mov dx, ax
87A8:0A4B mov di, [bp+arg_0]
87A8:0A4E les di, ss:[di+var_106]
87A8:0A53 mov ax, es
87A8:0A55 push ax
87A8:0A56 mov di, some_counter_for_CRC_calc_word?
87A8:0A5A pop es
87A8:0A5B mov al, es:[di] ; takes encrypted byte
87A8:0A5E xor ah, ah
87A8:0A60 xor ax, dx ; decrypts it
87A8:0A62 mov dl, al
87A8:0A64 mov di, [bp+arg_0]
87A8:0A67 les di, ss:[di+var_106]
87A8:0A6C mov ax, es
87A8:0A6E push ax
87A8:0A6F mov di, some_counter_for_CRC_calc_word?
87A8:0A73 pop es
87A8:0A74 mov es:[di], dl ; store it
87A8:0A77 mov ax, some_counter_for_CRC_calc_word?
87A8:0A7A cmp ax, [bp+var_end_offset_of_CRC_calc?] ; enough ?
87A8:0A7D jnz loc_77A8_A3C
For third decryption T uses values 325Ch,0 as seed for "calc_xor_mask1":
87A8:0A7F loc_77A8_A7F: ; CODE XREF: validate_key+638j
87A8:0A7F mov magic1_for_calc_xor_mask_1_word, 325Ch
87A8:0A85 mov magic2_for_calc_xor_mask_1_word, 0
....
same decrypting
.....
For last decryption T uses values 904h,33EEh as seed for "calc_xor_mask1":
87A8:0AF4 loc_77A8_AF4: ; CODE XREF: validate_key+6ADj
87A8:0AF4 mov magic1_for_calc_xor_mask_1_word, 904h
87A8:0AFA mov magic2_for_calc_xor_mask_1_word, 33EEh
....
same decrypting
.....
After decrypting T calcs CRC for decrypted block 4(5b-15e range) and compares
it with CRC stored at offsets 15Eh,160h in decrypted block 4.
87A8:0C04 loc_77A8_C04: ; CODE XREF: validate_key+799j
87A8:0C04 mov di, [bp+arg_0]
87A8:0C07 les di, ss:[di+var_106]
87A8:0C0C mov ax, es:[di+15Eh] ; take CRC stored in decrypted block 4
87A8:0C11 mov dx, es:[di+160h]
87A8:0C16 mov di, [bp+arg_0]
87A8:0C19 cmp dx, ss:[di+var_DX_CRC32_MAGIC] ; compare it with just calculated
87A8:0C1E jnz loc_77A8_C27
87A8:0C20 cmp ax, ss:[di+var_AX_CRC32_MAGIC]
87A8:0C25 jz loc_77A8_C2D ; CRC ok
87A8:0C27 loc_77A8_C27: ; CODE XREF: validate_key+821j
87A8:0C27 jmp loc_77A8_22A7 ; bad keyfile
After CRC checked T begins prepare for main check. It takes 32bit CRC
values from decrypted block 4, convert it to real format, change sign
if "real" CRC <0.
87A8:0C2D loc_77A8_C2D: ; CODE XREF: validate_key+828j
87A8:0C2D mov di, [bp+arg_0]
87A8:0C30 mov ax, ss:[di+var_AX_CRC32_MAGIC] ; take CRC from decrypted
87A8:0C35 mov dx, ss:[di+var_DX_CRC32_MAGIC] ; block 4
87A8:0C3A call @Real$q7Longint ; convert it to real number
87A8:0C3F mov di, [bp+arg_0]
87A8:0C42 mov ss:[di+var_temp_AX], ax ; store it to temp var
87A8:0C47 mov ss:[di+var_temp_BX], bx ;
87A8:0C4C mov ss:[di+var_temp_DX], dx ;
87A8:0C51 mov ax, ss:[di+var_temp_AX]
87A8:0C56 mov bx, ss:[di+var_temp_BX]
87A8:0C5B mov dx, ss:[di+var_temp_DX]
87A8:0C60 xor cx, cx ;0
87A8:0C62 xor si, si
87A8:0C64 xor di, di
87A8:0C66 call @__Cmp$q4Realt1 ; "real" CRC <0 ?
87A8:0C6B jnb loc_77A8_C9E ; no
87A8:0C6D mov di, [bp+arg_0] ; yes, change sign
87A8:0C70 mov ax, ss:[di+var_temp_AX]
87A8:0C75 mov bx, ss:[di+var_temp_BX]
87A8:0C7A mov dx, ss:[di+var_temp_DX]
87A8:0C7F mov cx, 81h ; 'ª' ; -1
87A8:0C82 xor si, si
87A8:0C84 mov di, 8000h
87A8:0C87 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
87A8:0C8C mov di, [bp+arg_0]
87A8:0C8F mov ss:[di+var_temp_AX], ax
87A8:0C94 mov ss:[di+var_temp_BX], bx
87A8:0C99 mov ss:[di+var_temp_DX], dx
87A8:0C9E loc_77A8_C9E: ; CODE XREF: validate_key+86Ej
87A8:0C9E mov di, [bp+arg_0]
87A8:0CA1 mov ax, ss:[di+var_temp_AX]
87A8:0CA6 mov bx, ss:[di+var_temp_BX]
87A8:0CAB mov dx, ss:[di+var_temp_DX]
87A8:0CB0 mov di, [bp+arg_0]
87A8:0CB3 mov ss:[di+var_real_AX_bl4], ax ; store "real" CRC from
87A8:0CB8 mov ss:[di+var_real_BX_bl4], bx ; decrypted block 4
87A8:0CBD mov ss:[di+var_real_DX_bl4], dx ; to it permanent var
...
Add to "real" CRC 10 and compares this value with
"magic" from block5. If they don't = then T sub 10 from "real" CRC of
block 4 and again compare this value with "magic" from block5.
87A8:0D10 mov ax, ss:[di+var_real_AX_bl4] ;
87A8:0D15 mov bx, ss:[di+var_real_BX_bl4] ;
87A8:0D1A mov dx, ss:[di+var_real_DX_bl4] ;
87A8:0D1F mov cx, 84h ; 10
87A8:0D22 xor si, si
87A8:0D24 mov di, 2000h
87A8:0D27 call @$brplu$q4Realt1 ; add 10
87A8:0D2C mov di, [bp+arg_0]
87A8:0D2F mov cx, ss:[di+var_real_AX_bl5] ;compare value we got after +
87A8:0D34 mov si, ss:[di+var_real_BX_bl5] ; with "magic" from block 5
87A8:0D39 mov di, ss:[di+var_real_DX_bl5]
87A8:0D3E call @__Cmp$q4Realt1 ; Compare two reals
87A8:0D43 jz loc_77A8_D7D ; jump if they equal
87A8:0D45 mov di, [bp+arg_0]
87A8:0D48 mov ax, ss:[di+var_real_AX_bl4]
87A8:0D4D mov bx, ss:[di+var_real_BX_bl4]
87A8:0D52 mov dx, ss:[di+var_real_DX_bl4]
87A8:0D57 mov cx, 84h ; 10
87A8:0D5A xor si, si
87A8:0D5C mov di, 2000h
87A8:0D5F call @$brmin$q4Realt1 ; else try to sub 10
87A8:0D64 mov di, [bp+arg_0]
87A8:0D67 mov cx, ss:[di+var_real_AX_bl5]
87A8:0D6C mov si, ss:[di+var_real_BX_bl5]
87A8:0D71 mov di, ss:[di+var_real_DX_bl5]
87A8:0D76 call @__Cmp$q4Realt1 ; compare again
87A8:0D7B jnz loc_77A8_DC6 ; jump if dont = €€€€€€€€€€€€€€€€€€À
87A8:0D7D loc_77A8_D7D: ; CODE XREF: validate_key+946j Ò
87A8:0D7D mov di, [bp+arg_0] Ò
... junk Ò
87A8:0DC4 jmp short loc_77A8_DF8€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€À
Ò Ò
7A8:0DC6 loc_77A8_DC6: ; CODE XREF: validate_key+97Ej <€€ô Ò
7A8:0DC6 mov di, [bp+arg_0] Ò
7A8:0DC9 mov ax, ss:[di+var_11C] ; this value was set above in sub Ò
7A8:0DCE mov bx, ss:[di+var_11A] ; it was 57544.23 Ò
7A8:0DD3 mov dx, ss:[di+var_118] ; It will be used in future check Ò
7A8:0DD8 mov cx, 1F8Eh ; 9823.23 Ò
7A8:0DDB mov si, 0EB85h ; so if we multiply it here we won't Ò
7A8:0DDE mov di, 197Ch ; bypass next check( I'll explain this later)Ò
7A8:0DE1 call @$brmul$q4Realt1 ; So this is very smart way to set flagÒ
7A8:0DE6 mov di, [bp+arg_0] Ò
7A8:0DE9 mov ss:[di+var_11C], ax Ò
7A8:0DEE mov ss:[di+var_11A], bx Ò
7A8:0DF3 mov ss:[di+var_118], dx Ò
7A8:0DF8 Ò
7A8:0DF8 loc_77A8_DF8: ; CODE XREF: validate_key+9C7j <€€€ô
7A8:0DF8 mov di, [bp+arg_0]
7A8:0DFB add di, 0FF80h
7A8:0DFF push ss
7A8:0E00 push di
7A8:0E01 mov di, [bp+arg_0]
7A8:0E04 push ss:[di+var_real_DX_bl5] ; push "magic" from block 5
7A8:0E09 push ss:[di+var_real_BX_bl5] ;
7A8:0E0E push ss:[di+var_real_AX_bl5] ;
7A8:0E13 push ss:[di+var_118] ; flag , by default =57544.23
7A8:0E18 push ss:[di+var_11A] ; but if "magic" from block 5 doesn't =("real" CRC from block4 + 10)
7A8:0E1D push ss:[di+var_11C] ; and doesn't=("real" CRC from block4-10) then var118*=9823.23 and
7A8:0E22 push ss:[di+var_real_DX_bl4] ; this won't let us to bypass next checks
7A8:0E27 push ss:[di+var_real_BX_bl4] ;
7A8:0E2C push ss:[di+var_real_AX_bl4] ; push "real" CRC from decrypted block 4
7A8:0E31 call j_check_integrity ; here in very tricky way "real" CRC and
"magic" compared.
This sub uses many fakes to stop us, it was really mess untill I rewrote
math part of this sub in pascal and simplify it - only then I was able
to see which operations were fakes and which were real and used.
And whats more funny, there are several decrypted "motivation" messages
in this sub, T decrypts them during other job, doesn't print, only
decrypts, its specially made for crackers which will explore code :))
Just for example:
97E8:16B1 lea di, [bp+var_218]
97E8:16B5 push ss
97E8:16B6 push di
97E8:16B7 lea di, [bp+var_118]
97E8:16BB push ss
97E8:16BC push di
97E8:16BD mov di, offset unk_87E8_F23
97E8:16C0 push cs
97E8:16C1 push di
97E8:16C2 call @$basg$qm6Stringt1 ; Load string
97E8:16C7 mov ax, 8374h
97E8:16CA xor dx, dx
97E8:16CC push dx
97E8:16CD push ax
97E8:16CE mov ax, 0FCEBh
97E8:16D1 mov dx, 0BABEh
97E8:16D4 push dx
97E8:16D5 push ax
97E8:16D6 call decrypt_string_and_verify_it ; "You are a weak lamer...."
97E8:16DB lea di, [bp+var_CA]
97E8:16DF push ss
97E8:16E0 push di
97E8:16E1 mov ax, 32h ; '2'
97E8:16E4 push ax
97E8:16E5 call @$basg$qm6Stringt14Byte ; Store string
And so on, like "I would not go there if i were you...",
"I also HATE debugging floating point op..",
"Why does an inteligent guy like you...",
"Wizard your time is running out...", and T even begins to count seconds
left before my death :))
Then it decrypts last one "End of motivation messages.Thank you for
support good product...". Very funny and I must admit that those msgs
were really very motivating and helped me not to stop exploring this boring
procedure :)
Ok, lets get back to "check_integrity" itself:
97E8:1085 check_integrity proc far ; CODE XREF: j_check_integrityJ
; I include all vars here specially coz I used same vars names in my
; Pascal program
97E8:1085 var_418= byte ptr -418h
97E8:1085 var_318= byte ptr -318h
97E8:1085 var_30C= byte ptr -30Ch
97E8:1085 var_2D0= byte ptr -2D0h
97E8:1085 var_21C= byte ptr -21Ch
97E8:1085 var_218= byte ptr -218h
97E8:1085 var_214= byte ptr -214h
97E8:1085 var_210= byte ptr -210h
97E8:1085 var_20C= byte ptr -20Ch
97E8:1085 var_20A= byte ptr -20Ah
97E8:1085 var_208= byte ptr -208h
97E8:1085 var_1DC= byte ptr -1DCh
97E8:1085 var_1D0= byte ptr -1D0h
97E8:1085 var_11C= byte ptr -11Ch
97E8:1085 var_118= byte ptr -118h
97E8:1085 var_114= byte ptr -114h
97E8:1085 var_110= byte ptr -110h
97E8:1085 var_10C= byte ptr -10Ch
97E8:1085 var_10A= byte ptr -10Ah
97E8:1085 var_108= byte ptr -108h
97E8:1085 var_DC= byte ptr -0DCh
97E8:1085 var_D0= byte ptr -0D0h
97E8:1085 var_CB= byte ptr -0CBh
97E8:1085 var_CA= byte ptr -0CAh
97E8:1085 var_96= word ptr -96h
97E8:1085 var_94= word ptr -94h
97E8:1085 var_92= word ptr -92h
97E8:1085 var_90= word ptr -90h
97E8:1085 var_8E= word ptr -8Eh
97E8:1085 var_8C= word ptr -8Ch
97E8:1085 var_AX_bl_4= word ptr -8Ah
97E8:1085 var_BX_bl_4= word ptr -88h
97E8:1085 var_DX_bl_4= word ptr -86h
97E8:1085 var_84= word ptr -84h
97E8:1085 var_82= word ptr -82h
97E8:1085 var_80= word ptr -80h
97E8:1085 var_7E= word ptr -7Eh
97E8:1085 var_7C= word ptr -7Ch
97E8:1085 var_7A= word ptr -7Ah
97E8:1085 var_Date_AX= word ptr -78h
97E8:1085 var_Date_BX= word ptr -76h
97E8:1085 var_Date_DX= word ptr -74h
97E8:1085 var_X4_AX= word ptr -72h
97E8:1085 var_X4_BX= word ptr -70h
97E8:1085 var_X4_DX= word ptr -6Eh
97E8:1085 var_6C= word ptr -6Ch
97E8:1085 var_6A= word ptr -6Ah
97E8:1085 var_68= word ptr -68h
97E8:1085 var_X5_AX= word ptr -66h
97E8:1085 var_X5_BX= word ptr -64h
97E8:1085 var_X5_DX= word ptr -62h
97E8:1085 var_X3_AX= word ptr -60h
97E8:1085 var_X3_BX= word ptr -5Eh
97E8:1085 var_X3_DX= word ptr -5Ch
97E8:1085 var_AX_bl_5= word ptr -5Ah
97E8:1085 var_AX_bl_5= word ptr -5Ah
97E8:1085 var_BX_bl_5= word ptr -58h
97E8:1085 var_DX_bl_5= word ptr -56h
97E8:1085 var_X2_AX= word ptr -54h
97E8:1085 var_X2_BX= word ptr -52h
97E8:1085 var_X2_DX= word ptr -50h
97E8:1085 var_X1_AX= word ptr -4Eh
97E8:1085 var_X1_BX= word ptr -4Ch
97E8:1085 var_X1_DX= word ptr -4Ah
97E8:1085 var_47= byte ptr -47h
97E8:1085 var_46= word ptr -46h
97E8:1085 var_44= word ptr -44h
97E8:1085 var_42= word ptr -42h
97E8:1085 var_40= word ptr -40h
97E8:1085 var_3E= word ptr -3Eh
97E8:1085 var_3C= word ptr -3Ch
97E8:1085 var_AX_11c= word ptr -3Ah
97E8:1085 var_BX_11c= word ptr -38h
97E8:1085 var_DX_11c= word ptr -36h
97E8:1085 var_34= word ptr -34h
97E8:1085 var_32= word ptr -32h
97E8:1085 var_30= word ptr -30h
97E8:1085 var_2E= word ptr -2Eh
97E8:1085 var_2C= word ptr -2Ch
97E8:1085 var_2A= word ptr -2Ah
97E8:1085 var_28= word ptr -28h
97E8:1085 var_26= word ptr -26h
97E8:1085 var_24= word ptr -24h
97E8:1085 var_22= word ptr -22h
97E8:1085 var_20= word ptr -20h
97E8:1085 var_1E= word ptr -1Eh
97E8:1085 var_1C= word ptr -1Ch
97E8:1085 var_1A= word ptr -1Ah
97E8:1085 var_18= word ptr -18h
97E8:1085 var_16= word ptr -16h
97E8:1085 var_14= word ptr -14h
97E8:1085 var_12= word ptr -12h
97E8:1085 var_10= word ptr -10h
97E8:1085 var_E= word ptr -0Eh
97E8:1085 var_mul_const_AX_1= word ptr -0Ch
97E8:1085 var_mul_const_BX_1= word ptr -0Ah
97E8:1085 var_mul_const_DX_1= word ptr -8
97E8:1085 var_6= word ptr -6
97E8:1085 var_4= word ptr -4
97E8:1085 var_2= word ptr -2
97E8:1085 arg_AX_bl_4= word ptr 6
97E8:1085 arg_BX_bl_4= word ptr 8
97E8:1085 arg_DX_bl_4= word ptr 0Ah
97E8:1085 arg_AX_11c= word ptr 0Ch
97E8:1085 arg_BX_11c= word ptr 0Eh
97E8:1085 arg_DX_11c= word ptr 10h
97E8:1085 arg_AX_bl_5= word ptr 12h
97E8:1085 arg_BX_bl_5= word ptr 14h
97E8:1085 arg_DX_bl_5= word ptr 16h
97E8:1085 arg_x= dword ptr 18h
97E8:1085
97E8:1085 push bp
97E8:1086 mov bp, sp
97E8:1088 sub sp, 418h
97E8:108C mov byte_192F_23E, 1
97E8:1091 mov al, 1
97E8:1093 push ax
97E8:1094 call j_get_time_and_some_shet
97E8:1099 mov [bp+var_6], 94h ; look at those MF'ers hehehe
97E8:109E mov [bp+var_4], 0A000h ; this stuff = 666666.0
97E8:10A3 mov [bp+var_2], 22C2h
97E8:10A8 mov ax, [bp+var_6] ; init a lot of vars
97E8:10AB mov bx, [bp+var_4] ; with 666666.0
97E8:10AE mov dx, [bp+var_2]
97E8:10B1 mov [bp+var_mul_const_AX_1], ax
97E8:10B4 mov [bp+var_mul_const_BX_1], bx
97E8:10B7 mov [bp+var_mul_const_DX_1], dx
97E8:10BA mov ax, [bp+var_6]
97E8:10BD mov bx, [bp+var_4]
97E8:10C0 mov dx, [bp+var_2]
97E8:10C3 mov [bp+var_12], ax
97E8:10C6 mov [bp+var_10], bx
97E8:10C9 mov [bp+var_E], dx
.... and so on
Then go a lot of floating point operations. I included almost all of
them here. After that I included my program in Pascal which emulates
work of math part of this sub.
97E8:13D0 ; check_integrity+318j
97E8:13D0 mov [bp+var_6], 0B95h
97E8:13D5 mov [bp+var_4], 7622h ; 1559918.7666
97E8:13DA mov [bp+var_2], 3E6Bh
97E8:13DF mov ax, [bp+arg_AX_bl_4] ;
97E8:13E2 mov bx, [bp+arg_BX_bl_4] ;
97E8:13E5 mov dx, [bp+arg_DX_bl_4] ;
97E8:13E8 mov [bp+var_AX_bl_4], ax
97E8:13EC mov [bp+var_BX_bl_4], bx
97E8:13F0 mov [bp+var_DX_bl_4], dx
.....
97E8:1428 call @$basg$qm6Stringt14Byte ; Store string
97E8:142D mov ax, 0DA95h ; 1485845.7817
97E8:1430 mov bx, 0AE40h
97E8:1433 mov dx, 3560h
97E8:1436 mov cx, [bp+var_6] ; 1559918.7666
97E8:1439 mov si, [bp+var_4]
97E8:143C mov di, [bp+var_2]
97E8:143F call @$brmul$q4Realt1 ; mul them = 2317798719100.0
97E8:1444 mov cx, 508Ch ; and mul them on 2313.412 =
97E8:1447 mov si, 978Dh
97E8:144A mov di, 1096h
97E8:144D call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:1452 mov cx, [bp+var_12] ; and mul them on 666666.0 =
97E8:1455 mov si, [bp+var_10]
97E8:1458 mov di, [bp+var_E]
97E8:145B call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:1460 mov [bp+var_mul_const_AX_1], ax
97E8:1463 mov [bp+var_mul_const_BX_1], bx
97E8:1466 mov [bp+var_mul_const_DX_1], dx
97E8:1469 mov [bp+var_X1_AX], 0E8Ch ; 2738.231
97E8:146E mov [bp+var_X1_BX], 0B22Dh
97E8:1473 mov [bp+var_X1_DX], 2B23h
97E8:1478 mov [bp+var_X4_AX], 508Ch ; 2313.412
97E8:147D mov [bp+var_X4_BX], 978Dh ; -|-|-
97E8:1482 mov [bp+var_X4_DX], 1096h ; -|-|-
97E8:1487 mov [bp+var_X2_AX], 0C98Eh ; 9823.436
97E8:148C mov [bp+var_X2_BX], 0BE76h
97E8:1491 mov [bp+var_X2_DX], 197Dh
....
97E8:14CF mov [bp+var_X3_AX], 6D8Dh ; 7863.123
97E8:14D4 mov [bp+var_X3_BX], 0FBE7h
97E8:14D9 mov [bp+var_X3_DX], 75B8h
97E8:14DE mov [bp+var_X5_AX], 0BE8Ch ; 2313.444
97E8:14E3 mov [bp+var_X5_BX], 1A9Fh
97E8:14E8 mov [bp+var_X5_DX], 1097h
97E8:14ED mov ax, [bp+arg_AX_11c] ;
97E8:14F0 mov bx, [bp+arg_BX_11c] ;
97E8:14F3 mov dx, [bp+arg_DX_11c] ;
97E8:14F6 mov [bp+var_AX_11c], ax
97E8:14F9 mov [bp+var_BX_11c], bx
97E8:14FC mov [bp+var_DX_11c], dx
97E8:14FF mov [bp+var_22], 666Fh
97E8:1504 mov [bp+var_20], 0FFFFh
97E8:1509 mov ax, [bp+arg_AX_bl_5] ;
97E8:150C mov bx, [bp+arg_BX_bl_5] ;
97E8:150F mov dx, [bp+arg_DX_bl_5] ;
97E8:1512 mov [bp+var_AX_bl_5], ax
97E8:1515 mov [bp+var_BX_bl_5], bx
97E8:1518 mov [bp+var_DX_bl_5], dx
97E8:151B mov [bp+var_12], 0BF94h ; 989856.94159
97E8:1520 mov [bp+var_10], 0F10h
...
97E8:1564 mov ax, [bp+var_AX_bl_4] ; here we mul values from block4 with
97E8:1568 mov bx, [bp+var_BX_bl_4]
97E8:156C mov dx, [bp+var_DX_bl_4]
97E8:1570 mov cx, [bp+var_X4_AX] ; 508c
97E8:1573 mov si, [bp+var_X4_BX] ; 978d
97E8:1576 mov di, [bp+var_X4_DX] ; 1096
97E8:1579 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:157E mov cx, [bp+var_AX_bl_5] ; then sub values from block 5
97E8:1581 mov si, [bp+var_BX_bl_5]
97E8:1584 mov di, [bp+var_DX_bl_5]
97E8:1587 call @$brmin$q4Realt1 ; Real(AX:BX:DX)-=Real(CX:SI:DI)
97E8:158C mov [bp+var_AX_bl_4], ax ; save results
97E8:1590 mov [bp+var_BX_bl_4], bx
97E8:1594 mov [bp+var_DX_bl_4], dx
97E8:1598 mov ax, 392h ; 235845.76666
97E8:159B mov bx, 7111h
97E8:159E mov dx, 6651h
97E8:15A1 mov cx, [bp+var_X4_AX] ; constants
97E8:15A4 mov si, [bp+var_X4_BX]
97E8:15A7 mov di, [bp+var_X4_DX]
97E8:15AA call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:15AF mov cx, [bp+arg_AX_bl_4] ; again use values from block 4
97E8:15B2 mov si, [bp+arg_BX_bl_4]
97E8:15B5 mov di, [bp+arg_DX_bl_4]
97E8:15B8 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:15BD mov [bp+var_18], ax
97E8:15C0 mov [bp+var_16], bx
97E8:15C3 mov [bp+var_14], dx
97E8:15C6 mov ax, 4890h ; 44545.781666
97E8:15C9 mov bx, 0C81Bh
97E8:15CC mov dx, 2E01h
97E8:15CF mov cx, [bp+var_18]
97E8:15D2 mov si, [bp+var_16]
97E8:15D5 mov di, [bp+var_14]
97E8:15D8 call @$brdiv$q4Realt1 ; Real(AX:BX:DX)/=Real(CX:SI:DI)
97E8:15D8 ; Real(CX:SI:DI)=Real(AX:BX:DX)%Real(CX:SI:DI)
97E8:15DD mov cx, [bp+var_mul_const_AX_1]
97E8:15E0 mov si, [bp+var_mul_const_BX_1]
97E8:15E3 mov di, [bp+var_mul_const_DX_1]
97E8:15E6 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:15EB mov cx, [bp+var_12]
97E8:15EE mov si, [bp+var_10]
97E8:15F1 mov di, [bp+var_E]
97E8:15F4 call @$brplu$q4Realt1 ; Real(AX:BX:DX)+=Real(CX:SI:DI)
97E8:15F9 mov [bp+var_1E], ax
97E8:15FC mov [bp+var_1C], bx
97E8:15FF mov [bp+var_1A], dx
97E8:1602 mov ax, [bp+var_6]
97E8:1605 mov bx, [bp+var_4]
97E8:1608 mov dx, [bp+var_2]
97E8:160B mov cx, [bp+var_AX_bl_4]
97E8:160F mov si, [bp+var_BX_bl_4]
97E8:1613 mov di, [bp+var_DX_bl_4]
97E8:1617 call @$brplu$q4Realt1 ; Real(AX:BX:DX)+=Real(CX:SI:DI)
97E8:161C mov cx, [bp+var_18]
97E8:161F mov si, [bp+var_16]
97E8:1622 mov di, [bp+var_14]
97E8:1625 call @__Cmp$q4Realt1 ; Compare two reals
97E8:162A ja loc_87E8_164E ;
97E8:162C mov ax, [bp+var_18]
97E8:162F mov bx, [bp+var_16]
97E8:1632 mov dx, [bp+var_14]
97E8:1635 mov cx, [bp+var_1E]
97E8:1638 mov si, [bp+var_1C]
97E8:163B mov di, [bp+var_1A]
97E8:163E call @$brplu$q4Realt1 ; Real(AX:BX:DX)+=Real(CX:SI:DI)
97E8:1643 mov [bp+var_2E], ax
97E8:1646 mov [bp+var_2C], bx
97E8:1649 mov [bp+var_2A], dx
97E8:164C jmp short loc_87E8_168A
97E8:164E ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:164E loc_87E8_164E: ; CODE XREF: check_integrity+5A5j
97E8:164E mov ax, [bp+var_mul_const_AX_1]
97E8:1651 mov bx, [bp+var_mul_const_BX_1]
97E8:1654 mov dx, [bp+var_mul_const_DX_1]
97E8:1657 mov cx, [bp+var_18]
97E8:165A mov si, [bp+var_16]
97E8:165D mov di, [bp+var_14]
97E8:1660 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:1665 mov cx, [bp+var_X4_AX]
97E8:1668 mov si, [bp+var_X4_BX]
97E8:166B mov di, [bp+var_X4_DX]
97E8:166E call @$brmin$q4Realt1 ; Real(AX:BX:DX)-=Real(CX:SI:DI)
97E8:1673 mov cx, [bp+arg_AX_bl_4]
97E8:1676 mov si, [bp+arg_BX_bl_4]
97E8:1679 mov di, [bp+arg_DX_bl_4]
97E8:167C call @$brplu$q4Realt1 ; Real(AX:BX:DX)+=Real(CX:SI:DI)
97E8:1681 mov [bp+var_mul_const_AX_1], ax
97E8:1684 mov [bp+var_mul_const_BX_1], bx
97E8:1687 mov [bp+var_mul_const_DX_1], dx
97E8:168A
97E8:168A loc_87E8_168A: ; CODE XREF: check_integrity+5C7j
97E8:168A mov ax, 0E925h
97E8:168D push ax
97E8:168E call @Random$q4Word ; Random(range: Word): Word{AX}
97E8:1693 xor dx, dx
97E8:1695 call @Real$q7Longint ; Real(x: Longint{DX:AX}): Real
97E8:169A mov cx, [bp+var_1E]
97E8:169D mov si, [bp+var_1C]
97E8:16A0 mov di, [bp+var_1A]
97E8:16A3 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:16A8 mov [bp+var_6C], ax
97E8:16AB mov [bp+var_6A], bx
97E8:16AE mov [bp+var_68], dx
....
97E8:16EA mov ax, [bp+var_AX_11c]
97E8:16ED mov bx, [bp+var_BX_11c]
97E8:16F0 mov dx, [bp+var_DX_11c]
97E8:16F3 mov cx, 0CD91h ; 89873.12948
97E8:16F6 mov si, 9092h
97E8:16F9 mov di, 2F88h
97E8:16FC call @__Cmp$q4Realt1 ; Compare two reals
97E8:1701 jbe loc_87E8_1712 ; no jump for our kf
97E8:1703 mov [bp+var_X4_AX], 0F494h ; 876478.312
97E8:1708 mov [bp+var_X4_BX], 0E4FDh
97E8:170D mov [bp+var_X4_DX], 55FBh
97E8:1712
97E8:1712 loc_87E8_1712: ; CODE XREF: check_integrity+67Cj
97E8:1712 mov ax, [bp+var_AX_bl_4]
97E8:1716 mov bx, [bp+var_BX_bl_4]
97E8:171A mov dx, [bp+var_DX_bl_4]
97E8:171E mov cx, [bp+var_X4_AX]
97E8:1721 mov si, [bp+var_X4_BX]
97E8:1724 mov di, [bp+var_X4_DX]
97E8:1727 call @$brmul$q4Realt1 ; Real(AX:BX:DX)*=Real(CX:SI:DI)
97E8:172C mov cx, [bp+arg_AX_bl_4]
97E8:172F mov si, [bp+arg_BX_bl_4]
97E8:1732 mov di, [bp+arg_DX_bl_4]
97E8:1735 call @$brmin$q4Realt1 ; Real(AX:BX:DX)-=Real(CX:SI:DI)
97E8:173A mov [bp+var_AX_bl_5], ax
97E8:173D mov [bp+var_BX_bl_5], bx
97E8:1740 mov [bp+var_DX_bl_5], dx
97E8:1743 mov ax, [bp+var_AX_bl_5]
97E8:1746 mov bx, [bp+var_BX_bl_5]
97E8:1749 mov dx, [bp+var_DX_bl_5]
97E8:174C mov [bp+var_X2_AX], ax
97E8:174F mov [bp+var_X2_BX], bx
97E8:1752 mov [bp+var_X2_DX], dx
...
After this goes some kind of CRC check on terminat.exe which will
stop only if several conditions will be true:
97E8:1ADC loc_87E8_1ADC: ; CODE XREF: check_integrity+A42j
97E8:1ADC ; check_integrity+A4Bj check_integrity+A52j
97E8:1ADC mov ax, [bp+var_X1_AX]
97E8:1ADF mov bx, [bp+var_X1_BX]
97E8:1AE2 mov dx, [bp+var_X1_DX]
97E8:1AE5 mov cx, 0DB94h ; 553745.561
97E8:1AE8 mov si, 18F9h
97E8:1AEB mov di, 731h
97E8:1AEE call @__Cmp$q4Realt1 ; Compare two reals
97E8:1AF3 jb loc_87E8_1AF8 ; we must bypass all checks here in order to
97E8:1AF5 jmp loc_87E8_191C ; return from this sub
97E8:1AF8 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:1AF8 loc_87E8_1AF8: ; CODE XREF: check_integrity+A6Ej
97E8:1AF8 mov ax, [bp+var_X2_AX]
97E8:1AFB mov bx, [bp+var_X2_BX]
97E8:1AFE mov dx, [bp+var_X2_DX]
97E8:1B01 mov cx, 358Dh ; 7823.466
97E8:1B04 mov si, 0BA5Eh
97E8:1B07 mov di, 747Bh
97E8:1B0A call @__Cmp$q4Realt1 ; Compare two reals
97E8:1B0F ja loc_87E8_1B14
97E8:1B11 jmp loc_87E8_191C ; back to check integrity on terminat.exe
97E8:1B14 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:1B14 loc_87E8_1B14: ; CODE XREF: check_integrity+A8Aj
97E8:1B14 mov ax, [bp+var_X3_AX]
97E8:1B17 mov bx, [bp+var_X3_BX]
97E8:1B1A mov dx, [bp+var_X3_DX]
97E8:1B1D mov cx, 9C94h ; 587863.173
97E8:1B20 mov si, 72C4h
97E8:1B23 mov di, 0F85h
97E8:1B26 call @__Cmp$q4Realt1 ; Compare two reals
97E8:1B2B jb loc_87E8_1B30
97E8:1B2D jmp loc_87E8_191C
97E8:1B30 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:1B30 loc_87E8_1B30: ; CODE XREF: check_integrity+AA6j
97E8:1B30 mov ax, [bp+var_X4_AX]
97E8:1B33 mov bx, [bp+var_X4_BX]
97E8:1B36 mov dx, [bp+var_X4_DX]
97E8:1B39 mov cx, 5294h ; 575498.545
97E8:1B3C mov si, 0A8B8h
97E8:1B3F mov di, 0C80h
97E8:1B42 call @__Cmp$q4Realt1 ; Compare two reals
97E8:1B47 jb loc_87E8_1B4C
97E8:1B49 jmp loc_87E8_191C
97E8:1B4C ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:1B4C loc_87E8_1B4C: ; CODE XREF: check_integrity+AC2j
97E8:1B4C mov ax, [bp+var_X5_AX]
97E8:1B4F mov bx, [bp+var_X5_BX]
97E8:1B52 mov dx, [bp+var_X5_DX]
97E8:1B55 mov cx, 8B8Ch ; 2213.494
97E8:1B58 mov si, 0E76Ch
97E8:1B5B mov di, 0A57h
97E8:1B5E call @__Cmp$q4Realt1 ; Compare two reals
97E8:1B63 ja loc_87E8_1B68
97E8:1B65 jmp loc_87E8_191C
97E8:1B68 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
97E8:1B68 loc_87E8_1B68: ; CODE XREF: check_integrity+ADEj
97E8:1B68 mov byte_192F_112D, 1
97E8:1B6D call j_some_check_n_bad_noise
97E8:1B72 mov byte_192F_E414, 7
97E8:1B77 call sub_153E_E09
97E8:1B7C mov byte_192F_23E, 0
97E8:1B81 loc_87E8_1B81: ; CODE XREF: check_integrity+348j
97E8:1B81 mov sp, bp
97E8:1B83 pop bp
97E8:1B84 retf 16h
97E8:1B84 check_integrity endp
Here is my proggy in Pascal. You can compile it with TP6 and run.
It will write "Key is valid", because by default I use values which lead
us to valid key. You can change g_bl4 to whatever you want (this var
emulates "real" CRC from decrypted block 4), g_bl5 is calculated in
right way. If you try to add someting other than 10 or sub or whatever
you get "Key is not valid". For full explantation see comments in source.
I use same vars names as in Ida listing,e.g.
97E8:1743 mov ax, [bp+var_AX_bl_5] ;
97E8:1746 mov bx, [bp+var_BX_bl_5]
97E8:1749 mov dx, [bp+var_DX_bl_5]
97E8:174C mov [bp+var_X2_AX], ax
97E8:174F mov [bp+var_X2_BX], bx
97E8:1752 mov [bp+var_X2_DX], dx
will be:
X2:=bl_5;
Anyway, here is pascal source:
{--------------------------- cut ---------------------------------------}
{ global vars}
Var
g_bl_4,g_bl_5,g_var118:Real;
{ "check_integrity" emulation ;-) }
{ it takes as args 3 values: "real" crc from decrypted block4,
"real" "magic" from block5 and "real" var118 - its "floating point" flag }
function check_integrity(arg_bl4,arg_bl5,arg_var118:Real):Integer;
Var
X1,X2,X3,X4,X5:Real;
bl_4,bl_5,mul_const:Real;
var6,var12,var18,var1E,var2E,var6C,var118:Real;
flag:Integer;
begin
{ vars init }
flag:=0; { "bad key" by default }
X1:=2738.231;
X2:=9823.436;
X4:=2313.412;
X3:=7863.123;
X5:=2313.444;
var18:=666666.0;
var12:=666666.0;
var6:=1559918.7666;
mul_const:=666666.0;
bl_4:=arg_bl4;
bl_5:=arg_bl5;
var118:=arg_var118;
bl_4:=bl_4*X4-bl_5; {<-------------- !!!! }
var18:=X4*235845.76666*arg_bl4; { fake }
var1E:=44545.781666/var18*mul_const+var12; { fake }
if (var6+bl_4<=var18) then {fake}
begin
var2E:=var18+var1E; { don't used in future }
end
else
begin
mul_const:=mul_const*var18-X4+arg_bl4; { don't used in future }
end;
var6C:=var1E*random($E925); { don't used in future }
if (var118>89873.12948) then { 7823.466 } { 7823.466
arg_bl4*X4*X4-arg_bl5*X4-arg_bl4>7823.466
arg_bl4(X4*X4-1)-arg_bl5*X4>7823.466
arg_bl4*5351874.0817-arg_bl5*2313.412>7823.466
Left part of this expression is always > right part whatever arg_bl4
and arg_bl5 we have, because arg_bl5=arg_bl4+10 or =arg_bl4-10, i.e.
arg_bl5 is only slightly bigger or less than arg_bl4 and
constant near arg_bl4(5351874.0817) is MUCH bigger than
constant near arg_bl5(2313.412). So whatever arg_bl4 and arg_bl5 we
have X2 always >7823.466
There is only one var left to explore - X4. It must be <575498.545.
By default X4=2313.412. So, by default it bypasses check. But
it can be changed if var118(arg pushed to j_check_integrity) >
89873.12948. By default var118=57544.23. So by default it
<89873.12948 and therefore X4 doesnt changed and it bypasses check.
But var118 also can be changed in sub "validate key" if
"magic" from block 5 doesn't ="real"CRC from block4 + 10 and
if "magic" from block 5 doesn't ="real"CRC from block4 - 10.
So, to bypass all checks in "j_check_integrity" we need to do
next steps:
-take CRC from decrypted block 4;
-convert it to real
-check if it <0 and change sign then
-if it<10 we must add 10
-if it>10 we must sub 10
-convert it back to Longint (i.e. 32bit value)
-put calculated value to block5 at offsets 15eh,160h.
}
if X1<553745 then { doesn't change }
begin
if X2>7823.466 then { changes }
begin
if X3<587863.173 then { doesn't change }
begin
if X4<575498.545 then { changes - most important !!! }
begin
if X5>2213.494 then { doesn't change }
flag:=1 { valid key }
end
end
end
end;
check_integrity:=flag;
end;
{-------------------------------- MAIN ------------------------------------}
begin
g_bl_4:=50000.0; { simulate CRC from decrypted block 4}
g_var118:=57544.23; { by default - don't change }
{ simulate Terminate's check}
if(g_bl_4<0) then g_bl_4:=g_bl_4*(-1);
if(g_bl_5<0) then g_bl_5:=g_bl_5*(-1);
{calc "magic" for block 5}
g_bl_5:=g_bl_4+10; { try to add other value or sub or whatever else}
if(g_bl_4+10<>g_bl_5) then
begin
if(g_bl_4-10<>g_bl_5) then
g_var118:=g_var118*9823.23 { set "bad key" flag }
end;
if(check_integrity(g_bl_4,g_bl_5,g_var118)=0) then { call integrity_check }
begin
writeln(' Key is not valid ');
end
else
begin
writeln(' Key is valid ');
end;
end.
{--------------------------- cut ---------------------------------------}
This proggy is very helpfull to understand that key file logic is very
simple. I was disappointed ;-))
Ok, lets imagine that we bypassed all checks in "check_integrity" (and
if we didn't - we will never return from "check_integrity", it
will produce very bad noise on PC speaker and go to infinite?? loop )
but it ain't over. Now T begins to check name from decrypted block 4
with several names, which were probably in bogus keys. See complete
list of these bad names in source of my keygen. Just for example:
...
87A8:0E74 mov di, offset unk_77A8_4
87A8:0E77 push cs
87A8:0E78 push di
87A8:0E79 call @$basg$qm6Stringt1 ; Load string
87A8:0E7E mov ax, 2711h
87A8:0E81 xor dx, dx
87A8:0E83 push dx
87A8:0E84 push ax
87A8:0E85 call decryptor1 ; decrypt "Peter Thompson"
87A8:0E8A call @$bsub$qm6Stringt1 ; Compare two strings
87A8:0E8F jnz loc_77A8_E9A
After that T performs last and most tricky check. It checks decrypted
block 4 at certain offsets for specific values and decide if key authentic
or not. Offsets in block 4 which are checked by T:
72h,74h
14eh,150h - most important, coz T rejects keys with certain values at this
offset
156h,158h
87A8:138C loc_77A8_138C: ; CODE XREF: validate_key+F5Bj
87A8:138C mov di, [bp+arg_0]
87A8:138F mov ss:[di+var_138], 0D51Bh
87A8:1396 mov ss:[di+var_136], 6660h
87A8:139D mov ax, ss:[di+var_138]
87A8:13A2 mov dx, ss:[di+var_136]
87A8:13A7 xor ax, 6660h ; ax=b37b
87A8:13AA xor dx, 6660h ; dx=0
87A8:13AE les di, ss:[di+var_106]
87A8:13B3 cmp dx, es:[di+150h]
87A8:13B8 jnz loc_77A8_13F3
87A8:13BA cmp ax, es:[di+14Eh]
87A8:13BF jnz loc_77A8_13F3
87A8:13C1 mov di, [bp+arg_0]
87A8:13C4 les di, ss:[di+var_106]
87A8:13C9 cmp word ptr es:[di+158h], 0E14Dh
87A8:13D0 jnz loc_77A8_13F3
87A8:13D2 cmp word ptr es:[di+156h], 2E1Ah
87A8:13D9 jnz loc_77A8_13F3
87A8:13DB mov di, [bp+arg_0]
87A8:13DE mov ss:[di+var_FLAG_ax], 5292h ; 234666.23
87A8:13E5 mov ss:[di+var_FLAG_bx], 8EB8h
87A8:13EC mov ss:[di+var_FLAG_dx], 652Ah
...
87A8:14C7 loc_77A8_14C7: ; CODE XREF: validate_key+1072j
87A8:14C7 mov di, [bp+arg_0]
87A8:14CA les di, ss:[di+var_106]
87A8:14CF cmp word ptr es:[di+158h], 0FA98h
87A8:14D6 jz loc_77A8_14DB
87A8:14D8 jmp loc_77A8_1598
87A8:14DB ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:14DB
87A8:14DB loc_77A8_14DB: ; CODE XREF: validate_key+10D9j
87A8:14DB cmp word ptr es:[di+156h], 12FDh
87A8:14E2 jz loc_77A8_14E7
87A8:14E4 jmp loc_77A8_1598
87A8:14E7 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:14E7
87A8:14E7 loc_77A8_14E7: ; CODE XREF: validate_key+10E5j
87A8:14E7 mov di, [bp+arg_0]
87A8:14EA les di, ss:[di+var_106]
87A8:14EF mov al, es:[di+76h] ; bit 0 of this byte must be 0
87A8:14F3 and al, 1 ; else we get message that this
87A8:14F5 cmp al, 1 ; key created for other platform
87A8:14F7 jnz loc_77A8_1545 ; or something like this
....
87A8:1545 loc_77A8_1545: ; CODE XREF: validate_key+10FAj
87A8:1545 mov di, [bp+arg_0]
87A8:1548 les di, ss:[di+var_106]
87A8:154D cmp word ptr es:[di+74h], 4638h
87A8:1553 jnz loc_77A8_155D
87A8:1555 cmp word ptr es:[di+72h], 2391h
87A8:155B jz loc_77A8_1561
87A8:155D loc_77A8_155D: ; CODE XREF: validate_key+1156j
87A8:155D mov al, 0
87A8:155F jmp short loc_77A8_1563
87A8:1561 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:1561 loc_77A8_1561: ; CODE XREF: validate_key+115Ej
87A8:1561 mov al, 1
87A8:1563 loc_77A8_1563: ; CODE XREF: validate_key+1162j
87A8:1563 mov [bp+var_FLAG_1_byte], al
87A8:1566 mov di, [bp+arg_0]
87A8:1569 mov ax, ss:[di+var_110]
87A8:156E sub ax, 2C92h ; 3ef
87A8:1571 xor dx, dx ; 0
87A8:1573 les di, ss:[di+var_106]
87A8:1578 cmp dx, es:[di+150h]
87A8:157D jnz loc_77A8_1598
87A8:157F cmp ax, es:[di+14Eh]
87A8:1584 jnz loc_77A8_1598
87A8:1586 mov word_192F_4A, 3D91h ; 102394.93
87A8:158C mov word_192F_4C, 770Ah
87A8:1592 mov word_192F_4E, 47FDh
87A8:1598 loc_77A8_1598: ; CODE XREF: validate_key+10DBj
87A8:1598 mov di, [bp+arg_0]
87A8:159B les di, ss:[di+var_106]
87A8:15A0 mov ax, es:[di+14Eh]
87A8:15A5 mov dx, es:[di+150h]
87A8:15AA mov Bl4_14e_AX, ax
87A8:15AD mov Bl4_150_DX, dx
87A8:15B1 mov di, [bp+arg_0]
87A8:15B4 les di, ss:[di+var_106]
Developers of T are very smart. They don't check for good values. They
check for "bad" ones, i.e. values from keys , generated by crackers for
previous version of T. This strategy allows them to hide "picture" of
authentic key. Cracker just couldn't know how valid key should looks like.
He can only see how this key shouldn't looks like :)) This is a big difference.
Cracker looks here, chooses values at offsets 15e,160 which are not STILL
checked and makes new keygen. Developers analyse that keygen and include
check for it it next version of T. Very smart move. So how can we produce
valid key for all future version of T ? There are several ways:
1) these keys must not contain values checked below at offsets 15eh,160h
So what could they contain there:
a) some fixed values, not checked in
current version of T. But this will lead us nowhere. Developers of T
will include check for these values in future ver of T.
b) random values, which will be different for every key our keygen will
produce. We only need to check it for "bad" ones as T does and regene
rate if need. This is good way but there is posibility that one day
our keygen will generate key, which will contain values, which will
be checked in future ver of T. And there is another posibility:
developers of T begins to check for "good" values, i.e. values which
exist in authentic keys - then our keys won't be valid.
c) We can make our keys look like old authentic keys, because T must
accept old authentic keys. T checks for such keys in very "softly"
and hidden way. First, it checks offsets 156h,158h for 0FA98h,12FDh:
87A8:14CF cmp word ptr es:[di+158h], 0FA98h
87A8:14D6 jz loc_77A8_14DB
87A8:14D8 jmp loc_77A8_1598
87A8:14DB loc_77A8_14DB: ; CODE XREF: validate_key+10D9j
87A8:14DB cmp word ptr es:[di+156h], 12FDh
87A8:14E2 jz loc_77A8_14E7
87A8:14E4 jmp loc_77A8_1598
If T founds these values then it checks if byte at offset 76h have
bit 0 set to 1.
87A8:14EF mov al, es:[di+76h] ; bit 0 of this byte must be 0
87A8:14F3 and al, 1 ; else we get message that this
87A8:14F5 cmp al, 1 ; key created for other platform
87A8:14F7 jnz loc_77A8_1545 ; or something like this
If bit0 set to 0 then T perform next check:
87A8:154D cmp word ptr es:[di+74h], 4638h ;
87A8:1553 jnz loc_77A8_155D
87A8:1555 cmp word ptr es:[di+72h], 2391h ;
87A8:155B jz loc_77A8_1561
87A8:155D loc_77A8_155D: ; CODE XREF: validate_key+1156j
87A8:155D mov al, 0 ; some flag, used when T patch itself
87A8:155F jmp short loc_77A8_1563
87A8:1561 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:1561 loc_77A8_1561: ; CODE XREF: validate_key+115Ej
87A8:1561 mov al, 1
87A8:1563 loc_77A8_1563: ; CODE XREF: validate_key+1162j
87A8:1563 mov [bp+var_FLAG_1_byte], al
87A8:1566 mov di, [bp+arg_0]
And finally T checks offset 14eh,150h for 3ef, 0. If such values found
T set its beloved "floating point" flag.
87A8:1569 mov ax, ss:[di+var_110]
87A8:156E sub ax, 2C92h ; 3ef
87A8:1571 xor dx, dx ; 0
87A8:1573 les di, ss:[di+var_106]
87A8:1578 cmp dx, es:[di+150h]
87A8:157D jnz loc_77A8_1598
87A8:157F cmp ax, es:[di+14Eh]
87A8:1584 jnz loc_77A8_1598
87A8:1586 mov word_192F_4A, 3D91h ; 102394.93
87A8:158C mov word_192F_4C, 770Ah ;"floating point" flag
87A8:1592 mov word_192F_4E, 47FDh
And thats all! After that T begins check offsets 14eh,150h for "bad"
values even if it found 3ef,0 there earlier. But it we have 3ef,0 at
offsets 14e,150 - we bypass those checks for sure !!!
87A8:15B9 cmp word ptr es:[di+150h], 0FE6Fh
87A8:15C0 jnz loc_77A8_15CB
87A8:15C0 jnz loc_77A8_15CB
87A8:15C2 cmp word ptr es:[di+14Eh], 0DF92h
87A8:15C9 jz loc_77A8_161E ; "unauthorized key"
87A8:15CB loc_77A8_15CB: ; CODE XREF: validate_key+11C3j
87A8:15CB mov di, [bp+arg_0]
87A8:15CE les di, ss:[di+var_106]
87A8:15D3 cmp word ptr es:[di+150h], 0C740h
87A8:15DA jnz loc_77A8_15E5
87A8:15DC cmp word ptr es:[di+14Eh], 0AE99h
87A8:15E3 jz loc_77A8_161E
87A8:15E5 loc_77A8_15E5: ; CODE XREF: validate_key+11DDj
87A8:15E5 mov di, [bp+arg_0]
87A8:15E8 les di, ss:[di+var_106]
87A8:15ED cmp word ptr es:[di+150h], 0
87A8:15F3 jnz loc_77A8_15FE
87A8:15F5 cmp word ptr es:[di+14Eh], 0B37Bh
87A8:15FC jz loc_77A8_161E
87A8:15FE loc_77A8_15FE: ; CODE XREF: validate_key+11F6j
87A8:15FE mov di, [bp+arg_0]
87A8:1601 les di, ss:[di+var_106]
87A8:1606 cmp word ptr es:[di+150h], 0C740h
87A8:160D jz loc_77A8_1612
87A8:160F jmp loc_77A8_1704
87A8:1612 ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:1612 loc_77A8_1612: ; CODE XREF: validate_key+1210j
87A8:1612 cmp word ptr es:[di+14Eh], 0AE66h
87A8:1619 jz loc_77A8_161E
87A8:161B jmp loc_77A8_1704
87A8:161E ; €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
87A8:161E loc_77A8_161E: ; CODE XREF: validate_key+11CCj
87A8:161E ; validate_key+11E6j validate_key+11FFj
87A8:161E ; here T display msg like "You are using unautorised key..."
So, we must put to decrypted block4: 0,3ef at offsets 14eh,150h;
0fa98h,12fdh at offsets 156h,158h; set to 0 bit0 of byte at offset 76h.
Such kind of key will be valid in any future ver of T.
My idea about these keys was confirmed when I saw old keygen for Terminate 3
by PREDATOR 666. I decrypted keys it creates and saw that is also uses such
strategy, i.e. 0,3ef to 14e,150 and so on as I described earlier. And what more
important that this keygen based on knowledge about original keys. After
it creates key it displays message like "thank to well known guy for
giving us 3 original keys". So as I thougth authentic keys looks like I described
above. Also this keygen gave me some other important info about original
key, i.e. key header; blocks 6-10 at offsets 15e,160 contains CRC of previous
blocks. So I wrote my keygen using this info. But if you think that its
CHEATING I made several command line switches which let generates valid
keys without header and CRC stored in blocks 6-10.
Ok, lets get back to T. After key at last checked T begins to create reginfo
and patch terminat.exe. See begining of me solution to look at reginfo format.
Here I'll explain how 4 bytes at offsets 1615E3h-1615E6h set.
1615E3h-1615E6h - 4 bytes 01,x,01,y.
x=84h by default
x=79h if offsets 72h,74h of decr. block 4 contains 2391h,4638h
y=DBh if offs. 14eh,150h contains 0,3ef
y=EAh if values at offs. 14eh,150h doesn't = 0,b37b and doesn't = 0,3ef
or values at offs. 156h,158h doesn't = 2e1a,e14d and doesn't = 12fd,fa98
y=6Dh if 14eh,150h contains 0,b37b and offs. 156h,158h contains 2e1a,e14d.
But this value cannot be set because such key won't bypass earlier check.
I don't know why T store these bytes, it doesn't even check it later. May be
all these made for checks in future versions of Terminate.
T takes values from offsets 154h,156h, xor them with FFFF,FFFF, convert
to ASCII string and put them at offsets 1615F1h-1615F9h in terminat.exe
This is also doesn't have any point for me now, may be it also one of
traps, may be they will check reginfo in future version of T. Anyway, I
made switch "i" in my keygen that allow you specify values you want at
offsets 152h,154h at decrypted block 4.
87A8:21E4 mov ax, es:[di+152h] ; take bytes from unpacked block4
87A8:21E9 mov dx, es:[di+154h] ; xor them and convert to ASCII
87A8:21EE xor ax, 0FFFFh
87A8:21F1 xor dx, 0FFFFh
87A8:21F5 push dx
87A8:21F6 push ax
87A8:21F7 call j_convert_32bit_to_ASCII
Also T xor , converts to ASCII and saves to terminat.exe at offsets
1615DAh-1615E2h some kind of seed used by encrypt/decrypt subs.
So then T can read this ASCII string, convert it to 32 bit value,
xor it and decrypt reginfo.
87A8:1D8F mov ax, ss:[di+var_AX_XOR_MAGIC]
87A8:1D94 mov dx, ss:[di+var_DX_XOR_MAGIC]
87A8:1D99 xor ax, 34FFh
87A8:1D9C xor dx, 12FFh
87A8:1DA0 mov ss:[di+var_AX_XOR_MAGIC], ax
87A8:1DA5 mov ss:[di+var_DX_XOR_MAGIC], dx
After everything done T display message and exit.
87A8:2279 loc_77A8_2279:
87A8:2279 mov di, [bp+arg_0]
87A8:227C push word ptr ss:[di+var_106+2]
87A8:2281 push word ptr ss:[di+var_106]
87A8:2286 mov ax, 162h
87A8:2289 push ax
87A8:228A call @FreeMem$qm7Pointer4Word ; Free mem allocated for key block
87A8:228F call sub_267_57 ; print "Terminat.reg" generated ....
87A8:2294 call call_write_to_screen
87A8:2299 xor ax, ax
87A8:229B call @Halt$q4Word ; Halt(Word)
Ok, at last. My powerfull advanced Terminate 3,4,5+ keygen :))
;-------------------------------- term_akg.asm ------------------------------
; Terminate 3,4,5+ Advanced Key Generator.
; (C)oded by iNT_03h in 1998.
; tasm 5.0r
; compile: tasm.exe term_akg.asm
; link: tlink.exe term_akg.obj
.286p
.MODEL Small
.Code
;----------------------------- macros ---------------------------------
PRINTF Macro ; macro for display string
mov AH,09 ; ds:dx must point to string
int 21h
Endm
GETCH Macro ; wait for press key
xor AH,AH
int 16h
Endm
SCANF Macro ; macro for enter string from kbd
mov AH,0Ah ; ds:dx must points to buffer
int 21h
Endm
WRT_BLOCK Macro ; write current block of key to keyfile
mov AH,40h
mov BX,Handle
mov CX,Blk_len
mov DX, Offset Temp_blk
int 21h
Endm
STORE_CRC Macro ; put crc in block
mov BX, Crc_ax
mov DI, Offset Temp_blk+15Eh
mov DS:[DI],BX
inc DI
inc DI
mov BX, Crc_dx
mov DS:[DI],BX
Endm
;================================ CODE_SEG ==============================
START: .Startup
;set mode 3
mov AX,3
int 10h
mov DX, Offset Start_msg ; display start message
PRINTF
mov CL,Byte Ptr ES:[80h] ; number+1 of char in cmd line
or CL,CL
jne LABEL1 ; damn, "relative jump out of range"
jmp GET_REGINFO
LABEL1:
inc CL ; +1 because we dec it at first step of loop
mov DI,80h ; start offset-1 of cmd line in PSP
CHECK_CMD_LINE:
inc DI
dec CL
jne LABEL2 ; damn, "relative jump out of range"
jmp GET_REGINFO
LABEL2:
mov AL, ES:[DI]
cmp AL,' ' ; dont notice spaces
je CHECK_CMD_LINE
cmp AL,'?' ; "?"
jne CHECK_SW2
mov DX,Offset Help ; display help
PRINTF
jmp QUIT
; set flags depending on cmd line
CHECK_SW2: cmp AL,'s' ; switches
je RND_SIG
cmp AL,'S'
jne CHECK_SW3
RND_SIG:
mov Rnd_sig_flag,1
jmp CHECK_CMD_LINE
CHECK_SW3: cmp AL,'z'
je Z_FIL
cmp AL,'Z'
jne CHECK_SW4
Z_FIL:
mov Z_fil_flag,1
jmp CHECK_CMD_LINE
CHECK_SW4: cmp AL,'l'
je FIX_LEN1
cmp AL,'L'
jne CHECK_SW5
FIX_LEN1:
mov Fixed_len_flag,1
jmp CHECK_CMD_LINE
CHECK_SW5: cmp AL,'a'
je FIX_LEN2
cmp AL,'A'
jne CHECK_SW6
FIX_LEN2:
mov Fixed_len_flag,2
jmp CHECK_CMD_LINE
CHECK_SW6: cmp AL,'c'
je NO_CRC
cmp AL,'C'
jne CHECK_SW7
NO_CRC:
mov No_crc_flag,1
jmp CHECK_CMD_LINE
CHECK_SW7: cmp AL,'o'
je OFF_72
cmp AL,'O'
jne CHECK_SW8
OFF_72: mov Off_72_flag,1
jmp CHECK_CMD_LINE
CHECK_SW8: cmp AL,'i'
je OFF_152__
cmp AL,'I'
jne CHECK_SW9
OFF_152__: call CONVERT
CHECK_SW9: cmp AL,'r'
je NO_HDR
cmp AL,'R'
je NO_HDR ; damn, "relative jump out of range"
jmp CHECK_CMD_LINE
NO_HDR:
mov No_hdr_flag,1
jmp CHECK_CMD_LINE
GET_REGINFO:
; -------------------------- get reginfo ----------------------------
push DS ; we dont need PSP anymore
pop ES
mov DX, Offset Start_msg1 ; display start message
PRINTF
GET_REGINFO1:
mov DX, Offset Name_
PRINTF
mov DX, Offset Nam ; get name
SCANF
mov DX, Offset Addr_ask
PRINTF
mov DX, Offset Addres ; get addres
SCANF
mov DX, Offset City_ask
PRINTF
mov DX, Offset City
SCANF
mov DX, Offset Country_ask
PRINTF
mov DX, Offset Country
SCANF
mov DX, Offset Nl
PRINTF
;------------------------------- check for bad names and city ----------
mov Counter1,21 ; number of names to check
xor DH,DH
xor CH,CH
mov DI,Offset Bad_names
AGAIN:
cmp Counter1,1 ; last one in list of bad name
jnz CHECK_NAME ; is acctualy city ( checked at "city" field)
mov SI,Offset City+2
jmp GET_LEN
CHECK_NAME:
mov SI,Offset Nam+2 ; bypass buffer len and num
GET_LEN:
mov Byte Ptr CL,[DI] ; length of bad name
mov Byte Ptr AL,[SI-1] ; length of entered name
cmp AL,CL ; if len of nam>len of bad name
jae USE_BAD_NAM_LEN ; use len if bad name as counter
xchg CL,AL ; else use nam len
USE_BAD_NAM_LEN:
mov DL,CL ; save for future use
inc DI ; bypass len. of bad name
repz cmpsb
jz BAD_NAME_FOUND
dec Counter1
jz CREAT_KEY
dec DI
sub DX,CX
sub DI,DX ; find next bad name
mov Byte Ptr DL,[DI]
add DI,DX
inc DI
jmp AGAIN
BAD_NAME_FOUND:
mov DX,Offset Bad_name_msg
PRINTF
GETCH
jmp GET_REGINFO1
;------------------------------- create key file -----------------------
CREAT_KEY:
mov AX, 3C00h
mov CX, 32 ; "archive" file
mov DX, Offset Keyfile
int 21h ; create keyfile
jnb NO_ERROR1
mov DX, Offset Error_1
PRINTF
jmp QUIT
NO_ERROR1:
mov Handle,AX;
call RANDOMIZE ; init "seed"
MAIN_LOOP:
mov AX,Crc_ax
mov Temp_ax,AX
mov AX,Crc_dx
mov Temp_dx,AX
inc Counter1
call FILL_BLK ; fill block with random words or 0
cmp Counter1,1 ; first block ?
jne CHECK4
cmp No_hdr_flag,1
je CHECK_BAD_BYTE
call FILL_1ST_BLK ; Write header to 1st block
jmp CRC
CHECK_BAD_BYTE: ; bypass Terminate check
cmp Byte Ptr [Offset Temp_blk+0Bh],46h
jne CRC
BAD_BYTE:
call RANDOM
cmp AL,46h
je BAD_BYTE
mov Byte Ptr [Offset Temp_blk+0Bh],AL
CHECK4: cmp Counter1,4 ; block 4?
jne CRC
call MAKE_BLK4 ; write reginfo, signatures, pak block4
CRC:
mov DI,Offset Temp_blk
and Word Ptr Counter,0 ; start offset in block =0
call CRC_CALC
; check if we need to adjust CRC in block 2,11
cmp Counter1,2 ;block 2?
jne CHECK11
mov DI, Offset Temp_blk
call CHK_5 ; check if first 5 bytes of block 2
or AL,AL ; = each other then modify CRC_AX,
jne ADJUST_CRC ; CRC_DX as Terminate does
jmp WRITE
ADJUST_CRC:
add Crc_ax,329h ; adjust CRC
adc Crc_dx,0h
jmp WRITE
CHECK11: cmp Counter1,0Bh
jne STORE_CHK
lea DI, Temp_blk[12Ch]
call CHK_5 ; check if 5 bytes of block 11
; = each other then modify CRC_AX,
; CRC_DX as Terminate does
or AL,AL
je CURENT_STORE
add Crc_ax,192h
adc Crc_dx,0h
jmp CURENT_STORE
; store CRC if needed
STORE_CHK:
cmp Counter1,5 ; we need to save magic values
jne CHECK6
mov BX, Bl5_ax ; to block 5
mov DI, Offset Temp_blk+15Eh
mov DS:[DI],BX
mov BX, Bl5_dx
inc DI
inc DI
mov DS:[DI],BX
jmp WRITE
CHECK6:
cmp Counter1,6 ; dont store crc in blocks 1-5
jb WRITE
cmp No_crc_flag,1
je CLEAR_CRC
STORE: ; previous block CRC store
mov BX, Temp_ax
mov DI, Offset Temp_blk+15Eh
mov DS:[DI],BX
inc DI
inc DI
mov BX, Temp_dx
mov DS:[DI],BX
jmp WRITE
CURENT_STORE: ; in block 11 we must save
STORE_CRC ; current block CRC
jmp WRITE
CLEAR_CRC:
lea DI, Temp_blk[15Eh]
and Word Ptr [DI],0 ; clear CRC place
inc DI
inc DI
and Word Ptr [DI],0
WRITE:
WRT_BLOCK
jnb NO_ERROR2
mov DX, Offset Error_2
PRINTF
jmp CLOSE_KF
NO_ERROR2:
cmp Counter1,Num_blk
je DONE
jmp MAIN_LOOP
DONE:
mov DX, Offset Done_msg
PRINTF
cmp Fixed_len_flag,1
je CLOSE_KF
inc Counter1 ;bypass own check for 0DCh in fill_blk
call FILL_BLK
call RANDOMIZE
RND_LEN:
call RANDOM ; get random length
cmp Fixed_len_flag,2
jne chk_rnd_part
mov AL,3905-3894 ;make fixed len = 3905
jmp ADD_RND_PART
CHK_RND_PART:
cmp AL,1 ; it should be from 1 to 105
jb RND_LEN
cmp AL,105
ja RND_LEN
ADD_RND_PART: ; write random size block
xor AH,AH ; to the end of keyfile
mov CX,AX
mov AH,40h
mov BX,Handle
mov DX, Offset Temp_blk
int 21h
CLOSE_KF:
mov AH,3Eh
mov BX,Handle
int 21h
QUIT:
mov AX,4C00h
int 21h
;------------------------------- CRC calc ---------------------------------
CRC_CALC Proc
; proc calc crc for current block of key in Temp_Block
; kills di,ax,bx,cx,dx
; di - start offset
; counter - number of bytes to proceed
; this sub taken from terminate, modified a little
; mov di, offset Temp_Blk
LOOP1:
mov Byte Ptr AL,DS:[DI]
mov Word Ptr BX,Crc_ax
mov Word Ptr DX,Crc_dx
mov CX,AX
push DX
push BX
xor BX,CX
xor BH,BH
shl BX,1
shl BX,1
add BX, Offset Crc_table
mov AX,DS:[BX]
mov CX,DS:[BX+2]
pop BX
pop DX
push CX
mov CX,8
LOOP2:
shr DX,1
rcr BX,1
loop LOOP2
and DX,0FFh
pop CX
xor AX,BX
mov BX,CX
xor DX,BX
mov Word Ptr Crc_ax,AX
mov Word Ptr Crc_dx,DX
inc DI
inc Counter
cmp Word Ptr Counter,15Eh
jne LOOP1
ret
CRC_CALC Endp
;------------------------------ Random -----------------------------
RANDOM Proc
; proc generate pseudo-random number
; kill ax
; return random num in ax
; after several experiments I found combination of math operations
; that lets create random numbers rather well
mul Rnd_word1
xchg AH,AL
xor AX,Rnd_word2
add Rnd_word1,AX
push AX
mov AX,Rnd_word1
xor Rnd_word2,AX
pop AX
; and ax,0ffh
ret
RANDOM Endp
;----------------------------- Randomize ----------------------------
RANDOMIZE Proc
; kill ax,dx
; modify "seed"
push DS
mov AX, 40h ; segment of timer counter
mov DS,AX
cli
mov AX, DS:[6Ch] ; offset of timer counter low word
mov DX, DS:[6Eh] ; high word
sti
pop DS
sub Rnd_word1,AX
add Rnd_word2,DX
ret
RANDOMIZE Endp
;--------------------------------- Fill block with random bytes or 0 ---
FILL_BLK Proc
; kill di,cx,ax,es
cld
mov Word Ptr CX,Blk_len/2
dec CX ; dont fill CRC place
dec CX
mov DI, Offset Temp_blk
cmp Z_fil_flag,1 ; if =0 then fill block with random
jne LOOP3 ; words
xor AX,AX ; else - with 0
push DS
pop ES
rep stosw
ret
LOOP3:
call RANDOM
mov Word Ptr DS:[DI],AX ; fill with random words
inc DI
inc DI
loop LOOP3
cmp Counter1,0Bh ; we need to check in block 11
; offset 15ah for value 0DCh
; because Terminate also does
; this check, but Term looks
; for whole signature DC,64,D9,E9
; Although its very small posibility
; to get that signature in our block 11
; after filling it with random values,
; I decided to check for first byte
; and randomly change it if it = DC
jne RET_
cmp Byte Ptr [Offset Temp_blk+15Ah],0DCh
jne RET_
BAD_BYTE1:
call RANDOM
cmp AL,0DCh
je BAD_BYTE1
mov Byte Ptr [Offset Temp_blk+15Ah],AL
RET_:
ret
FILL_BLK Endp
;----------------------------- Write header to 1st block ----------------
FILL_1ST_BLK Proc
;kill di,si,cx
cld
mov SI, Offset Key_header1
mov DI, Offset Temp_blk
mov CX,33 ; length of header1
rep movsb
xor CX,CX ;copy name
mov SI, Offset Nam+2
mov DI, Offset Temp_blk+33
mov CL, Nam_len ; length of Name
rep movsb
mov SI, Offset Key_header2
mov DI, Offset Temp_blk+33
mov CL, Nam_len
add DI, CX
mov CX,5 ; length of header2
rep movsb
ret
FILL_1ST_BLK Endp
;------------------------------- Check for 5 = bytes -----------
CHK_5 Proc
; di must point to start offset
; proc check 5 consecutive bytes, begin from es:[di]
; return al=1 if they equal each other
; else al=0
; kill cx
mov CX,4
mov Byte Ptr AL,[DI]
LOOP4:
inc DI
mov Byte Ptr AH,[DI]
cmp AL,AH
jne RET_1
xchg AL,AH
loop LOOP4
mov AL,1
ret
RET_1:
xor AL,AL
ret
CHK_5 Endp
;---------------------------- make block 4 ---------------------------
MAKE_BLK4 Proc
cld ; set direction - forward
xor CH,CH
lea SI, Nam[1] ; points to len of name
lea DI, Temp_blk[7Ah] ; points to offset of name in blk4
mov CL, Nam_len ; length of name
inc CL
rep movsb ; copy
lea SI, Addres[1] ;addres
lea DI, Temp_blk[0ADh]
mov CL, Addres_len
inc CL
rep movsb
lea SI, City[1] ;city
lea DI, Temp_blk[0E0h]
mov CL, City_len
inc CL
rep movsb
lea SI, Country[1] ;country
lea DI, Temp_blk[113h]
mov CL, Country_len
inc CL
rep movsb
cmp word ptr Off_152_Flag,0
je CHECK_OFF_72
lea di,Temp_blk[154h]
mov ax,Off_154_word
mov word ptr [di],ax
mov ax,Off_152_word
mov word ptr [di-2],ax
CHECK_OFF_72:
cmp Off_72_flag,1
je Off_72_Fill
jmp CHECK_RND_SIG
Off_72_Fill:
lea DI,Temp_blk[74h]
mov Word ptr [DI],4638h
mov Word ptr [DI-2],2391h
CHECK_RND_SIG:
cmp Byte Ptr Rnd_sig_flag,1
je RANDOM_SIG
; this set of constants create valid key
; for ver 3.0, 4.0 and 5.0
; future versions also should accept keys
; with these constants in order to accept old authentic keys
lea DI,Temp_blk[150h]
mov Word Ptr [DI],0
mov Word Ptr [DI-2],3EFh
lea DI,Temp_blk[158h]
mov Word Ptr [DI],0FA98h
mov Word Ptr [DI-2],12FDh
lea DI,Temp_blk[76h]
and Byte Ptr [DI],0FEh ;clear bit 0
jmp CRC_N_PACK
RANDOM_SIG: cmp Z_fil_flag,1 ; if we filled key with 0
jne CHECK_SIG ; we must fill signatures' offsets
; with random values
call RANDOMIZE
cmp Off_72_flag,1
je NO_RANDOM_72
call RANDOM
lea DI,Temp_blk[74h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
NO_RANDOM_72:
call RANDOM
lea DI,Temp_blk[150h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
call RANDOM
lea DI,Temp_blk[158h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
CHECK_SIG:
lea DI,Temp_blk[150h] ; else we already have random sig
cmp Word Ptr [DI],0 ; but we need check them for
jnz NEXT_CHK1 ; "bad" ones
cmp Word Ptr [DI-2],0B37Bh ; "thanx" to Predator666
jz BAD_SIG ; he used this const in Term4 keygen
cmp Word Ptr [DI-2],0B47Bh ; "thanx" to Predator666
jz BAD_SIG ; he used this const in Term5 keygen
; and for sure this value will be
;checked in next ver of Terminate
NEXT_CHK1: cmp Word Ptr [DI],0FE6Fh ; these constants checked
jnz NEXT_CHK2 ; in Term 4,5
cmp Word Ptr [DI-2],0DF92h ;
jz BAD_SIG
NEXT_CHK2: cmp Word Ptr [DI],0C740h ; these constants checked
jnz SIG_OK ; in Term 4,5
cmp Word Ptr [DI-2],0AE99h ;
jz BAD_SIG
cmp Word Ptr [DI-2],0AE66h
jz BAD_SIG
SIG_OK: ; lets check offsets 156h,158h
lea DI,Temp_blk[158h]
cmp Word Ptr [DI],0FA98h ; we need really random signatures
jnz NEXT_CHK3 ; which dont checked in Terminate
cmp Word Ptr [DI-2],12FDh ; at all
jz BAD_SIG1
NEXT_CHK3:
cmp Word Ptr [DI],0E14Dh
jnz NEXT_CHK4
cmp Word Ptr [DI-2],2E1Ah
jz BAD_SIG1
NEXT_CHK4:
cmp Off_72_flag,1
je CRC_N_PACK
lea DI,Temp_blk[74h]
cmp Word Ptr [DI],04638h ; we need really random signatures
jnz CRC_N_PACK ; which dont checked in Terminate
cmp Word Ptr [DI-2],2391h ; at all
jz BAD_SIG2
BAD_SIG: call RANDOMIZE
call RANDOM
lea DI,Temp_blk[150h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
jmp RANDOM_SIG ; check again
BAD_SIG1: call RANDOMIZE
call RANDOM
lea DI,Temp_blk[158h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
jmp RANDOM_SIG ; check again
BAD_SIG2: call RANDOMIZE
call RANDOM
lea DI,Temp_blk[74h]
mov Word Ptr [DI],AX
call RANDOM
mov Word Ptr [DI-2],AX
jmp RANDOM_SIG ; check again
CRC_N_PACK:
push Crc_ax
push Crc_dx
or Word Ptr Crc_ax,0FFFFh
or Word Ptr Crc_dx,0FFFFh
lea DI,Temp_blk[5Bh]
mov Counter,5Bh
call CRC_CALC ;calc crc of unpacked blk4
STORE_CRC ; from 5bh to 15eh
pop Crc_dx
pop Crc_ax
test DX,8000h ; if 32 bit value in dx:ax <0
je SIGN_PLUS ; then change sign
not DX
neg AX
SIGN_PLUS: cmp DX,0 ; this checks need because off:
; if dx:ax<0 and abs val of dx:ax<10
; after we change it sign
; and sub 10 we again get value <0
; example: CRC of unp block 4 is dx:ax=-9
; we change sign and get dx:ax=9
; lets dont check abs value and simply
; sub 10, we get dx:ax=-1 and store it to blk5
; then when Terminate begin to check key
; it take CRC of unpacked blk4 and
; change sign and get dx:ax=9
; it also take values from block5 and
; change their sign too: =1
; Term try to add 10 to CRC of blk4 and get 19
; not = 1
; Term try to sub 10 and get -1
; this also not = 1 => bad key
; so we must look for abs value of CRC
; of unpacked block4 and add 10 if it<10
jne MINUS_10
cmp AX,0Ah
ja MINUS_10
add AX,0Ah
jmp PAK
MINUS_10: ; dx:ax - 0ah , where 0ah in cx:bx
xor CX,CX
mov BX,0Ah
sub AX,BX
sbb DX,CX
PAK: push AX
push DX
mov CX,161h-5Ah ; encrypt reginfo 3 times
lea DI, Temp_blk[5Bh] ; in back order
mov Temp_ax,904h
mov Temp_dx,33EEh
call ED_XOR
mov CX,161h-5Ah
lea DI, Temp_blk[5Bh]
mov Temp_ax,325Ch
mov Temp_dx,0
call ED_XOR
mov CX,161h-5Ah
lea DI, Temp_blk[5Bh]
mov Temp_ax,7
mov Temp_dx,0
call ED_XOR
; simple xor with 0ffh
mov CX,161h-5Ah
lea DI, Temp_blk[5Bh]
LOOP6:
xor Byte Ptr DS:[DI],0FFh
inc DI
loop LOOP6
pop DX
pop AX
mov Bl5_ax,AX ; save values for future store
mov Bl5_dx,DX ; in block 5
ret
MAKE_BLK4 Endp
;-------------------------------- encrypt/decrypt ------------------------
ED_XOR Proc
; input: di must points to offset of place to encrypt/decrypt
; cx - number of bytes to encrypt/decrypt
; kills dx,ax,bx,cx,di
; this proc taken from Terminate, modified a little
mov Counter,CX
LOOP5:
; call CALC_XOR_MASK
mov AX,Temp_ax
mov BX,Temp_dx
mov CX, AX
mul Word Ptr Mul_val
; shl CX, 1
; shl CX, 1
; shl CX, 1
shl CX, 3
add CH, CL
add DX, CX
add DX, BX
shl BX, 1
shl BX, 1
add DX, BX
add DH, BL
mov CL, 5
shl BX, CL
add DH, BL
add AX, 1
adc DX, 0
mov Word Ptr Temp_ax,AX
mov Word Ptr Temp_dx,DX
xor AX, AX
xchg AX, DX
mov BX,X_val
div BX
xor Byte Ptr DS:[DI],DL
inc DI
dec Counter
jnz LOOP5
ret
ED_XOR Endp
;-------------------------- Convert ASCII to 32 bit --------------------
CONVERT proc
; kills ax ,bx
BEGIN:
xor BX,BX
xor ah,ah
mov counter1,4
LOOP10:
inc di
mov al, byte ptr ES:[DI]
cmp al,'0'
jb RET__
cmp al,'9'
ja CHECK_CAPS
sub al,'0'
DEC_C: shl bx,4 ; convert and dec counter
add bx,ax
dec counter1
jz SAVE_
jmp LOOP10
CHECK_CAPS: cmp al,'A'
jb RET__
cmp al,'F'
ja CHECK_LOW
sub al,37h
jmp DEC_C
CHECK_LOW:
cmp al,'a'
jb RET__
cmp al,'f'
ja RET__
sub al,57h
jmp DEC_C
SAVE_:
cmp word ptr Off_152_Flag,0
jnz SECOND_WORD
mov Off_152_word,BX
mov Off_152_Flag,1
jmp BEGIN
SECOND_WORD: mov Off_154_word,BX
RET__:
ret
CONVERT Endp
;--------------------------- STACK ------------------------------------
STACK_SEG Segment Para Stack 'STACK'
Db 256 Dup ('S')
STACK_SEG Ends
;------------------------------ DATA ----------------------------------
.Data
;-------------------------------- constants -----------------------------
Bb Equ 0FEh
Blk_len Equ 162h ; keyfile block's length
Num_blk Equ 0Bh ; number of block in kf
X_val Equ 100h ; constant, used in calc xor mask
;-------------------------------- common variables ----------------------
Mul_val Dw 8405h ; constant, used in calc xor mask
Counter Dw 0
Counter1 Db 0
Temp_byte Db 0
Rnd_word1 Dw 1234h ; "seed" for pseudo-random number generator
Rnd_word2 Dw 5678h
;----------------------- flags, affected by command line switches --------
Z_fil_flag Db 0 ; if =1 then blocks of key will be filled with 0
; bytes before calc crc, write reginfo, pak, etc.
; else they will be filled with random bytes
; but with check for "bad" bytes in certain blocks
Rnd_sig_flag Db 0 ; if =1 then random signatures will be used
Fixed_len_flag Db 0 ; if =1 then length of key will be = 3894
; else 3894+rnd_len,where rnd_len is value in range 1-105
; if =2 then length of key will be = 3905
No_hdr_flag Db 0 ; if=1 then dont write key header in block 1
No_crc_flag Db 0 ; if=1 then dont store CRC in blocks 6-10
Off_72_flag db 0 ; if =1 then values 2391h and 4638h will be written
; at offsets 72h and 74h in block 4
Off_152_Word dw 0 ; these words will be written at off 152
Off_154_Word dw 0 ; and 154 in block 4 if Off_152_Flag not = 0
Off_152_Flag db 0
Temp_ax Dw 0 ; temp vars
Temp_dx Dw 0
Bl5_ax Dw 0 ; values , calculated from CRC of block4
Bl5_dx Dw 0 ; and written to block 5
;-------------------------------- text data ------------------------------
Nl Db 0Dh,0Ah,'$' ; next line
Start_msg Db 0Dh,0Ah,Bb,' TERMINATE 3,4,5+ Advanced Key Generator ',BB,0dh,0ah
Db 43 Dup ('€'),0dh,0ah
Db 'Coded by iNT_03h in hot summer of 1998.',0dh,0ah,0dh,0ah,'$'
Start_msg1 Db 'Use: term_akg /? to get list of advanced options.',0dh,0ah,0dh,0ah
Db 'Enter info needed for create key',0dh,0ah,'$'
Name_ Db 0Dh,0Ah,0Dh,0Ah,Bb,' First and last name:$'
Addr_ask Db 0Dh,0Ah,0Dh,0Ah,Bb,' Addres:$'
City_ask Db 0Dh,0Ah,0Dh,0Ah,Bb,' City:$'
Country_ask Db 0Dh,0Ah,0Dh,0Ah,Bb,' Country:$'
Error_1 Db 0Dh,0Ah,Bb,' Could not create Terminat.key !!!$'
Error_2 Db 0Dh,0Ah,Bb,' Could not write Terminat.key !!!$'
Help Db 0Dh,0Ah,Bb,' Command line syntax: term_akg [option1][option2][...]',0Dh,0Ah,0Dh,0Ah
Db ' options:',0dh,0ah
Db ' ? - get this help',0dh,0ah
Db ' s - use random, but valid "magic" values in block 4 ( else use constant',0dh,0ah
Db ' "magic" values, which provide valid key for Terminate 3,4,5+ )',0dh,0ah
db ' o - write 2391h,4638h to offset 72h,74h in block 4 ( else random values )',0dh,0ah
db ' i - enter value to put at offset 152h,154h ( else fill with 0 ),',0dh,0ah
db ' e.g. i00ef3445 - 00efh will be written to offset 152h, 3445 - to offset 154h.',0dh,0ah
Db ' z - fill unimportant part of key with 0 ( else with random bytes )',0dh,0ah
Db ' l - fixed length (3894 bytes) of key ( else add random length block )',0dh,0ah
Db ' a - fixed length (3905 bytes) of key ( else add random length block )',0dh,0ah
Db ' c - dont store CRC in blocks 6-10',0dh,0ah
Db ' r - dont write key header in block 1',0dh,0ah,0dh,0ah
Db Bb,' Use these advanced settings only if you know what are you doing. Default',0dh,0ah
Db ' settings will create 100% valid key for Terminate 3,4,5 and all future',0dh,0ah
Db ' versions too if encryption remains same.$'
Done_msg Db 0Dh,0Ah,0Dh,0Ah,Bb,' Done. Key succesfully created.$'
Bad_names Db 13,'Peter Thomsen',18,'Terry Rossid / BAI',10,'Tom Nelson',3,'N/A'
Db 15,'TIMUCIN KIZILAY',8,'JIKO A.S',17,'Heiner Marczewski'
Db 14,'Joshua Schultz',14,'John McCormick',17,'Stephen B. Browne'
Db 8,'R NATHAN',14,'Angela G Dubin',16,'Per Angelo Camia'
Db 19,'William R. Rininger',34,'Velibor Cagalj / Zeit.Systeme GmbH'
Db 16,'Louvier Bertrand',11,'Jenny Hines',10,'Gilad Avni'
Db 24,'mic-mega industries gmbh',30,'Marcus Pullen / Computer Buyer'
Db 20,'Herr Erland Lorenzen' ;bad city
Bad_name_msg Db 7,0Dh,0Ah,0Dh,0Ah,'*** You''ve entered name and/or city that TERMINATE won''t accept ***',0dh,0ah
Db Bb,' Press any key to reenter reg info...$'
;--------------------------------- reg info ------------------------------
Nam Db 51
Nam_len Db 52 Dup (0)
Addres Db 51
Addres_len Db 52 Dup (0)
City Db 51
City_len Db 52 Dup (0)
Country Db 51
Country_len Db 52 Dup (0)
;--------------------------------- key specific info --------------------
Keyfile Db 'terminat.key',0
Handle Dw 0
Key_header1 Db 0Dh,1Bh,5Bh,32h,4Ah,0Dh,'Terminat personal keyfile',0dh,0ah
Key_header2 Db 0Dh,0Ah,7,7,1Ah
Temp_blk Db 162h Dup(0)
Crc_ax Dw 0FFFFh
Crc_dx Dw 0FFFFh
; table, used for calculate CRC, I took it from terminat.exe. I used my
; own C programm to convert binary file to what you see below.
Crc_table Db 0,0,0,0,96h,30h,7,77h,2Ch,61h,0Eh,0EEh,0BAh,51h,9,99h,19h,0C4h
Db 6Dh,7,8Fh,0F4h,6Ah,70h,35h,0A5h,63h,0E9h,0A3h,95h,64h,9Eh,32h,88h,0DBh
Db 0Eh,0A4h,0B8h,0DCh,79h,1Eh,0E9h,0D5h,0E0h,88h,0D9h,0D2h,97h,2Bh,4Ch
Db 0B6h,9,0BDh,7Ch,0B1h,7Eh,7,2Dh,0B8h,0E7h,91h,1Dh,0BFh,90h,64h,10h,0B7h
Db 1Dh,0F2h,20h,0B0h,6Ah,48h,71h,0B9h,0F3h,0DEh,41h,0BEh,84h,7Dh,0D4h,0DAh
Db 1Ah,0EBh,0E4h,0DDh,6Dh,51h,0B5h,0D4h,0F4h,0C7h,85h,0D3h,83h,56h,98h,6Ch
Db 13h,0C0h,0A8h,6Bh,64h,7Ah,0F9h,62h,0FDh,0ECh,0C9h,65h,8Ah,4Fh,5Ch,1
Db 14h,0D9h,6Ch,6,63h,63h,3Dh,0Fh,0FAh,0F5h,0Dh,8,8Dh,0C8h,20h,6Eh,3Bh
Db 5Eh,10h,69h,4Ch,0E4h,41h,60h,0D5h,72h,71h,67h,0A2h,0D1h,0E4h,3,3Ch,47h
Db 0D4h,4,4Bh,0FDh,85h,0Dh,0D2h,6Bh,0B5h,0Ah,0A5h,0FAh,0A8h,0B5h,35h
Db 6Ch,98h,0B2h,42h,0D6h,0C9h,0BBh,0DBh,40h,0F9h,0BCh,0ACh,0E3h,6Ch,0D8h
Db 32h,75h,5Ch,0DFh,45h,0CFh,0Dh,0D6h,0DCh,59h,3Dh,0D1h,0ABh,0ACh,30h,0D9h
Db 26h,3Ah,0,0DEh,51h,80h,51h,0D7h,0C8h,16h,61h,0D0h,0BFh,0B5h,0F4h,0B4h
Db 21h,23h,0C4h,0B3h,56h,99h,95h,0BAh,0CFh,0Fh,0A5h,0BDh,0B8h,9Eh,0B8h
Db 2,28h,8,88h,5,5Fh,0B2h,0D9h,0Ch,0C6h,24h,0E9h,0Bh,0B1h,87h,7Ch,6Fh
Db 2Fh,11h,4Ch,68h,58h,0ABh,1Dh,61h,0C1h,3Dh,2Dh,66h,0B6h,90h,41h,0DCh,76h
Db 6,71h,0DBh,1,0BCh,20h,0D2h,98h,2Ah,10h,0D5h,0EFh,89h,85h,0B1h,71h,1Fh
Db 0B5h,0B6h,6,0A5h,0E4h,0BFh,9Fh,33h,0D4h,0B8h,0E8h,0A2h,0C9h,7,78h,34h
Db 0F9h,0,0Fh,8Eh,0A8h,9,96h,18h,98h,0Eh,0E1h,0BBh,0Dh,6Ah,7Fh,2Dh
Db 3Dh,6Dh,8,97h,6Ch,64h,91h,1,5Ch,63h,0E6h,0F4h,51h,6Bh,6Bh,62h,61h,6Ch
Db 1Ch,0D8h,30h,65h,85h,4Eh,0,62h,0F2h,0EDh,95h,6,6Ch,7Bh,0A5h,1,1Bh
Db 0C1h,0F4h,8,82h,57h,0C4h,0Fh,0F5h,0C6h,0D9h,0B0h,65h,50h,0E9h,0B7h
Db 12h,0EAh,0B8h,0BEh,8Bh,7Ch,88h,0B9h,0FCh,0DFh,1Dh,0DDh,62h,49h,2Dh,0DAh
Db 15h,0F3h,7Ch,0D3h,8Ch,65h,4Ch,0D4h,0FBh,58h,61h,0B2h,4Dh,0CEh,51h,0B5h
Db 3Ah,74h,0,0BCh,0A3h,0E2h,30h,0BBh,0D4h,41h,0A5h,0DFh,4Ah,0D7h,95h,0D8h
Db 3Dh,6Dh,0C4h,0D1h,0A4h,0FBh,0F4h,0D6h,0D3h,6Ah,0E9h,69h,43h,0FCh,0D9h
Db 6Eh,34h,46h,88h,67h,0ADh,0D0h,0B8h,60h,0DAh,73h,2Dh,4,44h,0E5h,1Dh,3
Db 33h,5Fh,4Ch,0Ah,0AAh,0C9h,7Ch,0Dh,0DDh,3Ch,71h,5,50h,0AAh,41h,2,27h
Db 10h,10h,0Bh,0BEh,86h,20h,0Ch,0C9h,25h,0B5h,68h,57h,0B3h,85h,6Fh,20h
Db 9,0D4h,66h,0B9h,9Fh,0E4h,61h,0CEh,0Eh,0F9h,0DEh,5Eh,98h,0C9h,0D9h,29h
Db 22h,98h,0D0h,0B0h,0B4h,0A8h,0D7h,0C7h,17h,3Dh,0B3h,59h,81h,0Dh,0B4h
Db 2Eh,3Bh,5Ch,0BDh,0B7h,0ADh,6Ch,0BAh,0C0h,20h,83h,0B8h,0EDh,0B6h,0B3h
Db 0BFh,9Ah,0Ch,0E2h,0B6h,3,9Ah,0D2h,0B1h,74h,39h,47h,0D5h,0EAh,0AFh,77h
Db 0D2h,9Dh,15h,26h,0DBh,4,83h,16h,0DCh,73h,12h,0Bh,63h,0E3h,84h,3Bh,64h
Db 94h,3Eh,6Ah,6Dh,0Dh,0A8h,5Ah,6Ah,7Ah,0Bh,0CFh,0Eh,0E4h,9Dh,0FFh,9
Db 93h,27h,0AEh,0,0Ah,0B1h,9Eh,7,7Dh,44h,93h,0Fh,0F0h,0D2h,0A3h,8,87h
Db 68h,0F2h,1,1Eh,0FEh,0C2h,6,69h,5Dh,57h,62h,0F7h,0CBh,67h,65h,80h,71h
Db 36h,6Ch,19h,0E7h,6,6Bh,6Eh,76h,1Bh,0D4h,0FEh,0E0h,2Bh,0D3h,89h,5Ah,7Ah
Db 0DAh,10h,0CCh,4Ah,0DDh,67h,6Fh,0DFh,0B9h,0F9h,0F9h,0EFh,0BEh,8Eh,43h
Db 0BEh,0B7h,17h,0D5h,8Eh,0B0h,60h,0E8h,0A3h,0D6h,0D6h,7Eh,93h,0D1h,0A1h
Db 0C4h,0C2h,0D8h,38h,52h,0F2h,0DFh,4Fh,0F1h,67h,0BBh,0D1h,67h,57h,0BCh
Db 0A6h,0DDh,6,0B5h,3Fh,4Bh,36h,0B2h,48h,0DAh,2Bh,0Dh,0D8h,4Ch,1Bh,0Ah
Db 0AFh,0F6h,4Ah,3,36h,60h,7Ah,4,41h,0C3h,0EFh,60h,0DFh,55h,0DFh,67h,0A8h
Db 0EFh,8Eh,6Eh,31h,79h,0BEh,69h,46h,8Ch,0B3h,61h,0CBh,1Ah,83h,66h,0BCh
Db 0A0h,0D2h,6Fh,25h,36h,0E2h,68h,52h,95h,77h,0Ch,0CCh,3,47h,0Bh,0BBh
Db 0B9h,16h,2,22h,2Fh,26h,5,55h,0BEh,3Bh,0BAh,0C5h,28h,0Bh,0BDh,0B2h
Db 92h,5Ah,0B4h,2Bh,4,6Ah,0B3h,5Ch,0A7h,0FFh,0D7h,0C2h,31h,0CFh,0D0h,0B5h
Db 8Bh,9Eh,0D9h,2Ch,1Dh,0AEh,0DEh,5Bh,0B0h,0C2h,64h,9Bh,26h,0F2h,63h,0ECh
Db 9Ch,0A3h,6Ah,75h,0Ah,93h,6Dh,2,0A9h,6,9,9Ch,3Fh,36h,0Eh,0EBh,85h
Db 67h,7,72h,13h,57h,0,5,82h,4Ah,0BFh,95h,14h,7Ah,0B8h,0E2h,0AEh,2Bh
Db 0B1h,7Bh,38h,1Bh,0B6h,0Ch,9Bh,8Eh,0D2h,92h,0Dh,0BEh,0D5h,0E5h,0B7h
Db 0EFh,0DCh,7Ch,21h,0DFh,0DBh,0Bh,0D4h,0D2h,0D3h,86h,42h,0E2h,0D4h,0F1h
Db 0F8h,0B3h,0DDh,68h,6Eh,83h,0DAh,1Fh,0CDh,16h,0BEh,81h,5Bh,26h,0B9h,0F6h
Db 0E1h,77h,0B0h,6Fh,77h,47h,0B7h,18h,0E6h,5Ah,8,88h,70h,6Ah,0Fh,0FFh
Db 0CAh,3Bh,6,66h,5Ch,0Bh,1,11h,0FFh,9Eh,65h,8Fh,69h,0AEh,62h,0F8h,0D3h
Db 0FFh,6Bh,61h,45h,0CFh,6Ch,16h,78h,0E2h,0Ah,0A0h,0EEh,0D2h,0Dh,0D7h
Db 54h,83h,4,4Eh,0C2h,0B3h,3,39h,61h,26h,67h,0A7h,0F7h,16h,60h,0D0h,4Dh
Db 47h,69h,49h,0DBh,77h,6Eh,3Eh,4Ah,6Ah,0D1h,0AEh,0DCh,5Ah,0D6h,0D9h,66h
Db 0Bh,0DFh,40h,0F0h,3Bh,0D8h,37h,53h,0AEh,0BCh,0A9h,0C5h,9Eh,0BBh,0DEh
Db 7Fh,0CFh,0B2h,47h,0E9h,0FFh,0B5h,30h,1Ch,0F2h,0BDh,0BDh,8Ah,0C2h,0BAh
Db 0CAh,30h,93h,0B3h,53h,0A6h,0A3h,0B4h,24h,5,36h,0D0h,0BAh,93h,6,0D7h
Db 0CDh,29h,57h,0DEh,54h,0BFh,67h,0D9h,23h,2Eh,7Ah,66h,0B3h,0B8h,4Ah,61h
Db 0C4h,2,1Bh,68h,5Dh,94h,2Bh,6Fh,2Ah,37h,0BEh,0Bh,0B4h,0A1h,8Eh,0Ch
Db 0C3h,1Bh,0DFh,5,5Ah,8Dh,0EFh,2,2Dh
End START
;-------------------------------- cut --------------------------------------
As I said before if you think that info I get about original keys from
PREDATOR 666's keygen for Term 3 is cheat, use:
term_akg.exe c r
And my keygen will generate _valid_ key based only on my knowledge I got
from terminate.exe , it will be key without header and CRC stored in
block 6-10. But in this case we cannot be sure that this key will be valid
in all future ver of T, because developers can begin to check for key
header, or CRC stored in block 6-10...
More about keygen switches:
s - will put random values at offsets 14Eh,150h instead of values 3ef,0.
Of course, values 3ef,0 are very good and they are from authentic keys
I think, but I decided to make this switch in case developers will
change keyfile logic and just for fun. "Design your own key!!!"
o - will put 2391h,4638h to offset 72h,74h in block 4. This values doesn't
means much, they only used for set bytes x ( see 1615E3h-1615E6h -
4 bytes 01,x,01,y ) to 79h (instead of 84h) also only if offsets
156h,158h contains values 12FD, FA98.
i - let you put your values to offsets 152h,154h in decrypted block 4.
These values then will be xored ,converted to ASCII and stored in
terminat.exe at offsets 1615F1h-1615F9h. These values not checked
in Term 5, but who knows about future versions?
z - fill unimportant areas in keyfile with 0. Else this areas will be
filled with random values. Of course, filling blocks with 0 is not very
good idea, because future vers of T can check for it, but "D.y.o.k!!!"
l - length of keyfile will be 3894 bytes, i.e. length only really needed.
162h=384, 384*11=3894. Also not good idea to create such keys,
coz they can begin to check length of key.
a - length of kf will be 3905. I decided to make this switch coz when T
already registered and T search for keyfile in its directory, it
tries to open and read 3905 bytes from kf.( See IDA listing )
May be this is fake or trap (i.e. make your key 3905 bytes long , stupid
cracker, we will reject such keys in future ver of T :)), but who
knows.
I think we should create keys with as many random parametrs as
possible, i.e. random keys length, unimportant areas filled with
random values. Any fixed values crackers use in their keygens will
only creates problems with futures versions of Terminate, because
developers of T include check for those values in Terminate.
Of course, creating keys with random params can force developers to
begin to check for real params, i.e. params they use in their original
keys, but then we will able to create our keys absolutely like original
ones :)) infinite war. I think there is only one way for developers:
they must completely change protection scheme, keyfile logic (even if
good honest users which paid money for keys will have to get new ones).
Several things I forgot to tell about:
T uses next sub to calc how many days you have in your evaluation period:
97E8:71C8 Calc_remained_days_in_eval proc far ; CODE XREF: j_Calc_remained_da
... vars
97E8:71C8 push bp
97E8:71C9 mov bp, sp
97E8:71CB sub sp, 0Ah
97E8:71CE mov ax, CURRENT_DATE_word ; word_192F_1766
97E8:71D1 xor dx, dx
97E8:71D3 mov [bp+var_6], ax
97E8:71D6 mov [bp+var_4], dx
97E8:71D9 mov ax, DATE_OF_INSTALL_word ; word_192F_61C4
97E8:71DC xor dx, dx
97E8:71DE mov [bp+var_A], ax
97E8:71E1 mov [bp+var_8], dx
97E8:71E4 mov ax, [bp+var_6]
97E8:71E7 mov dx, [bp+var_4]
97E8:71EA sub ax, [bp+var_A]
97E8:71ED sbb dx, [bp+var_8]
97E8:71F0 mov [bp+var_6], ax
97E8:71F3 mov [bp+var_4], dx
97E8:71F6 cmp [bp+var_4], 0
97E8:71FA jl loc_87E8_7204
97E8:71FC jg loc_87E8_720E
97E8:71FE cmp [bp+var_6], 0
97E8:7202 jnb loc_87E8_720E
97E8:7204 loc_87E8_7204: ; CODE XREF: Calc_remained_days_in_eval+32j
97E8:7204 mov [bp+var_6], 16h
97E8:7209 mov [bp+var_4], 0
97E8:720E loc_87E8_720E: ; CODE XREF: Calc_remained_days_in_eval+34j
97E8:720E ; Calc_remained_days_in_eval+3Aj
97E8:720E mov ax, [bp+var_6]
97E8:7211 mov [bp+var_2], ax ; return number of days left in eval period
97E8:7214 mov ax, [bp+var_2] ; return them in ax
97E8:7217 mov sp, bp
97E8:7219 pop bp
97E8:721A retf
97E8:721A Calc_remained_days_in_eval endp
About files which needed by T:
- terminat.dat - contains encrypted "Unbranded version" msg
- terminat.reg - contains encrypted reginfo you entered when
Terminate was installing. Terminate needs this file not every time
you run it, but only if some conditions become true:
87A8:2D0B something_with_T_REG proc far ; CODE XREF: j_something_with_T_REGJ
87A8:2D0B ; validate_key+1E79p
87A8:2D0B var_206= byte ptr -206h
87A8:2D0B var_12A= byte ptr -12Ah
87A8:2D0B var_106= byte ptr -106h
87A8:2D0B var_2A= byte ptr -2Ah
87A8:2D0B var_6= byte ptr -6
87A8:2D0B var_1= byte ptr -1
87A8:2D0B arg_0= byte ptr 6
87A8:2D0B
87A8:2D0B push bp
87A8:2D0C mov bp, sp
87A8:2D0E sub sp, 206h
87A8:2D12 call @Randomize$qv ; Randomize
87A8:2D17 mov ax, 19h
87A8:2D1A push ax
87A8:2D1B call calc_xor_mask_1
87A8:2D20 cmp ax, 3 ; only if calc_xor_mask1 return 3
87A8:2D23 jz loc_77A8_2D28 ; T begins to do something with terminat.reg
87A8:2D25 jmp loc_77A8_2E7F ; else just return
loc_77A8_2D28: here T reads terminat.reg and encrypts it again or
something like that, I don't really care about this.
- terminat.dis -- this file is not requiered by Terminate, but it contains
encrypted list of authors of Terminate, or may be those ppl are not authors
hell knows...
PS. By the way, there is another posibility to get registered Terminate.
It possible to write not keygenerator, but reginfo generator, i.e.
user enter name,address,city,country, and reggen encrypts it like
T does and patch terminat.exe. After that we only need to put some
file to Terminate directory and rename it to terminat.key :))
PPS. Another way: rough patch ;-). I.e. we can change byte at offset
161504h in terminat.exe from BDh to 77h and patch:
87A8:24BB call @$bsub$qm6Stringt1 ; compare "ascii" crc from reginfo with just calculated
offset 69ac3h in terminat.exe - 87A8:24C0 jz loc_77A8_24C5 ;HERE WE NEED JMP
87A8:24C2 jmp loc_77A8_27F5 ; if we go there - we have unreg. T
and Terminate registered!!! But its not very good way, because
instead of our name,city,etc we'll get complete mess for some
reason, may be because of T use other mask for decrypt reginfo
when it registered? So reginfo decrypted incorrectly and even
if it would decrypted correctlly it doesn't contain our name,city,
etc. So this way is most rough and useless, but possible.
End of the solution.