翻譯FLEXlm9.2的破解教學四

看雪資料發表於2004-12-08

翻譯第四篇 On Software Reverse Engineering - 4
            Reverse Engineering, FLEXlm, IMSL
    因此,我們有了兩個處理:在cmath.exe中進行檢查和在lmcrypt.exe或makekey.exe.中產生金鑰(keygen)。它們都可以產生相同的正確的許可證程式碼,但這兩個處理不是完全相同的。我們已經在前面的章節對第一個處理進行了一定深度的分析,現在按順序列出重要的呼叫連結。

1.lc_new_job() ® l_n36_buf() ® l_x77_buf()
2.lc_new_job() ® lc_init() ® l_init() ® l_sg() ® l_key() ® l_zinit()
3.lc_set_attr() ® l_set_attr() ® l_set_license_path() ® l_flush_config() ® l_init_file() ®
 l_allfeat() ® l_parse_feature_line() ® oldkey() ® l_crypt_private() ® real_crypt() ®l_string_key()

4.lc_checkout() ® l_checkout() ® lm_start_real() ® l_good_lic_key() ® l_xorname()
5.lc_checkout() ® l_checkout() ® lm_start_real() ® l_good_lic_key() ® l_sg() ® l_n36_buff()
6.lc_checkout() ® l_checkout() ® lm_start_real() ® l_good_lic_key() ® l_crypt_private() ®
real_crypt() ® l_string_key()

    一個很感興趣的問題是:為什麼l_sg()在第二個連結呼叫l_key()而在第五個連結呼叫l_n36_buff()?檢查程式碼摘錄,我們發現答案在於LM_OPTFLAG_CUSTOM_KEY5和 L_UNIQ_KEY5_FUNC。後者由l_x77_buf()(也就是L_SET_KEY5_FUNC) 在第一個連結中設定,因此在兩個呼叫中,l_n36_buff將非空。那麼原因就是LM_OPT_FLAG_CUSTOM_KEY5了:它在呼叫lc_init()後進行開關轉換,這就是為什麼l_key() 在第二個連結中被呼叫。有趣的是,在現在的FLEXLM版本中,l_key()是一個無用的子程式(它在引入l_n36_buff()之前用於早期的版本)。此外,它完全沒有必要在初始化階段呼叫譯碼加密種子的l_sg();那本應該在檢查時進行的。 

lm_njob.c:
int lc_new_job(oldjob, l_new_job, vcode, newjobp)
{
... ...
(*L_NEW_JOB)(vendor_name, vcode, 0, 0, 0, &sign_level);
(*L_NEW_JOB)(0, 0, 0, 0, 0, 0);
if (!(ret = lc_init(oldjob, vendor_name, vcode, newjobp)))
{
(*newjobp)->options->flags |= LM_OPTFLAG_CUSTOM_KEY5;
... ...
}
return ret;
}
 
lm_ckout.c:
void l_sg(LM_HANDLE* job, char* vendor_id, VENDORCODE* key)
{
... ...
unsigned long x = 0x6f7330b8; /* v8.x */
if (( job->options->flags & LM_OPTFLAG_CUSTOM_KEY5) && L_UNIQ_KEY5_FUNC)
{
(*L_UNIQ_KEY5_FUNC)(job, vendor_id, key);
return;
}
l_key(vendor_id, &(key->keys[0]), keys, 4); /* Pre v6.1 style */
... ... /* 在VKEY5()中進行相同的與或操作(xor) */
}
 
lm_init.c:
void (*L_UNIQ_KEY5_FUNC)() = 0;
void L_SET_KEY5_FUNC( void (*f)())
{
if (!L_UNIQ_KEY5_FUNC) L_UNIQ_KEY5_FUNC = f;
}

   在檢查處理的同時,我們也在金鑰產生(keygen)處理中呼叫連結。我們將使用lmcrypt.exe用於分析,因為它比makekey.exe來得更直接 (它們都執行相同的工作)。
1.lmcrypt.c!main() ® lc_init() ® l_init() ® l_sg() ® l_key() ® l_zinit()
2.lmcrypt.c!main() ® dofilecrypt() ® dofpcrypt() ® lm_crstr.c!lc_cryptstr() ® parsefeaturelist() 
 ® l_parse_feature_line() ® oldkey() ® l_crypt_private() ® real_crypt() ® l_string_key()
