首页
论坛
课程
招聘
[原创]谈谈软件追踪技巧兼AotuRun的算法分析
2007-1-12 17:04 10715

[原创]谈谈软件追踪技巧兼AotuRun的算法分析

wulje 活跃值
14
2007-1-12 17:04
10715
网友在贴子上说AotuRun的注册码引起了他的困惑,我出于好奇下载并打开了在Round1中的bac.bin。在OD(可直接打开)监控下,我随便输入注册码,当弹出“错误注册码”框后,在堆栈栏中出现了字串“Jzsvvi1lkFid”,果然它也是一个正确的注册码。我当时认为这个程序只不过是小菜一碟,网友说真正的注册码是“3u8A-5DKU1-JDAf-3vvE15-KlsHW2”,并且说用不同的方式打开后,画面不同(我用不同的码打开后,看不出有什么不同。由于界面没有任何操作按钮,程序不能运行,我估计是网络游戏)。接着我分析了它的比较过程,发现它先用明码比较“Jzsvvi1lkFid”,如果不对,再将“3u8A-5DKU1-JDAf-3vvE15-KlsHW2”通过冗长的计算来比较。我只是猜想“Jzsvvi1lkFid”是方便作者对程序的调试,不对外,或者本身是个诱饵,使你进入一个功能不全的界面,忽略对真正注册码的研究?当时我认为这个问题已完美解决了,但当我试图写一个注册机来验证它的注册方法时,才发现该程序写得十分诡秘,远比想象的复杂得多。
        它的诡秘大约在这几个方面:
        1.程序所有的窗口都由CreateDialogIndirectParamA函数创建,包括出错警告框。它们共用一个回调过程,所有的消息和操作都通过该通道。想拦截到注册框,非常困难。
        2.明码比较“Jzsvvi1lkFid”,或许是个诱饵?
3.注册码共25个字符,它的前18个字符由软件作者安排,第19个字符由Jzs-vvi-1lk-Fid计算值与3u8A-5DKU1-JDAf-3vvE1的计算值的商再除以3D的余数来定,最后6个字符由前19个字符逻辑运算产生。
        4.当你的注册码符合第3条后,然后再四分组检验前18个字符的计算值,如果不符合它暗藏的条件并不报错,但程序立即崩溃。使你对崩溃的原因一头雾水。
        由于要说清楚这些问题的来龙去脉不是一两句话的事,若你不感兴趣就此打住;否则就要稍有点耐心。下面分条细说:
一、        截注册框,查找“确定”按钮的入口地址是破解注册码的关键:
1.从查找注册窗口回调函数入口入手:
打开SoftICE和Spy&Cap,用后者找出注册框句柄,再用SoftICE找出注册框的回调函数入口是00443860。进入该入口,什么回调程序呀?竟然没有消息处理分支结构,无论怎样设断和跟踪都不会出现明显与注册框操作有关的代码,甚至一动鼠标程序就停在没断处,除非取消设断。单步跟踪也没有任何结果,累到你主动放弃跟踪。总之,它不是你熟习的窗口回调函数。这一招是失败了!
2.在OD中查找所有的分支,寻找111(WM_COMMAND)消息入手:
        窗口的所有操作都必须有windows消息中的WM_COMMAND指令,该指令值为111。在OD中找到包含111的case (有多个窗口一般就有多个111,但本程序共用一个通道,就只有一个111。屏幕上表现为打开一个新窗口,就自动关闭原来的窗口),双击来到下列代码:
0047CC8B                81FB 11010000..cmp                ebx, 111             ;  Switch (cases 6..111)
0047CC91                56………………push                esi
0047CC92                  57………………push            edi
0047CC93                  8BF9……………mov             edi, ecx
0047CC95                 75 1B……………jnz             short 0047CCB2
0047CC97                FF75 10…………push            dword ptr [ebp+10]    ;  Case 111 (WM_COMMAND) of switch
0047CC9A                 8B07……………mov             eax, [edi]
0047CC9C                 FF75 0C…………push            dword ptr [ebp+C]
0047CC9F                FF90 E8000000…call            [eax+E8]
        注意0047CC95处转跳,当ebx=111时进入0047CC97,这就是窗口WM_COMMAND指令的入口。但问题远没有解决,所有的窗口(动作)操作都由它进入,还必须找出你需要的按钮操作。
        3.在消息WM_COMMAND携带的参数wParam中找出“确定”按钮的ID值:
        “确定”按钮的默认值ID=1,但必须通过Spy&Cap软件去验证一下。现在必需在0047CC97处将代码分流出来,当[ebp+c]=1时将它拦截下来。注意,常规情况下,消息传递中[ebp+8]是hwnd,[ebp+c]是uMsg,[ebp+10]是wParam。当然软件作者也可以任意安排。它隐藏了hwnd,将[ebp+8]=uMsg,[ebp+c]=wParam。这需要在0047CC9F处设断从OD的堆栈中得到确认(从数值上去判断,[bp+4]肯定是返回地址)。当这些都准备好后,对程序作如下手术:
        改动0047CC97处代码为:
0047CC97     E9 AFDA0000   jmp     0048A74B                ;转跳到空白处
        在0048A74B处(空白地址)添加下列代码,并在nop处设断:
0048A74B        FF75 10……….push            dword ptr [ebp+10]
0048A74E        8B07……………mov            eax, [edi]
0048A750        53………………push            ebx
0048A751        8B5D 0C……….mov            ebx, [ebp+C]
0048A754        83FB 01……….cmp            ebx, 1
0048A757        75 01……………jnz             short 0048A75A
0048A759        90………………nop                                                ;(设断)
0048A75A        5B………………pop             ebx
0048A75B        E9 3C25FFFF….jmp             0047CC9C
        当我们在0048A759处设断后,运行,输入假注册码,按下“确定”,立即被断了下来。然后跟踪,一律不跟进call,注意观察状态拦,看有无假注册码出现。一路小跑什么码也没出现,“嘭”的一声弹出密码错误警告框,按“确定”后程序就退出了。打开程序再试,居然还是没有发现是从什么位置弹出的警告框,第一次较量就这样败了下来!
        二、跟踪技巧:
        在windows自带的1000多条消息中,找到了携带“确定”按钮ID的WM_COMMAND,已经是很不容易了,接下来还要做深入细致的跟踪工作(0048A759断点不能取消)。
1.在所有的MessageBox函数处设断,创建窗口的CreateDialogIndirectParam函数处设断:
        在MessageBox设断,是企图寻找什么时候调用它们的。设断后再试,在第二个函数第一次中断后没有出现注册框,继续,弹出了注册框,输入假码后第二次在CreateDialogIndirectParam中断,用F8跟踪一步就中断在MessageBox处,往回走发现它完全是个无厘头。它是一个独立的函数过程,根本不知道是从什么地址进入的!(没想到小小程序这么猾头)显然单步跟踪CreateDialogIndirectParam是惟一希望了。
