2009年12月8日星期二

C++模板使用介绍

1. 模板的概念。

我们已经学过重载(Overloading),对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overload)版本。

//函数1.

int max(int x,int y);
{return(x>y)?x:y ;}

//函数2.
float max( float x,float y){
return (x>y)? x:y ;}

//函数3.
double max(double x,double y)
{return (c>y)? x:y ;}

但如果在主函数中,我们分别定义了 char a,b; 那么在执行max(a,b);时 程序就会出错,因为我们没有定义char类型的重载版本。

现在,我们再重新审视上述的max()函数,它们都具有同样的功能,即求两个数的最大值,能否只写一套代码解决这个问题呢?这样就会避免因重载函数定义不 全面而带来的调用错误。为解决上述问题C++引入模板机制,模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

2.   函数模板的写法

函数模板的一般形式如下:

Template <class或者也可以用typename T>

返回类型 函数名(形参表)
{//
函数定义体 }

说明: template是一个声明模板的关键字,表示声明一个模板关键字class不能省略,如果类型形参多余一个 ,每个形参前都要加class <类型 形参表>可以包含基本数据类型可以包含类类型.

请看以下程序:

//Test.cpp

#include <iostream>

using std::cout;

using std::endl;

//声明一个函数模版,用来比较输入的两个相同数据类型的参数的大小,class也可以被typename代替,

//T可以被任何字母或者数字代替。

template <class T>

T min(T x,T y)

{ return(x<y)?x:y;}

void main( )

{

     int n1=2,n2=10;

     double d1=1.5,d2=5.6;

     cout<< "较小整数:"<<min(n1,n2)<<endl;

     cout<< "较小实数:"<<min(d1,d2)<<endl;

     system("PAUSE");

}

程序运行结果: 

 

程序分析:main()函数中定义了两个整型变量n1 , n2 两个双精度类型变量d1 , d2然后调用min( n1, n2); 即实例化函数模板T min(T x, T y)其中T为int型,求出n1,n2中的最小值.同理调用min(d1,d2)时,求出d1,d2中的最小值.

3. 类模板的写法

定义一个类模板:

Template < class或者也可以用typename T >
class
类名{
//类定义......
};

说明:其中,template是声明各模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个。

例如:定义一个类模板:

// ClassTemplate.h
#ifndef ClassTemplate_HH

#define ClassTemplate_HH

template<typename T1,typename T2>

class myClass{

private:

     T1 I;

     T2 J;

public:

     myClass(T1 a, T2 b);//Constructor

     void show();

};

//这是构造函数

//注意这些格式

template <typename T1,typename T2>

myClass<T1,T2>::myClass(T1 a,T2 b):I(a),J(b){}

//这是void show();

template <typename T1,typename T2>

void myClass<T1,T2>::show()

{

     cout<<"I="<<I<<", J="<<J<<endl;

}

类模板与模板类

原创  类模板与模板类

1.类模板与模板类的概念

什么是类模板 一个类模板(也称为类属类或类生成类)允许用户为类定义一种模式,使得类中的某些数据成员、默写成员函数的参数、某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。

  如果一个类中数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表着一类类。

类模板定义 定义一个类模板,一般有两方面的内容:

A.       首先要定义类,其格式为:

template <class T>

class foo

{

……

}

foo 为类名,在类定义体中,如采用通用数据类型的成员,函数参数的前面需加上T,其中通用类型T可以作为普通成员变量的类型,还可以作为conststatic成员变量以及成员函数的参数和返回类型之用。例如:

template<class T>

class Test{

private:

    T n;

    const T i;

    static T cnt;

public:

    Test():i(0){}

    Test(T k);

    ~Test(){}

    void print();

    T operator+(T x);

};

B.       在类定义体外定义成员函数时,若此成员函数中有模板参数存在,则除了需要和一般类的体外定义成员函数一样的定义外,还需在函数体外进行模板声明

例如

template<class T>

void Test<T>::print(){

    std::cout<<"n="<<n<<std::endl;

    std::cout<<"i="<<i<<std::endl;

    std::cout<<"cnt="<<cnt<<std::endl;

 

}

如果函数是以通用类型为返回类型,则要在函数名前的类名后缀上"<T>"。例如:

template<class T>

Test<T>::Test(T k):i(k){n=k;cnt++;}

template<class T>

T Test<T>::operator+(T x){

               return n + x;

               }

C.       在类定义体外初始化const成员和static成员变量的做法和普通类体外初始化const成员和static成员变量的做法基本上是一样的,唯一的区别是需在对模板进行声明,例如

template<class T>

int Test<T>::cnt=0;

template<class T>

Test<T>::Test(T k):i(k){n=k;cnt++;}

类模板的使用 类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名<实际的类型>

  模板类是类模板实例化后的一个产物。说个形象点的例子吧。我把类模板比作一个做饼干同的模子,而模板类就是用这个模子做出来的饼干,至于这个饼干是什么味道的就要看你自己在实例化时用的是什么材料了,你可以做巧克力饼干,也可以做豆沙饼干,这些饼干的除了材料不一样外,其他的东西都是一样的了。

2.类模板的派生

  可以从类模板派生出新的类,既可以派生类模板,也可以派生非模板类。派生方法:

⑴ 从类模板派生类模板可以从类模板派生出新的类模板,它的派生格式如下例所示:

template <class T>

class base

{

……

};

 

template <class T>

class derive:public base<T>

{

……

};

与一般的类派生定义相似,只是在指出它的基类时要缀上模板参数,即base<T>

⑵ 从类模板派生非模板类  可以从类模板派生出非模板类,在派生中,作为非模板类的基类,必须是类模板实例化后的模板类,并且在定义派生类前不需要模板声明语句:template<class>。例如:

template <class T>

class base

{

……

};

 

class derive:public base<int>

{

……

};

在定义derive类时,base已实例化成了int型的模板类。

 

2009年6月27日星期六

FLEXLM9.5该洗洗睡了吧---200水帖纪念

FLEXLM9.5该洗洗睡了吧---200水帖纪念

【郑重声明】:本文纯属技术交流,无其他目的。 请勿用于商业或其他非法用途,否则后果自负。转载需征得看雪论坛同意。
【软件名称】:Imaris 4.16版本,官方网站有DOWN。
【下载页面】:http://www1.skycn.com/soft/20059.html
【软件简介】:Flexlm9.5保护,医学用(汗,我不懂-:))
【调试环境】:XP、OLLYICE、PEID0.93、calcseed.exe、lmcryptgui.exe、SIG文件
不会写破文,发了199个水帖,一直潜水不敢再发。据说200帖就可以成为中级了,我汗---我这么菜水平的都成中级?细想一下,我想看雪上的级别资格更多的是给那些有所奉献的朋友吧,不完全代表水平,于是菜鸟我心释然。
论坛上有很多值得我尊敬的人:看雪、fly、一二三水(哈哈)、laoqian等,无私奉献技术,指导菜鸟提高。当然,我在学习FLEXLM的时候,请教时也曾遇到有人索要RMB的问题(至今心里忿然BS)。学CRACK仅仅是我的兴趣而非职业,是想了解别人的思想,因此绝不会靠CRACK去谋取一分钱,这也不是我学习的目的。
受大水写了第500帖纪念的启发,于是发第200水帖纪念,希望大锅们不要扁我哈。论坛上有很多水牛,发了那么多帖子,按说水平也该很高了,希望大家也能在整百帖的时候发帖纪念一下,总结一下自己的经验,共同提高!
转入正题:本文是学习CRACKZ的文章《FLEXlm latest information by CrackZ》后总结的。同样的软件,版本不同。

一、信息收集

1、-8,著名标志,许可证密码错误(0xFFFFFFF8 如果爆破的话,有用)
2、-5,著名标志,FEATURE名称错误
3、-2,许可证语法错误
4、验证逻辑:
如果许可证里面给的种子不对,做出的许可证即使过期了,也会提示-8错误,表示许可证密码无效。这说明是先进行种子参与的密码运算,接着进行密码验证;而后,才是进行日期检查。
如果给出的种子对了,而日期过期了,则会提示-10,表示FEATUER到期;也说明先进行密码检查,后进行日期检查。哈哈。
5、使用假的许可证,启动后出现提示-8提示,表示密码不正确
6、通过分析,比较容易发现VENDOR DAEMON是哪个可执行程序。
7、对于多进程情况,直接调试主干程序;如果调试创建进程的程序,则可能不中断,因为新进程在附加前已经完成了许可证检查
8、认真读看雪大锅的书,学习FLEXLM的基本知识。另外CRACKZ上也有不少好文章。

二、制作一个假的许可证FAKE.LIC

FEATURE feature_name vendor_name 1.000 permanent uncounted 123456654321\
HOSTID=ANY SIGN=123456789123
格式一定要正确。如果用lmcryptgui.exe检查许可证时提示格式错误,它会自动校正;将前面的错误信息删除,再检查一次,则会成功。

三、确定VEDNDOR NAME

目的:确定VENDOR NAME
方法:搜索字符串"DEMO",其前面的je跳转处会出现VENDOR NAME名称。(其实程序中多处出现,该步可略去)

006CE285 |. 05 0C030000 add eax, 30C
006CE28A |. 85C0 test eax, eax
006CE28C |. 74 2B je short 006CE2B9
确定VENDOR=bitplane
006CE28E |. B9 B8A28E00 mov ecx, 008EA2B8 ;
ASCII "demo"
006CE293 |. 85C9 test ecx, ecx
006CE295 |. 74 22 je short 006CE2B9
006CE297 |. 68 C0A28E00 push 008EA2C0 ;
/s2 = "demo"
006CE29C |. 8B95 6CFDFFFF mov edx, [ebp-294] ; |
006CE2A2 |. 81C2 0C030000 add edx, 30C ; |

四、定位_l_init()函数

函数目的:定位_l_init()函数可确定_l_sg函数,确定VENDOR NAME
方法:查找默认的种子0x12345678 & 0x87654321 可以确定_l_init() 函数

该函数调用时堆栈出现正确的VENDOR NAME;
006CE309 |. 51 push ecx ; |Arg2
006CE30A |. 8B95 6CFDFFFF mov edx, [ebp-294] ; |
006CE310 |. 52 push edx ;
|lc_init函数
006CE311 |. E8 453DFFFF call 006C205B ;
\调用_l_sg函数,F7跟进
006CE316 |. 83C4 0C add esp, 0C
006CE319 |. 81BD 84FDFFFF>cmp dword ptr [ebp-27C], 87654321 ;
默认的种子1,定位标志1
006CE323 |. 74 0C je short 006CE331
006CE325 |. 81BD 88FDFFFF>cmp dword ptr [ebp-278], 12345678 ;
默认的种子2,定位标志2
006CE32F |. 75 5D jnz short 006CE38E
006CE331 |> 8B85 6CFDFFFF mov eax, [ebp-294]
注意调用_l_sg函数完毕后不会填入正确的种子,因为在_l_sg函数中会提前跳转

五、定位_lc_checkout()函数以及对其的调用处

