标 题:贴一篇不完整的――从SemCAD1.4的暴破谈FlexLM 7.2保护的破解 (10千字)
发信人:Passion
时 间:2002-6-6 18:14:48
详细信息:

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

Copyright © 2000-2002 KanXue Studio All Rights Reserved.