3.lmcrypt.c!main() ® dofilecrypt() ® dofpcrypt() ® lm_crstr.c!lc_cryptstr() ® cryptfeaturelist() 
 ® docryptfeat() ® lc_crypt() = l_crypt_private() ® real_crypt() ® l_string_key()

    注意:對於Vendor和job的初始化,cmath.exe將呼叫lc_new_job(),(該函式)輪流呼叫lc_init();但是lmcrypt.exe將直接呼叫lc_init(),因為vendor金鑰、種子和名稱都已經包含於lmcrypt.exe中了(一起透過宏放到vendor結構中),因此僅需要初始化工作。在這兩個處理中,有對l_string_key()的兩個呼叫;在這兩種情況下,第一個返回不重要的 oldkey()號為21D5B6E8572E,僅第二個呼叫其實質。這兩個處理在呼叫l_string_key()上僅有細微的差別。基本上,對於checksum(總和檢查)比較,checkout(檢查)需要提供使用者許可證金鑰,但是keygen則不需要。但是,對於計算實際的雜亂資訊上則是相同的。

int idx = (*job->vendor) % XOR_SEEDS_ARRAY_SIZ; /* idx = V % 20 = 86 % 20 = 6 */
... ...
memset(y, 0, L_STRKEY_BLOCKSIZE); /* L_STRKEY_BLOCKSIZE = 8, in lmachdep.h */
length = (inputlen) / L_STRKEY_BLOCKSIZE;
XOR_SEEDS_INIT_ARRAY(xor_arr) /*在l_strkey.h中定義的替換表 */
... ... /* memcpy() 從輸入到newinput和其他充填(值)*/
p = newinput;
for (i = 0; i < length; i++)
{
XOR(p, y, y);/*在l_strkey.h中定義的與或(XOR)和L_MOVELONG */
if (i == 0)
{
if (!user_crypt_filter && !user_crypt_filter_gen
&& (job->flags & LM_FLAG_MAKE_OLD_KEY))
{
q = y; /*在l_privat.h中定義的SEEDS_XOR = mem_ptr2_bytes */
L_MOVELONG(code->data[0]
^((long)(job->SEEDS_XOR[xor_arr[idx][0]])<<0)
^((long)(job->SEEDS_XOR[xor_arr[idx][1]])<<8) 
^((long)(job->SEEDS_XOR[xor_arr[idx][2]])<<16)
^((long)(job->SEEDS_XOR[xor_arr[idx][3]])<<24), q)
L_MOVELONG(code->data[1]
^((long)(job->SEEDS_XOR[xor_arr[idx][0]])<<0)
^((long)(job->SEEDS_XOR[xor_arr[idx][1]])<<8) 
^((long)(job->SEEDS_XOR[xor_arr[idx][2]])<<16)
^((long)(job->SEEDS_XOR[xor_arr[idx][3]])<<24), q)
}
... ...
}
if (!(job->flags & LM_FLAG_MAKE_OLD_KEY) && !demo)
our_encrypt2(y);
else
our_encrypt(y); /* our_encrypt()並不涉及程式碼或工作*/
p += L_STRKEY_BLOCKSIZE;
}
if (len == L_SECLEN_SHORT) /* 在l_privat.h中,L_SECLEN_SHORT = 0x66D8B337 */
{
... ...
y[6] = y[7] = 0;
}
 
   因為它們共享了相同的程式碼,因此為了計算相同的雜亂資訊,傳入的引數必須相同。實際的追蹤結果為:

cmath.exe:
job = 00887630, input = 0012e170, inputlen = 0x16, code = 0012ee20, len = 66d8b337, license_key = 6d5c...
[00887630] - 00000066 f... 整型;
[00887634] - 0089008e .... char *mem_ptr2;
[00887638] - a06aa84e N.j. unsigned char mem_ptr2_bytes[12]; (12是視進位制)
[0088763C] - 00c3a047 G...
[00887640] - 00660000 ..f.
[00887644] - 00000000 ....
[00887648] - 00000000 ....
[0088764C] - 00000000 ....
[00887650] - 00000000 ....
[00887654] - 54414d43 CMAT
[00887658] - 00000048 H...
 
[0012E170] - ab370fd2 ..7. 輸入串用於雜亂資訊,取決於FEATURE行資訊
[0012E174] - 414d4300 .CMA
[0012E178] - 88054854 TH..
[0012E17C] - 6a000113 ...j
[0012E180] - c5876e61 an..
[0012E184] - 000073d0 .s.. 總長度= 0x16 = 22,在73結束
[0012E188] - 00000000 ....
 
[0012EE20] - 00000004 .... 整型;
[0012EE24] - 52ed15b8 ...R 52xxxxb8, xxxx 是隨機的,在每次執行是不同的
[0012EE28] - 75cf780f .x.u 75yyyy0f, yyyy 是隨機的,在每次執行是不同的
[0012EE2C] - 7c2adb6a j.*| VENDOR_KEY1
[0012EE30] - b927f5a9 ..'. VENDOR_KEY2
[0012EE34] - 9cf311f8 .... VENDOR_KEY3
[0012EE38] - 0dbf7621 !v.. VENDOR_KEY4
[0012EE3C] - 00020009 .... FLEXlm 版本(這裡是 9.2)
[0012EE40] - 39300020 .09
[0012EE44] - 0000302e .0..
[0012EE48] - 00000000 ....
 