目的:确定FEATURE和产品版本号(不是FLEXLM的版本号)
方法:在字符串参考中查找'lm_ckout.c';总共也就出现几次,比较容易确定。一般是挨着的两个才是;另外有四个挨着的不是。
定位_lc_checkout()函数比较容易,可以执行到其返回,再F8,就可以确定主程序中对它的调用处。(直接查找可能有多处,运行一次找到)006BE4C0
/$ 55 push ebp
1、定位_lc_checkout函数
006BE4E8 |. 8990 FC030000 mov [eax+3FC], edx
006BE4EE |. 68 95000000 push 95 ;
/Arg3 = 00000095
006BE4F3 |. 68 8C9B8E00 push 008E9B8C ; |Arg2
= 008E9B8C ASCII "lm_ckout.c",确定标志1,
//堆栈和寄存器出现FEATURE
006BE4F8 |. 8B4D 08 mov ecx, [ebp+8] ; |
。。。。。
006BE543 |. E8 B3000000 call 006BE5FB ;
\堆栈出现产品版本和FEATUER名称
。。。。。
006BE5A6 |. E8 50000000 call 006BE5FB ;
\如果许可证密码错误,这里会间接调用_l_sg函数
(跟进发现有对l_valid_version 验证版本;lc_check_key 调用_l_sg函数等函数的调用)
(!!!注意:在这里初次不会调用该CALL。慢慢来,呵呵。我们在该函数里能得到FEATURE和版本号就足够了!)
。。。。。
006BE5DE |. 68 A6000000 push 0A6 ;
/Arg3 = 000000A6
006BE5E3 |. 68 989B8E00 push 008E9B98 ;
|Arg2 = 008E9B98 ASCII "lm_ckout.c 确定标志2
006BE5E8 |. 8B55 08 mov edx, [ebp+8] ; |
2、定位对其的调用
0041E785 . 51 push ecx
0041E786 . 53 push ebx
0041E787 . 52 push edx
0041E788 . E8 33FD2900 call 006BE4C0 ;
调用_l_checkout
0041E78D . 8BE8 mov ebp, eax ; |
0041E78F . 8B46 2C mov eax, [esi+2C] ; |
0041E792 . 53 push ebx ; |Arg2
0041E793 . 50 push eax ; |Arg1
0041E794 . E8 BDEC2900 call <_lc_hostid 009E:_l_hostid> ;
\堆栈出现产品版本号与FEATURE
0041E799 . 8B4E 08 mov ecx, [esi+8]
0041E79C . 83C4 24 add esp, 24
记录前面的版本号和FEATURE(有的软件可能有几个产品版本号)

六、修改假的许可证

为什么我们在前面不能再次调用_l_sg函数?这是因为FLEXLM的验证机制,如果FEATUER和产品号验证不正确,眼看要煮熟的鸭子_l_sg就直接飞了,就不会再断下来!
修改许可证,_l_sg函数就可以再次停下来了,这就相当于帅哥有钱,MM停下来;看来造假还是不好,来接近于真的:
FEATURE ImarisBase bitplane 4.1 permanent uncounted 123456654321\
HOSTID=ANY SIGN=123456789123

七、定位_l_sg函数及其返回

函数目的:_l_sg的意思是"签名vendor_key5",呵呵。其返回处是恢复加密种子的关键!
方法:搜索常量SEEDVAL,9.2版本的是6F7330B8;注意,有两个出现,其中一个是混淆函数_l_svk()里面的。可以在?_l_init函数里面通过F7跟进确定,也结合后面的固定结构
V7 glseed?=?0x788F71D2 seedval?=?0x7648B98E
V8 glseed?=?0x3CDE3EBF seedval?=?0x6F73330B8
V9 glseed?=?0x72346B53 seedval?=?0x6F7330B8
v10 glseed?=?0x5332322F seedval?=?0x6F7330B8
(seedval:用于确定_l_sg函数;glseed:可以用来确定vendor name,堆栈出现)
(这些值最早出现于WOODOOM网站,laoqian大锅的总结一文也提及了-惠及众生,爽)
可以执行到返回,然后用dd命令
1、定位函数
006C205B /$ 55 push ebp ; _l_sg函数
。。。。。
006C206B |. 8845 EF mov [ebp-11], al
006C206E |. C745 F4 B8307>mov dword ptr [ebp-C], 6F7330B8 ;
seedval 寻找L_SG,注意混淆函数
006C2075 |. C745 FC 00000>mov dword ptr [ebp-4], 0
。。。。。
006C21D7 |. 5D pop ebp ;
_l_sg函数返回

2、定位函数的返回:
006C02FF |. 8B45 08 mov eax, [ebp+8] ; |
006C0302 |. 50 push eax ; |Arg1
006C0303 |. E8 531D0000 call 006C205B ;
\调用_l_sg函数
006C0308 |. 83C4 0C add esp, 0C ;
返回到这里,该处下断,用dd [esp]和dd [esp+8]命令
006C030B |. 8B4D 08 mov ecx, [ebp+8]

八、追踪加密种子和许可证

方法:在调用_l_sg函数的返回处(后面一般是一个add…命令,就可以在add命令处下断,使用dd命令),用dd [esp] dd
[esp+8]命令查看内存中的内容。
dd [esp] 得到的是00000066。。。job[]结构内容
dd [esp+8] 得到的是00000004。。。data[]内容
如果是正确的许可证,与调用_l_sg函数的次数关系不大,除了_l_init函数不填充正确的job结构外,每一个FEATUER都要调用_l_sg函数一次;
其返回值将是正确的job和data结构数据;也就是说,每个FEATUER对应的job和data值不相等,但用它们算出的加密种子却一样。
1、追踪加密种子
在_l_sg函数的返回处,使用dd命令
006C0308 |. 83C4 0C add esp, 0C ;
返回到这里,该处下断,用dd [esp]和dd [esp+8]命令

dd [esp]
03A7C350 00000066
03A7C354 00000000
03A7C358 ******** job+08
03A7C35C ******** job+0c
03A7C360 ******** job+10
03A7C364 00000000

dd [esp+8]
0012F5CC 00000004
0012F5D0 ******** data[0]
0012F5D4 ******** data[1]
0012F5D8 18A011ED
0012F5DC 274BE197
0012F5E0 E26A21A8
0012F5E4 52580441
0012F5E8 00020009

用calcseed.exe计算加密种子
vendor=bitplane
enseed1=******* enseed2=******** 与VENDOR NAME的关系是仅仅与其第一个字母有关

vendor=Bitplane

追出的VENDOR NAME是小写:bitplane,算出的LIC正确;
而网上有DOWN的许可证,第一个字母是大写的VENDOR
NAME:Bitplane也正确。这里将VENDOR换成大、小写都没有关系,但用小写VENDOR计算的加密SEEDS。
2、做出许可证
用lmcryptgui.exe。具体方法参见laoqian大锅的文章。
用SDK也可以制作,只不过在lm_code.h中要添加一些信息,麻烦哦。我懒,如果你做好了,别忘了送我一份啊:)

后记:该文的方法对于7.2、8.0、9.2的版本都适用。7之前的版本应该也可以用该方法,因为FLEXLM的某些关键结构(哈哈)一直没有改变!这是通用的方法。
本想做个循序渐进册子,突然发现该方法其实对老版本的_l_init,_l_new_job等方法都包容了。
可以这样说,对于没有使用ECC等技术(需要PATCH,SIGN超过12位则使用了该技术,以后讨论)的情况,FLEXLM就像一张纸。
关键点:定位对_l_sg函数的调用返回处,逐步修改假许可证(格式正确)中的内容,就会在_l_sg函数处停下来恢复加密的种子。

感谢

CrackZ
Nolan Blender
tulipfan[CCG]
laoqian
看雪论坛上的朋友们!

附:一些常见问题的处理,希望大家补充:
1、FLEXLM追出了正确加密种子,7.2等SDK做不出正确的许可证?
如果能正确启动genlic32.exe,恭喜你找对了加密的种子,试试添加选项"advanced->Add Compatible License Key"
2、_l?_sg函数函数只调用一次,不来第二次?
CRACKZ的文章也曾提及,但没有说明原因和处理办法。常见处理:
A:许可证格式要正确
B:设置正确的环境变量,可在"我的电脑->属性"里面或者autoexec.bat里面设置
C:许可证里面逐步填入正确的VENDOR,FEATUER,产品版本号等
CRACKZ的另外一篇文章还提及了另外一种处理办法:终止DAEMON进程,在许可证里面加入UER_...信息,但我没有用它试验成功过。
3、如何让后台程序安息?
运行程序,会发现至少有三样不少:lmgrd.exe是一陪,vendor daemon是二陪,Cracker是"三陪",哈哈。
将许可证做成无任何限制的格式,前面两陪直接上床睡觉(进程不启动),晕吧。如果不知道什么是无任何限制的格式,赶快买看雪大锅的书看看(书店一般脱销,我托人才买到)。
4、有了正确的许可证,怎么找加密种子?
实在恭喜你,省事啊。VENDOR,FEATURE等等都不用找了。
一般情况了,有了正确的许可证,_l_sg函数会在每个FEATURE的验证后返回正确的值,用通用的种子恢复技术就可以了。
5、相互交流,才能共同提高!仅作技术交流,恳请不要用于非法用途!

2009年6月25日星期四

翻译FLEXlm9.2的破解教学六

  对于4个Vendor密钥,很明显它们是初始化时在l_n36_buf()中被模糊的,在l_xorname()中进行模糊逆向,l_xorname()未做别的仅进行了一些与或操作。我们都知道与或的特征:x ^ x = 0, x ^ 0 = x, x ^ 1 = ~x,因此进行两次相同的与或操作将相互取消。这个好特性将与或适用于编码和译码。如果我们的猜测对的话,那么l_n36_buf()将采用与 l_good_lic_key()相同的方式调用l_xorname()。因为l_n36_buf()驻留于lm_new.c,它由 lmnewgen.exe产生,因此直接检查lmnewgen.c更好。


#include "l_strkey.h"
... ...
int main(int argc, char **argv)
{
      ... ...
      strcpy(outfile, "lm_new.c");
      ... ...
/* 初始化工作,能报告错误并能退出 */
if (lc_init(0, vendor_name, &vendorkeys[0], &job))
      {  
fprintf(stderr, "lc_init failed: %s\n", lc_errstring(job));
         exit(1);
      }  
/* 通过sb_rngFIPS186Session()产生的随机数 */
l_genrand(job, lmseed1, lmseed2, lmseed3, NEWSEEDSIZ, newseeds);
      ... ... /* 把新种子转换为seed1 ? seed4,及其它*/
/* 把4个加密种子写进lmseeds.h */
if (!(ofp = fopen("lmseeds.h", "w")))
      {  
perror("Can't write lmseeds.h file, exiting");
         exit(1);
      }  
fprintf(ofp, "... ...", seed1, seed2, seed3, seed4);
      fclose(ofp);

/*现在是 lm_new.c */
if (!(ofp = fopen(outfile, "w")))
      {  
perror("Can't open output file, exiting");
         exit(1);
      }  
      ... ...
/* 确定缺省的和弱的种子被排除在外 */
if (!l_reasonable_seed(seed3) || !l_reasonable_seed(seed4) ||
         !l_reasonable_seed(lmseed1) || !l_reasonable_seed(lmseed2) ||
         !l_reasonable_seed(lmseed3))
      {  
         ... ...
         fprintf(stderr, "Existing.\n");
         exit(1);
      }  
      ... ...
      fputs("#include <time.h>\n", ofp);
      do_real(); /* 写lm_new.c 的主要内容*/
      fclose(ofp);
      return 0;
}

