在技术论坛里,经常有人提及软件防伪、反汇编技术。在提倡运用知识产权法的同时,有一个很重要的因素在于,加强软件自身的设计安全,远比事后采用法律等手段进行约束要有效得多。因为,现今的技术盗窃已远远不同于以往那种文件复制的方式那么简单,很多公司通常采用剥皮战术,即通过追踪他人程序代码的方式,了解程序运行逻辑,然后改换界面上市推销“自己”产品,以节省大量研发经费,为程序设计者带来巨大的经济损失。面对这种剽窃,目前国内的常规软件设计,可以说无任何安全可言,而且,其著作权益也无法收到任何法律保护,因为只要技术本身是未公开的,其算法的一致性并不能够作为专利技术加以保护,根据软件中超过70%以上代码不同可以判定非抄袭这种常规方法,反汇编的程序有可能做到80%以上、甚至100%与原代码不同的实质剽窃产品。因此,如何针对这种现状,完全从设计的角度出发,避免各种反汇编工具及跟踪程序对程序的破解,在现实工作中就很有必要。这里,我将我多年从事的研究拿出来与大家分享,同时,也希望听听众人的建议,将其作为一种开放的开发用平台技术工具。
设计思想
反汇编及跟踪技术,运行机制在于在程序运行初期保留内存中的运行代码,通过设置断点的方式判断程序运行中的实际走向,通过一般的中断替换技术,很难对这类程序作出防御处理。但这类程序的一大致命问题在于:程序的控制流向必须是固定的,如果控制流向本身的非确定分支较多,当几次迭代运行后,因分支的几何性增长导致分析过于复杂而失败。
动态分支处理从技术上讲,就是将控制流程改造成控制序列数组,通过改变数组下标的方式,获得不同的程序控制流程。现在的问题是如何反跟踪,如果,程序的流程序列是相对固定的话,改成控制数组方式并不能从根本上解决问题,这样就需要建立控制流程相互拼装,通过拼装小函数的方式的到大函数运算目的。由于中间可设立监控防范环节很多,在数据追踪过程中,任意环节的生效都可以给程序带来似是而非的效果,给最终破解分析的程序可靠性带来影响。从而在设计上最大限度的保护原创人员的设计思想(原因是目前的软件知识产权不保护软件的设计思想)。
可行性论证
建立有效的控制数组,是反跟踪的技术成功与否的关键。然而,运用控制数组,以动态方式解决程序的设计问题,又与崇尚静态解决问题STL相违背。因此,如何分片的设计,将代码小型化,通过建立完善逻辑流程分析,来避免过分依赖类型分析,将程序的设计从一开始就建立拥有清晰的算法基础要比建立功能函数重要得多。
控制数组的设计,要求每一个控制本身是一个小的独立的函数模块,函数模块相互间的运行时间相差尽可能要小,这样便于建立以时序为基础的反汇编及跟踪断点函数,通过建立时序比较与转移,将程序的正常走向加以改动,令跟踪技术走向歧途。每一个功能小函数,就可以作为象操作数那样可以参与加碱运算的算子,以此存在于程序之中,而我们所要调整的是这些算子间的算法逻辑关系。而如何建立每个软件自己的逻辑关系表,则是软件设计人员今后所面临的真正任务。
但作为开放式的C++整合工具,确立一个通用的标准接口,将每个小功能函数的参数传递通过某种相对固定的方式进行,而本身又符合OO的设计原则,这就要求单元函数本身在设计时尽可能是独立的、与数据分离的功能性模块,而这点在技术上是完全可行的。
结构性分析与效益分析
运用控制数组与建立传统的程序流程相比,程序本身的功能性被压缩了,程序间相互传递的数据成了程序设计间的真正主角。
单一程序函数的功能被明确的固定下来,以减少运行期不必要的数据延时,影响程序的运行效益。因此,单元函数在功能和结构上都被微型化了,以至于在必要的时候完全可以作为固化的微码,用硬件方式实现。
至于运用内存中的动态控制流程与静态固定控制流程,在运行效益上并不存在大的差异,如果说有差异的话,那就是静态的控制流程可以在逻辑上设计得非常复杂独立,而动态的则是整个软件流程复杂,每个单元模块尽可能简单。从程序理解的角度出发,传统的设计功能模块的框架关系是明确的,功能模块内的功能逻辑是复杂的,因此总控的程序设计简单,功能设计复杂,功能间的调整牵涉到系统的整体升级;运用动态控制数组方式,程序的设计流程总体上是复杂的,然而单一功能单元却是明确而简单的,单一功能函数的替换并不需要带来程序整体流程改变或升级。
对后期维护来说,采用动态控制技术更符合软件开发设计原则:由系统设计人员设计程序控制流程,由代码设计人员完成功能实现。而目前传统的方式,恰恰是一个很普通的人员都可以去做总体的框架设计,模块设计却变得极为复杂,让后续接手人员通常不敢轻易替换原有模块,即便在有“文档”的情况下,依然要消化分析源程序的设计意图,这其实在程序设计中是极不合理的。
因此,从运行周期来说,两者在运行时间长短上,属于相似程度,动态控制方式在初始化方面较静态方式更费时,但运行过程但中,对事件的响应速度基本一致。但在后期维护上看,程序的结构和逻辑调整与代码优化完全是由不同的两拨人进行,传统方式要求的是通才,而后者则根据需要选择不同专长人员进行有针对性的处理。从后续维护升级的角度出发,采用动态控制技术的后期维护成本相对要小,而且软件的升级维护可以做成系列化,从人事工作安排的角度,也可以均衡工作量,便于压缩软件交付周期。
程序的检测与调试
动态控制技术调试与传统方式不同,其程序的流程分析并不能借助编程工具通过实验的方式的来。因此,其要求程序设计人员必须在设计阶段就建立完整的控制流程与实现文档,可以说,如果没有文档,做程序的设计开发与后期维护都是不可想象的。这与目前国内的不少“软件公司”先开发产品,后补设计文档完全不同。因为,如果没有详细设计文档,数据间的相互逻辑关系就必然是模糊不清的,运用尝试的方式设计代码很难保证程序总体设计的可靠性,虽然每个模块本身是独立的,但数据的关联却是统一的,因此如果模块内的逻辑不合理,同样会影响总体程序的运行可靠性。将这部分的工作拎出来,进行整体的分段规划,将有利于程序设计人员从总体上把握系统,从而设计出更合理有效的代码。
程序的优化首先是算法本身的优化,逻辑关系的清晰是程序可靠性的首要保证。逻辑设计也为代码设计的调试提供了合理依据。这从另一个角度避免了程序内部大量黑箱代码的出现,让程序尽可能多地通过白箱测试的方式“预见”软件错误,而不是事后判断错误原因。对单元的苛刻性语句调试与程序数据的开放性逻辑测试,便于将程序设计变得更合理、也更安全。
实现实例及分析
假设:对象关系
struct ObjectTable{
int TypeID; //类型控制标识
void *Obj; //对象指针
int DataID; //数据标识
int CmdListID; //控制序列标识
...
};
//标签对象数据描述
struct LabelTable{
int Left,Top,Width,Height;
TColor Color;
AnsiString Caption;
AnsiString Hint;
TFont *Font;
...
};
//控制对象的数据描述
struct CommandTable{
TNotifyEvent OnClick;
TNotifyEvent OnEnter;
TNotifyEvent OnExit;
TMouseEvent OnMouseDown;
...
};
ObjectTable Obj[];
LabelTable Label[];
CommandTable Cmd[];
...
bool __fastcall Init()
{
...
switch(Obj[i].TypeID){
case LABEL:{
Obj[i].Obj = new TLabel(NULL);
TLabel *lab = static_cast<TLabel*>(Obj[i].Obj);
int data = IDSet(Obj[i].DataID); //取数据数组下标
lab->Left = Label[data].Left;
...
int cmd = CmdIDSet(Obj[i].CmdID); //取控制数组下表
lab->OnClick = Cmd[cmd].OnClick;
...}
break;
...
return true;
}
看了上述伪码,很多人一下子就可以看出,这其实与静态的设计没有什么不同啊,只是在设计阶段将屏幕表现方式移到数据表方式,而且还不容易编程!这又何苦呢?
其实不然,从运行的角度上说,动态控制流程所产生的代码与静态产生的代码没什么不同,运行出发时间的方式都是一样而且高效的,但是软件本身的数理逻辑确实不同的:静态程序是建立在总框架基础上的逐步细化过程,动态控制生成是建立在基础控制单元基础上的组装过程。程序本身的完全性测试与分析更容易通过测试来验证,对程序运行的逻辑错误可以象解释性语言那样直接验证,而不是等待一种运行bug的出现。更主要的是,控制流程是运行期可调整组合的,这就给代码跟踪技术带来困难。假定上述代码,前面增加一个时序控制:
void __fastcall CtrlTimer()
{
...
TDateTime test = Now();
if (test > RunTime + MaxRunTime){
Obj[i].Type = IMAGE; //改变对象类型
Obj[i].DataID &= 0x0734; //置换随机数据源
Obj[i].CmdID &= 0x0898; //置换随机控制源
}
...
if(!Init()){
Message("运行期出错!");
QuitProc(); //进入结束程序
}
...
}
这样会怎样呢?如果,将时序控制放到定时器的相应事件中,想想看追踪程序会得到什么样的答案!而这一切不正是本文的目的吗?