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