static void do_real()
{
      ... ... /* 产生随机的参数和函数名称 */
      while (!key5_uniqx)
      {  
key5_uniqx = our_rand(256) + (our_rand(256) << 8) +
                  (our_rand(256) << 16) + (our_rand(256) << 24);
      }  
      ... ... /* 产生随机的 key5_order[] */
for (i = 0; i < 200; i += 2)
         random_garbage();
      ... ... /* 更多的随机垃圾 */
for (counter=0; counter<keysize; counter++)/* 一般 keysize=1, 只有一个vendor */
      {  
         key5(&vendorkeys[counter]);  /* 模糊化vendorkeys->data[] */
         l_xorname(vname, &vendorkeys[counter]);/* 模糊化vendorkeys->keys[] */
/* 真正VENDORCODE的初始化 */
         do_ulong(vendorkeys[counter].data[0], "data[0]", counter);
         do_ulong(vendorkeys[counter].data[1], "data[1]", counter);
         do_ulong(vendorkeys[counter].keys[0], "keys[0]", counter);
         do_ulong(vendorkeys[counter].keys[1], "keys[1]", counter);
         do_ulong(vendorkeys[counter].keys[2], "keys[2]", counter);
         do_ulong(vendorkeys[counter].keys[3], "keys[3]", counter);
         ... ...
      }  
      l_puts_rand(ofp, fpVar, numvars); /* 以随机的顺序输出行 */
      fflush(ofp);
      uniqcode(); /* 输出lm_new.c!l_n36_buff()的源代码 */
      fflush(ofp);
      ... ...
}

static void do_ulong(unsigned long ul, char *varname, int counter)
{
      ... ...
randvarname(b1, "b1"); bnames[0] = b1; /* 产生混乱的参数名称 */
randvarname(b2, "b2"); bnames[1] = b2;
randvarname(b3, "b3"); bnames[2] = b3;
randvarname(b4, "b4"); bnames[3] = b4;

for (i = 0; i < 4; i++)
      {  
shift = i * 8;

         CLEARV; /* 把ul分成4 字节并且把它们指派为4个糊乱命名的参数 */
sprintf(vBuf, "static unsigned int %s = %d;\n",
bnames[i], (ul & (0xff << shift)) >> shift );
         fwrite(vBuf, sizeof(char), sizeof(vBuf), fpVar);

         CLEARF; /* 重新组装回4个参数,使v->varname = ul */
sprintf(fBuf, "\tif (%s == %d) v->%s += (%s << %d);\n",
               countervar, counter, varname, bnames[i], shift);
         fwrite(fBuf, sizeof(char), sizeof(fBuf), fpFunc);
      }  
}

static void uniqcode()
{
      ... ...
int idx = *vendor_name % XOR_SEEDS_ARRAY_SIZ; /* idx = V % 20 = 86 % 20 = 6 */
      XOR_SEEDS_INIT_ARRAY(xor_arr) /* l_strkey.h */

fprintf(ofp, "static void %s(job, vendor_id, key) \n\
            ... ...
unsigned long x = 0x%x; \n\
struct s_tmp {int i; char *cp; unsigned char a[12];} *t, t2; \n\
if (job) t = (struct s_tmp *)job; \n\
               else t = &t2; \n\
            ... ...", key5_fname, key5_uniqx); /* key5_fname 和key5_uniqx都是随机数*/
for(i = 0; i < SEEDS_XOR_NUM; i++) /* 解码 t->a[], i.e. job->mem_ptr2_bytes[] */
      {  
         unsigned char num;
         cpp[i] = cp;
sprintf(cp, "t->cp=(char *)(((long)t->cp) ^ (time(0) ^ ((0x%x << 16) + 0x%x)));\n\
t->a[%d] = (time(0) & 0xff) ^ 0x%x;\n", /* 运行时随机化 */
               our_rand(0xff), our_rand(0xff), i, our_rand(0xff));
cp += strlen(cp) + 1;
      }  
      l_puts_rand1(ofp, SEEDS_XOR_NUM, cpp); /* 以随机的顺序输出行*/

fprintf(ofp, "for (i = 0; i < %d; i++) \n\
            { \n\
               if (sig[i%%SIGSIZE] != vendor_id[i%%len]) \n\
                  sig[i%%SIGSIZE] ^= vendor_id[i%%len]; \n\
            } \n\
unsigned long y = ((((long)sig[0] << %d) \n\
| ((long)sig[1] << %d) \n\
   | ((long)sig[2] << %d) \n\
| ((long)sig[3] << %d)) \n\
^ ((long)(t->a[%d]) <<  0) \n\
^ ((long)(t->a[%d]) <<  8) \n\
^ ((long)(t->a[%d]) << 16) \n\
^ ((long)(t->a[%d]) << 24) \n\
^ x \n\
^ key->keys[0] \n\
^ key->keys[1]) & 0xffffffff; \n\
key->data[0] ^= y; \n\
key->data[1] ^= y; \n\
      ... ...", MAX_DAEMON_NAME, /* MAX_DAEMON_NAME = 10 在lmclient.h中定义 */
key5_order[0], key5_order[1], key5_order[2], key5_order[3],
xor_arr[idx][0], xor_arr[idx][1], xor_arr[idx][2], xor_arr[idx][3]));
}

static void key5(VENDORCODE *k) /* 模糊化加密种子, 即 k->data[] */
{
      ... ...
/* 和uniqcode()中相同的key5_uniqx, key5_order[], sig[]和MAX_DAEMON_NAME*/
for (i = 0; i < MAX_DAEMON_NAME; i++)
      {  
if (sig[i%SIGSIZE] != vname[i % len])
            sig[i%SIGSIZE] ^= vname[i % len];
      }  
y = ((((long)sig[0] << key5_order[0])
         | ((long)sig[1] << key5_order[1])
         | ((long)sig[2] << key5_order[2])
         | ((long)sig[3] << key5_order[3]))
         ^ key5_uniqx
         ^ k->keys[0]
         ^ k->keys[1]) & 0xffffffff;
      k->data[0] ^= y; /* k->data[0] = 加密种子SEED1 ^ y */
      k->data[1] ^= y; /* k->data[1] = 加密种子SEED2 ^ y */
}


  源码有点长,但是它的结构一旦被我们揭开就不再非常难懂。主要的过程在lmseeds.h中花费大量时间,并且lm_new.c主要是在 do_real()中产生的。在几个子程序的帮助下,do_real()本身是在制造l_n36_buf(),它把l_n36_buff()留给 uniqcode()。那么l_xorname()干什么用呢? 它虽然没有出现在l_n36_buf()中,但是在do_real()中出现了。因此这种说法是正确的:4个vendor key在不同的位置被同一个异或操作来进行加密和解密,它们分别是 do_real() / l_n36_buf() 和l_good_lic_key() 
  注意lm_new.c本身被高度模糊化:这里有大量完全无用的垃圾代码, 标识符是随机、无任何意义的, 真代码和垃圾代码看上去相似并且混在一起,真代码的行顺序被打乱……。很明显,它以这种方式设计的目的是用来迷惑读者,并且它成功了,因为理解C源码几乎是不可能的,更不用说反编译码了。这正是我们为什么要把注意力放在它的起源lmnewgen.c上的原因, 因为后者能给我们更多提示。例如,l_xorname()在do_real()中被调用,而不是l_n36_buf()。这意味着密钥的模糊化是在vendor site的内部进行的,并且加密后的密钥被链接到传送到终端用户的目标中,然后它们在l_good_lic_key()中被实时解码。这种方案是为了最小化真实密钥的暴露。
  但是,我们对加密种子(encryption seeds)比vendor key更感兴趣。回想一下,job->mem_ptr2_bytes[] 和code->data[] 在验证和密钥产生的过程中是不一样的。很可能 FLEXlm 也使用了异或进行种子加密,但是远不止这些,因为我们在运行时看到它是随机的。我们将逐步分析。
  恰好在do_real()->l_xorname()前面一行,这儿有一处对key5()的调用, 它在vendor那边对原有的加密种子进行模糊化 (在工作结构上没做任何事情). vendor key 的经验告诉我们它们一定在用户方的某个地方进行了反向模糊。这时从事这项工作的不再是 l_good_lic_key(), 而是l_n36_buff(). 它在lm_new.c中的源码是可读的,但我们仍然依然顷向于研究它的产生器 uniqcode()。不多久我们发现l_n36_buff()对一些和key5()中一样的参数进行了异或, 并加上额外的t->a[]。那么它又是什么东西呢?t->a[]正是job->mem_ptr2_bytes[]的另外一个名称. 怎样去解决那些额外的异或呢?让我们翻回去几页…,噢,在l_string_key()中宏L_MOVELONG()处,正好在散列(hashing)开始之前。
  现在我们看到种子加密/解密在两个步骤中完成, l_n36_buf()/l_n36_buff() 和 l_n36_buff() / l_string_key()。在相应函数中的xor代码相互镜像,从而保证清除掉所有的干扰。l_n36_buff()中的time(0)因子引入了运行时的随机性,它同时影响了我们在l_string_key()中看到的code->data[]和 job->mem_ptr2_bytes[] . 确实,与vendor keys相对而言,FLEXlm以一种更加模糊的方式隐藏了加密种子。我们现在知道了lm_new.c中两个名字神秘的函数的意义(事实上只有两个): l_n36_buf() ? 初始化VENDORCODE 、加密种子和密钥;l_n36_buff() ? 解开 l_n36_buf(),并进行第二步加密。
  我们需要强调模糊化只用于checkout过程。在keygen过程中lmcrypt.exe并不调用 l_n36_buf()、 l_n36_buff(),或者 l_good_lic_key(),并且L_MOVELONG对原始种子无任何影响,因为 job->mem_ptr2_bytes[]总是0。在checkout 的加密/解密过程中,xor操作涉及了众多的常数、变量和数组。它们常被放置在两个地方,一个用于编码,另一个用于解码。为了方便,下表对它们进行了概括。



  相关的对象  编码  解码
VENDOR_KEY  l_xorname(),
VENDORNAME,  do_real(), l_n36_buf()  l_good_lic_key()
ENCRYPTION_SEED,步骤1
  VENDORMAGIC_V7  do_real(),key5(), l_n36_buf()  uniqcode(), l_n36_buff()
