显示标签为“Programming”的博文。显示所有博文
显示标签为“Programming”的博文。显示所有博文

2019年1月29日星期二

python 分行方法

python 一行如果太长会不容易看
分行方法:
行尾加 \

a = b + c \
       +3

2018年6月28日星期四

回调函数 callback 是什么

摘取自:https://www.zhihu.com/question/19801131 (侵删)
 
打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。如下图所示(图片来源:维基百科):
 
 


可以看到,回调函数通常和应用处于同一抽象层(因为传入什么样的回调函数是在应用级别决定的)。而回调就成了一个高层调用底层,底层再过头来用高层的过程。(我认为)这应该是回调最早的应用之处,也是其得名如此的原因。
 
 

2017年11月1日星期三

软件的配置文件一般方法

最基本 : INI 文件
level1.key1=value
level1.key2=value
level2.key1=value

JSON 文件
{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

YAML文件 
invoice: 34843
date   : 2001-01-23
bill-to: &id001
    given  : Chris
    family : Dumars

XML 文件 

2 <小纸条>
3  <收件人>大元</收件人>
4  <發件人>小張</發件人>
5  <主題>問候</主題>
6  <具體內容>早啊,飯吃了沒? </具體內容>
7 </小纸条>




2017年9月25日星期一

2016年6月15日星期三

QT Plot

http://www.qcustomplot.com/
http://qwt.sourceforge.net/index.html#installonmainpage
http://www.lofter.com/tag/qwt

2016年6月13日星期一

Doxygen

Doxygen是一个C++CJavaObjective-CPythonIDLCORBA和Microsoft flavors)、FortranVHDLPHPC#D语言的文檔生成器。可以在大多数类Unix的系统上运行,以及Mac OS X操作系统和Microsoft Windows。初始版本的Doxygen使用了一些旧版本DOC++的源代码;随后,Doxygen源代码由Dimitri van Heesch重写。
Doxygen是一个编写软件参考文檔的工具。该文檔是直接写在源代码中,因此比较容易保持更新。Doxygen可以交叉引用文檔和源代码,使文件的读者可以很容易地引用实际的源代码。
KDE使用Doxygen作为其部分文档且KDevelop具有内置的支持。 Doxygen的发布遵守GNU通用公共许可证,并且是自由软件

2015年12月16日星期三

如何保护自己的共享软件