单步进入CreateDialogIndirectParam函数(它是USER32中的一个函数)后,好象万里长征,对里面的call一个也不要跟进,一旦弹出出错框,立即右键打开OD弹出式菜单,选“转到――上个”,记下该call地址设断,下次跟进。在这个函数中的断点是:77D24E23,下一次跟踪时一定要跟进。
        2.在77D24E23进入后,还是使用对call不跟进的方针,走了无数步的后稀里糊涂一下就转跳到了00404BC7,居然回到了主程序(在XP的很多*.dll中,都是用int 20中断弹回到主程序的,int 20是什么机制我不知道)。现在离目标近了。按继定方针,走到了00404CC2漏掉了,下次跟进,进入call 403E60后,才走几步就在状态拦中出现了输入的假注册码,注册码比较过程就在眼前了。注册过程藏得这么深,在众多软件中还是比较少见的。
        3.进入call 403E60后,还是不跟进call,但要密切注意状态拦和堆栈。一路走下去,发现一连串的call  4013F0,每跳过一次堆栈中就出现一段注册码,原来注册码是分段存放在内存不同的地址的,需要时才一段段的将它取出(大家都知道搜索功能,他不防范你么?该软件内存中也根本没有比较码Jzsvvi1lkFid,那怕是它的一段)。接着是一连串的call 4026B0,每过一次,注册码就连接一段。当堆栈中出现了连接好了的注册码和比较码后,要警惕了!比较过程就在眼前,刚跳过call 469860,eax就返回了FFFFFFFF,这是比较不能通过的信号,call 469860是比较过程无疑了。真是走过了万水千山,终于逮住了这个小猾头。搞软件是苦职业,搞破解更是苦差事!幸好我两样都不沾。
        三、注册算法原理:
        1.00469888地址是明码比较过程,比较过不了就进入00405985,然后将比较字串和注册字串都转换成16进制数,显然它的算法原理就在00405985里面。然后在00404409: call 469E40处理两个计算结果,它将两数相除,再将商除以3D取余数,将余数转换成字符与注册码的第19位比较,不同就报错,然后将正确的前19个字符,进入00405903用另一种算法得到8位整数,通过call 405A10得到最后6个字符,将这6个字符与注册码的最后6个比较,正确后算是初步通过。(算法原理后面给出)
        2.到此,我以为写个注册机也不困难了,还不知道程序后面暗藏杀机!试着按它的原理,手动的编出了一个25字符的注册码,输入后试验,没有报错,但程序一下子就崩溃了。弹出了一个C++系统的警告窗口,大意是程序执行了非法操作,强令退出,请与开发商联系。啊~啊!什么非法操作?分明是作者在402F14:call 46997B返回后设置的一个int 3死亡陷阱!
原来后面还有很长的计算和慢长的检验!它将初步过关的注册码前18个字符按4、5、4、5个分成四组,每组的计算值都被2710除,商和余数都必须小于19A1,但它不警告你,不管你的商和余数是多少都进入后面的校验。不满足商和余数条件的打入死亡陷阱,满足的则将商和余数查表(一个庞大的表)转换成6~8位的一个大数,然后通过循环运算归零(不归零怎样,没实验)。每一组都通过后,对注册码的检验才算完成!
        啊~,一个不大程序的注册方法看起来都累,还不知注册保护今后如何发展?看来不累死几个破解者在计算机桌前,誓不罢休!(嘻!谁叫你没事专搞破坏?)
四、构造注册机的几个必要模块:
        1.字符与数值的互换:
        原程序的转换思路是查表。将字符的ASCII码减去30后,将其值乘4作为转跳距离,跳到指定位置后取该位置的值作为转换值返回。由于反过来将数值转换成字符时,该项表不能用,又搞了一个反查值表,整个程序显得臃肿(00404F80)。完全可以把字符排序,将序列号作为转换值返回,其程序结构将大为简化。
下面给出用汇编实现上述功能的模块。模块1是把字符串整体转换成数值串,目的是为后面的自动选择适合条件的字符运算简化了程序。
        .const
szlist        db        '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-',0
        .code
;(模块1)--------------------------------------------------
;字串转换成数串函数:(参数:_字(数)串地址,_串长度)
StringToNumber          proc   _esi,_lench
               