ENCRYPTION_SEED,步骤2  x = key5_uniqx, 
key5_order[], 
sig[], MAX_DAEMON_NAME
idx, xor_arr  uniqcode(), l_n36_buff()  L_MOVELONG(), l_string_key()

  现在是我们从cmath.exe中找回真正的加密种子的时候了。真正的种子作为L_MOVELONG()的第一个参数被恢复。使用idx=6,和前面所述的置换表 xor_arr[],xor操作数包含 4 字节,,在job->mem_ptr2_bytes[]中被编址在7、3、5、11。经过汇编后是00A0A000.因此, ENCRYPTION_SEED1 =  code-> data[0] ^ 00A0A000 = 52ED15B8 ^ 00A0A000 = 524DB5B8,ENCRYPTION_SEED2 =  code-& gt;data[1] ^ 00A0A000 = 75CF780F ^ 00A0A000 = 756FD80F。
  在lmseeds.h中试用它们.呜呼! lmcrypt.exe产生了亲爱的6D5C01FD71C9。改变版本号为5.0,我们得到 3F23BE3056E4, 又是正确的鉴名档! 这无疑确定我们已经获得Visual Numerics的可信的加密种子 (encryption seeds)和vendor keys。 最终,我们能生成任何我们所想要的VNI license keys, 可以应用于其它的版本, 其它的features,其它的产品…只要FLEXLM没做重大的修改, 它应能够正常工作而不会有任何故障发生.

F:\flexlm>type license.dat
FEATURE CMATH VNI 5.0 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 5.0 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 5.5 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 7.1 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 8.3 permanent uncounted 0 HOSTID=ANY
FEATURE Hello VNI 2.9 permanent uncounted 0 HOSTID=ANY
FEATURE cRaCk VNI 4.0 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted HOSTID=ANY SIGN=0

E:\flexlm>utils\lmcrypt -i license.dat
FEATURE CMATH VNI 5.0 permanent uncounted 3F23BE3056E4 HOSTID=ANY
FEATURE CSTAT VNI 5.0 permanent uncounted 2C60CD4570B0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted 6D5C01FD71C9 HOSTID=ANY
FEATURE CSTAT VNI 5.5 permanent uncounted 369B56AC8B35 HOSTID=ANY
FEATURE CMATH VNI 7.1 permanent uncounted F218B30D7129 HOSTID=ANY
FEATURE CSTAT VNI 8.3 permanent uncounted CC5FA3C48B85 HOSTID=ANY
FEATURE Hello VNI 2.9 permanent uncounted 505E4E243D1B HOSTID=ANY
FEATURE cRaCk VNI 4.0 permanent uncounted 93D0E20E2D20 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted HOSTID=ANY \
        SIGN=B5E1542279DC

进一步的讨论

尽管我们已经对FLEXLM保护系统进行了完全的反向工程,这里仍有许多东西值得讨论. 最显著的一个是, VENDOR_KEY5在哪里?

(待续。。。。。。)

http://bbs.pediy.com/archive/index.php?t-32787.html

准备工作:

1. 用 w32dasm89 反汇编 vendor daemon 程序
按照下面的说明找到 "FLEXwrap" 前面的地址,
和相应的算法地址,为下一步设置断点做准备.

2. 假的格式正确的 license, 一定要有 SERVER 和 DAEMON 或者 VENDOR 这两行.

SERVER this_host ANY 2080
DAEMMON vendor_name

FEATURE feature_name vendor_name 1.000 permanent uncounted 123456654321 \
HOSTID=ANY

3. 常用工具:
calcseed.exe ---------- 用 job 和 data 计算种子,这种方法可以不用.
lmkg.exe (v4.x-11.x) -- 计算 vendor key 包括 CRO 和 TRL
lmcryptgui.exe ------- 用找到的种子和 vendor name 生成 lmcryptxxxx.exe 文件
相当于 FlexLM SDK 中的 lmcrypt.exe, 但是不适用带 ECC 的 license.
FlexLM SDK ------------ 用计算的种子和 vendor keys 修改 lm_code.h
方法按 laoqian 提供的.
softice --------------- 加载 vendor daemon 程序用.

开始工作:

1. 启动 softice, 用 Symbol Loader 加载 vendor daemon 程序 vendor?.exe
再用 lmtools 启动 server, 会在下面设置的断点出停下. 然后, .....

2. 寻找标志代码段 "FLEXwrap" 并且设置断点
在 "FLEXwrap" 之前的第一个 CALL xxxxxxx
前面的 :004ABDDE push ecx 出设置断点, 一定会停.
这时可设置内存断点 bpm ds:ecx+2 是 feature 名的内存地址

:004ABDC3 8B4DF4 mov ecx, dword ptr [ebp-0C]
:004ABDC6 33D2 xor edx, edx
:004ABDC8 8A9118030000 mov dl, byte ptr [ecx+00000318]
:004ABDCE 83FA04 cmp edx, 00000004
:004ABDD1 0F8451010000 je 004ABF28
:004ABDD7 8B450C mov eax, dword ptr [ebp+0C]
:004ABDDA 50 push eax
:004ABDDB 8B4DF4 mov ecx, dword ptr [ebp-0C] ;
:004ABDDE 51 push ecx ;设置断点,一定会停
:004ABDDF 8B15A83A5100 mov edx, dword ptr [00513AA8] ;
:004ABDE5 52 push edx ;bpm ds:ecx+2 是 feature 名
:004ABDE6 E821190000 call 004AD70C ;
:004ABDEB 83C40C add esp, 0000000C
:004ABDEE 85C0 test eax, eax
:004ABDF0 0F8532010000 jne 004ABF28

* Possible StringData Ref from Data Obj ->"FLEXwrap"
|
:004ABDF6 6814FF4F00 push 004FFF14
:004ABDFB 8B4508 mov eax, dword ptr [ebp+08]
:004ABDFE 50 push eax
:004ABDFF E88A7DF6FF Call 00413B8E
:004ABE04 83C408 add esp, 00000008
:004ABE07 894594 mov dword ptr [ebp-6C], eax
:004ABE0A 837D9400 cmp dword ptr [ebp-6C], 00000000
:004ABE0E 7414 je 004ABE24

* Possible StringData Ref from Data Obj ->"SAMwrap"
|
:004ABE10 6820FF4F00 push 004FFF20
:004ABE15 8B4D08 mov ecx, dword ptr [ebp+08]
:004ABE18 51 push ecx
:004ABE19 E8707DF6FF Call 00413B8E
:004ABE1E 83C408 add esp, 00000008
:004ABE21 894594 mov dword ptr [ebp-6C], eax
:004ABE24 833DD8FD4F0000 cmp dword ptr [004FFDD8], 00000000
:004ABE2B 7409 je 004ABE36
:004ABE2D 833DDCFD4F0000 cmp dword ptr [004FFDDC], 00000000

3. 寻找算法代码 83BD90FEFFFF08 是 FlexLM v10.x 以后的
83BD74FEFFFF08 是 FlexLM v9.x 以前的
个别版本需要自己验证,是这种代码 83BDxxFEFFFF08
在它的前面一定有一个 add ecx, 00000001
在地址 004CB131 处设置断点, 如果停了,大功告成,往下马上就可以找到种子
如果不停,还请 marstj 给出具体解决方案.

4. 接下来是找种子:
(i)在地址 004CB25A 设置断点,在 eax 直接得到种子 seed1,不需计算.
(ii)在地址 004CB422 设置断点,在 eax 直接得到种子 seed2,不需计算.

5. 注意代码中这个参数 3D4DA1D6 是清除种子的缺省值
:004CB367 mov dword ptr [ebp+FFFFFE70], 3D4DA1D6
在这里也可以直接得到种子 seed1 = [ebp+FFFFFE70]
:004CB52E mov dword ptr [ebp+FFFFFE6C], 3D4DA1D6
在这里也可以直接得到种子 seed2 = [ebp+FFFFFE6C]

