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 { <-------------- !!! } X4:=876478.312; bl_5:=bl_4*X4-arg_bl4; { <-------------- !!! } X2:=bl_5; { x2 must be > 7823.466 } { <-------------- !!! } { therefore: (arg_bl4*X4-arg_bl5)*X4-arg_bl4>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 checke