…………pushad
…………mov                esi,_esi                                        ;字串和数串的共用地址
…………xor                ebx,ebx
………….while        ebx < _lench
………………mov                al,byte ptr [esi+ebx]
………………mov                ecx,3fh       
………………lea                edi,szlist                        ;取转换表地址,即开始的那个排序表
………………repnz        scasb                        ;注意,repnz返回的ecx自动减1,要从差值上调整
……………….if                ecx = = 0                                ;表中没有的字符或“-”,转换值为0
…………………………jz        @F
……………….endif
………………sub                ecx,3eh
………………neg                ecx
……@@:...mov                byte ptr [esi+ebx],cl
………………inc                ebx
………….endw       
…………popad
…………ret
StringToNumber  endp
;(模块2)-------------------------------------------------------
;单个数值转换成单个字符函数:(无参数,al代入数值,al返回字符)
NumberToString          proc        uses esi                       
…………movzx        eax,al
…………lea                esi,szlist                                ;还是那个排序表,通用
…………mov                al,byte ptr [esi+eax]
…………ret
NumberToString  endp
        2.字符串转换成大整数:(模块3)
        严格的说,是数串转换成大整数,因为字符串经模块1已经变成了数值串。它的算法原理是:
        N=x1*3D^0+x2*3D^1+x3*3D^3+……+xn*3D^n
        其中,x1,x2等是转换后的数值。原程序写的算法过程还可以,只是转跳太多,读起来不方便,且有一个漏洞:即加数的进位被忽略了。好在是程序本身对结果的准确度要求不高,而且这种进位的几率太低。什么意思?所谓“准确度要求不高”是指运算超过了16位,高位会被舍去。相除取商,余数也被忽略。“几率太低”例如,167A38C4加上一个小于3D的数,在第8位上产生了进位,惟一的情况是前6位都是FFFFFFxx,好象永远都不会发生。但作为一个计算算法,还是加上恐怕才严谨些。下面给出一个源代码:
;----(模块3)-----------------------------------------------------
;数串转换成大整数函数:(参数:_结果地址(高位在前4字节),_数串地址,数串长度)
CharToNumb        proc    _IntH,_esi,_lench
…………pushad
…………xor                ecx,ecx
…………mov                edi,_IntH
…………mov                [edi],ecx
…………mov                [edi+4],ecx
…………mov                ebx,3dh
…………mov                esi,_esi
…………mov                ecx,_lench
…………dec                ecx
…………js        @F                        ;数串长=0退出
………….while        ecx != 0
………………mov                al,byte ptr [esi+ecx]
………………movzx        eax,al
………………add                eax,[edi+4]
………………jae                step
………………inc                byte ptr [edi]        ;若有进位,高位+1
…….step:.mul                ebx
………………mov                [edi+4],eax        ;乘积结果低位
………………push                edx
………………mov                eax,[edi]
………………mul                ebx
………………pop                edx
………………add                eax,edx
………………mov                [edi],eax        ;乘积结果高位
………………dec                ecx
………….endw
…………mov                al,byte ptr [esi]
…………movzx        eax,al
…………add                [edi+4],eax
…………jae                @F
…………inc                byte ptr [edi]
@@:…popad
…………ret
CharToNumb  endp
        3.检验码的生成:(模块4)
        程序用注册码的前19个字符生成6个字符的检验码。原程序的这一段写得还可以,但转跳太多阅读容易看花眼。稍加改动后,少了两个转跳,代码行数不变,读起来清楚了许多(有兴趣的可以查看00405903处)。后半段是我添上去的,目的让字串一气呵成。顺便说几句,请不要评价C++与汇编谁个更优,各自发挥优势的地方不同。垃圾代码的多少,软件的大小、资源占有率、运行效率等在电脑硬件飞速发展的今天早已不是任何问题了。苦苦地追求程序的优化只是自找苦吃和自我陶醉!扯远了,回头来。生成检验码是一个逻辑运算,本来贴个图,逻辑流程一目了然,但我没有贴图权限,只有用嘴说了。将字符串排序, eax初值为0,(转入点)eax自加,若eax>=8000,则xor eax,1021,从最高位开始进行位测试,若该位为1,则xor eax,1021,否则测试下一位并回跳“转入点”。实现该逻辑运算代码如下:
;--(模块4)-----------------------------------------------------
;生成检验码函数:(参数:_生成码地址,_源字串地址,_字串长度)
TestChar        proc         _edi,_esi,_lench
               
