貼一篇不完整的――從SemCAD1.4的暴破談FlexLM 7.2保護的破解 (10千字)

看雪資料發表於2002-06-06

軟接縫的弱點――從SemCAD1.4的暴破談FlexLM 7.2保護的破解
Passion拋磚引玉
2001年底
(感謝看雪提供的FlexLM 7.2的SDK和其中的SIG檔案)

早聽說過高手關於狗加密弱點的論述,說許多作者過於相信狗的保護能力,在程式的保護部分中沒有作過多的手腳,甚至只讀狗驗證一次即透過,狗的多種加密與保護本領沒有得到利用。遺憾的是運氣不好,這種“接縫”的弱點我偏偏沒有碰到過。不過最近在對付Flexible Licence Manager保護的軟體的時候,倒是領會了一點點這個“接縫”的弱點,這裡寫出來讓大夥兒“斧正斧正”,算是拋磚引玉,嘿嘿。^_^
Flexible Licence Manager的保護不同於加殼,後者是對軟體產品的可執行檔案進行處理,而前者提供的是原始碼的形式,需要開發者手工將加密保護程式碼加入產品中再重新編譯。這種原始碼保護比加殼在形式上更復雜,至少不存在對這種保護“脫殼”的說法。據我的一點點見識,FlexLM所保護的軟體大多是CAD等或者其他大型一點的工程軟體,像我初學的時候對付的Rational Rose 2000(當然,當時沒破出來,^_^)和最近看到的SemCAD便是這樣。
FlexLM同樣有其弱點。SDK中即使原始碼不公開,其加密介面也算是公開的,只是不同軟體中使用的加密種子和Features等引數不同,因此Licence檔案也就不通用,不是隨便生成一個冒牌貨就能矇混過關。
用FlexLM進行保護,需要在自己的原始碼中加入FlexLM帶的各種庫和標頭檔案,然後在需要保護的部分加保護程式碼,FlexLM 7.2的SDK文件中的例子程式是這樣的:

#include "lmpolicy.h"
LP_HANDLE *lp_handle;
      /*...*/
      if (lp_checkout(LPCODE, LM_RESTRICTIVE|LM_MANUAL_HEARTBEAT,
                      "myfeature","1.0", 1, "license.dat", &lp_handle))
      {
              fprintf(stderr, "Checkout failed: %s",
                                    lp_errstring(lp_handle));
              exit(-1);
      /* FlexLM校驗出錯 */
      }
/*
*    檢驗透過,執行程式。
*/
      /*...*/
/*
*    校驗完畢,釋放Licence佔用的資源。
*/
      lp_checkin(lp_handle);

既然正兒八經的文件裡頭是這麼指導的,那麼有幾個軟體的保護程式碼會和這裡的流程不同?倘若找到了呼叫lp_checkout的地方再人為修改掉返回值,那麼FlexLM的保護就算被暴力砍掉了,不管lp_checkout裡面演算法多複雜都沒用。

(IDA中載入FLEXLM7.2 SDK的SIG檔案,反SEMCad.exe,18M,費了半個上午、一箇中午加一個下午。)

反出來的程式碼中有這麼一段:

.text:006A19E4                push    offset a1_4    ; "1.4"
.text:006A19E9                push    offset aSemcad_core ; "SEMCAD_CORE"
.text:006A19EE                push    eax
.text:006A19EF                push    edx
.text:006A19F0                call    sub_69DCE0

.text:006A0210                push    offset a1_4    ; "1.4"
.text:006A0215                push    offset aSemcad_gui ; "SEMCAD_GUI"
.text:006A021A                push    eax
.text:006A021B                push    edx
.text:006A021C                call    sub_69DCE0

(這裡sub_69DCE0大概是初始化。暴露了Features是"SEMCAD_GUI"和"SEMCAD_CORE")

lp_checkout函式的一般呼叫形式是:

lp_checkout(LPCODE, policy, feature, version, 1, path, &lp)


正巧對應到程式中的這段程式碼:

.text:0069DDDB                push    esp
.text:0069DDDC                push    eax                // lp地址
.text:0069DDDD                push    edx          // Licence檔案路徑
.text:0069DDDE                push    1          // 就是那個1
.text:0069DDE0                push    ecx                // Feature版本地址
.text:0069DDE1                push    ebx                // Feature地址 
.text:0069DDE2                push    0A00h              // policy
.text:0069DDE7                push    offset off_1409940  // LPCODE地址
.text:0069DDEC                call    _lp_checkout


//.text:00841820處是_lp_checkout的地址。

