宠物小精灵之赤的同人:共享软件加密的一些误区

来源:百度文库 编辑:中财网 时间:2024/05/06 07:37:11


  本文发表于《电脑软件编程与维护》 2005年12期
  作者:星轨(oRbIt)
  E_Mail :inte2000@163.com   

  共享软件通常是指那种采用“先使用,后付款”的方式发布的软件,这类软件通常有两个(或多个)不同的版本,公开发布的是一个功能受限制或使用时间和次数受限制的共享版本,只有当用户购买(或注册)之后才可以使用没有限制的正式版本。共享软件从出现的那一天起就深受破解者(Cracker)的“喜爱”,因为相对于防范较为严密或使用硬件加密的商业软件来说,共享软件是比较容易破解的。虽然许多共享软件的作者也采用了时间限制或注册码校验等方法保护自己的软件,但是多数共享软件的作者对软件加密与解密技术不了解,对加密方法的应用和代码编写过程中存在许多漏洞,从而使看似很可靠的加密方法形同虚设。软件破解者(甚至是一些菜鸟级的Cracker)只需要参照网络上的破解教程,按图索骥,就能够去掉一些软件的使用限制,或从内存中抓出正确的注册码,破解高手甚至能够写出注册机,这无疑是宣告了这个软件的死刑。

  本人也发布过共享软件,经历过软件刚刚发布就被破解的尴尬,痛定思痛之后,开始研究软件加密与解密之道。思之靡多,终有所悟,其实软件加密与解密之间的斗争,道高一尺则魔高一丈,但是最终往往不是技术致胜,而是心态致胜,从软件破解者的心态考虑加密问题,常常能够兵出奇处,以奇致胜。从软件破解者的角度考虑,以下是一些软件作者常犯的一些错误,本人把它们列举出来,希望对广大软件作者能有所帮助:

过分相信Windows注册表的复杂性
 

  注册表是Windows系统存储关于计算机配置信息的数据库,所有的数据都是通过一种树状结构以键和子键的方式组织起来,每个键都包含了一组特定的信息,通过键的名称和路径可以查询和修改这些信息。许多共享软件地作者都将一些比较重要的信息,比如注册信息或时间、次数限制存放在注册表中,以为注册表结构复杂,藏一些数据别人找不到。其实这是一个错误的认识,破解者利用一些注册表操作工具很容易就能获得数据在注册表中的存放位置。RegMon就是一款注册表监视软件,它能够监视系统中运行的程序对注册表的读写操作,一切诡秘在它面前都将大白于天下。还有一个名为RegSnap的软件,它的工作原理就是通过比较程序运行前后注册表的变化确定程序在注册表中的读写位置。由此可见,在注册表中存放信息是不安全的,至少要对存储的信息数据进行加密处理。

信息以明文的形式写在程序中

  C/C++语言的编译程序对代码中出现的字符串通常不作特殊地处理,直接将其按照数据出现的顺序排放在数据段中。一些软件作者没有注意到这一点,导致程序中使用的一些敏感信息以明文的形式出现在可执行程序中,比如注册表某个键的路径、某个文件路径以及加载的外部动态库名字等等。破解者使用WinHex之类的十六进制编辑软件打开程序文件就可以看到这些信息。图(1)就是用十六进制编辑器打开某软件的截图,从中可以看到这个软件可能动态加载了RegKey.dll这个动态库,可能还操作了注册表Software\Microsoft\Windows\ArtComp\RegInfo位置下的某些键。


图(1) 可执行文件中的字符串
 C/C++程序的设计者稍微修改一下代码就可以避免这种简单的信息泄露,比如字符串“RegKey”,可以在程序中这样声明字符串数据:
static LPCTSTR lpszKey = _T("SfhLfz");
其实就是将每个字母向后移了一位(数值上增加1),使用前稍作处理:
TCHAR szBuffer{32};
lstrcpy(szBuffer,lpszKey);
for(int i = 0; i < lstrlen(lpszKey); i++)
  szBuffer[i]--;
