全国职称计算机考试辅导软件5.0版爆破过程分享
本帖最后由 miceeagle 于 2017-4-10 13:30 编辑本文分为三部分,第一部分为尝试破解的整个过程,本萌新和论坛小伙伴分享一下共同提高,第二部分为干货,主要总结需要修改的关
键部分,赶时间的小伙伴直接跳到第二部分吧,第三部分为个人的总结,在这次逆向过程中遇到的问题,走过的弯路,以及自身的不足。
static/image/hrline/line3.png
第一部分尝试破解
这款软件名字叫做“全国职称计算机考试辅导软件5.0版”,其中有很诸如Excel,Word等等的很多课程和模拟试题。软件来源是老婆大人要考试去买来
的,所以是在有一枚注册码的参考下破解的。下面就以其中的“WinXP全能版”为目标来尝试破解。软件会在最后打包到网盘里,各位小伙伴有兴趣的话
可自行下载研究。软件安装完成如下图(那个副本是我拷贝的备份,大家不要在意)
图1-1 软件安装完成
首先查壳:没有加壳,Delphi编写
图1-2查壳
先运行起来试试
图1-3试用提示
点击注册,有说明“一个授权码只能注册一个模块”等等,一直到输入授权码,随便输入,然后下一步,注册方式有在线注册,电话,QQ等,我选电
话,然后提示输入注册码了。
图1-4输入注册码
随便输入注册码后出现重启的提示
图1-5重启提示
到这一步,得到一些关于注册机制的一些信息:需要授权码和机器码来验证注册码,属于重启验证,重启过后仍然提示注册,那么验证的过程就在
图1-3的弹窗之前。那么就先用IDR分析,并导出map文件,然后再用OD加载,这样的话Delphi带的系统函数就直接有名称了,有助于分析。
图1-6第一次载入OD,并加载map文件
按照套路,进行中文字符搜索,发现“您当前注册的是全能主机版”等字样,然后很高兴的改了相关跳转,结果发现提示已经注册了,但是除此之外
什么用都没有,然后就两眼一抹黑了,上网寻找重启验证的教程,下载了“吾爱破解培训第五课:反击作者的挑衅--实战解除程序重启验证.7z”,
发现好像不适用当前这个软件,但是还是进行了尝试,尝试的结果如下:
找到直接对输入的注册码进行的比较:
005512FE mov eax,dword ptrss: 假码第1位
00551304 mov edx,dword ptrss: 真码第1位是9
00551307 call<Hzkqs.System.@LStrCmp>
0055130C je short Hzkqs.00551323 ;跳
0055130E mov eax,dword ptr ss:
00551311 call<Hzkqs.Main.sub_00554E0C> ;Call到试用版段首
0055133C mov eax,dword ptrss:
00551342 mov edx,dword ptrss:
00551345 call<Hzkqs.System.@LStrCmp>
0055134A jnz Hzkqs.00553A4C ;不能跳
通过调试,确认真码第一位是9,第20位是7。
根据ReadFile这个api,找到了读入注册码的地方,一直跟,又发现了提取MAC地址的地方,没有找到生成机器码的地方,但是通过某中运算产生了
一组子码,以下是在堆栈中找到的两组:
0018FB60 02198BD8 ASCII "967249"
0018FB64 02198C60 ASCII "672492"
0018FB68 02198C1C ASCII "724925"
0018FB6C 02198C8C ASCII "249255"
0018FB70 02198C74 ASCII "492553"
0018FB74 02198CB8 ASCII "925533"
0018FB78 02198D40 ASCII "553312"
0018FB7C 02198DC8 ASCII "255331"
0018FB80 02198E50 ASCII "533125"
0018FB84 02198ED8 ASCII "312522"
0018FB88 02198F60 ASCII "331252"
0018FB8C 02198FE8 ASCII "125229"
0018FB90 02198FA4 ASCII "252290"
0018FB94 021BA680 ASCII "929337"
0018FB98 02198A50 ASCII "293372"
0018FB9C 02198A64 ASCII "933722"
0018FBA0 02198A78 ASCII "337229"
0018FBA4 02198A8C ASCII "372299"
0018FBA8 02198AA0 ASCII "722990"
0018FBAC 02198AD8 ASCII "229903"
0018FBB0 02198AEC ASCII "299033"
0018FBB4 02198B00 ASCII "990338"
0018FBB8 02198B14 ASCII "903385"
0018FBBC 02198B28 ASCII "033850"
0018FBC0 02198B3C ASCII "338505"
0018FBC4 02198B50 ASCII "385054"
然后在接下来的代码中,发现一个循环的比较,就是比较以上两组数字
图1-7标志位表
注释是我后来写的,刚开始的时候不知道这是在干什么,下面有一系列跟据这个标志位表的跳转,我当时是一个一个跟过去看的,确实费了些时间,
但是好处还是有的:
1、让我熟悉了试用版的入口地址,比如这个地址00554E0C是试用版的段首,0054E3D0这个地址往下出现试用版的标题,00551311这个地址Call试用
版的段首;
2、我本来认为对子码的比较就是直接全为1,就是注册版,有一项比较为0,就跳试用版,结果发现不是,还是太低估它了。以下是当时做的记录,
完全不知道哪个该跳,哪个不能跳:
00552359 je Hzkqs.0055390A ;跳转到sleep
0055237A je Hzkqs.005538F1 ;不能跳
005523CC je Hzkqs.005538BF
005523F0 je Hzkqs.005538A6
00552414 jeHzkqs.0055388D
005524CD je Hzkqs.00553871
00552586 je Hzkqs.00553855
005525AAje Hzkqs.00553839
然后就懵了,感觉这套路太深,第一次的尝试到此就失败了。
过了一段时间,打算进行第二次尝试,然后想起了一件事,我不是有一枚正确的注册码嘛?然后为借了那台注册了的笔记本,直接对比,发现了
正确的标志位表:
0018FB50 01000000
0018FB54 01010101
0018FB58 01000001 表T7 注册标志位 13个
0018FB5C 01010000
后边为当时写的注释,其实写错了,当时以为T7这个表就存了这个标志位,这个后面再说。这些标志位直接放到堆栈里面,那么我就直接加代码
修改了:
原来的指令:
005522B5 8802 mov byte ptr ds:,al
005522B7 FF85 7CFFFFFF inc dword ptr ss:
在地址005522B5处修改jmp 0055A42A(这个地址是找到的空白空间),然后在这个地址处添加如下指令:
0055A42A 83FB 07 cmp ebx,0x7
0055A42D 74 17 je short Hzkqs.0055A446
0055A42F 83FB 06 cmp ebx,0x6
0055A432 74 12 je short Hzkqs.0055A446
0055A434 83FB 04 cmp ebx,0x4
0055A437 74 0D je short Hzkqs.0055A446
0055A439 83FB 03 cmp ebx,0x3
0055A43C 74 08 je short Hzkqs.0055A446
0055A43E B0 01 mov al,0x1
0055A440 EB 02 jmp short Hzkqs.0055A444
0055A442 B0 00 mov al,0x0
0055A444 8802 mov byte ptr ds:,al
0055A446 FF85 7CFFFFFF inc dword ptr ss:
0055A44C ^ E9 6C7EFFFF jmp Hzkqs.005522BD
好了,第一个问题通过“作弊”解决了,然后往下。其实我一直纠结为什么搜索不到相关字符串,后来发现OD的字符串搜索只能搜索当前区块,
所以想要搜索程序的全部空间是不太可能的,只能运行到哪儿都搜索一下,可能有意外的收获,所以我搜到了这个:
图1-8 判断版本
因为购买的光盘本身是全能主机板,所以就让它走全能主机板的流程,通过分析发现,判断流程取决于一个值,00561A08,这个内存地址中的值初
始为0x2,当它为0x1的时候就注册为主机版了,否则为副机版,至于全能和高级为就不管了。然后在这个地址下内存断点,在以下指令处断下:
0055150F > \85DB test ebx,ebx
00551511 .750B jnz short Hzkqs.0055151E
00551513 .A1081A5600 mov eax,dword ptrds:
00551518 . C700 02000000 mov dword ptr ds:,0x2
那么,就在这儿修改了,直接把0x2改成0x1,然后测试发现,只是标题变了,其实还影响了注册时的某些流程,这是以后发现的,当时还不知道,
只是觉得改了完美些。接下来运行软件,发现直接就停在软件初始化界面了,不动了,很奇怪,不知道问题出在哪儿,我采取的方法是先用单步步
过,依次测试深入,看哪个函数导致了程序假死,然后找到了如下的地方:
图1-9 sleep
它判断ebp-0x28里的值是否为ERR,如果是的话就sleep 99999ms,然后再跳回来再sleep,再重复……怪不得不动了,那么关键就是ebp-0x28这个
值了,在哪里给它赋的ERR这个值呢?一直往上发现如下图的一个Call很可疑:
图1-10 可疑的Call
调试后发现确实是它导致的ebp-0x28指向的值变成了ERR,那么它Call到什么地方去了呢?在此下断后再次来到这里:
图1-11 可疑的Call
发现OD给的提示是File1.F_TZCM,这个很眼熟啊,软件目录有个文件是File1.Hz,难道有关系?能够直接调用,那么可能是Dll文件了?验证一下:
图1-12 File1.Hz
果然是伪装的Dll文件啊!那么其他的呢?同样的方法,发现File1-3是Dll文件,file4-5加壳了,File6不知道是什么,FileE-G也是Dll文件,然后
我想到了一个工具ViewApi:
图1-13 File2.Hz
其他的图我就不截了,File1-3导出函数分别为F_TZCM,F_InPutZCM,F_ZC,疑似“注册码”,“输入注册码”和“注册”,其他的没有导出函数,
加壳的因为暂时无法脱Dll的壳,所以也暂时不考虑,就目前来看这3个文件最可疑。接着单步步入,进入File1的空间,嘿嘿,先搜索字符串:
图1-14 File1.Hz中的ERR
果然搜到了字符ERR,有两个地方的跳转使得ebx被赋值ERR,直接改成jmp,那么在File2中是否也有类似的校验呢?经测试,有的,修改方法与此类似。
继续往下单步走,多注意跳转,然后又有发现
00551CF4 . /0F85 CD1C0000 jnz Hzkqs.005539C7 ; 不能跳上面这条指令跳转到005539C7
005539C7 > \8B45 FC mov eax,dword ptr ss:
005539CA .E83D140000 call<Hzkqs.Main.sub_00554E0C>
紧跟着就是一个Call,00554E0C这个地址是试用版的入口,上面在测试标志位表的时候就发现了,所以这儿又是一个修改点,不能让它跳了。
同样的套路还有
005523A8 . /0F85 2A150000 jnz Hzkqs.005538D8 ; 不能跳
它的跳转地址如下
005538D8 . 55 push ebp
005538D9 . 8D85 BBFDFFFF lea eax,dword ptr ss:
005538DF .B902000000 mov ecx,0x2
005538E4 .BA0C000000 mov edx,0xC
005538E9 .E822B1FFFF call<Hzkqs.Main.sub_0054EA10>
这个0054EA10也是试用版的入口地址。
继续单步,过程很繁琐,时间很长,终于到了File3的验证了
005527EE.FF15 34585600 call dword ptrds: ;在File3.Hz中校验
图1-15在File3.Hz中的校验
这儿给个提示,上图中进入到File3的空间时,其实是看不到这些系统Call的函数名字的,我加载了在IDR中导出的File3.map文件,没有显示出函数名
的就是软件作者的自定义函数了。连续改了3个文件,发现了可疑的地方,比较的对象都包含两个常量0x361600和0x1DC906E8,这代表什么意思呢?想
起之前看过的教程,似乎是个校验值,校验的对象不是大小就是MD5值,验证一下:
图1-16 校验值
果然,第二个值是主程序的CRC32值,那么第一个就该是大小了,在计算器中将十进制3544576换算成十六进制,果然是361600,那么这些校验就没什
么秘密可言了:假如主程序文件被改动过,那么软件就会永远的Sleep,没了。
到此为止,我还是比较满意的,顺便检查了一下记录,确认一下有没有漏了什么,结果就在
005527EE .FF15 34585600 call dword ptr ds:
这个Call进File3空间的指令前一点,发现了两个地方:
1、如下图,又有个常量,下面是个永久的Sleep,经验证这个常量是File3.Hz的大小,因为只修改了指针,所以这个值不会变化,可以忽略。
图1-17 File3.Hz大小验证
2、0055260E /0F8E 0A130000jle Hzkqs.0055391E
这个jle假如跳转的话会完全跳过Call File3.Hz,这里我将它nop掉了。继续往下,我直接运行了程序,结果像下面这样:
图1-18乱码
当时感觉就是大写的点点点……全是乱码啊!什么原因造成的呢?
可能的原因分析:
1、难道是不跳过File3的校验的关系?不是,结果是一样的,只是乱码不是同一个乱码了,不过还是乱码。
2、监视数据库文件Hzkqs4J.db后发现,这个文件会被修改,而试用版却不会修改文件,那么应该是修改数据库文件后导致的乱码,也就是说我非法修
改被发现了?那应该找到合法的修改方式。
3、在监视数据库文件Hzkqs4J.db时还发现,每次执行主程序,这个数据库文件都会有变化,这个变化通过MD5码的变化表现出来,而且每次执行文件
都会显示注册的进度条,我想正式版的话,应该只注册一次就能使用了啊,不可能次次都注册,再想到File3.Hz中的导出函数F_ZC,那么跳过注册的
那个指令
0055260E /0F8E 0A130000 jle Hzkqs.0055391E
就是检测是否注册的地方,而且还是在数据库中存放的注册标志,那么简单验证一下:将指令改回去,在注册一次过后,再次运行软件,注册进度条
不再出现,重复几次都不出现,将指令nop掉后,注册进度条次次出现。那么就很明显了,这个指令不能动。
4、乱码是否由那几个加壳的Dll文件造成的呢?检查发现整段指令没有读取调用相关文件的地方,测试方法简单粗暴:直接将这几个文件删掉,软件
能照常启动,仍然是乱码。
5、是不是需要打开这个数据库文件呢?如果能看到这个数据库中的表结构,是不是可以帮助我分析理解呢?
到现在为止没有明确的方向了,那就试试打开数据库好了。以下为打开Hzkqs4J.db文件的尝试:
1、后缀为db,百度一下,发现是Delphi的数据库,正好,这软件不正是由Delphi编写的吗?那么下载Delphi7,用Database Desktop打开,提示错
误,不能打开表。这就奇怪了。
2、继续上网搜,有小伙伴说SQLite Expert这个软件可能能打开db数据库,下载试试。打开提示:文件被加密或它不是一个数据库。我能肯
定这是一个数据库,那么可能就是它被加密了,有没有什么办法确认呢?上网搜索了一下数据库文件的头部特征,表示如果是数据库文件的话,头
部通常会有字符标识出文件类型,用十六进制编辑工具打开试试。
图1-19直接打开数据库文件
那么这确实是被加密了,没办法了,使用现有的工具是打不开的。
还是再看看搜索的字符串,也许有看漏的呢,然后发现了这个,如下图:
图1-20 数据库的删除操作
直接明码写到软件中的字符串,删除T5和T3表的所有Fi的值不为1的所有记录,都开始删了,那表示到这步的时候数据库应该都注册完了,最后一个
committee,就提交生效了。这有什么用呢?T5这个表是做什么的?Fi又是记录的什么值?那还是做个测试对比吧:把这段指令上面的jnz(图没截出
来)改成jmp,前后运行对比,修改前注册成副机版了,当然标题还是主机版,只有1-8章的内容,修改后第9章以后的内容出现了,但是只有标题,内
容是空白的,连乱码都没有。那么结论就是这个Fi控制注册的章数,1-8章这个值为1,以后的章节,这个值为0,但是还是不知道T3和T5表是干什么的。
继续乱翻,这次目标是那些明码写成的SQL语句,记事本上记录了很多,后来发现没什么用,就不贴上来了,然后有时候果然还是要靠运气啊,在翻找内
存数据的时候(我也不知道为什么要去翻内存数据)有了意外的发现:
023A93C4 4F 4C 45 44 42 3A 44 61 74 61 62 61 7365 OLEDB:Database
023A93D420 50 61 73 73 77 6F 72 64 3D 74 65 73 74 6D64 Password=testmd
023A93E462 31 31 31 6C 6C 6C 31 31 6C b111lll11l
下面是第二段:
023A92D4 00 48 7A 6B 71 73 34 4A 30 2E 64 623B .Hzkqs4J0.db;
023A92E450 65 72 73 69 73 74 20 53 65 63 75 72 69 7479Persist Security
023A92F420 49 6E 66 6F 3D 46 61 6C 73 65 3B 4A 65 7420 Info=False;Jet
023A93044F 4C 45 44 42 3A 44 61 74 61 62 61 73 65 2050OLEDB:Database P
023A931461 73 73 77 6F 72 64 3D 74 65 73 74 6D 64 6231assword=testmdb1
023A932431 31 6C 6C 6C 31 31 6C 11lll11l
我居然发现了数据库的密码,虽然还没找到方法打开,但是先把这个记下来吧(软件作者的恶趣味啊,那么多的l和1,很伤眼睛啊)。
接下来怎么办呢?我又想到了那台注册了正版软件的笔记本了,没错,我又要“作弊”了,因为我的想法是假如我能模拟这个笔记本的机器码,那么我
就能用这枚正版注册码无限制的注册了,虽然不能完美破解,但好歹也算艰难的前进了一步吧。
好的,接下来这么干:
既然已经知道了File1-3的导出函数名,那么它们肯定与注册码的计算有关,可以把它们当成黑盒子,我只管输入与输出,不管它进行了怎么样的计算,
劫持它的输入与输出看是否能模拟已注册的正版,当然前提是我能找到正版的相关输入和输出。
下面主要分享下思路和测试过程,有些地方就不截图了。
首先确定并记下正版的相关值:
File1的输入
7037991083409157865918259232-5A5A5C47-9-7
输出
2569203B1933ED5F0DEBB1E8E3CF14CD3ED63741B5A40B00977162FCD7FC059CAA88333A9B2B3717A5ADE87A33EB9AF0
File2的输入
140000001EB7BB03C0D247669750F567E314C13B562B61E0B6C55E84C6FE54A527B4995B-9-7
输出433D42403D3B413C403D413B3E3A3C3D3E403F412A
由于正版已经注册过了,所以没有File3的相关信息,没关系,暂时不管它。那么在Call进File1之前,选择一个合适的时机添加自己的指令,我加的指令
像这样:
图1-21 添加指令
这里遇到了一个奇怪的问题,指令添加后由于原有的字符串和新加的字符串长度不一致,下一步读取的时候老是有问题,跟进File1的时候才发现它还记录
了字符串的长度,然后又出来找长度,就在原字符串首地址减去0x4的位置上,然后读取才正常。
同样的方法修改,发现总共需修改3个地方,File1的输入,File2的输入和输出。File1的输出在修改了输入后就和注册版一致了,这就表示File1中只是对
注册码的计算,File2中才有机器码参与运算。
这样就可以开始测试了,保存后直接运行,还是乱码,等等,既然注册要修改数据库,那么把注册版的数据库拷贝过来再试试,结果是成功的,现在我们
得到了一个模拟版的软件!
但是不能自行注册,必须有注册好的数据库,这算什么呢?
看来关键还是File3的注册过程啊,那么下一步要研究一下File3内部的指令了。
进入File3的空间,搜索所有标签和调用,有很多,慢慢看,有发现了:
图1-22 UTF8Decode和UTF8Encode
这么多行,一翻就看到了,全靠运气啊。这个调用摆明了是加密和解密,而且是将Unicode和Utf8互转来加密字符,那么跟踪它也许会有突破。
我的做法是在这些点全部下断,依次看返回,到底是在哪里调用的,过程是繁杂的,经过几个小时的跟踪,最后到了这里:
0055A3E8 .E81FA4EAFF call<Hzkq.System.@Halt0>
这是退出软件前的Call啊,软件作者在软件完全退出前对数据库进行了加密,等等,如果在0055A3E8处下断,运行到此处,那么数据库应该还没有
被加密,马上测试,软件断下来之后用UE打开Hzkqs4J.db这个文件:
图1-23 解密后的数据库
这货居然是Access的数据库!!居然伪装成了Delphi数据库!!这个UTF8Encode居然是对数据库文件的加密!!完全被骗了……
先将这个已解密的数据库备份出来,然后用mdb数据库查看器打开(家里面电脑没有装Access -_-!),果然提示输入密码,输入找到的密码,好的,
数据库打开了,如下图:
图1-24 T2表
总共6张表(T1不见了),T2表F2列是章节的标题,F3列点开后才能看到,1-8章都为1,后面的是0,将副机版和主机版分开了。
图1-25 T3表
T3表的数据比较多,关键是没有一个中文字符,但是仔细观察F2列字符数,然后上下对比,发现有些相同的字符,所有字符数都是4的倍数,运行
试用版对比一下题目,可以确定F2列确实是章节里面的题目,只不过中文被加密了,F3列同样被加密了,表示的是题目的描述信息,其他列就看不
懂了。值得一提的是,由于我打开的是试用版的数据库,T3表的数据虽然共有527条之多,但是在第2章之后,F2F3列就没有数据了,刚好对应于试
用版只有2章,3章以后是灰化的状态。
图1-26 T5表
T5表只有6条记录,很明显了,这个是视频讲解的那个部分,F3列同样被加密了,F4列猜测是对应文件夹HzT下的.str 文件,其他列看不懂。
图1-27 T7表
这个表厉害了,共有1060条数据,同样是所有字符加密,但是拉到最后,看到最后6条记录的时候,发现F1列值居然是2,和之前的T5表的记录数
很巧合,更巧合的是剩下的1054条记录刚好是T3表记录数527的两倍,那么接下来是我的猜测:T7表为原始数据表,注册后将数据写入T3表,根
据注册的版本决定写入多少数据。
还有其他的几张表,完全看不懂,就不截图了,有兴趣的小伙伴自行下载研究。
那么回到刚才的疑问:T1表在哪儿?想来想去,也只有试试另一个文件了:Hzkqs4J0.db
但是它也是加密的,用刚才的方法解密,发现在软件运行的时候,这个文件全程占用,无法复制,那么仔细对比文件Hzkqs4J.db加密后和解密后
的文件头,也就是图1-19和图1-23,发现作者似乎只加密了开始的0x70个字节的字符,那么直接用解密后的文件Hzkqs4J.db的头部替换加密的文件
Hzkqs4J0.db的头0x70个字符,会发生什么情况呢?
替换过程不多说,替换后打开,居然提示输入密码,然后……ma’de 截图的时候打不开了,这张图就省了,我来描述一下吧,打开提示文件无法正
确打开,但是显示出了数据库的表名,没错就是T1,只能看看表名,其实也没什么帮助。
那么图1-20的数据库删除语句通过对比数据库就能理解了,假如让删除操作执行的话,就会把主机板删成副机版,也就是说不能让它执行,同时
也说明软件在注册的时候并不对版本进行判断,直接注册成主机版,然后再通过相关标志删掉它认为多余的记录,这对破解是有帮助的。
有了数据库进行参考,接下来分析指令就很轻松了,在File3校验通过之后,如下图:
图1-28 开始注册数据库
在push ebp这条指令之前有几个系统的Call,很容易理解,是注册进度条上的相关指令,比如显示“正在注册”什么的,BenginTrans这个是操作数据
库的指令,意思是“下面我要开始操作数据库了”,Call 004F24D4这个函数是解密函数,它将一个字符串解密成SQL语句,通过单步步过就可以发现;
最下面那个Call采用同样的方法查看输出,发现是个数字,转成十进制就都明白了,它统计了T7表的记录数。
通过对比数据库,这段指令都能轻松的分析出来,我就不一一解说了。以下贴一些我认为比较关键的点,指令地址不连续:
0055297A.E8 9D2FF6FFcall <Hzkqs.DB.TDataSet.First> ;设置数据库记录指针在第一条记录上
0055297F .E9 E6000000 jmp Hzkqs.00552A6A;开始读T7表
005529CC .E8 CB77FDFF call <Hzkqs.DMUnit.sub_0052A19C>;算出来的是将要写入T3的Fb
00552A11 .FF51 60 call dword ptr ds: ;读表T7的F3列
00552A20 .E8 AFFAF9FF call <Hzkqs._Unit115.sub_004F24D4> ;解密T7表中F3列
00552A74 .80B8 A1000000>cmp byte ptr ds:,0x0 ;这个0应该表示记录结尾
00552A7B .^ 0F84 03FFFFFF je Hzkqs.00552984 ;完全读取T7表,不检查版本区别
00552ADE .E8 392EF6FF call <Hzkqs.DB.TDataSet.First>;设置在T3表中指针指向第一条记录
00552AE3 .E9 92000000 jmp Hzkqs.00552B7A;开始往T3表写入数据
00552BB8 .BB 01000000 mov ebx,0x1;ebx初始值1,下面是循环体
00552BCD .E8 02F9F9FF call <Hzkqs._Unit115.sub_004F24D4> ;Select * from T7 where F1=1 order by F2,F3
00552BD2 .8B95 28F8FFFF mov edx,dword ptr ss:;选择表T7中的题目527*2,F1=2的记录是教程动画共6条
00552C34 .E8 9BF8F9FF call <Hzkqs._Unit115.sub_004F24D4>;Select * from T7 where F1=2 order by F2,F3
00552C39 .8B95 20F8FFFF mov edx,dword ptr ss:;F1=2的时候,记录的是教程动画
00552CA2 > /A0 B8435500 mov al,byte ptr ds: ;以下开始注册动画教程
0055316F .^\0F84 2DFBFFFF je Hzkqs.00552CA2;以上注册了副机版
到此为止,我已经大概了解了注册的过程,现在我有试用版的解密数据库,自行注册版的解密数据库和正式版的解密数据库,对比发现这3个数据库
的字符串加密后的结果都不一样,那么唯一的解释就是它在注册时将字符解密,然后再加密写入数据库,这样只有使用加密时的密钥才能在软件启动
时正确解密,否则就是乱码,这样就达到了数据库不能通用的目地。那么接下来目标就是找到它解密后又加密的地方。
我采用的方法很简单,也很繁琐,就是在开始注册数据库的时候,一步一步的跟,注意堆栈和寄存器,什么时候出现了中文,就表示已经解密了。
下面是找到的一处:
0055308C .8BC8 mov ecx,eax ;kernel32.BaseThreadInitThunk
0055308E .66:81C1 50C3add cx,0xC350
00553093 .A1 DC205600 mov eax,dword ptr ds:;XRV
00553098 .8B00 mov eax,dword ptr ds:
0055309A .5A pop edx ;kernel32.75F3338A
0055309B .E8 FC70FDFF call <Hzkqs.DMUnit.sub_0052A19C> ;此函数解密F4列
005530A0 .8B85 B8F7FFFF mov eax,dword ptr ss:
005530A6 .50 push eax ;kernel32.BaseThreadInitThunk
005530A7 .8B45 C4 mov eax,dword ptr ss:;和注册码相关
005530AA .E8 2967EBFF call <Hzkqs.SysUtils.StrToInt>
005530AF .8BC8 mov ecx,eax ;解密修改点
005530A1 .66:81C1 50C3add cx,0xC350
005530B6 .A1 DC205600 mov eax,dword ptr ds: ;XRV
005530BB .8B00 mov eax,dword ptr ds:
005530BD .5A pop edx;kernel32.75F3338A
005530BE .E8 D970FDFF call <Hzkqs.DMUnit.sub_0052A19C>;同时也是加密函数
005530C3 .8B85 BCF7FFFF mov eax,dword ptr ss:
005530C9 .50 push eax;kernel32.BaseThreadInitThunk
005530CA .A1 DC205600 mov eax,dword ptr ds: ;XRV
005530CF .8B00 mov eax,dword ptr ds:
005530D1 .8B40 60 mov eax,dword ptr ds:
005530D4 .BA 0C445500 mov edx,Hzkqs.0055440C;F3
005530D9 .E8 1614F6FF call <Hzkqs.DB.TDataSet.FieldByName>
005530DE .8D95 ACF7FFFF lea edx,dword ptr ss:
005530E4 .8B08 mov ecx,dword ptr ds:
005530E6 .FF51 60 call dword ptr ds:
005530E9 .8B95 ACF7FFFF mov edx,dword ptr ss:
005530EF .A1 DC205600 mov eax,dword ptr ds: ;XRV
005530F4 .8B00 mov eax,dword ptr ds:
005530F6 .8B40 5C mov eax,dword ptr ds:
005530F9 .E8 F613F6FF call <Hzkqs.DB.TDataSet.FieldByName>
005530FE .5A pop edx;kernel32.75F3338A
005530FF .8B08 mov ecx,dword ptr ds:
00553101 .FF91 B0000000 call dword ptr ds: ;重新加密的字符
00553107 .A1 DC205600 mov eax,dword ptr ds: ;XRV
0055310C .8B00 mov eax,dword ptr ds:
0055310E .8B40 5C mov eax,dword ptr ds:
00553111 .8B10 mov edx,dword ptr ds:
00553113 .FF92 4C020000 call dword ptr ds:;DB.TDataSet.Post将数据写入数据库
以上指令就是将F4列解密再加密,解密用的是内置的密钥0xC71F,加密用的密钥和输入的注册码相关,关键的问题是解密和加密使用同一个函数
0052A19C。那么问题来了,假如我指定一个加密密钥来加密,再找到软件启动时候的解密密钥,也指定成同样的密钥,那么这就不依赖于注册码
了,是否能行呢?首先搜索所有的对0052A19C这个函数的调用,如下,后面是调试后写的:
地址 反汇编 注释
0052666A call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528?
005266D4 call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528?
0052687C call <Hzkqs.DMUnit.sub_0052A19C>
005268EF call <Hzkqs.DMUnit.sub_0052A19C>
00526A9F call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528 此处调用函数解码视频讲解动画 ------------------
0052AD5A call <Hzkqs.DMUnit.sub_0052A19C>---密钥006F
0052ADD0 call <Hzkqs.DMUnit.sub_0052A19C>
0052AE4E call <Hzkqs.DMUnit.sub_0052A19C>---密钥006F
0052AF60 call <Hzkqs.DMUnit.sub_0052A19C>
0052B024 call <Hzkqs.DMUnit.sub_0052A19C>---密钥006F
0052C30F call <Hzkqs.DMUnit.sub_0052A19C>---解密Fj密钥为F0即序号+0x457
0052C35A call <Hzkqs.DMUnit.sub_0052A19C>
0052EC06 call <Hzkqs.DMUnit.sub_0052A19C>
005364C0 call <Hzkqs.DMUnit.sub_0052A19C>
0053651F call <Hzkqs.DMUnit.sub_0052A19C>
00538897 call <Hzkqs.DMUnit.sub_0052A19C>
00538F10 call <Hzkqs.DMUnit.sub_0052A19C>
0054AE3C call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528 此处调用函数解码,二级菜单
0054C28E call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528 此处调用函数解码,二级菜单
0054DAA3 call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528 此处调用函数解码视频讲解动画 ------------------
0054F6B6 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399不知道是加密还是解密 3D4D*->3C3B*
0054F775 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399 不知道是加密还是解密 3D4B*->3E31*
0054F808 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399不知道是加密还是解密 3D3C*->3236*
0054F901 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399不知道是加密还是解密 3D4F*->3A3B*
0054F994 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399不知道是加密还是解密 3D37*->3936*
0054FA2D call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FA72 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FC08 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FC9C call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FCE1 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FE77 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FF23 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
0054FF95 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399解密:46543631303F3D3D574B4C535B->HZ020801WINXP
00550043 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
005500B5 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
00550163 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
005501D5 call <Hzkqs.DMUnit.sub_0052A19C>---密钥E399
00550A1F call <Hzkqs.DMUnit.sub_0052A19C>---密钥007F 解密:39533240454E4B513F->9S2AAMNR8
00550A84 call <Hzkqs.DMUnit.sub_0052A19C>---密钥007F
00550AC2 call <Hzkqs.DMUnit.sub_0052A19C>---密钥007F 加密:9S2AAMNR8->39533240454E4B513F
005529CC call <Hzkqs.DMUnit.sub_0052A19C>---密钥014D 算出来的是将要写入T3的Fb T3的Fb列使用密钥014D,固定值应该不用管
00552B30 call <Hzkqs.DMUnit.sub_0052A19C>---密钥006F 3339373535->39938
00552DB6 call <Hzkqs.DMUnit.sub_0052A19C>
00552DD9 call <Hzkqs.DMUnit.sub_0052A19C>
00552F5E call <Hzkqs.DMUnit.sub_0052A19C>---密钥04D4 对F5列进行解码 不使用相同密钥
00553005 call <Hzkqs.DMUnit.sub_0052A19C>---密钥0465 对F5列进行解码 不使用相同密钥
0055309B call <Hzkqs.DMUnit.sub_0052A19C>---密钥C71F 此函数解密F4列 这是原数据库加密的密钥
005530BE call <Hzkqs.DMUnit.sub_0052A19C>---密钥2528 同时也是加密函数 这是注册时重新加密写入时用的密钥
00555F49 call <Hzkqs.DMUnit.sub_0052A19C>
00555F89 call <Hzkqs.DMUnit.sub_0052A19C>
其中密钥2528和使用的注册码相关,使用的指令像下面这样:
mov ecx,eax
add cx,0xC35
0所以搜索命令序列:
mov ecx,eax
add cx,0xC350
mov eax,dword ptr ds:
mov eax,dword ptr ds:
pop edx
call 0052A19C
将找到的命令序列做如下改动,未改动的为解密函数,自定义密钥0xC350重新加密和解密
地址 反汇编 注释
0052665B mov ecx,0xC350 (初始 CPU 选择)
005266C5 mov ecx,0xC350
0052686D mov ecx,0xC350
005268E0 mov ecx,0xC350
00526A90 mov ecx,0xC350
0052EBF7 mov ecx,0xC350
0054AE2D mov ecx,0xC350 修改解码点
0054C27F mov ecx,0xC350
0054DA94 mov ecx,0xC350
00552DA7 mov ecx,eax
00552DCA mov ecx,0xC350
0055308C mov ecx,eax
005530AF mov ecx,0xC350 解密修改点
00555F3A mov ecx,eax
00555F7A mov ecx,0xC350
这样的话就完成了所有文字的解码,再也没有乱码了,但是测试发现没有动画,任何动画都没有,包括题目的动画和视频讲解的动画,这又是怎么回事呢?
图1-29 视频讲解白屏
这里出现了弹出消息提示,感觉应该很好处理,跟着这个消息一直追,发现调用的函数一层套一层,有这么多:
Hzkqs.Windows.user32.MessageBoxA
Hzkqs.Forms.TApplication.MessageBox
Hzkqs.Forms.TApplication.ShowException
Hzkqs.AppEvnts.TMultiCaster.DoException
Hzkqs.Forms.TApplication.HandleException
追了很久,无果,然后仔细观察后才发现,这些全部是系统函数,没有作者自定义的东西,那么这就不是作者在检测然后弹出错误,可能是系统读取
文件时的报错,也就是说要么文件错误,要么文件加密,没有正确解密。
已经确认a1.str为动画文件,用十六进制编辑器打开a1-16.str,发现文件头都不一致,猜测可能和数据库文件一样,采用了伪装和加密,随便在网上
下载一个.swf格式文件查看,头3个字节有CWF字样,表明格式,所以现在应找到解密函数。
上网搜Delphi调用Flash的接口,找到与本软件相关函数LoadFromStream,是将文件以流方式读入内存,然后再TStream.ReadBuffer,所以还是对比
模拟版,看到哪一步能正确解出。
在004213BB下断,第一次到此处,单步过TStream.ReadBuffer后,在ESI指向的内存地址再加0x4个字节的地址指向的地址,正确读出a1初始值,第
二次到此处,单步过TStream.ReadBuffer,同样的套路,模拟版正确解码,自行注册版解码不正确。
图1-30 正确解码的Flash文件
那么记下这个存放正确文件的首地址,往上追溯,肯定有函数会调用这个地址,然后往这个地址写数据,最后确定是这个自定义函数Hzkqs._Unit116.sub_0051F2AC。
图1-31 函数0051F2AC
还是先测试一下:在EDX指向的内存中修改函数带的参数,发现图1-30中的内存中的字符串发生变化,也就是说这个函数确实是解密函数,而EDX是
解密的密钥。
搜索命令 Call 0051F2AC,结果如下:
地址 反汇编 注释
0052A834 call <Hzkqs._Unit116.sub_0051F2AC> 135287965
0052A85C call <Hzkqs._Unit116.sub_0051F2AC> 135287965
0052AD23 call <Hzkqs._Unit116.sub_0051F2AC> 135287965
0052AEC1 call <Hzkqs._Unit116.sub_0051F2AC>
0052AF07 call <Hzkqs._Unit116.sub_0051F2AC>
0052C044 call <Hzkqs._Unit116.sub_0051F2AC> 点击视屏讲解时断在此处
0052C089 call <Hzkqs._Unit116.sub_0051F2AC> 点击视屏讲解时第二次断在此处
0052C6EE call <Hzkqs._Unit116.sub_0051F2AC> 运行后第一次断在此处,软件界面显示正在注册,解码
0052C728 call <Hzkqs._Unit116.sub_0051F2AC> 第二次断在此处,参数和第一次相同,此两处为注册相关
依次测试,发现前3条指令为固定密钥,不是关键,不用管,最后两条在软件注册界面断下,为加密函数(为当时认为是加密函数),然后发现紧接
着这两处,还有两个自定义函数:
图1-32 注册时的解密和重新加密
再往下就是TObject.Free,然后就没有了,由于指令call <Hzkqs._Unit116.sub_0051F2AC>同时出现在软件启动时和注册时,但指令call
<Hzkqs._Unit116.sub_0051F130>只在注册时出现,那么有理由猜测,0051F2AC是解密函数,0051F130是加密函数,在注册时先使用一个指定的
密钥将原始的数据解密,然后再根据输入的用户注册码将数据重新加密,这样得到的数据库在不同的用户之间是不能通用的。
现在测试就简单了,跟踪注册时的函数,记下其使用的密钥,我这里是31885993217236744115,共20位,在软件启动时发现解密密钥,
3281122932269210034,共19位,注入代码修改,这里还是和文字加解密的函数一样,密钥在内存的地址往翻,也就是地址减去0x4个字节存放了
密钥的长度信息,也要同时改,否则这里又要像我一样浪费一天时间了。
改完就成功了,天啊,终于成功了!!
static/image/hrline/line3.png
第二部分软件注册过程总结和修改点
1、软件需输入授权码和注册码。
2、软件采用重启验证。
3、数据库文件加密,在使用时解密,使用完重新加密。
4、数据库关键字段加密,注册时先用一个自定的密钥解密,然后再使用注册码相关的一个值作为密钥加密,这样来使得注册用户间的数据库不能通用。
加解密函数为同一个,加解密密钥也是同一个。
5、教程动画和题目动画加密,采用第4条的方法,加解密函数不同,密钥相同。
6、不同版本的注册在写入数据库时没有检测,写数据库完成后才根据注册码删掉相应的内容,最后删除原始数据表,也就是说,如果没有保留初始数据
库的话,只能注册一次。
7、以下为修改点:
调试使用授权码:9111111111111117
调试使用注册码:91111111111111111117
ps:授权码输成这个鬼样子是因为输错了,以为在输注册码,后来就将就了 @_@!
(1)、00551518 . C700 02000000 mov dword ptr ds:,0x2 将0x2改为0x1
(2)、00551304 . 8B55 A0 mov edx,dword ptrss: 真码第1位是9
(3)、00551342 . 8B55 9C mov edx,dword ptrss: 真码第20位是7
(4)、File1File2File3各修改两处跳转(搜索字符ERR)
(5)、
00551CE8 8B95 0CF9FFFF mov edx,dword ptrss:;
00551CEE . 58 pop eax ;0018F4B8
改为
00551CE8 58 pop eax;交换两个指令并修改
00551CE9 8BD0 mov edx,eax
(6)、
005522B5 8802 mov byte ptr ds:,al
005522B7 FF85 7CFFFFFF inc dword ptr ss:
在地址005522B5处添加jmp 0055A42A
0055A42A 83FB 07 cmp ebx,0x7
0055A42D 74 17 je short Hzkqs.0055A446
0055A42F 83FB 06 cmp ebx,0x6
0055A432 74 12 je short Hzkqs.0055A446
0055A434 83FB 04 cmp ebx,0x4
0055A437 74 0D je short Hzkqs.0055A446
0055A439 83FB 03 cmp ebx,0x3
0055A43C 74 08 je short Hzkqs.0055A446
0055A43E B0 01 mov al,0x1
0055A440 EB 02 jmp short Hzkqs.0055A444
0055A442 B0 00 mov al,0x0
0055A444 8802 mov byte ptr ds:,al
0055A446 FF85 7CFFFFFF inc dword ptr ss:
0055A44C ^ E96C7EFFFF jmp Hzkqs.005522BD
(7)、005523A8 0F85 2A150000 jnz Hzkqs.005538D8 ;不能跳,直接nop
(8)、00553427 /75 46 jnz short Hzkqs.0055346F ;删除操作,改为jmp
(9)、搜索命令序列
mov ecx,eax
add cx,0xC350
mov eax,dword ptrds:
mov eax,dword ptr ds:
pop edx
call 0052A19C
将找到的命令序列一次改为如下,未改动的为解密函数,自定义密钥0xC350重新加密和解密
地址 反汇编 注释
0052665B mov ecx,0xC350 (初始 CPU 选择)
005266C5 mov ecx,0xC350
0052686D mov ecx,0xC350
005268E0 mov ecx,0xC350
00526A90 mov ecx,0xC350
0052EBF7 mov ecx,0xC350
0054AE2D mov ecx,0xC350 修改解码点
0054C27F mov ecx,0xC350
0054DA94 mov ecx,0xC350
00552DA7 mov ecx,eax
00552DCA mov ecx,0xC350
0055308C mov ecx,eax
005530AF mov ecx,0xC350 解密修改点
00555F3A mov ecx,eax
00555F7A mov ecx,0xC350
(10)、
00552EB8 .7414 je short Hzkqs.00552ECE 改为jmp
00552F6F . /0F85 A6010000 jnz Hzkqs.0055311B 改为0055302C
static/image/hrline/line3.png
第三部分总结
整个分析过程断断续续大概半年,刚开始时面对算法真的是无能为力,暴露出了本萌新在算法方面的不足,在算法方面的素质还有待提高。
不过正是因为什么都不知道,才在开始时没有发现所有资源都被加密了,这样才能硬抗着上了,要是一上来就发现这也加密那也加密,可能直接
就放弃了。
心里面现在最大的感悟就是论坛某个小伙伴说的一句话,算法啊什么的都不重要,破解最重要的是想法,要有想法,有思路!
好了,完。
2017年4月10日最后一次编辑更新,以后不再更新
链接: http://pan.baidu.com/s/1i4PNHzf 密码: 6h3q
楼主大神,膜拜 正在调整排版,各位小伙伴不要着急,没想到这次这么快就通过了,上次等了好久的 位楼主的坚持加油 好长,先做个标记,慢慢看 楼主辛苦啦 楼主的精神,值得我们学习 楼主辛苦,虽然没看懂但是感觉很厉害!!楼主能不能给我讲讲新手应该怎么过来! 楼主真厉害 weizhe 发表于 2017-3-13 14:35
楼主辛苦,虽然没看懂但是感觉很厉害!!楼主能不能给我讲讲新手应该怎么过来!
第一部分就是新手部分啊,只是省略了一些操作,比如单步啊,搜索啊
也不可能详细到“此处单步200次”这样啊,其实有些地方确实是按了F9两百多次才找到地方