.text:0069DDF1                add    esp, 20h
.text:0069DDF4                test    eax, eax
.text:0069DDF6                jnz    loc_69E618

再看看loc_69E618:嘿嘿!

  .text:0069E618 loc_69E618:                            ; CODE XREF: sub_69DCE0+116j
  .text:0069E618                lea    ecx, [ebp+var_94]
  .text:0069E61E                lea    eax, [ebp+var_84]
  .text:0069E624                push    eax
  .text:0069E625                push    offset aLicenseCheckFa ;
                "License check failed: "
這裡是多麼的明顯哇。

.text:0069ddec處呼叫_lp_checkout。

.data1:015DB960 aLicense_dat    db '\license.dat',0    ; DATA XREF: sub_69DCE0+60o
.data1:015DB960                                        ; sub_6A1940+45o


因此,要暴力解決SemCAD,只需要把.text:0069DDF4處的test eax,eax和jnz loc_69e618改為mov eax,0和三個nop即可。

_________________________________________________________________________________


如果要跟蹤出正確的Key和Seed以生成永久Licence檔案,可以從lp_checkout的引數處分析:

lp_checkout(LPCODE, policy, feature, version, 1, path, &lp)

#define LPCODE &_lpcode 所以第一個引數是一個叫_lpcode變數的地址。

static LPCODE_HANDLE _lpcode  = { &lp_code,
#if defined (WINNT) && defined(FLEXLM_DLL)
    VENDOR_NAME
#else
    0
#endif
    };
這裡定義了_lpcode變數,它是一個靜態的結構,其第一個屬性是一個叫lp_code變數的地址。

#if defined (WINNT) && defined(FLEXLM_DLL)
LM_CODE(lp_code, ENCRYPTION_SEED1, ENCRYPTION_SEED2, VENDOR_KEY1,
    VENDOR_KEY2, VENDOR_KEY3, VENDOR_KEY4, VENDOR_KEY5);
// 這裡其實也就是定義一個VENDERCODE型的變數lp_code,具體見下一個宏定義。
#else
static VENDORCODE lp_code;
#endif
// 到這裡為止,不管條件如何,總歸定義了一個全域性變數lp_code

#define LM_CODE(name, x, y, k1, k2, k3, k4, k5)  \
                    static VENDORCODE name = \
                        { VENDORCODE_6, \
                          { (x)^(k5), (y)^(k5) }, \
                          { (k1), (k2), (k3), (k4) }, \
                          {0}, \
                          {0}, \
                          LM_STRENGTH, \
                          0, \
                          FLEXLM_VERSION, \
                          FLEXLM_REVISION, \
                          FLEXLM_PATCH, \
                          LM_VER_BEHAVIOR, \
                          CRO_KEY1, \
                          CRO_KEY2 \
                          }

這裡常數VENDORCODE_6的值是4,因為有一句#define VENDORCODE_6    4

VENDORCODE結構定義如下:

#define VENDORCODE VENDORCODE6
// 這裡說明VENDORCODE其實就是VENDORCODE6型的結構

下面是核心VENDORCODE6結構的定義:

typedef struct vendorcode6 {
                short type;      /* Type of structure */
                unsigned long data[2];
                            /* 64-bit code 放兩個ENCRYPTION_SEED和VENDOR_KEY5異或後的數值*/
                unsigned long keys[4]; /* 放四個VENDOR_KEY*/
#define LM_PUBKEYS     3
#define LM_MAXPUBKEYSIZ 40
/*
*                pubkey is for both the public and private keys
*                The public key goes here when authenticating
*                the private key when generating licenses
*/
                int pubkeysize[LM_PUBKEYS];
                unsigned char pubkey[LM_PUBKEYS][LM_MAXPUBKEYSIZ];
                int pubkeyinfo1;
                int (*pubkey_fptr)();
                short flexlm_version;
                short flexlm_revision;
                char flexlm_patch[2];
#define LM_MAX_BEH_VER 4
                char behavior_ver[LM_MAX_BEH_VER + 1];
                unsigned long crokeys[2];
              } VENDORCODE6, *VENDORCODE_PTR;

// 以上定義了VENDORCODE型也就是VENDORCODE6的結構

(這裡defined裡頭是根據開發平臺上的條件定義的,被保護軟體採用的是何種define可根據軟體本身適應的平臺、是否使用了FLEXLM的DLL等來稍微確定一下。)

現在顆顆珠子終於串到一起來了。