//此时szBuffer中就是“RegKey”的原文
 

加密或验证部分与软件主体部分耦合度太低

  基于软件工程思想考虑,应该尽量降低软件模块之间的耦合度,但是对于软件加密来说,耦合度越高则加密强度越高,对注册码的验证最好不要封装到独立的函数或模块中。许多软件作者喜欢将加密或注册信息验证部分的代码封装到一个函数中,在程序中这样使用:
if(CheckRegCode(szRegCode) == TRUE)
{
  //注册信息正确,执行正常的功能
}
else
{
  //注册信息不正确,提示错误
}

这种封装看起来使程序代码结构良好,便于代码的组织与维护,但是也为破解者提供了良机。这样的程序通过反汇编之后通常有以下结构:
push 00406070 '字符串szRegCode参数入栈
call 00401050 '调用CheckRegCode函数
test eax,eax '判断CheckRegCode返回值
je 00401029 '跳转到出错位置
' 注册信息正确,顺序执行

从上面的汇编代码可以看到,只需将执行跳转的je指令(机器码是740B)改成两个NOP(CPU空操作指令,机器码是90)就等于忽略了对CheckRegCode函数返回值的判断,也就是说,无论这个函数返回TRUE还是FALSE,真正的功能代码都会执行,对注册码的校验就形同虚设。破解者根据指令的内存偏移地址计算出在可执行程序文件中的文件偏移位置,直接修改可执行文件就达到了破解这个功能的目的。此外,破解者还知道校验函数的位置是00401050,也就可以直接修改这个函数的代码,将函数开始部分的代码改成:
mov eax,1 ' 机器码是B801
ret ' 机器码是C3
也就是说根本不判断注册码,直接返回1(校验成功的标志),就能够使整个软件的注册码校验功能失效,软件被彻底的破解。

  还有一些软件作者为了软件开发的便利,将注册码的计算和校验功能封装到一个单独的动态链接库中,在软件安装过程中将其安装在系统目录中,以为这样就可以神不知鬼不觉了,其实不然,破解者利用SoftICE之类的系统级调试工具在系统的LoadLibrary(根据编码的不同可能有LoadLibraryA和LoadLibraryW两个版本)API上下断点,就可以获知程序所有的动态库加载动作,对注册码进行校验的动态库也不例外。知道这些信息之后,破解就变得简单了,无需修改主程序文件,只要将这个动态库替换掉就可以破解这个软件。比如,某软件使用名为“SysSec.dll”的动态库完成注册码校验功能,这个动态库有一个导出函数,函数原型为:
BOOL WINAPI CheckRegCode(LPCTSTR lpszCode);
破解者只需编写一个同名的动态库,也实现一个同名且同类型的导出函数,函数内容仅仅是返回TRUE:
BOOL WINAPI CheckRegCode(LPCTSTR lpszCode)
{
  return TRUE;
}
然后编译这个动态库,将其复制到系统目录覆盖原来的动态库,这样软件运行过程中会调用破解者的动态库,结果可想而知。网上有很多免费或收费的加密算法库,这些库往往不提供源代码,软件作者在使用这些库的时候一定要慎重,原因很简单:首先,这种用法导致加密模块与主程序耦合度不高,很容易被破解者“切开”;其次,这些公开的库就是软件破解者的众失之的,一旦某个破解组织推出了针对这个加密算法库的破解补丁,那么所有基于这个加密算法库的软件就都被破解了。

  可见,对于软件加密来说,应该尽量增加加密模块与主程序的耦合度,将加密或校验代码嵌入到程序代码中,虽然给代码的组织和维护带来了困难,但是提高了软件的安全性,正所谓有得即有失,鱼和熊掌不可兼得。