=====================================================================
:004CB122 8B8D90FEFFFF mov ecx, dword ptr [ebp+FFFFFE90] ;算法的特征代码
:004CB128 83C101 add ecx, 00000001 ;83BDxxFEFFFF08
:004CB12B 898D90FEFFFF mov dword ptr [ebp+FFFFFE90], ecx ;注意:前面的 add
ecx, 00000001
:004CB131 83BD90FEFFFF08 cmp dword ptr [ebp+FFFFFE90], 00000008 ;是重要的判断标志
:004CB138 7D2B jge 004CB165 ;
:004CB13A 8B55F4 mov edx, dword ptr [ebp-0C]
:004CB13D 039590FEFFFF add edx, dword ptr [ebp+FFFFFE90]
:004CB143 33C0 xor eax, eax
:004CB145 8A02 mov al, byte ptr [edx]
:004CB147 8B8D90FEFFFF mov ecx, dword ptr [ebp+FFFFFE90]
:004CB14D 33D2 xor edx, edx
:004CB14F 8A919CEE5000 mov dl, byte ptr [ecx+0050EE9C]
:004CB155 33C2 xor eax, edx
:004CB157 8B8D90FEFFFF mov ecx, dword ptr [ebp+FFFFFE90]
:004CB15D 88819CEE5000 mov byte ptr [ecx+0050EE9C], al ;
:004CB163 EBBD jmp 004CB122
:004CB165 83BD94FEFFFF00 cmp dword ptr [ebp+FFFFFE94], 00000000
:004CB16C 0F85D6040000 jne 004CB648
:004CB172 83BDA4FEFFFF00 cmp dword ptr [ebp+FFFFFEA4], 00000000
:004CB179 0F85BE030000 jne 004CB53D
:004CB17F 837DFC00 cmp dword ptr [ebp-04], 00000000
:004CB183 0F85B4030000 jne 004CB53D
:004CB189 6A01 push 00000001
:004CB18B 8B5508 mov edx, dword ptr [ebp+08]
:004CB18E 52 push edx
:004CB18F E8C916F7FF call 0043C85D
:004CB194 83C408 add esp, 00000008
:004CB197 85C0 test eax, eax
:004CB199 0F849E030000 je 004CB53D
:004CB19F C78574FEFFFF9CEE5000 mov dword ptr [ebp+FFFFFE74], 0050EE9C
:004CB1A9 8B4508 mov eax, dword ptr [ebp+08]
:004CB1AC 8B889C010000 mov ecx, dword ptr [eax+0000019C]
:004CB1B2 8B91E81C0000 mov edx, dword ptr [ecx+00001CE8]
:004CB1B8 8B45F8 mov eax, dword ptr [ebp-08]
:004CB1BB C1E004 shl eax, 04
:004CB1BE 8B8C05B0FEFFFF mov ecx, dword ptr [ebp+eax-00000150]
:004CB1C5 33C0 xor eax, eax
:004CB1C7 8A840A38050000 mov al, byte ptr [edx+ecx+00000538]
:004CB1CE 8B4D14 mov ecx, dword ptr [ebp+14]
:004CB1D1 8B5104 mov edx, dword ptr [ecx+04]
:004CB1D4 33D0 xor edx, eax
:004CB1D6 8B4508 mov eax, dword ptr [ebp+08]
:004CB1D9 8B889C010000 mov ecx, dword ptr [eax+0000019C]
:004CB1DF 8B81E81C0000 mov eax, dword ptr [ecx+00001CE8]
:004CB1E5 8B4DF8 mov ecx, dword ptr [ebp-08]
:004CB1E8 C1E104 shl ecx, 04
:004CB1EB 8B8C0DB4FEFFFF mov ecx, dword ptr [ebp+ecx-0000014C]
:004CB1F2 33DB xor ebx, ebx
:004CB1F4 8A9C0838050000 mov bl, byte ptr [eax+ecx+00000538]
:004CB1FB C1E308 shl ebx, 08
:004CB1FE 33D3 xor edx, ebx
:004CB200 8B4508 mov eax, dword ptr [ebp+08]
:004CB203 8B889C010000 mov ecx, dword ptr [eax+0000019C]
:004CB209 8B81E81C0000 mov eax, dword ptr [ecx+00001CE8]
:004CB20F 8B4DF8 mov ecx, dword ptr [ebp-08]
:004CB212 C1E104 shl ecx, 04
:004CB215 8B8C0DB8FEFFFF mov ecx, dword ptr [ebp+ecx-00000148]
:004CB21C 33DB xor ebx, ebx
:004CB21E 8A9C0838050000 mov bl, byte ptr [eax+ecx+00000538]
:004CB225 C1E310 shl ebx, 10
:004CB228 33D3 xor edx, ebx
:004CB22A 8B4508 mov eax, dword ptr [ebp+08]
:004CB22D 8B889C010000 mov ecx, dword ptr [eax+0000019C]
:004CB233 8B81E81C0000 mov eax, dword ptr [ecx+00001CE8]
:004CB239 8B4DF8 mov ecx, dword ptr [ebp-08]
:004CB23C C1E104 shl ecx, 04
:004CB23F 8B8C0DBCFEFFFF mov ecx, dword ptr [ebp+ecx-00000144]
:004CB246 33DB xor ebx, ebx
:004CB248 8A9C0838050000 mov bl, byte ptr [eax+ecx+00000538]
:004CB24F C1E318 shl ebx, 18
:004CB252 33D3 xor edx, ebx
:004CB254 52 push edx
:004CB255 E86D050000 call 004CB7C7 ;
:004CB25A 83C404 add esp, 00000004 ;seed1 = eax 直接得到种子 seed1
:004CB25D 898570FEFFFF mov dword ptr [ebp+FFFFFE70], eax ;
:004CB263 8B9570FEFFFF mov edx, dword ptr [ebp+FFFFFE70]
:004CB269 81E2FF000000 and edx, 000000FF
:004CB26F 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB275 8A08 mov cl, byte ptr [eax]
:004CB277 32CA xor cl, dl
:004CB279 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB27F 880A mov byte ptr [edx], cl
:004CB281 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB287 83C001 add eax, 00000001
:004CB28A 898574FEFFFF mov dword ptr [ebp+FFFFFE74], eax
:004CB290 81BD70FEFFFFFF000000 cmp dword ptr [ebp+FFFFFE70], 000000FF
:004CB29A 7F0C jg 004CB2A8
:004CB29C 81BD70FEFFFF00FFFFFF cmp dword ptr [ebp+FFFFFE70], FFFFFF00
:004CB2A6 7D30 jge 004CB2D8
:004CB2A8 8B8D70FEFFFF mov ecx, dword ptr [ebp+FFFFFE70]
:004CB2AE C1F908 sar ecx, 08
:004CB2B1 81E1FF000000 and ecx, 000000FF
:004CB2B7 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB2BD 8A02 mov al, byte ptr [edx]
:004CB2BF 32C1 xor al, cl
:004CB2C1 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB2C7 8801 mov byte ptr [ecx], al
:004CB2C9 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB2CF 83C201 add edx, 00000001
:004CB2D2 899574FEFFFF mov dword ptr [ebp+FFFFFE74], edx
:004CB2D8 81BD70FEFFFF007D0000 cmp dword ptr [ebp+FFFFFE70], 00007D00
:004CB2E2 7F0C jg 004CB2F0
:004CB2E4 81BD70FEFFFF0083FFFF cmp dword ptr [ebp+FFFFFE70], FFFF8300
:004CB2EE 7D2F jge 004CB31F
:004CB2F0 8B8570FEFFFF mov eax, dword ptr [ebp+FFFFFE70]
:004CB2F6 C1F810 sar eax, 10
:004CB2F9 25FF000000 and eax, 000000FF
:004CB2FE 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB304 8A11 mov dl, byte ptr [ecx]
:004CB306 32D0 xor dl, al
:004CB308 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB30E 8810 mov byte ptr [eax], dl
:004CB310 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB316 83C101 add ecx, 00000001
:004CB319 898D74FEFFFF mov dword ptr [ebp+FFFFFE74], ecx
:004CB31F 81BD70FEFFFF0024F400 cmp dword ptr [ebp+FFFFFE70], 00F42400
:004CB329 7F0C jg 004CB337
:004CB32B 81BD70FEFFFF00DC0BFF cmp dword ptr [ebp+FFFFFE70], FF0BDC00
:004CB335 7D30 jge 004CB367
:004CB337 8B9570FEFFFF mov edx, dword ptr [ebp+FFFFFE70]
:004CB33D C1FA18 sar edx, 18
:004CB340 81E2FF000000 and edx, 000000FF
:004CB346 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB34C 8A08 mov cl, byte ptr [eax]
:004CB34E 32CA xor cl, dl
:004CB350 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB356 880A mov byte ptr [edx], cl
:004CB358 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB35E 83C001 add eax, 00000001 ;3D4DA1D6 是清除种子的缺省值
:004CB361 898574FEFFFF mov dword ptr [ebp+FFFFFE74], eax ;
:004CB367 C78570FEFFFFD6A14D3D mov dword ptr [ebp+FFFFFE70], 3D4DA1D6
;seed1 = [ebp+FFFFFE70]
:004CB371 8B4D08 mov ecx, dword ptr [ebp+08] ;
:004CB374 8B919C010000 mov edx, dword ptr [ecx+0000019C] ;[ebp+xxxxxxxx]是种子seed1
:004CB37A 8B82E81C0000 mov eax, dword ptr [edx+00001CE8]
:004CB380 8B4DF8 mov ecx, dword ptr [ebp-08]
:004CB383 C1E104 shl ecx, 04
:004CB386 8B940DB0FEFFFF mov edx, dword ptr [ebp+ecx-00000150]
:004CB38D 33C9 xor ecx, ecx
:004CB38F 8A8C1038050000 mov cl, byte ptr [eax+edx+00000538]
:004CB396 8B5514 mov edx, dword ptr [ebp+14]
:004CB399 8B4208 mov eax, dword ptr [edx+08]
:004CB39C 33C1 xor eax, ecx
:004CB39E 8B4D08 mov ecx, dword ptr [ebp+08]
:004CB3A1 8B919C010000 mov edx, dword ptr [ecx+0000019C]
:004CB3A7 8B8AE81C0000 mov ecx, dword ptr [edx+00001CE8]
:004CB3AD 8B55F8 mov edx, dword ptr [ebp-08]
:004CB3B0 C1E204 shl edx, 04
:004CB3B3 8B9415B4FEFFFF mov edx, dword ptr [ebp+edx-0000014C]
:004CB3BA 33DB xor ebx, ebx
:004CB3BC 8A9C1138050000 mov bl, byte ptr [ecx+edx+00000538]
:004CB3C3 C1E308 shl ebx, 08
:004CB3C6 33C3 xor eax, ebx
:004CB3C8 8B4D08 mov ecx, dword ptr [ebp+08]
:004CB3CB 8B919C010000 mov edx, dword ptr [ecx+0000019C]
:004CB3D1 8B8AE81C0000 mov ecx, dword ptr [edx+00001CE8]
:004CB3D7 8B55F8 mov edx, dword ptr [ebp-08]
:004CB3DA C1E204 shl edx, 04
:004CB3DD 8B9415B8FEFFFF mov edx, dword ptr [ebp+edx-00000148]
:004CB3E4 33DB xor ebx, ebx
:004CB3E6 8A9C1138050000 mov bl, byte ptr [ecx+edx+00000538]
:004CB3ED C1E310 shl ebx, 10
:004CB3F0 33C3 xor eax, ebx
:004CB3F2 8B4D08 mov ecx, dword ptr [ebp+08]
:004CB3F5 8B919C010000 mov edx, dword ptr [ecx+0000019C]
:004CB3FB 8B8AE81C0000 mov ecx, dword ptr [edx+00001CE8]
:004CB401 8B55F8 mov edx, dword ptr [ebp-08]
:004CB404 C1E204 shl edx, 04
:004CB407 8B9415BCFEFFFF mov edx, dword ptr [ebp+edx-00000144]
:004CB40E 33DB xor ebx, ebx
:004CB410 8A9C1138050000 mov bl, byte ptr [ecx+edx+00000538]
:004CB417 C1E318 shl ebx, 18
:004CB41A 33C3 xor eax, ebx
:004CB41C 50 push eax
:004CB41D E8A5030000 call 004CB7C7 ;
:004CB422 83C404 add esp, 00000004 ;seed2 = eax 直接得到种子 seed2
:004CB425 89856CFEFFFF mov dword ptr [ebp+FFFFFE6C], eax ;
:004CB42B 8B856CFEFFFF mov eax, dword ptr [ebp+FFFFFE6C]
:004CB431 25FF000000 and eax, 000000FF
:004CB436 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB43C 8A11 mov dl, byte ptr [ecx]
:004CB43E 32D0 xor dl, al
:004CB440 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB446 8810 mov byte ptr [eax], dl
:004CB448 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB44E 83C101 add ecx, 00000001
:004CB451 898D74FEFFFF mov dword ptr [ebp+FFFFFE74], ecx
:004CB457 81BD6CFEFFFFFF000000 cmp dword ptr [ebp+FFFFFE6C], 000000FF
:004CB461 7F0C jg 004CB46F
:004CB463 81BD6CFEFFFF00FFFFFF cmp dword ptr [ebp+FFFFFE6C], FFFFFF00
:004CB46D 7D30 jge 004CB49F
:004CB46F 8B956CFEFFFF mov edx, dword ptr [ebp+FFFFFE6C]
:004CB475 C1FA08 sar edx, 08
:004CB478 81E2FF000000 and edx, 000000FF
:004CB47E 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB484 8A08 mov cl, byte ptr [eax]
:004CB486 32CA xor cl, dl
:004CB488 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB48E 880A mov byte ptr [edx], cl
:004CB490 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB496 83C001 add eax, 00000001
:004CB499 898574FEFFFF mov dword ptr [ebp+FFFFFE74], eax
:004CB49F 81BD6CFEFFFF007D0000 cmp dword ptr [ebp+FFFFFE6C], 00007D00
:004CB4A9 7F0C jg 004CB4B7
:004CB4AB 81BD6CFEFFFF0083FFFF cmp dword ptr [ebp+FFFFFE6C], FFFF8300
:004CB4B5 7D30 jge 004CB4E7
:004CB4B7 8B8D6CFEFFFF mov ecx, dword ptr [ebp+FFFFFE6C]
:004CB4BD C1F910 sar ecx, 10
:004CB4C0 81E1FF000000 and ecx, 000000FF
:004CB4C6 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB4CC 8A02 mov al, byte ptr [edx]
:004CB4CE 32C1 xor al, cl
:004CB4D0 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB4D6 8801 mov byte ptr [ecx], al
:004CB4D8 8B9574FEFFFF mov edx, dword ptr [ebp+FFFFFE74]
:004CB4DE 83C201 add edx, 00000001
:004CB4E1 899574FEFFFF mov dword ptr [ebp+FFFFFE74], edx
:004CB4E7 81BD6CFEFFFF0024F400 cmp dword ptr [ebp+FFFFFE6C], 00F42400
:004CB4F1 7F0C jg 004CB4FF
:004CB4F3 81BD6CFEFFFF00DC0BFF cmp dword ptr [ebp+FFFFFE6C], FF0BDC00
:004CB4FD 7D2F jge 004CB52E
:004CB4FF 8B856CFEFFFF mov eax, dword ptr [ebp+FFFFFE6C]
:004CB505 C1F818 sar eax, 18
:004CB508 25FF000000 and eax, 000000FF
:004CB50D 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB513 8A11 mov dl, byte ptr [ecx]
:004CB515 32D0 xor dl, al
:004CB517 8B8574FEFFFF mov eax, dword ptr [ebp+FFFFFE74]
:004CB51D 8810 mov byte ptr [eax], dl
:004CB51F 8B8D74FEFFFF mov ecx, dword ptr [ebp+FFFFFE74]
:004CB525 83C101 add ecx, 00000001 ;3D4DA1D6 是清除种子的缺省值
:004CB528 898D74FEFFFF mov dword ptr [ebp+FFFFFE74], ecx ;
:004CB52E C7856CFEFFFFD6A14D3D mov dword ptr [ebp+FFFFFE6C], 3D4DA1D6
;seed2 = [ebp+FFFFFE6C]
:004CB538 E90B010000 jmp 004CB648 ;
:004CB53D C78568FEFFFF00000000 mov dword ptr [ebp+FFFFFE68], 00000000
;[ebp+xxxxxxxx]是种子 seed2
:004CB547 EB0F jmp 004CB558
:004CB549 8B9568FEFFFF mov edx, dword ptr [ebp+FFFFFE68]
:004CB54F 83C201 add edx, 00000001
:004CB552 899568FEFFFF mov dword ptr [ebp+FFFFFE68], edx
:004CB558 83BD68FEFFFF08 cmp dword ptr [ebp+FFFFFE68], 00000008
:004CB55F 0F8DE3000000 jnl 004CB648
:004CB565 8B8568FEFFFF mov eax, dword ptr [ebp+FFFFFE68]
:004CB56B 2503000080 and eax, 80000003
:004CB570 7905 jns 004CB577
:004CB572 48 dec eax
:004CB573 83C8FC or eax, FFFFFFFC
:004CB576 40 inc eax
:004CB577 C1E003 shl eax, 03
:004CB57A 898564FEFFFF mov dword ptr [ebp+FFFFFE64], eax
..............
:004CB7AC 50 push eax
:004CB7AD E81D000000 call 004CB7CF
:004CB7B2 83C40C add esp, 0000000C
:004CB7B5 8985A0FEFFFF mov dword ptr [ebp+FFFFFEA0], eax
:004CB7BB 8B85A0FEFFFF mov eax, dword ptr [ebp+FFFFFEA0]
:004CB7C1 5E pop esi
:004CB7C2 5B pop ebx
:004CB7C3 8BE5 mov esp, ebp
:004CB7C5 5D pop ebp
:004CB7C6 C3 ret
=====================================================================

