共享软件是软件业目前世界上比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报;但,实际并非如此,绝大多数的人都弑羽而归。值得注意的是:除了选题和技术上的原因外,最大的原因就是共享软件被破解(Crack)了。
破解见得多了,不免有些麻木。大部分作者都是新软件发布一个星期左右甚至一天之内就会在网上发现注册机或者被修改过的软件(行话称之为“爆破”)。破解者制作了英文、中文、俄文、德文等语种的注册机大肆发散不说,还常常给作者寄一份,外加一封挖苦辱骂的信。唉!我们得罪了谁?没日没夜地熬夜编码,难道得到的就是这连绵的挖苦和不尽的羞辱吗?
不!决不!我们有理由也有能力保护自己的劳动成果!但问题是:如何保护?关注国内,网上关于破解资料和教程俯拾皆是,而关于软件保护方面的资料则是凤毛麟角(大多都关系到什么技术垄断),这种畸形现状就导致了相当一部分朋友的加密非常脆弱甚至可以称得上是“弱智” !要知道,你要面对的是已经形成团伙的众多破解高手呀,国内的什么CCG、BCG,国外的eGis、King、Core、TNT、DAMN和TMG,皆为水平一流的破解组织。全球盗版软件不少于80%都是由他们的破解的,技术实力连大软件公司都不可小视。
看到这里,你是否已经已经灰心了?别怕,虽然我们理论上无法完全避免被破解,但如果能够有效地拖延被破解的时间,并充分打击破解者的自信心,是可以让破解者无法忍受这种折磨从而最终放弃的。
破解,通常的做法有两种——暴力破解(爆破)和写注册机。下面我就来依次讲解每种破解方法的原理和应对方法,这些都是鄙人积累的一些共享软件保护经验,某些关键地方还有例程讲解(Delphi代码,使用C++和VB的朋友可以自己稍微修改一下),希望能对新手们有些帮助,能够更有效地保护自己的劳动成果。
§暴力破解(爆破)
这是最常见,也是最简单的破解的方法。该法最适合于对付没有CRC效验的软件,破解新手乐于采用。
大凡共享软件,验证是否注册大多数要采用if条件语句来进行判断,即使你采用了什么RSA或ECC等强力加密算法,也免不了使用if条件语句。呵呵,这里就是共享软件最为危险的地方哦,当然也是爆破手孜孜不倦所寻求的目标呀!
例如,你的注册函数类似如下:
{ 利用RSA进行注册码的数字签名验证 }
if RSAVerify(MD5(Key), MD5(Code), e, n) then
ShowMessage('注册成功!')
else
ShowMessage('注册失败!');
{ 这里Key是用户输入的注册码,是由你发送给注册用户的 }
{ Code是根据用户输入的用户名自动计算出来的注册码 }
{ e是RSA算法的公匙,而n是RSA算法的模数。 }
这个注册函数即使使用了强劲的RSA算法进行注册码验证,可是依然很容易被破解,我们只要把这里修改为:
{ 将逻辑判断改为否 }
if not RSAVerify(MD5(Key), MD5(Code), e, n) then
ShowMessage('注册成功!')
else
ShowMessage('注册失败!');
就可以了。这时戏剧性的结果会产生:随便输入任何注册码都可以注册通过,相反输入正确的注册码却无法通过注册。:) 其具体操作是先反汇编或者跟踪你的程序,找到判断注册码的cmp、test等汇编指令后的关键跳转指令处,通常是je、jz之类的汇编指令,把它们修改为jne或jnz即可,这样常常只需要修改一个字节就可以完美破解之。:)
令人遗憾的是,目前大部分共享软件都是这样进行判断的,这也是为什么网上被破解的软件铺天盖地的主要原因。因为这样破解实在是太简单了...
难道没有什么可以防止的方法吗?当然有啊!只要把软件的关键代码嵌入到注册码或者注册文件中就可以充分防止破解。但现在问题是,怎么嵌入呢?
回复 1楼 2006-06-27 21:33 举报 |
我不自豪567
最简单的方法就是把关键代码(你的软件功能限制部分最关键而且最简单的一个函数)做成一个小Dll(动态链接库),用强力对称算法加密(密匙可以是主程序某一固定不变的部分或壳的特征Hash值)后生成一个注册文件(License文件,呵呵,格式只有你知道哦!),或者Base64编码后生成一个注册表文件,用户可以双击导入注册表内。
效验流程如下:已注册用户验证注册码时,先验证有没有文件,没有文件者自然受限制的功能无法使用。如果有注册文件,解密之即生成一个小临时文件。如果主程序被脱壳或者被修改(爆破),自然Hash值密码不符,解密出来的肯定都是垃圾码,没有一点用处。只有没有被修改的主程序才能正确的解码,而且当然只有解密正确的文件才是一个真正的Dll文件,才能被GetProcAddress函数找到欲调用的关键函数地址。这样只有已注册用户才可以享受到你软件的全部功能了。
如此一来,Cracker破解你的软件就变得很困难了:
首先,他如果没有注册文件,即使他把主程序脱壳了,由于受限制的部分和注册文件是关联的,他也根本无法修补完整。
第二,即使他得到了你的注册文件,由于是加密文件,他也无法直接利用之,这样就逼迫他去拆解你的算法,这可是他们最不愿意的碰到的事情哦!如果到了这一步,我想99%的Cracker都是会放弃的,呵呵,只有真正对加密算法有研究的Cracker高手才会继续破解下去。
第三,你是可以用些小技巧来使他的生活更加痛苦一些的,呵呵。这里我推荐大家使用DSA公开密匙加密算法,它和RSA一样,可以进行数字签名(RSA还可以加密,DSA则只能进行数字签名)。我这里选用它的原因就是它有一项非常实用的特性:随机数填充机制。即DSA每次签名都要使用一个随机数K,正因为有这个K的存在,即使是相同的用户名和机器识别码,由DSA加密过的每份注册文件都不会相同。这对Cracker拆解你的注册文件来说是一个极大的障碍。
第四,即使他得到了解密后的Dll文件,他也需要大幅度地修改主程序或者把你的Dll部分的关键代码拆出来填到主可执行文件中。呵呵,这就看他对PE文件格式理解得如何了。
即使这样,如果你的程序中有大量的Hash效验和死机代码,呵呵,你就耐心等着我们可爱的Cracker同志吐血吧„„:)
所以记住啊:用完这个Dll临时文件后立即从内存中卸载此Dll并删掉,而且注意在解密之前探测一下,系统中有没有FileMon这个威胁极大的探测器呀!
{ 探测FileMon }
function DetectFileMon: Boolean;
begin
if CreateFile(PChar('\\.\FILEVXD'),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0) <> INVALID_HANDLE_VALUE then
Result := True //如果有,就Down机!
else
Result := False;
end;
当然,你可以保护得更好一些:可以不采用临时Dll,而把解密后的关键代码用WriteProcessMemory这个API函数写入到主可执行文件自己进程被提交(Committed)的内存页面的指定位置去。这样由于磁盘上没有解密后的临时文件,破解更加困难。事实上,目前世界上最强劲的专业保护软件Armadillo就是用的这种方法。而且这种方法可以充分防止被调试器Dump。但实现起来比较困难,尤其是在WinNT 5以后的操作系统中。
由于这种方法将注册文件和受限制代码唯一关联,爆破手拿到你的软件也只有干瞪眼。建议大家都给共享软件加上功能限制,这样比时间和次数限制更加安全。
§写注册机
顾名思义,这种方法就是模仿你的注册码生成算法或者逆向注册码验证算法而写出来的和你一模一样的注册机。这玩意威胁极大,被爆破了还可以升级。如果被写出注册机,呵呵,你的软件只好免费了。或者你必须更换算法,但以前注册过的合法用户都得被迫更换注册码了,累死你!呵呵...
回复
2楼 2006-06-27 21:33 举报 |
我不自豪567
上面的方法虽然可以避免爆破,但注册机的威胁还是存在的。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算法)
虽然我不了解Blowfish和MD5算法的原理,也不会逆向它们,但我了解你的效验算法的流程和算法名,我马上就可以从网上找到类似的Blowfish和MD5算法包,从而模拟你的软件仿造出注册机,啊?!真是„„$&*&($#%@!
如果你用的什么其它不常见的算法(如Skipjack (NASA美国航天局标准算法), LOKI, 3-WAY, Safer之类不出名但强度很高的算法),并且全部改名,就让他们去研究软件中成堆的如下代码是什么加密算法吧!:)
0167:005B9F70 MOV EAX,[EBP-10]
0167:005B9F73 CALL 00404000
0167:005B9F78 PUSH EAX
0167:005B9F79 MOV EAX,[EBP-10]
0167:005B9F7C CALL 004041C4
0167:005B9F81 LEA ECX,[EBP-14]
0167:005B9F84 POP EDX
0167:005B9F85 CALL 004B860C
当然,最好把Hash算法也全部改名,给会给他们制造更多的困难。但注意,MD5和SHA之类的Hash的初始值会被Cracker从内存中找到,这样他就知道了你用的Hash了。所有建议同时使用MD5的变形算法Ripe-MD(RMD)128或160和其它的Hash,如Tiger, Haval等算法。
另外,请注意要经常效验你的程序是否被修改(Hash效验),如果被修改则退出。但请注意,有些病毒会修改进程的句柄表和它指向的内核对象,这样病毒就可以直接修改运行中的PE文件而感染之了,另外还有网络传输错误的问题也会导致软件CRC出错。因此请不要认为可执行文件的CRC不符而此时程序已被脱壳了。
回复
3楼 2006-06-27 21:33 举报 |
我不自豪567
其实,程序被脱壳最明显的标志是其大小明显大于脱壳前。1M的PE文件被UPX、ASPack之类的软件压缩后通常只有400左右。如果你的软件在运行中发现自己的大小大于800K,我想你应该知道如何做了吧?呵呵... :)
还有一点,调试器对我们的威胁很大,我们不会肯定让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) + '\EXPLORER.EXE'; //得到Explorer的路径 //得到所有进程的列表快照
sHandle := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0);
Found := Process32First(sHandle, Pn); //查找进程
while Found do //遍历所有进程
begin
if Pn.szExeFile = ParamStr(0) then //自己的进程
begin
ParentProc := Pn.th32ParentProcessID; //得到父进程的进程ID //父进程的句柄
H := OpenProcess(PROCESS_ALL_ACCESS, True, Pn.th32ParentProcessID); end
else if UpperCase(Pn.szExeFile) = Path then
ExplProc := Pn.th32ProcessID; //Explorer的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找到漏洞。加密字符串不需要太复杂的算法,随便找一个快速的对称算法就可以了。
最后提醒你一句,不要在加密上花太多的功夫!你应该把更多的时间和精力都用来完善你的软件,这样会更合算。借用一位前辈的话来忠告大家吧:花点时间考虑你自己的软件,看看它是否值得保护?如果没人用你的软件,保护也就没有意义了,不要过高估计你的软件“对世界的重要性”!
这篇文章写得很好,让我对那些花费时间去做盗版并卖钱的人非常气愤。你可以破解,也可以使用,但用破解来卖钱和强盗何异。
共享软件是软件业比较热门的话题,在中国更是如此。成千上万的程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报。但实际并非如此,相当数量的人都弑羽而归。究其原因,除了选题和技术(这两点不在本文的讨论范围之类),最主要的就是破解(Crack)了。
由于中国大陆相关法律的薄弱性和偏袒性,因此国内的盗版和破解现象异常猖獗。对盗版业(如果还能够被称呼为一个产业的话),我们有一个笑话,那就是:美国人的盗版业还处于原始时期(私下地自己破解),西欧的盗版业则是手工作坊时期(用CD-Record私下复制、交换),而中国的盗版业十分宏伟壮丽,已远远领先于他们,进入了大机器工业时代(专人破解、制作、测试,最后用专业压盘机大规模复制和发散),我们应该感到自豪,不是吗?呵呵,我们已经站到了时代的最前沿!
悲哀!不知您看到这里笑不笑得起来?好像盗版是挺自然的事,完全可以目无法纪。下面是中国软件(www.csdn.net)上一些做共享兄弟们的真实故事(原句):“发布了半个月,就被强行破解, 2.0改进的一点算法,不能被强行破解,但发布近一月就被写注册机。3.0才发三天,又被写注册机。这软件我花了三年时间写的。好辛苦,得不偿失。我真想不通,编程高手们都做破解高手去了吗?这些网站怎么不会被人黑?”还有:“才一个星期就被破解了,黑客还跑到我的论坛上散布注册码!”,“上午才提交到Download上,下午就有注册机了,那位老兄还给我做了一个俄文版!”,“太狂了,Cracker破解了我的软件后,还给我发了一份!全都是国内的人干的……”。而这种帖子几乎每天都有,哭天抢地,唉!我们得罪了谁?没日没夜地熬夜遍程,我们得到的就是连绵的挖苦和不尽的羞辱吗?
不!决不!我们有理由也有能力保护自己的劳动成果!但问题是:如何保护?关注国内,网上关于破解资料和教程俯拾皆是,而关于软件保护方面的资料则是凤毛麟角;唯一的一本《加密与解密——软件保护技术及完全解决方案》还算不错,可我怎么看都像是一本Cracker教程!共享论坛上也都只是涉及一点点毛皮(都怕暴露自己的算法),因此只有靠自己钻研。下面是我的一些经验,某些关键地方还有例程(Delphi代码,使用C++的朋友自己稍微修改一下)。^_^
一、字符串
字符串在注册模块中非常重要!当一个Cracker破解你的软件时,首先做的就是摄取你的字符串。比如他会输入错误的注册码,得到你关于错误注册码的提示,通常是“无效的注册码,请重新输入!”或者“Invalid key, please input again!”等等,然后用WinDASM或IDA Pro等静态分析工具在被他脱壳后的程序中查找那个字符串,找到后进行分析。如果你的注册算法很简单,那么你就倒霉了,注册机诞生了!
我们来做个实验,假设我有固定的注册码(序列号),而且这个注册码放在程序中: procedure TForm1.Button1Click(Sender: TObject); begin if Edit1.Text <> 'Registration Code' then ShowMessage('无效的注册码,请重新输入!') else ShowMessage('注册成功!'); end;
然后用Delphi5编译、连接后得到一个.exe程序,用WinDAsm打开之,反汇编,点击“参考”——“串式参考”,就可以看到所有程序中用到的字符串,找到你眼熟的那一句“无效的注册码,请重新输入!”,如图01所示:
双击定位,就……注册码出来了!看到没有?上面绿色的“Registration Code”就是,如图02所示!
当然,一般的软件没这么简单,但这绝对是一个入口点,找到了入口,仔细分析程序的汇编代码,稍加修改就可以写出注册机来。拼音加加的注册机就是这样的,如果没有注册,它会修改你浏览器的首页,强迫你每次浏览它的网页。你可以随便用什么十六进制编辑器打开拼音加加的主程序,查找它的网址,找到后改为你的首页或“about:blank”就破解了,注册机也可以在5分钟内写出来!
因此,我们要保护我们的字符串!让Cracker连入口都找不到!方法很简单:加密字符串。将你所用过的所有字符串加密既可。如下:
const {加密过后的字符串} Str01 = '旺Q?量?T伝栲A0j橝姜U'; Str02 = '?C?齫\'; Str03 = 'Q﹊tpち犻乕S頊';
procedure TForm1.Button1Click(Sender: TObject); begin if Edit1.Text <> EncodeString(Str03) then //使用时临时解密
ShowMessage(EncodeString(Str01)) //使用时临时解密 else
ShowMessage(EncodeString(Str02)); //使用时临时解密 end;
好,编译连接后,在用WinDAsm试试,还找得到吗?
字符串加密的算法在网上有现成的代码可以用,你也可以自己写。当然你还可以发挥一下:将一句话拆开,用不同的方法加密,使用时解密后再组合起来。还可以同时将大量无用的字符串也加密,放到一起进行灌水,迷惑Cracker,让他不知道你会用哪一个。甚至你可以将敏感的假明文放到程序中,让Cracker自以为已得手,却被你引向一个死机的陷阱!^_^
二、算法
由于并不是每个程序员都是对密码学有研究,因此建议您如果有时间,好好研究一下加密算法,这对您非常有好处!我强烈推荐大家使用RSA算法,只要是使用得法,被破解的可能性相当小。
加密算法大致分为三种:对称加密算法、非对称加密算法和单向散列算法。对称算法就像你家的门,开门和关门都用一把钥匙,此类算法有DES、IDEA、Blowfish、GOST、RC6等等;非对称算法就是一种特殊的门,关门和开门使用不同的钥匙,此类算法有RSA、DSA、PGP等等;单向散列算法顾名思义,就是只能加密,不能解密(其实严格意义上它们不能叫做加密算法),此类算法有CRC(面熟吧,就是WinZip和WinRar中常见的出错信息)、MD5、SHA等等。这里我着重讲解第二种的代表:RSA。
确实,很多人使用RSA,这种强力算法,但不少人都用错了!此算法有两个密匙:公匙(e,n)和私匙(d)。生成注册码(加密)用私匙,验证注册码(解密)用公匙。由于私匙根本不会出现在程序中,Cracker就是得到公匙已没有用!这实际上用到的是RSA的数字签名功能,加密强度依赖于密匙(公匙和私匙)的位数。虽然RSA公司还在推荐个人用户使用512位的密匙,但CPU速度越来越快,我建议大家使用1024位的密匙或更高。虽然RSA算法的安全性没有得到数学上的证明,但从上个世纪七十年代到现在经历了各种形式攻击,至今未被完全攻破。况且大名鼎鼎的混合算法PGP的安全保证也很大程度上来自于RSA,具体的例子还有,Offline Explorer采用了仅五百多位的RSA密匙但至今未被攻破,Windows Commander中也只八百多位而已,依然没有注册机(当然注册码被散布还是有的)! RSA的保密性基于一个数学假设:对一个很大的合数进行质因数分解是不可能的。RSA用到的是两个非常大的质数的乘积,用目前的计算机水平是无法分解的。但是这说明不了什么,没有“证明”RSA的安全性。这既不说明分解这个大数是攻击RSA唯一的(或者说是最佳的)途径,也不能证明这种分解真的那么困难。RSA有可能存在一些密码学方面的缺陷,随着数论的发展也许会找到一种耗时以多项式方式增长的分解算法。不过目前这还只是展望,甚至连发展的方向都还没有找到。有三种事物的发展会威胁到RSA的安全性:分解技术、计算机能力的提高和计算机造价的降低。特别是第一条对RSA的威胁最大,因为只要大数分解的问题不解决,做乘法总是比分解因数快得多,计算机能力强大了尽可以加长密匙来防御,因为那时加密也会快得多的。因此目前2048位可以保证你的需要,但5年后就难说了。:-(
目前较好的RSA的源码是由比利时人Walied Othman写的,你可以从这位老兄的网页 http://ace.ulyssis.student.kuleuven.ac.be/~triade/
上得到它的最新版。然后,在你自己生成注册码时使用该算法的数字签名功能(加密算法):
UserCode := Crc(UserName) + Crc(Key); //用户名和机器效验码
Code := RSASign(UserCode, d, n); //用私匙d和公匙n进行数字签名 然后你将注册码发给用户,他输入之并进行验证(解密算法):
UserCode := Crc(UserName) + Crc(Key); //用户名和机器效验码
if RSAVerify(UserCode, Code, e, n) then //使用公匙e, n进行验证
ShowMessage('注册成功!'); //注意字符串要加密 else ShowMessage('无效注册码!');
注意:加密前一定要用单向散列算法处理一遍(本例用的是Crc),因为RSA对选择密文攻击是很脆弱的。由于RSA密文是通过公开渠道传播的(如Internet),攻击者可以获取密文。我们假设攻击者为A,密文收件人为T,A得到了发往T的一份密文c,他想不通过分解质因数的方法得到明文。换句话说,他需要 m = c^d。
为了恢复 m,他找一个随机数 r ,r < n,当然他有T的公匙(e,n)。他计算:
x=r^e % n (用 T 的公匙加密 r) y=x*c % n (将临时密文x与c相乘) t=r^-1 % n
A 知道RSA具有下面的一个特性:
如果 x=r^e % n,那么 r=x^d % n
因此他想办法让T对 y 用T自己的私匙签名(实际上就是把 y 解密了),然后将结果 u=y^d % n 寄回给A。A只要简单地计算:
m = t*u % n
上面结论的推导是这样的:
t*u % n = (r^-1)*(y^d) & n = (r^-1)*(x^d)(c^d) % n = (c^d) % n = m 要防止这种攻击的办法就是不要对外来的随机信息签名,或者只对信息的单向散列特征值签名。在这里就很容易明白为什么要强调Crc的单向性了,因为Crc的结果是不能预定的,就是说A难以凑出一份刚好能产生y这样的Crc特征值的明文来让T签名。
1. 密匙位数要长(最好2048位,如果Cracker用大数分解法进行攻击,至少用目前PC
上的CPU在他有生之年是无法完成的^_^);
2. 私匙d不要出现在软件中(我们论坛上有不少人将私匙d甚至密匙种子p, q都放到
软件了,被轻易破解了后还大骂RSA不管用!^_^);
加密前一定要用单向散列算法处理一遍(如果可能,可以再用Blowfish或DES再加一道密);
这样就可以最大限度利用RSA的数字签名特性。
请注意:不要以为你自己设计的,长达几KB的注册算法可靠,因为不论怎样,真正的注册码还是会在某时某刻出现在内存中的某个位置!用SoftIce/TRW之类的动态跟踪工具可以轻易在内存或CPU寄存器内找到!而RSA之类的非对称加密算法,会使Cracker胡乱输入一个注册码,并试图在你的程序进行注册码比较时窃取你的正确注册码的企图化为泡影(不过你用N KB长的假算法进行灌水倒是一个不错的注意^_^)。 3.
三、探测调试器(Debugger)
SoftIce和TRW几乎是每个Cracker的必备利器,不过我自己喜欢用OllyDbg ^_^,它使用很简单,加载后点击右键,选择“查找”——“所有参考文本字符串”,就可以找到你需要的关键字符串(再次强度字符串加密的作用!),如图03所示:
双击之,来到该代码段,向上找,找到最可能的CMP命令处(这里需要懂汇编语言,作用是比较数据。效验注册码时是比较注册码),按F2下断点,按F9运行该程序,胡乱输入一个注册码,肯定会出现错误注册码对话框,这时该程序就被OllyDbg中断(你刚才在这里下过断点),点击中间的CPU寄存器观察器,注册码以十六进制储存在DS寄存器中(如:图04),转换一下就得到正确的注册码了。
对付调试器比较困难,有效的方法就是灌水,使用大量无用的数据迷惑Cracker,从而使他不知在何处下断点。或者干脆不让Cracker使用调试器,这就要先发现它们,发现有调试器不要给出任何提示,直接退出,或者然他死机(发现RegMon和FileMon后干脆杀掉它们的进程)^_^。调试器和普通程序不同,它们必须运行于Ring0级(普通程序运行于Ring3级),为了实现目的,它们必须使用设备驱动,打开你的“系统信息”,察看“软件环境”——“驱动程序”,就可以看到当前加载的所有设备驱动,如果看到SICE、NTICE之类的,那就是已加载了SoftIce,而TRW、IceDump、Reg Monitor、File Monitor同理。下面是我用的Delphi函数,C++稍稍改写一下既可。代码如下:
function SoftIce9x32: Boolean; //探测Win9x下的SoftIce, 发现为True,否则为False begin {Detect Softice Win9x 32bit} if CreateFile('\\.\SICE', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end;
function SoftIce9x16: Boolean; //探测Dos下的SoftIce, 发现为True,否则为False begin {Detect Softice Win9x 16bit} if _lopen(PChar('\\.\SICE'), OF_READWRITE) <> HFILE_ERROR then Result := True else Result := False; end;
function SoftIceNT32: Boolean; //探测WinNt下的SoftIce, 发现为True,否则为False begin {Detect Softice WinNt 32bit} if CreateFile('\\.\NTICE', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end;
function SoftIceNt16: Boolean; //探测WinNt下的16位的SoftIce, 发现为True,否则为False begin {Detect Softice WinNt 16bit} if
_lopen(PChar('\\.\NTICE'), OF_READWRITE) <> HFILE_ERROR then Result := True else Result := False; end;
function SIWDEBUG: Boolean; //探测Win9x/Nt下的SoftIce, 发现为True,否则为False begin {Detect Softice SIWDEBUG} if
CreateFile('\\.\SIWDEBUG', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function SIWVID: Boolean; ////探测Win9x/Nt下的SoftIce, 发现为True,否则为False begin {Detect Softice SIWVID} if CreateFile('\\.\SIWVID', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end;
function FileMon: Boolean; //探测File Monitor, 发现为True,否则为False begin {Detect File Monitor} if CreateFile('\\.\FILEMON', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function RegMon: Boolean; //探测Reg Monitor 发现为True,否则为False begin {Detect File Monitor} if
CreateFile('\\.\REGMON', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <>
INVALID_HANDLE_VALUE then Result := True else Result := False; end; function Trw: Boolean; //探测Win9x下的TRW, 发现为True,否则为False var Trw, TrwDebug: Boolean; begin {Detect Trw} if CreateFile('\\.\Trw', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Trw := True else Trw := False; if CreateFile('\\.\TRWDEBUG', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then TrwDebug := True else TrwDebug := False; Result := Trw or TrwDebug; end; function IceDump: Boolean; //探测Win9x下的IceDump, 发现为True,否则为False begin {Detect IceDump} if CreateFile('\\.\ICEDUMP', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; procedure FindSoftIce; //探测Win9x/WinNt 下的 SoftIce begin try //容错代码 asm mov ebp, 04243484Bh //Bounds Checker为SoftICE预留的后门 mov ax, 04h int 3 cmp al,4 jnz GotSoftIce end; except end; end; procedure FindSoftIce9x; //探测Win9x 下的 SoftIce begin try asm mov ah, 43h int 68h cmp ax, 0f386h //检测此处是否被调试器设置0f386h jz GotSoftIce end; except end; end;
还有一个办法就是使用上面讲到的RSA数字签名算法,由于内存中根本不会出现正确的注册码,Cracker再怎么调试也白搭。^_^
四、加壳
加壳十分重要,你从Cracker的关注程度来看就知道了。
首先我想大家应该先明白“壳”的概念。在自然界中,我想大家对壳这东西应该都不会陌生了,植物用它来保护种子,动物用它来保护身体等等。同样,在共享软件里也有一段专门负责保护软件不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。就像动植物的壳一般都是在身体外面一样理所当然(但后来也出现了所谓的“壳中带籽”的壳)。由于这段程序和自然界的壳在功能上有很多相同的地方,基于命名的规则,大家就把这样的程序称为“壳”了。就像计算机病毒和自然界的病毒一样,其实都是命名上的方法罢了。
由于不脱壳就无法调试和反汇编,因此Cracker们做的第一件事就是脱壳。壳可以很好地保护算法和字符串,但一旦被逆向工程脱掉,一切秘密就都暴露在Cracker面前,因此壳的选择很重要。比较流行的加壳软件有Aspack、Upx、PcCompact、NeoLite、Shrink、PkLite等等,不过它们都有专用的脱壳机或者可以被ProcDump脱掉。还有添加了探测Debugger功能和其他一些十分实用功能的专用加壳软件——保护软件,这种软件十分强大,常见的有Asprotect、Amadillo、License Control和幻影。不过License Control的保护性不是很好,很容易被破解;Asprotect强一点,不过被Cracker们研究得最多,况且网上也有它自己的注册机(连保护软件自己都被Crack了,可想而知它的保护能力)。推荐使用Amadillo,新版本至今仍没有被破解,不过价格太贵——专业版要$199。幻影是国产软件,效果也不错,价格也不菲(不过比Amadillo便宜),要人民币298元,并且提供SDK。孰是孰非,阁下自己看着办吧!不过请注意:一定要加壳,这至少会让Cracker的生活更加艰苦些^_^
五、小结
除了上面的四大点外,还有其他一些保护技巧,如:
使用Crc效验 Exe和Dll相互效验 不要用一目了然的名字命名你的敏感函数(如Registe、License、Key、Code……) 将注册码、安装时间记录在多个不同的地方 尽可能少地给用户提示信息,因为这些蛛丝马迹都可能导致解密者直接深入到保护的核心 将注册码、安装时间记录在多个不同的地方 检查注册信息和时间的代码越分散越好 不要调用同一个函数或判断同一个全局标志,因为这样做的话只要修改了一个地方则全部都被破解了
看到这里,您觉得累不累呀?注意细节,并谨慎处理之,破解的可能性会大大减小。不过我要告诉你,世界上没有破解不了的软件,我们的目的不过是尽量拖延时间,让破解者无法忍受这种折磨从而最终放弃。如果一个软件能抵住各种攻击而在一年内不被破解,那这个成绩已经相当不错了。祝你好运!
本人才疏艺浅,如有疏漏之处,还望各路高手不吝赐教——bruce2000@btamail.net.cn
共享软件是软件业目前世界上比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报;但,实际并非如此,绝大多数的人都弑羽而归。值得注意的是:除了选题和技术上的原因外,最大的原因就是共享软件被破解(Crack)了。
破解见得多了,不免有些麻木。大部分作者都是新软件发布一个星期左右甚至一天之内就会在网上发现注册机或者被修改过的软件(行话称之为“爆破”)。破解者制作了英文、中文、俄文、德文等语种的注册机大肆发散不说,还常常给作者寄一份,外加一封挖苦辱骂的信。唉!我们得罪了谁?没日没夜地熬夜编码,难道得到的就是这连绵的挖苦和不尽的羞辱吗?
不!决不!我们有理由也有能力保护自己的劳动成果!但问题是:如何保护?关注国内,网上关于破解资料和教程俯拾皆是,而关于软件保护方面的资料则是凤毛麟角(大多都关系到什么技术垄断),这种畸形现状就导致了相当一部分朋友的加密非常脆弱甚至可以称得上是“弱智” !要知道,你要面对的是已经形成团伙的众多破解高手呀,国内的什么CCG、BCG,国外的eGis、King、Core、TNT、DAMN和TMG,皆为水平一流的破解组织。全球盗版软件不少于80%都是由他们的破解的,技术实力连大软件公司都不可小视。
看到这里,你是否已经已经灰心了?别怕,虽然我们理论上无法完全避免被破解,但如果能够有效地拖延被破解的时间,并充分打击破解者的自信心,是可以让破解者无法忍受这种折磨从而最终放弃的。
破解,通常的做法有两种——暴力破解(爆破)和写注册机。下面我就来依次讲解每种破解方法的原理和应对方法,这些都是鄙人积累的一些共享软件保护经验,某些关键地方还有例程讲解(Delphi代码,使用C++和VB的朋友可以自己稍微修改一下),希望能对新手们有些帮助,能够更有效地保护自己的劳动成果。
§暴力破解(爆破)
这是最常见,也是最简单的破解的方法。该法最适合于对付没有CRC效验的软件,破解新手乐于采用。
大凡共享软件,验证是否注册大多数要采用if条件语句来进行判断,即使你采用了什么RSA或ECC等强力加密算法,也免不了使用if条件语句。呵呵,这里就是共享软件最为危险的地方哦,当然也是爆破手孜孜不倦所寻求的目标呀!
例如,你的注册函数类似如下:
{ 利用RSA进行注册码的数字签名验证 }
if RSAVerify(MD5(Key), MD5(Code), e, n) then
ShowMessage(注册成功!)
else
ShowMessage(注册失败!);
{ 这里Key是用户输入的注册码,是由你发送给注册用户的 }
{ Code是根据用户输入的用户名自动计算出来的注册码 }
{ e是RSA算法的公匙,而n是RSA算法的模数。 }
这个注册函数即使使用了强劲的RSA算法进行注册码验证,可是依然很容易被破解,我们只要把这里修改为: { 将逻辑判断改为否 }
if not RSAVerify(MD5(Key), MD5(Code), e, n) then
ShowMessage(注册成功!)
else
ShowMessage(注册失败!);
就可以了。这时戏剧性的结果会产生:随便输入任何注册码都可以注册通过,相反输入正确的注册码却无法通过注册。:) 其具体操作是先反汇编或者跟踪你的程序,找到判断注册码的cmp、test等汇编指令后的关键跳转指令处,通常是je、jz之类的汇编指令,把它们修改为jne或jnz即可,这样常常只需要修改一个字节就可以完美破解之。:)
令人遗憾的是,目前大部分共享软件都是这样进行判断的,这也是为什么网上被破解的软件铺天盖地的主要原因。因为这样破解实在是太简单了...
难道没有什么可以防止的方法吗?当然有啊!只要把软件的关键代码嵌入到注册码或者注册文件中就可以充分防止破解。但现在问题是,怎么嵌入呢?
最简单的方法就是把关键代码(你的软件功能限制部分最关键而且最简单的一个函数)做成一个小Dll(动态链接库),用强力对称算法加密(密匙可以是主程序某一固定不变的部分或壳的特征Hash值)后生成一个注册文件(License文件,呵呵,格式只有你知道哦!),或者Base64编码后生成一个注册表文件,用户可以双击导入注册表内。
效验流程如下:已注册用户验证注册码时,先验证有没有文件,没有文件者自然受限制的功能无法使用。如果有注册文件,解密之即生成一个小临时文件。如果主程序被脱壳或者被修改(爆破),自然Hash值密码不符,解密出来的肯定都是垃圾码,没有一点用处。只有没有被修改的主程序才能正确的解码,而且当然只有解密正确的文件才是一个真正的Dll文件,才能被
GetProcAddress函数找到欲调用的关键函数地址。这样只有已注册用户才可以享受到你软件的全部功能了。
如此一来,Cracker破解你的软件就变得很困难了:
首先,他如果没有注册文件,即使他把主程序脱壳了,由于受限制的部分和注册文件是关联的,他也根本无法修补完整。
第二,即使他得到了你的注册文件,由于是加密文件,他也无法直接利用之,这样就逼迫他去拆解你的算法,这可是他们最不愿意的碰到的事情哦!如果到了这一步,我想99%的Cracker都是会放弃的,呵呵,只有真正对加密算法有研究的Cracker高手才会继续破解下去。
第三,你是可以用些小技巧来使他的生活更加痛苦一些的,呵呵。这里我推荐大家使用DSA公开密匙加密算法,它和RSA一样,可以进行数字签名(RSA还可以加密,DSA则只能进行数字签名)。我这里选用它的原因就是它有一项非常实用的特性:随机数填充机制。即DSA每次签名都要使用一个随机数K,正因为有这个K的存在,即使是相同的用户名和机器识别码,由DSA加密过的每份注册文件都不会相同。这对Cracker拆解你的注册文件来说是一个极大的障碍。
第四,即使他得到了解密后的Dll文件,他也需要大幅度地修改主程序或者把你的Dll部分的关键代码拆出来填到主可执行文件中。呵呵,这就看他对PE文件格式理解得如何了。即使这样,如果你的程序中有大量的Hash效验和死机代码,呵呵,你就耐心等着我们可爱的Cracker同志吐血吧„„:)
所以记住啊:用完这个Dll临时文件后立即从内存中卸载此Dll并删掉,而且注意在解密之前探测一下,系统中有没有FileMon这个威胁极大的探测器呀!
{ 探测FileMon }
function DetectFileMon: Boolean;
begin
if CreateFile(PChar(\\.\FILEVXD),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0) <> INVALID_HANDLE_VALUE then
Result := True //如果有,就Down机!
else
Result := False;
end;
当然,你可以保护得更好一些:可以不采用临时Dll,而把解密后的关键代码用WriteProcessMemory这个API函数写入到主可执行文件自己进程被提交(Committed)的内存页面的指定位置去。这样由于磁盘上没有解密后的临时文件,破解更加困难。事实上,目前世界上最强劲的专业保护软件Armadillo就是用的这种方法。而且这种方法可以充分防止被调试器Dump。但实现起来比较困难,尤其是在WinNT 5以后的操作系统中。
由于这种方法将注册文件和受限制代码唯一关联,爆破手拿到你的软件也只有干瞪眼。建议大家都给共享软件加上功能限制,这样比时间和次数限制更加安全。
§写注册机
顾名思义,这种方法就是模仿你的注册码生成算法或者逆向注册码验证算法而写出来的和你一模一样的注册机。这玩意威胁极大,被爆破了还可以升级。如果被写出注册机,呵呵,你的软件只好免费了。或者你必须更换算法,但以前注册过的合法用户都得被迫更换注册码了,累死你!呵呵...
上面的方法虽然可以避免爆破,但注册机的威胁还是存在的。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算法) 虽然我不了解Blowfish和MD5算法的原理,也不会逆向它们,但我了解你的效验算法的流程和算法名,我马上就可以从网上找到类似的Blowfish和MD5算法包,从而模拟你的软件仿造出注册机,啊?!真是„„$&*&($#%@!
如果你用的什么其它不常见的算法(如Skipjack (NASA美国航天局标准算法), LOKI, 3-WAY, Safer之类不出名但强度很高的算法),并且全部改名,就让他们去研究软件中成堆的如下代码是什么加密算法吧!:)
0167:005B9F70 MOV EAX,[EBP-10]
0167:005B9F73 CALL 00404000
0167:005B9F78 PUSH EAX
0167:005B9F79 MOV EAX,[EBP-10]
0167:005B9F7C CALL 004041C4
0167:005B9F81 LEA ECX,[EBP-14]
0167:005B9F84 POP EDX
0167:005B9F85 CALL 004B860C 当然,最好把Hash算法也全部改名,给会给他们制造更多的困难。但注意,MD5和SHA之类的Hash的初始值会被Cracker从内存中找到,这样他就知道了你用的Hash了。所有建议同时使用MD5的变形算法Ripe-MD(RMD)128或160和其它的Hash,如Tiger, Haval等算法。
另外,请注意要经常效验你的程序是否被修改(Hash效验),如果被修改则退出。但请注意,有些病毒会修改进程的句柄表和它指向的内核对象,这样病毒就可以直接修改运行中的PE文件而感染之了,另外还有网络传输错误的问题也会导致软件CRC出错。因此请不要认为可执行文件的CRC不符而此时程序已被脱壳了。
其实,程序被脱壳最明显的标志是其大小明显大于脱壳前。1M的PE文件被UPX、ASPack之类的软件压缩后通常只有400左右。如果你的软件在运行中发现自己的大小大于800K,我想你应该知道如何做了吧?呵呵... :)
还有一点,调试器对我们的威胁很大,我们不会肯定让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) + \EXPLORER.EXE; //得到Explorer的路径 //得到所有进程的列表快照
sHandle := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0); Found := Process32First(sHandle, Pn); //查找进程
while Found do //遍历所有进程
begin
if Pn.szExeFile = ParamStr(0) then //自己的进程
begin
ParentProc := Pn.th32ParentProcessID; //得到父进程的进程ID //父进程的句柄
H := OpenProcess(PROCESS_ALL_ACCESS, True,
Pn.th32ParentProcessID);
end
else if UpperCase(Pn.szExeFile) = Path then
ExplProc := Pn.th32ProcessID; //Explorer的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找到漏洞。加密字符串不需要太复杂的算法,随便找一个快速的对称算法就可以了。
最后提醒你一句,不要在加密上花太多的功夫!你应该把更多的时间和精力都用来完善你的软件,这样会更合算。借用一位前辈的话来忠告大家吧:花点时间考虑你自己的软件,看看它是否值得保护?如果没人用你的软件,保护也就没有意义了,不要过高估计你的软件“对世界的重要性”!
没有评论:
发表评论