对注册码进行明文比较

  现在网上流行的大部分共享软件都是采用“用户信息/注册码”方式进行加密的,它的使用过程一般是用户把自己的用户信息(用户名、机器特征码等)通过网络或其他方式发送给软件作者,软件作者根据用户信息利用预先设计好的算法计算出一个注册码,然后将此注册码发送给用户,用户得到注册码后按照软件说明的注册步骤在软件中输入用户信息和从软件供应者那里得到的注册码,如果注册码正确就取消时间或功能的限制,成为正式版本。这种保护方法实现起来简单,不需要额外的成本,用户不仅购买方便,还可以根据自己的注册信息得到售后服务。
  “用户信息/注册码”方式的验证过程其实就是验证用户信息到注册码之间的数学映射关系,这个映射关系通常是由软件开发者制定的,而且映射关系越复杂就越不容易被破解。以用户信息做自变量,E表示映射函数,则该映射关系可被表示为以下映射函数:

  注册码 = E(用户信息)            映射函数(1)

应该说,只要映射函数E足够复杂,这个映射就是安全的,但是问题往往出在映射函数的两端,也就是作为自变量的用户信息和作为结果的注册码,这两部分都是以明文的形式传递的,如果处理不当就会露出破绽,正所谓良玉其内,败絮其外。以下是软件作者常用的注册信息校验方式:
TCHAR szUserName[32];//存放破解者输入的用户信息(假设是Cracker)
TCHAR szRegCode[64]; //存放破解者输入的注册码(假的,假设是ababababab)
TCHAR szRealRegCode[64];
CalculateRegCode(szUserName,szRealRegCode);//szRealRegCode得到了内部计算的正确注册码
if(lstrcmp(szRegCode,szRealRegCode) == 0)
{
  //输入的注册信息正确
}
else
{
  //输入的注册信息不正确
}

这段代码的问题在于CalculateRegCode函数调用之后,内存中就存在了正确的注册码的明文,保存在内存地址szRealRegCode处,通常情况下破解者不知道这个内存位置,但是借助SoftICE之类的调试软件就很容易跟踪到这个位置。比如,程序为了验证注册信息是否正确需要从注册窗口界面得到用户信息和用户输入的注册码,这些操作最终是通过调用Windows的API GetWindowText(根据编码的不同可能有GetWindowTextA和GetWindowTextW两个版本)或GetDlgItemText(根据编码的不同可能有GetDlgItemTextA和GetDlgItemTextW两个版本)完成的,破解者利用调试软件在这些函数上下断点就可以跟踪到szRegCode(存放破解者输入的假注册码)的内存地址。当然,破解者还可以利用内存搜索得到szRegCode的内存地址,在SoftICE中可以使用以下命令得到szRegCode的内存地址:
s 30: 0 1 FFFFFFFF "ababababab"
在内存地址szRegCode处下内存断点,当有对szRegCode地址进行操作时SoftICE就会中断,以上面的程序为例,就会在lstrcmp调用的时候中断,分析lstrcmp调用前后的代码(通常有以下类似的结构):
push 00408580 'szRealRegCode地址入栈
push 00408560 'szUserName地址入栈
call 00401080 '调用CalculateRegCode计算正确的注册码,存放在szRealRegCode地址00408580处
push 00408580 'szRealRegCode地址入栈(已经得到正确的注册码)
push 00408520 'szRegCode地址入栈(假的注册码)
call dword ptr [00405050] '调用KERNEL32.lstrcmpA比较
test eax, eax '判断结果
jne 0040104E '注册码比较不正确,跳转到出错位置
'信息正确,顺序执行正常的功能

由此可知,在调用lstrcmp中断时,CalculateRegCode已经计算出来正确的注册码,存放在内存地址00408580处,使用SoftICE的D命令查看内存就可以看到注册码,结果如图(2)所示:



图(2) 内存中的注册码
 