…………pushad
…………mov                edi,_lench
…………or                edi,edi
…………jz                _Off                        ;字串长为0退出
…………xor                eax,eax
…………mov                esi,_esi
step1:.mov                dl,byte ptr [esi]
…………mov                cl,80h
step2:.push        eax
…………add                eax,eax
…………pop                ebx
…………test                bh,bh
…………jns                step3
…………xor                eax,1021h
step3:.test                cl,dl                        ;位测试
…………je                step4
…………xor                eax,1021h
step4:.shr                cl,1
…………test                cl,cl
…………jnz                step2
…………inc                esi
…………dec                edi
…………jnz                step1                        ;以上是产生注册码前19个字符转换的数值
…………mov                edi,_edi                       
…………mov                ecx,3dh                        ;以下生成注册码的后6个字符
.repeat       
…………xor                edx,edx
…………div                ecx
…………push                eax
…………mov                eax,edx
…………invoke        NumberToString        ;查字符表
…………mov                byte ptr [edi],al
…………pop                eax
…………inc                edi
.until…eax                = = 0
…………mov                byte ptr [edi],al        ;字串尾部添个0
_Off:.popad
…………ret
TestChar        endp
        4.自动调整注册码字符,使之适合苛刻条件:(模块5)
        注册码前18个字符,按4、5、4、5分组,每组的计算值被2710除,商和余数都必需小于19A1。下列代码可以实现这一功能,在主程序模块中随机输入18个字符,先转换成数值串,再分别按4、5、4、5四次调用本模块,就会得到满足条件的前18个字符的数值串,最后整体还原为字符串。
;---------------------------------------------------------------------------------------------
;检验数值串是否符合要求并自动校正函数:(参数:_数值串地址,_数串长度)
TestNumber        proc         _esi,_lench
…………local        @szIntHL[8]:byte
…………pushad
…………mov                esi,_esi                                        ;地址共用,返回值也用该地址
…………lea                edi,@szIntHL
…………mov                ecx,2710h
…………mov                ebx,_lench
@@:...invoke        CharToNumb,addr @szIntHL,esi,_lench       
…………xor                edx,edx
…………mov                eax,[edi+4]               
…………div                ecx
………….if                (eax >= 19a1h)||(edx >= 19a1h)
……………….if        byte ptr [esi+ebx-1] = = 0
……………………dec        ebx
……………….endif       
………………dec        byte ptr [esi+ebx-1]
………………jmp        @B
………….endif                               
…………popad
…………ret
TestNumber        endp
        5.最后的组装:
        注册机的所需功能函数全都有了,剩下只须组装即可。不过,我不再说了。再说下去就与本论坛的精神相悖了。只是被充一点,第19个字符由下列方法产生:A=第1组的计算值,B=第2组的计算值,C=第3组的计算值,D=第4组的计算值,E=Jzs-vvi-1lk-Fid(包含-号)的计算值。计算K=E/(A*B+C*D),K只取整数。K/3D取余数。该余数换成字符就是第19个字符。其实算K时,大整数只取高8位也可以。
        只是一种学习探讨,不对的地方,特别是源代码,敬请指正!

2022 KCTF春季赛【最佳人气奖】火热评选中!快来投票吧~

收藏
点赞0
打赏
分享
最新回复 (16)
雪    币: 502
活跃值: 活跃值 (35)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
BlueT 活跃值 2 2007-1-12 18:48
2
0
精彩!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:570 )
在线值:
发帖
回帖
粉丝
wulje 活跃值 14 2007-1-12 20:28
3
0
不好意思,忙乱中居然将“AutoRun”错写为AotuRun,特此更正。
雪    币: 661
活跃值: 活跃值 (682)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
qyc 活跃值 4 2007-1-12 22:08
4
0
你真行啊! 坚持了那么久强奸它
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mydear256 活跃值 2007-1-13 12:30
5
0
学习!
佩服作者的耐心
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ljjue 活跃值 2007-1-13 13:01
6
0
佩服LZ,谢谢LZ,好文
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
七星飘虫 活跃值 2007-1-13 13:15
7
0
激动 感谢
此时心情言语难以形容!