lmcrypt.exe:
job = 008C49E8, input = 0012D8D4, inputlen = 0x16, code = 004D7B48, len = 66D8B337
0x008C49E8 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f...............
0x008C49F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x008C4A08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x008C4A18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
 
0x0012D8D4 d2 0f 37 ab 00 43 4d 41 54 48 05 88 13 01 00 6a Ò.7«.CMATH.....j
0x0012D8E4 61 6e 87 c5 d0 73 00 00 00 00 00 00 00 00 00 00 an.ÅÐs..........
0x0012D8F4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0012D904 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
 
0x004D7B48 04 00 00 00 b8 39 c1 52 0f 54 e3 75 6a db 2a 7c ....¸9ÁR.TãujÛ*|
0x004D7B58 a9 f5 27 b9 f8 11 f3 9c 21 76 bf 0d 09 00 02 00 ©õ'¹ø.ó.!v¿.....
0x004D7B68 20 00 30 38 2e 30 00 00 c3 80 f4 83 2c c0 1c 77 .08.0..Ã.ô.,À.w
0x004D7B78 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................

    注意:我們在這裡處理諸如6D5C01FD71C9的非-CRO的短金鑰,它由12個ASCII字元組成表示6個十六進位制位,這也就是為什麼我們在上面的程式碼中看到“y[6] = y[7] = 0;”。除了VENDORCODE和job結構之外,所有其它引數是相等的[9],且在這兩個結構中並非所有的組成部分都是重要的。很容易勾勒出在雜亂化(hashing?)輸入串中所參與的東西為:code->data[], code->keys[] 和job->mem_ptr2_bytes[]。
   我們立即複製在檢查(checkout)處理中揭示的四個vendor金鑰到lm_code.h中(並證實在記憶體資訊轉儲中),但是我們仍然缺少加密種子和神秘的VENDOR_LEY5。現在,更進一步察看n金鑰產生(keyge)處理,它在檢查(checkout)的code->data[]和job->mem_ptr2_bytes[](它的job結構是空的)中出現不同。那是為什麼呢?

#include "lmprikey.h"
#include "lmclient.h"
#include "lm_code.h"
#include "lmseeds.h"
... ...
/* 設定site_code.data = {ENCRYPTION_SEED1 ^ VENDOR_KEY5, 
ENCRYPTION_SEED2 ^ VENDOR_KEY5}
site_code.keys = {VENDOR_KEY1, VENDOR_KEY2, VENDOR_KEY3, VENDOR_KEY4} */
LM_CODE(site_code, ENCRYPTION_SEED1, ENCRYPTION_SEED2, VENDOR_KEY1,
VENDOR_KEY2, VENDOR_KEY3, VENDOR_KEY4, VENDOR_KEY5);
... ...
int main(int argc, char **argv)
{
... ...
/*設定site_code.data = {ENCRYPTION_SEED1, ENCRYPTION_SEED2} */
LM_CODE_GEN_INIT(&site_code);
if (lc_init((LM_HANDLE *)0, VENDOR_NAME, &site_code, &lm_job))
{
lc_perror(lm_job, "lc_init failed");
exit(-1);
}
... ...
/*呼叫連結dofilecrypt() -> dofpcrypt() -> lc_cryptstr() */
estat |= dofilecrypt(infilename, outfilename, &site_code);
return 0;
}
 
    這是lmcrypt.c的簡明原始碼及其自身說明。仔細注意到在lmclient.h中定義的兩個宏LM_CODE和LM_CODE_GEN_INIT。前者初始化site_code.data為與VENDOR_KEY5進行與或的加密種子(與omcode.c一致),但後者很快逆向它為原始的加密種子。我是否曾告訴你FLEXLM有很糟的程式碼型別?:)
    不管怎樣,原始的加密種子和零job->mem_ptr2_bytes值被用於金鑰產生(keygen),它不同於檢查(checkout)。很自然的事情是從檢查處理複製code->data到lmseeds.h作為加密種子1和2,並重新編譯lmcrypt.exe。但它並不工作,因為在表格52xxxxB8和75yyyy0F中,code->data是隨機的,而xxxx和yyyy在每次執行中都將改變。相同的是,對於job->mem_ptr2_bytes是真實的。我們推斷,加密種子必定被模糊並儲存在vendor軟體中的兩個地方:code->data[]和job->mem_ptr2_bytes[](它們應該是關係很近的一對),因為它提供給終端使用者而原始種子需要保護。相反,lmcrypt.exe僅對vendor可獲得,因此加密種子可以以一般形式出現。

相關文章