從呼叫實現過程上來說,lp_checkout傳入的第一個引數是一個地址,根據這個地址找到一個指向的記憶體區域,該區域是LPCODE_HANDLE型結構,變數名為 _lpcode,它的第一個屬性還是一個地址,指向lp_code結構(第二個屬性不是一個不相干的數便是0,因此不用管了)。所以,將lp_checkout傳入的第一個引數進行二次定址便能找到lp_code結構,該結構的第一個部分是type,從第二個部分開始的24個位元組也就是6個DWord分別儲存了兩個ENCRYPTION_SEED和VENDOR_KEY5異或後的數值加上四個VENDOR_KEY。
由於變數是自左至右推入堆疊的,因此程式中最近的一個push便是LPCODE引數:

.text:0069DDE7                push    offset off_1409940
.text:0069DDEC                call    _lp_checkout

由此可知,地址1409940處是LPCODE_HANDLE結構,1409940處的值在本程式中是1552260,它是VENDORCODE6結構的地址。
值得注意的是,根據FlexLM SDK的宣告,該處是存放一個VENDORCODE6結構,但並沒規定呼叫_lp_checkout前此結構必須要填充必要的數值。事實上剛進入_lp_checkout的時候該記憶體區域全是0,VendorKey等數值是_lp_checkout內部才進行填充的,如下:

_lp_checkout內部:

.text:00841820                push    ebp
.text:00841821                mov    ebp, esp
.text:00841823                sub    esp, 0E4h
.text:00841829                mov    [ebp+var_68], 0
.text:00841830                mov    [ebp+var_C], 0
.text:00841837                mov    [ebp+var_10], 0
.text:0084183E                mov    eax, [ebp+arg_18]
.text:00841841                mov    dword ptr [eax], offset unk_157D6B8
.text:00841847                lea    ecx, [ebp+var_5C]
.text:0084184A                push    ecx
.text:0084184B                mov    edx, [ebp+arg_0]
.text:0084184E                mov    eax, [edx]
.text:00841850                push    eax
.text:00841851                push    0
.text:00841853                push    0
.text:00841855                call    _lc_new_job

這裡呼叫完_lc_new_job後,EAX所指的區域(也就是VendorCode結構所在地)才被填入兩個加密Seed和四個Key。其值如下:

5E51FD8:

04 00 00 00 A2 0A BD 51 84 0F BE CC FA FE 77 FF
32 46 AC C8 CF DF D0 35 F8 AD 1E 83

倒序後:
00000004 51BD0AA2 CCBE0F84 FAFE77FF C8AC4632 35D0DFCF 831EADF8

這裡剛好對應到:
static VENDORCODE name = \
                      { VENDORCODE_6, \            // 其實就是一個4。
               { (x)^(k5), (y)^(k5) }, \
              { (k1), (k2), (k3), (k4) }, \
                      ……
                      (下面的不管了)

所以可知:
VENDOR_KEY1 0xFAFE77FF
VENDOR_KEY2 0xC8AC4632
VENDOR_KEY3 0x35D0DFCF
VENDOR_KEY4 0x831EADF8
VENDOR_KEY5 0x0 //還不知道,^_^

寫到這裡,本人水平有限,在跟蹤下面的程式碼過程中始終找不到第五個Key的校驗處,也就沒法子完成這篇文章,請高手指教指教。

――――――――――――――――――――――――――――――――――――――


下面是另外一些有用的定義:

typedef struct _lp_handle {
    char feature[MAX_FEATURE_LEN + 1];
    char lickey[MAX_CRYPT_LEN + 1];
#ifdef LM_INTERNAL
    long last_failed_reconnect;
    long *recent_reconnects;
    int num_minutes;
    LM_HANDLE *job;
    int policy;
#endif /* LM_INTERNAL */
} LP_HANDLE, FAR * LP_HANDLE_PTR;


typedef struct _lpcode_handle {
    VENDORCODE * code;
    char *        vendor_name;
} LPCODE_HANDLE;

VENDORCODE vendorkeys[] = {        /* Possible keys for vendor daemons */
        { VENDORCODE_5,
        ENCRYPTION_SEED1^VENDOR_KEY5, ENCRYPTION_SEED2^VENDOR_KEY5,
          VENDOR_KEY1, VENDOR_KEY2, VENDOR_KEY3, VENDOR_KEY4,
          {0}, {0}, LM_STRENGTH,  0,
          FLEXLM_VERSION, FLEXLM_REVISION, FLEXLM_PATCH,
          LM_BEHAVIOR_CURRENT, {CRO_KEY1, CRO_KEY2}},
                  };

相關文章