谢谢wulje老师,您辛苦了~!

他的算法比我想象中还要难的多,您能分析出算法让我佩服之至。

我原来在我的贴子中说过的,谁能帮我分析出来我会付筹金的

请wulje老师与我联系,我QQ 456727 mail: [email]yin.hang@163.com[/email]

也同时跟看雪老师说,
您如果要屏蔽我留的联系信息的话, 请帮我把联系方式转给wulje 谢谢!
我知道不应该留自己的联系方式,但是我这个人说过的话是不会食言的,承诺过的事一定会兑现的。

雪    币: 325
活跃值: 活跃值 (20)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
morning 活跃值 1 2007-1-13 13:35
8
0
楼主windows下编程技术不够一点,很多其实是很明了的,举轻若重了.

算法部分我没试过,不清楚
雪    币: 1001
活跃值: 活跃值 (31)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lingyu 活跃值 1 2007-1-13 14:42
9
0
精彩
雪    币: 1298
活跃值: 活跃值 (63)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
yijun8354 活跃值 12 2007-1-13 17:14
10
0
好文,支持+学习~~~
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bingxiao 活跃值 2007-1-13 19:16
11
0
精彩 学习ING
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ljy3282393 活跃值 1 2007-1-13 22:13
12
0
支持+学习
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:570 )
在线值:
发帖
回帖
粉丝
wulje 活跃值 14 2007-1-14 00:01
13
0
谢谢你的好意,心情我领了!我只是个软件爱好者,绝不会收筹金的!再表谢意,真心的。正如文中说的“两者都不沾”,我没有撒谎。
顺便回8楼:
谢谢支持!汇编我是编译通过后才写上去的,应该没有问题,是不是还可以优化,我没有细致思考。
其实文中真正的错误的四点:一是,算法公式中最后一项应该是:……xn*3D^(n-1),是误写,程序中没问题。二是,逻辑算法原理的解说中,eax>=8000是错的,准确的说是:eax的第15位为1(在第16的位置上)(由于要和国际接轨,小学教学生数数都开始从0起)。
三是,“几率”说法不规范,“机率”或“概率”准确些。四是:[bp+4]应该是[ebp+4]。

再次谢谢各网友的支持!鼓励!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
七星飘虫 活跃值 2007-1-14 00:29
14
0
最初由 wulje 发布
谢谢你的好意,心情我领了!我只是个软件爱好者,绝不会收筹金的!再表谢意,真心的。正如文中说的“两者都不沾”,我没有撒谎。
顺便回8楼:
谢谢支持!汇编我是编译通过后才写上去的,应该没有问题,是不是还可以优化,我没有细致思考。
其实文中真正的错误的四点:一是,算法公式中最后一项应该是:……xn*3D^(n-1),是误写,程序中没问题。二是,逻辑算法原理的解说中,eax>=8000是错的,准确的说是:eax的第15位为1(在第16的位置上)(由于要和国际接轨,小学教学生数数都开始从0起)。
三是,“几率”说法不规范,“机率”或“概率”准确些。四是:[bp+4]应该是[ebp+4]。
........


佩服崇敬!
再表感谢!

真希望能有您这样的朋友,
小弟以前曾在重庆工作过一年,对重庆人以及重庆话都非常的喜欢。
现暂居广州,您如有驾临, 定有联系小弟,以聊表谢意

如您不弃,加我QQ 456727  您这个朋友我是交定了~!
雪    币: 212
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
zhy_qie 活跃值 1 2007-1-15 10:10
15
0
好文!学习ing.
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
无法无天 活跃值 2007-1-15 16:23
16
0
文章不错,我的朋友也分析过,结论错不多,不过现在已经不搞这个啦

这个论坛就要像你这样的文章来烘托,不然我就不来啦,赫呵,看看
赤诚!~
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
soychino 活跃值 2007-1-16 09:47
17
0
你太强了!!!
游客
登录 | 注册 方可回帖
返回