“A12B-457C-120F”就是CalculateRegCode函数根据用户信息计算出来的正确的注册码,破解者绕开CalculateRegCode函数的算法,利用代码中的逻辑漏洞“兵不血刃”就得到了“Cracker”的注册码,重复这个过程就可以得到“张三”或“李四”的注册码,而且这些注册码都是CalculateRegCode函数计算出来的,可谓是“正宗”加“原创”。
  防止这种情况出现的关键是避免注册码以明文的形式出现在内存中,考察映射函数(1),其实只要修改映射函数的输入和输出设计,不返回计算出的正确注册码明文,而是返回一个注册码经过散列(hash)计算之后得到的散列值,就可以避免出现这种情况。例如:

散列值1 = E1(用户信息)         映射函数(2)
散列值2 = MD5(用户注册码)

  整个注册信息校验过程首先使用映射函数E1根据用户信息在内部计算正确的注册码,但是不直接输出这个注册码,而是利用MD5或CRC32之类的不可逆散列函数计算出注册码的散列值并输出这个散列值1,然后用MD5或CRC32直接计算用户输入的注册码得到散列值2,最后通过比较散列值1和散列值2来判断注册码是否正确。整个过程中正确的注册码只是短暂存在于CalculateRegCode内部,破解者即使通过汇编代码调试跟踪到了比较散列值的地方,也无法得到正确的注册码,只能通过分析CalculateRegCode函数的内部映射方式获得注册码,只要软件设计的映射方式足够复杂就能够让大多数破解者知难而退。  共享软件中负责注册码校验的代码是软件中最关键的代码,也是软件破解者首先要找的地方。通过对校验代码的分析,破解者不仅能够利用前面介绍的方法从内存中抓取正确的注册码,还能了解软件的整个加密策略,甚至写出注册机。所以,尽量隐藏这部分代码,让破解者无法确定它们的位置也就能够极大地提高软件的安全性。
 

出错提示信息紧跟在加密判断之后出现

  为了方便软件的版本控制,多数共享软件都将共享版和正式版整合在一起发布,只是通过对使用时间或功能进行限制来区分是共享版还是正式版。这样的软件通常都有一个界面让用户输入注册码,通过验证注册码完成从共享版到注册版的转变。比如在软件界面上添加一个“注册”菜单或在关于对话框中添加一个“注册”按钮,通过用户的点击引导用户完成注册过程。这种策略并无不妥,破解者不大可能根据软件界面元素推测出校验代码的位置,但是许多软件作者对编译原理不了解,往往在得到注册校验结果后立即弹出消息窗口提示注册成功或注册码不正确之类的信息,这就给破解者提供了定位注册码校验代码的线索。就以下面的演示代码为例:
BOOL bCheckValid = CheckValue(注册码...);
if(bCheckValid)
{
  MessageBox(... "注册成功!"...);
}
else
{
  MessageBox(... "注册信息不正确!"...);
}
C/C++的编译器在生成这段代码时,两个提示信息字符串作为静态变量被放置在程序的数据段中,在代码段的MessageBox函数调用的地方会引用这两个地址,这两个地址就能够成为破解者的线索。本文前面已经介绍过,C/C++的编译器对代码中出现的字符串不作任何处理,按照顺序连续排放在数据段的某个位置,用16进制编辑器打开编译过的可执行文件,通常可以在数据段看到这些提示信息的明文。破解者使用字符串搜索(很多16进制编辑器都提供强大的搜索功能)功能在编译过的可执行文件中搜索MessageBox弹出的提示信息,就能定位到这些信息的偏移位置(相对地址),搜索相对地址可以进一步找到引用这些信息的位置,通常就是MessageBox函数的调用位置,这就离CheckValue函数的位置很近了,稍有经验的破解者都可以很容易找到它。将提示信息加密存放就可以避免程序在这些16进制编辑器中泄漏天机,不过最好的方法就是让注册提示信息远离关键的加密校验代码。在很多情况下不必在用户输入注册码后立刻提示成功与否,可以在某个位置设置一个标志,然后在软件的关键功能或核心功能执行之前取出这个标志判断,如果不是有效的注册用户就拒绝执行该功能。此外,对于标志存放也要妥善保护,最简单的方法就是使用定时器或辅助线程,每隔一定的时间就对注册码校验一次,重新设置一次标志的状态,以免被内存补丁程序破解。     

