• 标 题:贴一篇不完整的——从SemCAD1.4的暴破谈FlexLM 7.2保护的破解 (10千字)
  • 作 者:Passion
  • 时 间:2002-6-6 18:14:48
  • 链 接:http://bbs.pediy.com

软接缝的弱点——从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}},
                  };