共享软件是软件业目前世界上比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报;但,实际并非如此,绝大多数的人都弑羽而归。值得注意的是:除了选题和技术上的原因外,最大的原因就是共享软件被破解(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找到漏洞。加密字符串不需要太复杂的算法,随便找一个快速的对称算法就可以了。
最后提醒你一句,不要在加密上花太多的功夫!你应该把更多的时间和精力都用来完善你的软件,这样会更合算。借用一位前辈的话来忠告大家吧:花点时间考虑你自己的软件,看看它是否值得保护?如果没人用你的软件,保护也就没有意义了,不要过高估计你的软件“对世界的重要性”!

2015年5月16日星期六

Qt 在win上发布打包dll生成exe方法

Reference: http://www.tripleboot.org/?p=138#more-138
       http://blog.csdn.net/maverick1990/article/details/9123785
Qt静态编译链接生成的exe文件,不需依赖七大姑八大姨的一堆dll,可以独立运行,发布很方便。但绝大多数用的都是Qt开源版本,如果用静态链接,会有些限制。那有没有办法即能享受静态编译的方便,又不受开源许可的限制呢?方法之一,就是用动态编译,然后把exe和需要的dll整合成一个文件,问题就解决了。
具体步骤:
一,先动态编译链接,生成exe文件
二,找到依赖的dll文件
这里,我们可以用Process Explore来获取所依赖的dll文件

打开procexp.exe,通过菜单View==》Lower Pane View==》DLLs 勾选,显示dll窗口






这里,我们以test.exe为例,
在Qt环境下,将exe运行起来,在ProcessExplor中,找到就可以找到test.exe,选中,在下面就看到依赖的dll了。








系统目录下的,一般不用关心,按Path排序,看Qt目录及mingw目录下的,将这些dll拷贝出来,单独放到一个目录下。


换台电脑会出现如下的问题
This application failed to start because it could not find or load the Qt platform plugin "windows".
Reinstalling the application may fix this problem.

解决办法是
建一个文件夹 叫做 platforms 把qwindows.dll放进去就可以用了




2014年8月15日星期五

即時作業系統 (RTOS) 的基本概念

转自:http://cms.mcuapps.com/devscenes/ds0001/

筆者在 2012 年 2 月時收到一份 IAR 的電子刊物,裡面附帶有一篇不錯的 PDF 文章 – Basic Concepts for Real Time Operating Systems 
筆者閱讀之餘,覺得該文足夠精簡摘要地歸納出 RTOS 的要旨,因此嘗試作個翻譯,以饗懶得看英文的讀者。又筆者翻譯的方式有時會較為接近意譯,並不拘泥於原文的字句,也可能會配合文意,另外附加一些補充資料。
此外,MCUApps 將會持續整理一些各具特色的 RTOS 系統的資料,置於 RTOS 技術資料 提供給大家參考。
以下就是我們的譯文

本文將會闡釋 RTOS 的一些基本概念,但不會擴及個別的 RTOS 與其特點。為了簡化起見,只會討論 RTOS 最具代表性的一些重要特性。
在談過 RTOS 的架構,以及為何我們需要採用它之後,我 (IAR System 的 Mats Pettersson) 將會解釋典型 RTOS 中的每一個基本元件,並且展示他們是如何被整合到系統之中。
在本文中出現的少許程式碼範例,所採用的是 Express Logic 的 ThreadX 系統。

Thread 導向的設計

設計嵌入式應用幾乎總是相當具有挑戰性的。為了降低複雜度,我們通常會採用 threads 導向的設計,把一個專案切分成比較易於管理的小塊(也就是 threads),而後每一個 thread 負責該應用程式的一部分。這樣的系統有助於識別 threads 之間的重要順序。也就是說,某些 threads 具有即時性的需求,必須盡量快速並且正確地回應它們。如果你的系統採用了專業的 RTOS,一定會有劃分 threads 優先權 (prioritization) 的設計。除了優先權之外,也會提供一套乾淨而且被仔細測試過的 API,有助於簡化 threads 之間的通訊。
所以如果我們採用 RTOS 的話,就會獲得一些工具能夠:
  • 確保能夠在即時約束條件 (real-time constraints) 內執行時間關鍵 (time-critical) 部分的程式碼。或許同樣都很重要,但高優先權的 threads 所需要的即時行為,並不會受到低優先權 threads 的影響。
  • 確保易於開發和維護複雜的應用。開發和維護小的 threads,比起硬搞整套應用更為容易。此外,對於低優先權 threads 的更動也不會影響到高優先權 threads 的即時處理。
  • 能夠將整體應用程式的不同部分,分派給多個開發人員。每一個開發人員能夠擔負應用程式的一個或多個 threads,而且當他們在進行開發工作時,還會有一套乾淨的 API 能夠讓不同的 modules / threads 相互溝通。
當然也可以不必動用到 RTOS,就把應用大卸八塊成為不同的 threads。但是採用了 RTOS 的話,你不但能夠創建 threads,同時也具備一些讓它們能夠彼此溝通的工具,再加上能夠確保能夠在即時約束條件內執行完畢 threads 具時間關鍵的部分工作。由於採用了 RTOS,不同 threads 之間的介面將會變得非常乾淨,在進行開發的時候你就可以省時省力。

RTOS 是如何工作的?

RTOS 的核心被稱為 kernel,並提供有一個可以透過 kernel 去創建 threads 的 API。一個 thread 就像是一個擁有自己的堆疊、並帶有 Thread 控制區塊(TCB – Thread Control Block)的函式。除了 thread 本身私有的堆疊之外,每個 TCB 也保有一部分該 thread 的狀態訊息。
kernel 還包含有一個 scheuler,scheuler 會按照一套排程機制來執行 threads。各種 scheulers 之間主要的差異,就是如何分配執行他們所管理之各種 threads 的時間。基於優先權的 preemptive scheuler 是嵌入式 RTOS 之間最流行和普遍的 threads 調度演算法。通常情況下,相同優先權的 threads 會以 round-robin 循環的方式加以執行。
多數內核還會利用系統時脈 (system tick) 中斷,其典型的頻率為 10ms。如果在 RTOS 中缺乏系統時鐘,仍然能夠有某種基本形式的調度,但時間相關的服務則否。這種與時間有關的服務內容包括:軟體定時器、thread 睡眠 API 呼叫、thread 時間片段、以及逾時的 API 呼叫。
為了實現系統時脈中斷,可以透過嵌入式晶片的硬體計時器。大多數的 RTOS 有能力動態地擴增或重新設置計時器的中斷頻率,以便讓該系統進入睡眠,直到被下一個計時器期限或外部事件喚醒。例如,如果你有一個對耗能敏感的應用程式,您可能不希望每 10ms 就運行一次不必要的系統時脈處理程序。所以假設應用程式處於閒置狀態,想要把下一個定時器期限改為 1000ms。在這種情況下,計時器可以被重新規劃成 1000ms,應用程式則會進入低功耗模式。一旦在這種模式下,處理器將呈現休眠狀態,直到產生了外部事件、或是計時器的 1000ms 到期。在任一種情況之下,當處理器恢復執行時,RTOS 就會根據已經經過了多少時間來調整內部時間,並恢復 RTOS 和應用程式處理。如此一來,處理器只會在執行應用程式有事可做時進行運算。空閒期間處理器可以睡眠,並且節省電力。
在本文稍後,將有進一步關於調度演算法和系統時脈的討論。

思考 thread…

也許開始一個 RTOS 應用程式最好的方式,就是去思考如何將一個應用程式劃分為不同的 threads。例如,一個簡化的引擎輸入控制應用程式可以劃分為以下 threads:
  • 引擎溫度
  • 機油壓力
  • 每分鐘轉數 (RPM, rotation per minute)
  • 用戶輸入
這些模組可以被設置為 threads,也可以被劃分成子 threads。例如:
  • 引擎溫度
    • 讀取引擎溫度
    • 更新 LCD 的目前溫度
  • 機油壓力
    • 讀取目前的石油壓力
    • 展開應急發動機關機
  • 每分鐘轉速
    • 讀入 RPM
    • 更新 LCD 的目前 RPM
  • 用戶輸入
    • 油門踏板的角度
    • 獲取當前檔位
這種劃分成子 threads 的工作可以不斷繼續下去,直到可以被一個單獨的 thread 加以處理為止。

RTOS 的組件

讓我們來看看一個 RTOS 必須提供哪些功能,而這些功能又如何在不同的應用中派上用場。

Threads

 Threads 類似於函式,但每個 thread 都會有它自己的堆疊和 thread 控制塊(TCB)。然而與大多數函式不同的是,一個 thread 幾乎總是一個無限循環。也就是說,一旦它被創建,它(經常是)永遠不會退出。
1
2
3
4
5
6
void ThreadSendData( void )
{
  while (1) {
    // Send the data...
  }
}
一個 thread 總是處於幾種 states 其中之一。一個 thread 可以準備好被執行,也就是說,在 READY 狀態。或者該 thread 可能會被暫停(pending),也就是該 thread 在進入 READY 狀態之前,正在等待某事發生。這就是所謂的 WAITING state。
以下是我們對於 ThreadX 中 states 的一段簡短描述。
State說明
Executing這是當前正在運行的 thread。
Ready這個 thread 已經就緒
Suspended這個 thread 正在等待某件東西。這可能是一個事件或一個消息,也可能是等 RTOS 時鐘到達某個特定的值(延遲)。
Completed一個處於完成狀態的 thread 已經完成其運算處理、 並且自它的入口函數返回。(處於完成狀態的 thread 不會再次被執行。)
Terminated一個 thread 之所以會處於終止狀態,是因為另一個 thread 或是該 thread 本身呼叫了 tx_thread_terminate 服務。(處於終止狀態的 thread 不會再次被執行。)
  • :不同的 RTOS 可能會對這些 states 賦予不同的名稱*
Scheduler
你可以從兩種主要類型的 schedulers 中加以挑選:
  1. 事件驅動 (Event-driven) – 具優先權控制的調度演算法
    通常,不同的 threads 會有不同的響應要求。例如,在一個控制馬達、鍵盤和顯示器的應用程式中,馬達通常比鍵盤和顯示器需要更快的反應時間。這必須得靠一個事件驅動的 scheduler。
    在事件驅動的系統中,每個 thread 都會被分配到一個優先權,而優先權最高的 thread 就會被執行。執行的順序都仰賴於這個優先權。規則非常簡單:schedule 從所有就續的 threads 中挑出具備最高優先權的 thread 予以執行。
  2. 分時共享 (Time-sharing)
    最常見的分時演算法叫做 round-robin。也就是 scheduler 列出系統中所有 threads 的清單,然後一一查驗下一個 thread 是否就緒可以被執行。如果 thread 為 READY 時,該 thread 就會執行。每個 thread 又分派到一份 時間切片 (time-slice)。時間切片是每一回合中,單一 thread 被容許之最長的執行時間。
典型的基於優先權的 preemptive scheduler 會同時支援 preemptive 與 non-preemptive 的調度。在 preemptive 的情況下,較高優先權的 thread 會立即打斷(搶佔)執行中的低優先權 thread。而具備相同優先權的 threads 則會以 non-preemptive 的方式進行調度,而正在被執行的 thread 會繼續完成它的執行,再輪到另一個具備相同或較低優先權的 thread。
指派優先權 (Assigning priorities)
將正確的優先權指派給不同的 threads 是相當重要的。許多論文都在探討在基於 RTOS 的應用程式中,如何把這件工作做到完美。我們並不會深入這個題目,但是在此要談一些有用的規則:
  1. 盡量採用最少的優先權層級
    僅在搶佔是絕對必要的狀況下指派不同的優先權。這樣可以降低系統中 context switches 的數量,越少的 context switches,就表示有越多的時間是花費在執行應用程式代碼。
  2. 確認滿足了你應用程式中所有的時間關鍵約束條件
    端視你的應用程式類型而定,這可能會很難搞。有個解法是採用 RMA (Rate Monotonic Algorithm)。ThreadX RTOS 也提供了一個獨門絕活叫做 preemption-threshold。這能夠被用於降低 context switches,也能確保應用程式 threads 的執行。詳見 http://www.nxtbook.com/nxtbooks/cmp/esd0311/#/26
Internet 上有許多關於指派優先權的相關資訊。想要深入的朋友可以看下列兩則文章:
Thread 通訊 (Thread communications)
在 RTOS 應用程式中,我們也必須能夠在 threads 之間相互通訊。通訊可以採用 event、semaphore(旗號)的形式,或者是以訊息的方式傳送給另外一個 thread。
最基本的通訊是透過 event。一個中斷服務函式 (ISR) 也能夠傳送一個 event 給某個 thread。有些 RTOS 還能將單一 event 傳送給多個 threads。
Semaphores 通常被用於保護共享的資源,譬如說不只一個 thread 想要對同一塊記憶體(變數)進行讀寫時。作法是讓一個變數不會隨著另外一個作用中的 thread 而被改變。原則就是在你讀寫這塊記憶體之前,你必須先獲取一個以一個變數保護著的 semaphore。一旦你獲得這個 semaphore 之後,其他人都不能對這塊記憶體進行讀寫,直到你釋放 semaphore 為止。這樣一來你就可以確保同時只有一個 thread 會對該記憶體位置或變數進行讀寫。
訊息 (messages) 則能夠讓你將資料傳送給一個或多個 threads。這些訊息幾乎可以是任意的大小,通常以 mailbox 或 queue 的方式來實作。而 mailboxes 與 message queues 的行為會隨不同的 RTOS 而有所差異。
通盤整合 (Putting it all tegether)…
讓我們透過下面這張圖重新歸納我們至今所談過的東西。
那麼現在,讓我們看看我們能夠利用手上的組件做些什麼。試著回想一下,我們需要創建一個引擎控制的應用,而我們有一個微控制器和 LCD 顯示屏。我們還想在 LCD 顯示屏上顯示當前時間。我們將會重用早先將此應用程式分為不同的模組和 threads 的例子。(我們會忽略 “用戶輸入” 便於解說。)
針對這個練習,我們將創建下面的 threads:
  1. // Read the temperature and oil pressure of the engine
    void T_OilRead(void) and void T_TempRead(void)
  2. // Print the temperature and oil pressure on the LCD
    void T_OilLCD(void) and void T_TempLCD(void)
  3. // Controls the engine
    void T_EngineCTRL(void)
系統方塊圖如下所式:
其中我們以不同的顏色和外觀,分別表示了 threads、mailboxes、以及 semaphores 等元素。
關於這個系統,我們將需要一個 semaphore 來控制對 LCD 的寫入。因為我們有兩個不同的 threads,所以我們需要這個機制,如果其中一個 thread 被另一個打斷,輸出很有可能會被搞爛掉。
我們需要以下的信號傳導機制。
  • mailboxes M_TempLCD 與 M_OilLCD
    這些 mailboxes 包含要列印在 LCD 上的訊息。
  • mailboxes M_TempCTRL 和 M_OilCTRL
    這些 mailboxes 包含要送給引擎控制 thread 的訊息。
  • semaphore S_LCD
    這個 semaphore 將會確保在同一時刻當下,只有唯一的一個 thread 能夠列印到 LCD 上。
系統時脈 System ticks
我們現在有了一個系統,它可以更新 LCD上的馬達資訊、並顯示當前時間。然而,有一個非常重要的事情還沒搞定,就是 RTOS 的時脈功能。正如早些時候所言,kernel 需要隨時能夠掌控系統,然後才能夠執行例如根據 RTOS 的 scheduler 切換 threads 的工作。這通常是透過 MCU 內部計時器所驅動的時脈處理 API 呼叫達成。如果沒有系統時脈,整個系統就無法動彈。
連接到 MCU 的硬體
拼圖的最後一塊就是將這一切連接到硬體。在我們的例子中,我們將假設我們可以利用一套韌體函式庫。我們假設我們有以下的韌體 API 函式:
  • // Set text X,Y coordinate in characters
    void GLCD_TextSetPos(int X, int Y);
  • // Set draw window XY coordinate in pixels
    void GLCD_SetWindow(int X_Left, int Y_Up, int X_Right, int Y_Down);
  • // Function that reads the engine temperature
    char Engine_ReadTemp(void);
  • // Function that reads the engine oil pressure
    char Engine_ReadOilPressure(void);
  • // Function that controls the engine
    char Engine_Control(int CtrlValue);
現在,我們只要把這些片段兜在一起,看看我們的系統是否如同預期一般工作。最簡單的方法就是使用 RTOS 廠商的 BSP,如果有辦法取得的話。對於大多數 RTOS 而言,都會有為各種設備所設計的許多不同的 BSP。
如果是要在沒有 BSP 的情況下來建立一個應用,則需要:
  • 將編譯器/組譯器所需 include paths 加入到建構工具。
    我們需要確保編譯器和組譯器可以找到我們系統所需要的頭文件。
  • 添加共通的 RTOS 函式庫或源碼文件。
    我們需要添加 kernel。kernel 可能是源碼,也可能會是函式庫。
  • 添加 target-specific 的 RTOS 文件。
    很多 RTOS 對於不同的電路板/設備都會有一個電路板支援包(BSP, Board Support Package)。在這樣的一個情況下,你只需要確保你已經包含了這些 target-specific 的文件。在這樣的一個 BSP 環境下,你應該就會擁有已經配置好時鐘的程式碼,能夠正確配置 RTOS 的系統時脈。
  • 您的應用程式必須初始化 RTOS,並且在啟動 RTOS 之前就創建至少一個 thread。這通常是在 main() 函式中,但也可以在你應用程式的其他地方。
假設我們已經根據 RTOS 的規格添加了所有的文件,並且設置好建構選項,我們就可以開始創建我們應用程式中的 threads。但是在此之前,我們需要定義它們的 Thread Control Blocks(TCB)。
下面就是 Express Logic 之 ThreadX 的 TCB 定義範例。
1
2
3
4
5
6
// Express Logic ThreadX example
TX_THREAD TCBEngineCTRL;
TX_THREAD TCBOilLCD;
TX_THREAD TCBTempLCD;
TX_THREAD TCBOilRead;
TX_THREAD TCBTempRead;
現在,剩下的就是在我們創建的 threads 中建立我們的主要功能,以便完成我們所需要的應用程式。
在 ThreadX 中,我們會在 tx_application_define 這個 API 函式中創建我們最初的 threads。因此,在 main中我們唯一必須做的事情就是去呼叫 API 函式tx_kernel_enter。隨後 ThreadX 就會呼叫 tx_application_define(),我們在其中創建了我們的 threads、semaphores、message queues 之類一開始就會需要用到的東西。
大多數的 RTOS 都允許你在運行應用程式時才動態地創建資源,例如 threads 之類的。但是,我們至少需要有一個 thread 作為 schedular,用以接管我們的應用程式。
當做到這一步時,我們就可以啟動我們的 RTOS,並且讓 schedular 接手,以確保在正確的時間執行正確的 thread。

總結

這篇文章解釋了 kernel、threads、訊息(如 events 和 mailboxes)、scheduler、以及 RTOS 應用程式中主要元件的概念。一個真正的應用程式還會需要更多的細節,例如,如何創建 mailboxes 和 events。
如果你剛接觸 RTOS,上手最好的辦法就是從各家 RTOS 供應商的許多範例中,挑一個架起來並且用用看。在上述文章中,我已經用上了 Express Logic 的程式碼片段作為示範。Express Logic 提供了許多的應用範例,並有許多不同電路板和設備的 BSPs (Board Support Packages)。