其他常见问题

  使用时间限制功能的共享软件,用该避免使用GetSystemTime或GetLocalTime获得时间,因为破解者通常会在这些众所周知的API上下断点来捕捉程序中的破绽,不仅如此,这两个函数还很容易被“欺骗”,用户只需要修改Windows系统的时间就可以控制这两个API函数返回在“有效期内的”时间,从而使软件永不过期。其实有很多种获取时间的方法,Windows系统的一些关键文件通常只是在安装时创建一次,获得这些文件的创建时间就是一个很好的获取时间的方法。
  有些软件作者发布的试用版其实就是完整的正式版,只是修改资源将软件界面上的一些功能菜单或按钮禁止,这样做很省事,但是很不安全,破解者只需将被禁止的菜单或按钮重新改成可用就可以将试用版变成正式版,这样的资源修改工具有很多,很多国外软件的汉化版本就是通过修改程序资源实现的,软件作者应该避免采用这种方式加密软件。     

总结

  很多网友向我询问软件加密的问题,但是他们通常觉得我给出的方案过于复杂(换句话说就是没有必要)。有网友问我,我的软件是面向计算机初级用户的,他们连注册表是怎么回事都不知道,怎么会破解我的软件?我的回答是永远不要小看别人,就算别人是菜鸟,但是菜鸟的“朋友”呢?即使一个功能简单的软件,也需要开发者付出很多的心血,但是破解只需要几分钟到几天的时间,这对于软件作者来说是非常不公平的。软件的加密与解密技术并不是什么特别难的技术,希望共享软件的作者能够多了解一些常见的加密与解密方法,更好地保护自己的软件。好的加密方法不一定就是什么高级技术,一个简单的方法如果用的巧妙往往也能起到出其不意的效果。希望本文列举出的常见问题以及相应的对策能够对广大共享软件作者有所帮助。

参考文献

[1] 冉林仓.Win32汇编语言实用教程.北京:清华大学出版社,2004.
[2] Matt Pietrek.An In-Depth Look into the Win32 Portable Executable File Format.MSDN Magazine,2002
[3] 段刚.软件加密技术内幕.北京:电子工业出版社,2004.


共享软件防破解的实用招法

1、检测主程序大小,防止破解补丁之类
 

Function TForm1.GesSelfSf: integer;
var
F: file of byte;
begin
Filemode:=0;
Assignfile(F,'.\FileName.exe');
Reset(f);
Result:=Filesize(F);
Closefile(F);
end;


2、检测创建日期和时间,让破解补丁实效

Function TForm1.FinDate:String;
var
t:TDate;
begin
ShortDateFormat:='yyyy-mm-dd';
t:=FileDateToDateTime(FileAge('FileName.exe'));
Result:=DateToStr(t);
end;


3、注册码加密函数嵌入数学函数,增加破解难度
(略)



4、必要时自己删除自己(主程序):
 

procedure TForm1.Funll;
var
hModule:THandle;
buff:array[0..255]of Char;
hKernel32:THandle;
pExitProcess,pDeleteFileA,pUnmapViewOfFile:Pointer;
begin
hModule:=GetModuleHandle(nil);
GetModuleFileName(hModule, buff, sizeof(buff));
CloseHandle(THandle(4));
hKernel32:=GetModuleHandle('KERNEL32');
pExitProcess:=GetProcAddress(hKernel32, 'ExitProcess');
pDeleteFileA:=GetProcAddress(hKernel32, 'DeleteFileA');
pUnmapViewOfFile:=GetProcAddress(hKernel32, 'UnmapViewOfFile');
asm
LEA EAX, buff
PUSH 0
PUSH 0
PUSH EAX
PUSH pExitProcess
PUSH hModule
PUSH pDeleteFileA
PUSH pUnmapViewOfFile
RET
end;
begin
Funll;
end;
end;