2009年6月23日星期二

汇编指令


        不知哪位大哥总结的,先借来用一下,免得老是翻书^_^ 
附一个汇编的在线学习网站

数据传输指令
───────────────────────────────────────
它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.
1. 通用数据传送指令.
MOV 传送字或字节.
MOVSX 先符号扩展,再传送.
MOVZX 先零扩展,再传送.
PUSH 把字压入堆栈.
POP 把字弹出堆栈.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
BSWAP 交换32位寄存器里字节的顺序
XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
XADD 先交换再累加.( 结果在第一个操作数里 )
XLAT 字节查表转换.
── BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即
0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL )
2. 输入输出端口传送指令.
IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )
输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,
其范围是 0-65535.
3. 目的地址传送指令.
LEA 装入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 传送目标指针,把指针内容装入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 传送目标指针,把指针内容装入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 传送目标指针,把指针内容装入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 传送目标指针,把指针内容装入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 传送目标指针,把指针内容装入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
4. 标志传送指令.
LAHF 标志寄存器传送,把标志装入AH.
SAHF 标志寄存器传送,把AH内容装入标志寄存器.
PUSHF 标志入栈.
POPF 标志出栈.
PUSHD 32位标志入栈.
POPD 32位标志出栈.

二、算术运算指令
───────────────────────────────────────
ADD 加法.
ADC 带进位加法.
INC 加 1.
AAA 加法的ASCII码调整.
DAA 加法的十进制调整.
SUB 减法.
SBB 带借位减法.
DEC 减 1.
NEC 求反(以 0 减之).
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果).
AAS 减法的ASCII码调整.
DAS 减法的十进制调整.
MUL 无符号乘法.
IMUL 整数乘法.
以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
AAM 乘法的ASCII码调整.
DIV 无符号除法.
IDIV 整数除法.
以上两条,结果回送:
商回送AL,余数回送AH, (字节运算);
或 商回送AX,余数回送DX, (字运算).
AAD 除法的ASCII码调整.
CBW 字节转换为字. (把AL中字节的符号扩展到AH中去)
CWD 字转换为双字. (把AX中的字的符号扩展到DX中去)
CWDE 字转换为双字. (把AX中的字符号扩展到EAX中去)
CDQ 双字扩展. (把EAX中的字的符号扩展到EDX中去)

三、逻辑运算指令
───────────────────────────────────────
AND 与运算.
OR 或运算.
XOR 异或运算.
NOT 取反.
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)  当值为负时,高位补 1 ;当值为正时,高位补 0
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.
以上八种移位指令,其移位次数可达255次.
移位一次时, 可直接用操作码. 如 SHL AX,1.
移位>1次时, 则由寄存器CL给出移位次数.
如 MOV CL,04
SHL AX,CL