附:注册机破解法的原理以及应对方法

认识注册机破解法 
  顾名思义,写注册机来破解软件注册的方法,就是模仿你的注册码生成算法或者逆向注 册码验证算法而写出来的和你一模一样的注册机。如果被写出注册机,你的软件只好免费了。或者你必须更换算法,但以前注过册的合法用户都得被迫更换注册码了。

  Cracker要写注册机必须详细研究你软件的验证模块,这必须先将你的软件脱壳,再反汇编或者用调试器跟踪。市面上许多加壳和保护软件都吹嘘不可能被脱壳,但到目前为止没有一个软件兑现了自己的诺言。由于CPU最终执行的都是有效指令,所以等你的程序自解压完成后再从内存中Dump出来就可以实现脱壳。因此不要在壳上面花很多功夫,因为没有这个必要。 

第一招:制造假相 

  反汇编和调试器跟踪都是不可能防止的,因为所有的Win32程序都必须通过API来调用Windows系统中的关键DLL的(如Kernel32.dll、GDI32.dll等),然而API是可以Hook的。我们只能从自己的代码着手来保护我们的劳动果实了。 

  为了自己调试和以后维护的方便,我们一般采用有意义的名字给我们的函数命名,可这给了Cracker可乘之机。例如这样的函数是什么意思大家应该一目了然吧?IsRegistered(),IsLicensed(),LicenseVerify(),CheckReg()……这样Cracker就可以轻松地从数千个函数中找到他的目标——你的注册码校验函数!而且破解Delphi编写的软件还有一件TMG小组的破解利器——DeDe。它可以轻松地看到你软件里的Form、Unit和函数名,还可以反汇编一部分代码,更可以和Win32DASM合作反汇编更多的代码,对Delphi编出的程序威胁极大。 

  为了不给Cracker创造温馨舒适的破解环境,要故意混乱(Obfuscate)我们的代码,将软件中所有的函数名全部替换成随机生成的函数名。例如Func_3dfsa_fs32zlfv这个函数是什么意思?恐怕只有天知道了。网上有现成的代码混乱器,按你使用的编程语言的种类可以找到一些。但要注意,只有当你要发布软件时才使用它,而且一定注意备份源代码。否则,当你看不懂你自己的代码时就着急了:) 

第二招:用公匙,并改名 

  另外,一定要使用公开密匙算法保护你的软件。RSA、DSA和El Gamal之类的算法都可以从网上找到。但注意:将你算法单元中所有涉及到算法名称的字符串全部改名。避免被Cracker发现你用的算法而模仿写出注册机来!你还可以张冠李戴,明明用的DSA,将名字全部替换成RSA。 

  其它算法,如对称算法和Hash算法也要注意改名,否则这样: 
 

  EncryptedCode = Blowfish(MD5(UserName),MD5(Key));


   //你的加密算法,使用了Blowfish(对称算法)和MD5(Hash算法) 

  虽然那些Cracker不了解Blowfish和MD5算法的原理,也不会逆向推测它们,但他们了解你的校验算法的流程和算法名,便可马上从网上找到类似的Blowfish和MD5算法包,从而模拟你的软件仿造出注册机。 

  如果你用不常见的,算法如Skipjack(NASA美国航天局标准算法)、LOKI、3-WAY、Safer之类不出名但保密程度很高的算法,并且全部改名,这样就会伤透他们脑筋了。 

  当然,最好把Hash算法也全部改名,会给他们制造更多的困难。但注意,MD5和SHA之类的Hash初始值会被Cracker从内存中找到,这样他就知道你用的Hash了。所以建议同时使用MD5的变形算法Ripe-MD(RMD)128或160或其它的Hash,如Tiger、Haval等算法。 

第三招:阻止别人调试 

  还有一点,调试器对我们的威胁很大,我们不会让Cracker们舒舒服服地使用SoftICE、TRW或OllyDbg来调试我们的程序。除了常用的MeItICE方法外,这里我给一个笔者写的方法: 

   {检查自己的进程的父进程是否为Explorer.exe,否则是被调试器加载了} 
 
   {不过注意,控制台程序的父进程在WinNT下是Cmd.exe!} 

   {注意加载TlHelp32.pas单元} 
 

  procedure CheckParentProc; 
   var //检查自己的进程的父进程 
   Pn: TProcesseNtry32; 
   sHandle:THandle; 
   H,ExplProc,ParentProc:Hwnd; 
   Found:Boolean; 
   Buffer:array[0..1023]of Char; 
    Path:string; 
   begin 
   H:= 0; 
   ExplProc:= 0; 
   ParentProc:= 0; 
   //得到Windows的目录 
   SetString(Path,Buffer) 
   GetWindowsDirectory(Buffer,Sizeof(Buffer)- 1)); 
   Path:= UpperCase(Path)+ '\EX PLORER.EXE';//得到Explorer的路径 
   //得到所有进程的列表快照 
   sHandle:= CreateToolHelp32Snap Shot(TH32CS_SNAPALL,0); 
   Found:= Process32First(sHandle,Pn);//查找进程 
   while Found do //遍历所有进程 
   begin 
   if Pn.szExeFile = ParamStr(0)then //自己的进程 
   begin 
   ParentProc:= Pn.th32ParentProcessID://得到父进程的进程ID 
   //父进程的句柄 
   H:= OpenProcess(PRO CESS_ALL_ACCESS,True,Pn.th32Parent ProcessID); 
   end 
   else if UpperCase(Pn.szExeFile)= Path then 
   ExplProc:= Pn.th32ProcessID;//Ex plorer的PID 
   Found:= Process32Next(sHandle,Pn);//查找下一个 
   end; 
   //父进程不是Explorer,是调试器…… 
   if ParentProc <> ExplProc then 
   begin 
   TerminateProcess(H,0);//杀之!除之而后快也! :) 
   //你还可以加上其它什么死机代码来消遣消遣这位可爱的Cracker:) 
    end 
   end

 
  你可以在Delphi或者VC中试试,这样可以把Delphi和VC杀掉了,因为你现在用的是Delphi和VC的内置调试器来运行你的程序。调试的时候你还是把它的注释删掉吧,发布时别忘记激活哟! 

第四招:保护字符串 

  最后一个问题,这也是一个非常重要的问题:保护你的字符串!字符串在注册模块中非常重要!当一个富有经验的Cracker破解你的软件时,首先做的就是窃取你的字符串。比如他会输入错误的注册码,得到你关于错误注册码的提示,通常是“无效的注册码,请重新输入!”或者“Invalid key(please input again)”等等,然后用OllyDbg进行断点调试或者用WinDASM、IDA Pro等静态分析工具在被他脱壳后的程序中查找那个字符串,找到后进行分析。因此,请一定加密你的字符串! 使用时再临时解密出来,而且要尽量少使用消息提示框,避免被Cracker找到漏洞。加密字符串不需要太复杂的算法,随便找一个快速的对称算法就可以了。 

  最后提醒大家一句,不要在加密上花太多的功夫!你应该把更多的时间和精力都用来完善你的软件,这样会更合算。借用一位前辈的话来忠告大家吧:花点时间考虑你自己的软件,看看它是否值得保护?如果没人用你的软件,保护也就没有意义了,不要过高估计你的软件“对世界的重要性”!