四、串指令
───────────────────────────────────────
DS:SI 源串段寄存器 :源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
Z标志 用来控制扫描或比较操作的结束.
MOVS 串传送.
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.
( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.
把AL或AX的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.
把源串中的元素(字或字节)逐一装入AL或AX中.
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串.
是LODS的逆过程.
REP 当CX/ECX<>0时重复.
REPE/REPZ 当ZF=1或比较结果相等,且CX/ECX<>0时重复.
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX<>0时重复.
REPC 当CF=1且CX/ECX<>0时重复.
REPNC 当CF=0且CX/ECX<>0时重复.

五、程序转移指令
───────────────────────────────────────
1>无条件转移指令 (长转移)
JMP 无条件转移指令
CALL 过程调用
RET/RETF过程返回.
2>条件转移指令 (短转移,-128到+127的距离内)
( 当且仅当(SF XOR OF)=1时,OP1 JA/JNBE 不小于或不等于时转移.
JAE/JNB 大于或等于转移.
JB/JNAE 小于转移.
JBE/JNA 小于或等于转移.
以上四条,测试无符号整数运算的结果(标志C和Z).
JG/JNLE 大于转移.
JGE/JNL 大于或等于转移.
JL/JNGE 小于转移.
JLE/JNG 小于或等于转移.
以上四条,测试带符号整数运算的结果(标志S,O和Z).
JE/JZ 等于转移.
JNE/JNZ 不等于时转移.
JC 有进位时转移.
JNC 无进位时转移.
JNO 不溢出时转移.
JNP/JPO 奇偶性为奇数时转移.
JNS 符号位为 "0" 时转移.
JO 溢出转移.
JP/JPE 奇偶性为偶数时转移.
JS 符号位为 "1" 时转移.
3>循环控制指令(短转移)
LOOP CX不为零时循环.
LOOPE/LOOPZ CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
JCXZ CX为零时转移.
JECXZ ECX为零时转移.
4>中断指令
INT 中断指令
INTO 溢出中断
IRET 中断返回
5>处理器控制指令
HLT 处理器暂停, 直到出现中断或复位信号才继续.
WAIT 当芯片引线TEST为高电平时使CPU进入等待状态.
ESC 转换到外处理器.
LOCK 封锁总线.
NOP 空操作.
STC 置进位标志位.
CLC 清进位标志位.
CMC 进位标志取反.
STD 置方向标志位.
CLD 清方向标志位.
STI 置中断允许位.
CLI 清中断允许位.

六、伪指令
─────────────────────────────────────
DW 定义字(2字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.

2009年6月16日星期二

翻译FLEXlm9.2的破解教学五

标 题: 翻译FLEXlm9.2的破解教学五
作 者: newsearch
时 间: 2004-12-10,01:03
链 接: http://bbs.pediy.com/showthread.php?t=8221

翻译第五篇 On Software Reverse Engineering - 5
            Reverse Engineering, FLEXlm, IMSL
模糊方法

    下面,我们将研究在目标程序中种子和密钥是怎样模糊的。我们的武器是数据流分析,也就是说通过调试cmath.exe,从初始化到l_string_kry()全程追踪vendor和job结构[10]。

VENDORCODE after calling l_n36_buf():
[004AEA20] - 00000004 ....
[004AEA24] - aa3342a8 .B3.
[004AEA28] - 8d112f1f ./..
[004AEA2C] - 74df9bc4 ...t 模糊了的 VENDOR_KEY1
[004AEA30] - b19bfb18 .... 模糊了的 VENDOR_KEY2
[004AEA34] - 94011f00 .... 模糊了的 VENDOR_KEY3
[004AEA38] - 054a2ed9 ..J. 模糊了的 VENDOR_KEY4
[004AEA3C] - 00020009 ....
[004AEA40] - 39300020 .09
[004AEA44] - 0000302e .0..
 
job after calling lc_init():
[00887630] - 00000066 f...
[00887634] - 00000000 ....
[00887638] - 00000000 ....
[0088763C] - 00000000 ....
[00887640] - 00000000 ....
 
// 上面是lc_new_job()之后的结构, 然后它们被传递到lc_checkout() &reg; l_checkout() &reg; lm_start_real() &reg; l_good_lic_key()

VENDORCODE 在l_xorname()调用之后:
[0012EE20] - 00000004 ....
[0012EE24] - aa3342a8 .B3.
[0012EE28] - 8d112f1f ./..
[0012EE2C] - 7c2adb6a j.*| 真的 VENDOR_KEY1
[0012EE30] - b927f5a9 ..'. 真的 VENDOR_KEY2
[0012EE34] - 9cf311f8 .... 真的 VENDOR_KEY3
[0012EE38] - 0dbf7621 !v.. 真的 VENDOR_KEY4
[0012EE3C] - 00020009 ....
[0012EE40] - 39300020 .09
[0012EE44] - 0000302e .0..
[0012EE48] - 00000000 ....
 
VENDORCODE after calling l_n36_buff():
[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 ....
 
job after calling l_n36_buff():
[00887630] - 00000066 f... 整型;
[00887634] - 0089008e .... char *mem_ptr2;
[00887638] - a06aa84e N.j. unsigned char mem_ptr2_bytes[12];
[0088763C] - 00c3a047 G...
[00887640] - 00660000 ..f.
[00887644] - 00000000 ....
[00887648] - 00000000 ....
[0088764C] - 00000000 ....
[00887650] - 00000000 ....
[00887654] - 54414d43 CMAT
[00887658] - 00000048 H...
 
// 上面是l_good_lic_key() &reg; l_sg()之后的结构, 然后它们被传递到 l_good_lic_key() &reg; l_crypt_private() &reg; real_crypt() &reg; l_string_key()
 VENDORCODE和job在3个函数中被改变:l_n36_buf(),l_xorname()和l_n36_buff() (在lc_init()中的job初始化并不是很感兴趣的,因为它仅填充O)。在l_good_lic_key()内调用后面的两个:

l_good_lic_key(LM_HANDLE* job, CONFIG* conf, VENDORCODE* key)
{
... ...
memcpy(&vc, key, sizeof(vc));
if (!(job->flags & LM_FLAG_CLEAR_VKEYS))
l_xorname(job->vendor, &vc);
l_sg(job, job->vendor, &vc); /* l_sg() 将调用 l_n36_buff() */
... ...
code = l_crypt_private(job, conf, sdate, &vc);
... ...
}

    对于4个Vendor密钥,很明显它们是初始化时在l_n36_buf()中被模糊的,在l_xorname()中进行模糊逆向,l_xorname()未做别的仅进行了一些与或操作。我们都知道与或的特征:,因此进行两次相同的与或操作将相互取消。该特性将使编码和译码的与或变得美妙。如果我们的猜测是对的话,那么l_n36_buf()将采用与l_good_lic_key()相同的方式调用l_xorname()。因为 l_n36_buf()驻留于lm_new.c, 它由lmnewgen.exe产生, 因此直接检查lmnewgen.c更好。

翻译FLEXlm9.2的破解教学四

标 题: 翻译FLEXlm9.2的破解教学四
作 者: newsearch
时 间: 2004-12-08,15:58
链 接: http://bbs.pediy.com/showthread.php?t=8148

翻译第四篇 On Software Reverse Engineering - 4
            Reverse Engineering, FLEXlm, IMSL
    因此,我们有了两个处理:在cmath.exe中进行检查和在lmcrypt.exe或makekey.exe.中产生密钥(keygen)。它们都可以产生相同的正确的许可证代码,但这两个处理不是完全相同的。我们已经在前面的章节对第一个处理进行了一定深度的分析,现在按顺序列出重要的调用链接。

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

4.lc_checkout() &reg; l_checkout() &reg; lm_start_real() &reg; l_good_lic_key() &reg; l_xorname()
5.lc_checkout() &reg; l_checkout() &reg; lm_start_real() &reg; l_good_lic_key() &reg; l_sg() &reg; l_n36_buff()
6.lc_checkout() &reg; l_checkout() &reg; lm_start_real() &reg; l_good_lic_key() &reg; l_crypt_private() &reg;
real_crypt() &reg; 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() &reg; lc_init() &reg; l_init() &reg; l_sg() &reg; l_key() &reg; l_zinit()
2.lmcrypt.c!main() &reg; dofilecrypt() &reg; dofpcrypt() &reg; lm_crstr.c!lc_cryptstr() &reg; parsefeaturelist() 
 &reg; l_parse_feature_line() &reg; oldkey() &reg; l_crypt_private() &reg; real_crypt() &reg; l_string_key()
3.lmcrypt.c!main() &reg; dofilecrypt() &reg; dofpcrypt() &reg; lm_crstr.c!lc_cryptstr() &reg; cryptfeaturelist() 
 &reg; docryptfeat() &reg; lc_crypt() = l_crypt_private() &reg; real_crypt() &reg; 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 &Ograve;.7&laquo;.CMATH.....j
0x0012D8E4 61 6e 87 c5 d0 73 00 00 00 00 00 00 00 00 00 00 an.&Aring;&ETH;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 ....&cedil;9&Aacute;R.T&atilde;uj&Ucirc;*|
0x004D7B58 a9 f5 27 b9 f8 11 f3 9c 21 76 bf 0d 09 00 02 00 &copy;&otilde;'&sup1;&oslash;.ó.!v&iquest;.....
0x004D7B68 20 00 30 38 2e 30 00 00 c3 80 f4 83 2c c0 1c 77 .08.0..&Atilde;.&ocirc;.,&Agrave;.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可获得,因此加密种子可以以一般形式出现。

Flexlm保护

《加密与解密》第二版 
 
13.6 Flexlm保护
 
http://www.pediy.com/book/book.gif
 
说明

这部分内容是《加密与解密》第二版的,没有放进第三版。书上Flexlm这部分比较基础,放出来与大家分享。由于与电子工业出版社还有版权合同问题,因此,这部分内容仅对论坛注册会员可见,不想大面积传播,还请谅解。
其中,主要部分"FlexGen工具用法"、"利用FlexLm SDK解密"由Fisheep参与编写,在此表示感谢!


13.7 Flexlm保护
完稿时间:2003.1
 
许可证管理(License manager)是一种授权加密保护方式,属于这类的保护的有Flexlm、ElanLM、SentinelLm等。其中以 Flexlm最为著名,很多大型专业软件都采用它进行加密。但是其安全性是没有保证的,不是因为它技术不好,而是因为人们对它的研究最深入和透彻。
许可证的原理通常是把要保护的软件同计算机固有的唯一的硬件特征联系起来,例如工作站的主机号(Hostid)、以太网卡得MAC地址等。
 
13.7.1 License文件格式
Flexlm加密的程序在使用时,一般都会有一个License文件,在此有必要先了解一下License文件的结构。License文件由注释行、Server行、Demon行和Feature行构成,另外有一个续行符"\"用来表示一行的继续。
 
名称:  Flexlm01.gif
查看次数: 2320
文件大小:  18.4 KB
 
名称:  Flexlm02.gif
查看次数: 2322
文件大小:  18.3 KB
 
13.7.2 设置环境变量
一般来说,License Manager需要一个环境变量才能找到License文件。有的软件不需要设置环境变量,只需要将license文件放置到它的某个目录下面就可以了,如ActiveVHDL3.3,只要将license.dat放在它的dat目录下。
通常环境变量名称是LM_LICENSE_FILE,大小写无关,Windows 9x下的设置方法是在Autoexec.bat中加入一行: 
 
名称:  Flexlm03.gif
查看次数: 2315
文件大小:  2.7 KB
 
如果有多个License文件,可以用分号隔开,如:
SET LM_LICENSE_FILE=c:\flexlm\license1.dat;d:\flexlm\license2.dat 
应用软件的License Manager自动遍历环境变量指定的所有License文件,查找它所需要的Feature。不同License与文件路径的先后次序无关。 
除了LM_LICENSE_FILE这个环境变量名称外,有些软件还使用自己特定的环境变量名称。安装软件的时候,安装程序一般会自动添加这些环境变量,如Specctra8.0使用CDS_LIC_FILE环境变量名称:
SET CDS_LIC_FILE=c:\cdsLic\license.dat 
MentorGraphicsRenoir以及ModelSimEE如果安装的时候选择Mentor Graphics License Manager,软件使用MGLS_LICENSE_FILE环境变量:
SET MGLS_LICENSE_FILE=c:\flexlm\license.dat 
如果License是由Server提供的,那么环境变量的路径应该给出Server的名称以及端口,而非License文件的路径以及名称,如:
 
名称:  Flexlm04.gif
查看次数: 2315
文件大小:  3.0 KB
 
Windows NT/2000/XP下设置环境变量需要在控制面板里面添加,格式相同。
 
13.7.3 Flexlm Server
Flexlm这个名字是Flexible License Manager的简写,Flexlm Server方式的License应该如下设置:
License.dat文件放在主机中,主机运行Server,提供License服务。
客户端的环境变量设置为:
SET LM_LICENSE_FILE=[TCP/IP端口]:[主机名称] 
其中端口和主机名称应该和主机的License文件中的Server Line一致。启动Server的方式有两种,一是DOS命令行方式,二是Windows的窗口方式。
 
1.DOS方式 
在c:\flexlm目录下可以找到一个Lmgrd.exe文件——License Manager Daemon,这个 Daemon是Flexlm Server。License.dat文件需要和Daemon文件放在同一个目录下,启动License server,可在 c:\flexlm 目录下执行:
c:\flexlm\lmgrd -app -c 
关闭License server,可在 c:\flexlm目录下执行: 
c:\flexlm\lmutil lmdown 
lmutil.exe是License Manager Utility。
 
2.Windows的窗口方式 
安装某些软件的时候,会有一个安装License Server的选项,选中它安装完成后,会在控制面板中发现一个 Flexlm License Manager的小工具。在Flexlm License Manager Setup的TAB里可以选择Daemon、 License文件,以及输出的日志文件。Control的TAB中可以启动/关闭Server以及查看Server状态,另外几个TAB还提供一些其他辅助功能。
Windows下的这个Flexlm License Manager要比命令行方式的方便一些,至少它可以浏览,选取Daemon/License文件,不过有时候发现控制面板里面找不到。事实上它是一个控制面板的扩展应用程序,如在PowerPCB目录下可以找到Flexlm.cpl这个文件,用鼠标双击它就运行上面提到的那个管理窗口。
 
13.7.4 FlexGen工具用法
 
1.FlexGen.ini配置
FlexGen是通用基于Flexlm的License生成器,它需要输入一些不同的信息。这些信息保存在FlexGen.Ini文件中,该文件是可以编辑和更新的,这样就可以添加信息以生成新软件的License。
FlexGen.ini包括生成License文件需要的不同信息。对于每个软件的信息,它都拥有表13-2的格式。
 
名称:  Flexlm05.gif
查看次数: 2321
文件大小:  29.2 KB
 
已经准备好INI文件,可以用它来生成License。运行FlexGen,按"Generate"试一下,如果得到一个错误信息,就表示有一些信息输错了;如信息正确,再按"Check"检测License是否合法,如果得到肯定的回答,就表示INI中输入的信息都是正确的。这样,就可自制INI中的信息来为程序生成License文件。按"Automatic License Generation"生成的INI文件中放入所有Feature的 License,生成License后按"Save"按钮将其保存。
 
2.编辑过的DLL使用方法
(1)先用FileMon等工具检测软件的运行,观察软件使用的lmgr32XX.dll文件名和所在的目录。
(2)退出应用软件的运行,用FlexGen提供的LMGRS目录下的相应DLL替换原先的DLL(最好先将原先的文件备份)。
(3)运行软件,将有一个对话框弹出,记录其标题(是Flexlm的Vendor Name)和显示的数值,有80个字符(称为Magic Number是经过处理的Flexlm的Key)。
(4)运行FlexGen程序,选择"Make INI from Magic Number"选项运行,填入上面得到的两个信息,然后按 "GENERATE",再按"SAVE"按钮,如果一切都正确,将得到一个TEMPLATE.INI。填写它的所有信息,添加到当前 FLEXGEN.INI文件后面,就可使用FlexGen来生成该软件的License文件了。
(5)退出应用软件的运行,用FlexGen提供的LMFEAT目录下的相应Lmgr32XX.dll替换原先的DLL,然后运行应用程序,出现一个对话框,其文本为FEATURE的名称,其标题为该FEATURE的版本号(Version)。记录下这两项内容,添加到先前生成的Template.ini 文件中。在应用程序中多运行一些功能,可能发现更多的FEATURE,都将它们记录下来,然后添加到先前的Template.ini文件中。
 
3.硬盘序列号修改
上面的方法固然很不错,但是有的时候会发现个别Flexlm加密的软件并不调用lmgr32XX.dll。
Flexlm是根据硬盘序列号生成机器号的,如果有了一个机器的序列号,这时解密者修改硬盘的序列号,让它们生成一样的机器号,那么那个密码就可以在多台机器上用了。以前在DOS下的时候可以用NU8中的diskedit来编辑硬盘的序列号,这样对那些不懂磁盘结构的人来说很不方便。用FlexGen配套的DISKSER.EXE就可轻松解决这个问题,打开这个软件,会出现图13.7的界面。
 
名称:  Flexlm06.gif
查看次数: 2300
文件大小:  5.0 KB
 
输入8位数字后它会自动提示:"Serial Number Changed",这样硬盘序列号就改变了。
FlexGen配套的DGET.EXE是查看硬盘序列号的软件。
对软件作者加密时的建议:计算本机序列号的时候尽量使用稳定的不容易变化的东西作为计算的依据,比如BIOS中的厂家的名字,但不要用BIOS的版本号因为这个东西要经常变,而主板厂家的名字却很少有人去刻意改变。硬盘序列号是一个很不错的依据,但是有了DISKSER.EXE就不怎么安全了。可把这类可以依靠的判断条件选择几个,计算出机器号,切不可只用一个来计算,不然很容易被修改!
 
13.7.5 利用FlexLm SDK解密
Flexlm的License文件可以用函数f如下表示: 
f( 主机号,软件名,版本,期限,许可证个数,加密参数)= 加密串 
这个函数还和Flexlm的版本有关系,实际上只要得到相应版本的 Flexlm SDK,就可以执行此函数。Flexlm SDK是公开的(以前如此),唯一保密的是f函数中的"加密参数"由Flexlm公司通过保密的途径提供给其客户,这些参数由五个Vendor Key(客户码)和两个 Encryption Seed(加密种子)组成,只要知道了这七个参数,就可以用相应版本的SDK构造出完整的License文件。该方法是一种最彻底的、最完美的办法,但也是flexlm 重点防范的对象,他们采用了很多技术隐藏上述信息。
根据SDK的描述,如下两个函数最重要:
 lc_init( )——使用基本的Vendor Key等信息进行Flexlm的初始化,通过它可以得到基本信息;
 lc_checkout( )——检查是否可以使用指定Feature的功能,通过它可以得到程序的所有Feature名称。
为了讲述方便,在此以FlexLm加密的XprismPro 1.0程序为例。
 
1.SDK开发包
首先安装Flexlm开发包,这里所要关心的主要文件是: 
 lm_code.h——在这要设置所要解密程序的加密信息,有Vendor名称、Vender Key和Seed等,这些内容是重点;
 lm_client.h——包含Flexlm用来保护软件的函数原型和出错码的定义;
 GenLic32.exe——该程序用来检查在lm_code.h中设置的Key等信息生成Flexlm的License文件。
 
2.使用lc_init( )得到信息
使用lc_init( )来找Vendor Name、Vendor Key 1 - Vendor Key 4。载入目标EXE文件,然后在lc_init调用处设置一个断点,运行程序,直到它停止。
在lm_client.h中,可以看到lc_init的原型如下: 
 
代码:
lm_extern API_ENTRY lc_init lm_args(                     (LM_HANDLE_PTR job,                       LM_CHAR_PTR vendor_id,                       VENDORCODE_PTR vendor_key,                      LM_HANDLE_PTR_PTR job_id)            );  
 
在最前面的job将是NULL,job_id是一个指向将被填充的job结构的指针,这里所关心的是Vendor_id和vendor_key,其中Vendor_id是一个文本字符串。
再看看lm_client.h中的结构:
 
 
代码:
typedef struct vendorcode5 {                               short type;             /* 结构类型*/                               unsigned long data[2];  /* 加密的Seed信息 */                               unsigned long keys[4];  /* Vendor Keys 1 到 4 */                              short flexlm_version;                               short flexlm_revision;                               char flexlm_patch[2];                               char behavior_ver[LM_MAX_BEH_VER + 1];                             } VENDORCODE5,  FAR *VENDORCODE_PTR;   #define LM_MAX_BEH_VER  "06.0"  
 
在上面的结构中:
data[0] = Seed1 XOR VendorKey5
data[1] = Seed2 XOR VendorKey5
keys[0..3] = vendor keys 1 到 4 
behavior_ver[] = 包含Flexlm版本的字符串(在这儿是"06.0") 
在调用lc_init前,上面的的内容将被压到栈中。
程序中断后将看到如下代码:
 
名称:  Flexlm07.gif
查看次数: 2307
文件大小:  2.6 KB
 
这里获得了vendor ID字符串(这里是"VENDOR"),用W32Dasm反汇编主程序,再看00528DC8处的内容,可以看到code结构的内容:
 
代码:
[00528DC8] - 00000004  ....  [00528DCC] - ab5e32e5  .?^    ; seed1 XOR key5  [00528DD0] - 7bc6313d  =1.{   ; seed2 XOR key5   [00528DD4] - fc62965d  ].l.   ; key 1   [00528DD8] - 853df75c  \7=.   ; key 2  [00528DDC] - 2f324f23  #O:.   ; key 3   [00528DE0] - 1133e43b  ;.3.   ; key 4   [00528DE4] - 00000006  ....   ; version and revision  [00528DE8] - 36300069  i.06   ; patch和behaviour_ver   [00528DEC] - 0000302e  .0..  
 
这里面获得了4个Keys和2个加密的Seed值。
 
3.找Feature名称
需要Feature名称,得到一个可以使用的License文件。清除在lc_init( )处设的断点后,然后用lc_checkout设置断点。
在lm_client.h中可以看到如下的函数原形:
 
代码:
lm_extern API_ENTRY lc_checkout lm_args((LM_HANDLE_PTR job,                                    const LM_CHAR_PTR feature,                                   const LM_CHAR_PTR  version,                                    int nlic,                                    int flag,                                    const VENDORCODE_PTR key,                                    int dup));  
 
在程序中看起来:
 
代码:
push  00004000   push  0528DC8                 ; code结构  push  00000000   lea   eax, dword ptr [esp+10]   push  00000001   push  eax                     ; 版本  push  ecx                     ; feature名称   push  edx   Call  LMGR326A.lc_checkout   
 
这里获得一个Feature名称,反汇编后就可在该Feature名称的周围,看到一些程序可能用到的其他Feature名称。
 
4.制作license.dat文件
编辑lm_code.h文件,在其中填入所找到的加了密的seeds值和vendor keys 1到4,先设置key 5为0。
运行genlic32.exe。如果跳出一个错误框,则表示lm_code.h中信息有错;输入在前面找到的feature名称,点击 "permanent"和"run anywhere",这样就不需要一个server daemon在运行。点击"Make License",填好文件名license.dat,然后点击"Make License",查看License文件,应该看起来像这样:
 
名称:  Flexlm08.gif
查看次数: 2312
文件大小:  2.2 KB
 
5.寻找Vendor Key 5
FlexLm提供基于Vendor名称的5个Key,因此它必须以某种方式进行校验。如果在lm_code.h里的Key1到Key4或Vendor名称有错,那么genlic32.exe就不会正常工作。
再次装载目标文件,设置lc_checkout断点,载入daemon DLL,开始单步执行,单步跟踪到里面。程序检查licence.dat文件:
l_validversion( ), l_compare_version( ) 检查license文件中的版本;
l_date( ), l_extract_date( ) 在加密的Key中展开日期(无时间限制的日期被转化为1-jan-0);
l_start_ok( ) 运行日期是正确的;
l_host( ) 运行的机器是否正确;
然后进入一些有很多XOR运算的函数中,这里是用XOR运算加密Seed的地方,可以看到Key1、Key 2、Key 3、Key 4以及Vendor名称,然后碰到如下代码:
 
代码:
call 10021160                    ; 将key 5放到ebx   add  esp, 0000000C   mov  eax, dword ptr [edi+04]     ; 从license.dat取seed1   xor  eax, ebx                    ; seed1 XOR key5     lea  ecx, dword ptr [ebp-58]      push ecx   mov  dword ptr [ebp-54], eax     ; 保存seed1   mov  eax, dword ptr [edi+08]     ; 从license.dat取Seed2  mov  edi, dword ptr [ebp+0C]   xor  eax, ebx                    ; seed2 XOR key5   mov  dword ptr [ebp-50], eax     ; 保存seed2   lea  eax, dword ptr [edi+54]   push eax   push esi   call LMGR326A.l_extract_date  
 
就这样,得到key5和经过XOR的seeds1和Seed2。
 
 
6.制作最终的license.dat
在lm_code.h中替换掉seeds和key5的值,运行genlic32.exe制作完全的licence文件。
上面这一篇教程是针对使用Flexlm的DLL加密的程序。其函数名称可以从DLL的输出表中得到,给予解密一个很大的提示。但是对于那些没有使用DLL的加密程序,该如何发现这些关键函数的位置呢?
这时候可以使用IDA和SoftICE来进行解密。首先用IDA的辅助工具Flair(该工具仅IDA注册用户有)建立Flexlm SDK的Sig文件,然后使用IDA对目标程序进行反汇编,再将建立好的Flexlm的Sig作用于已经反汇编的目标文件,这样就能直观地看到Flex关键函数所在的地址。
在SoftICE中载入该目标程序,然后在IDA中得出的关键函数地址处设置断点,运行就可以在所期望的地方停下来,使用D命令得到需要的信息。
因为已经存在Flexlm的辅助解密工具,可以根据部分信息Key1~Key4、Vendor名称得到另外的信息Key5和Seeds。这样,解密工作就可简化为在_lc_init处设置断点,获得部分加密信息,使用Flexlm辅助工具得到另外的信息Key5等,然后在_lc_checkout处设置断点获得加密程序的Feature名称,最后使用所有得到的信息,生成没有限制的license文件。
 
看雪软件安全
http://www.pediy.com
本文结稿时间:2003.1