-
-
[原创]菜鸟看懂算法以后之一:头痛的64次左移
-
发表于:
2005-4-22 13:07
10306
-
菜鸟看懂算法以后之一:头痛的64次左移
由于jney2上网在线的时间不多,加上老大的论坛也经常上不了,有限的在线时间也就是从天空软件站里拉些共享软件供自己练练手、在论坛里看看新的精华帖,所以与论坛的交流不算很多。加上本人做过电脑培训的工作,也非常愿意把自己的一点点收获告诉象我一样的菜鸟。于是前两天分析了“中华压缩V10.8”,写好了贴子,准备今天发上来,结果一搜索“中华压缩”,便看到baby2008于三月二十几号就已经把一篇完整的算法帖发在论坛上了。也就只好作罢。
但jney2的收获又好象不在写出了一篇几乎与baby2008一模一样的破文,因为我的收获是真正搞懂了一段复杂汇编代码的高级语言的含义,而这段代码也难倒了不少好汉,甚至包括早期的Fly大侠。baby2008应该是懂的,他的文章中点出了要害,但可惜也没有详细说明这段代码为什么是这样写,它的原理又是什么。于是我又有了共享的冲动,只不过它是免费的。算是一篇教程吧,这样大家只要看到这段代码就知道是实现的是什么功能了。
因为程序中有多处调用该函数,且其它软件的破文中也找到类似的函数,我就断定,这不可能是作者写的,应该是编译系统的系统函数,最终印证了我的想法是正确的。
好了,引出我们的主角―――:四字长的MOD函数:
先看一下入口参数:
0050151A |. 52 |push edx
0050151B |. 50 |push eax ;EDX(高双字)+EAX(低双字):这里是除数,压缩入堆栈保存。
0050151C |. 8B46 68 |mov eax,dword ptr ds:[esi+68]
0050151F |. 8B56 6C |mov edx,dword ptr ds:[esi+6C] ;EDX(高双字)+EAX(低双字):这里是被除数。
00501522 |. E8 0945F0FF |call _ChinaZi.00405A30
再看函数体:
00405A30 /$ 55 push ebp
00405A31 |. 53 push ebx
00405A32 |. 56 push esi
00405A33 |. 57 push edi ;保护现场,因为下面要用到这几个寄存器。
00405A34 |. 31FF xor edi,edi ; EDI清0
00405A36 |. 8B5C24 14 mov ebx,dword ptr ss:[esp+14]
00405A3A |. 8B4C24 18 mov ecx,dword ptr ss:[esp+18] ;ECX(高双字)+EBX(低双字):取除数到寄存器。
00405A3E |. 09C9 or ecx,ecx
00405A40 |. 75 08 jnz short _ChinaZi.00405A4A ;ECX不为0则跳走,也就说除数超过32位则跳走
00405A42 |. 09D2 or edx,edx
00405A44 |. 74 5D je short _ChinaZi.00405AA3 ;如果EDX也为0,则跳到最简便的32位除法运算,一条指令搞定。
00405A46 |. 09DB or ebx,ebx
00405A48 |. 74 59 je short _ChinaZi.00405AA3 ;如果EBX也为0,则跳到最简便的32位除法运算,一条指令搞定。如果从这里跳走的话,实际上就是除以0,应该会出现异常的啦!
00405A4A |> 09D2 or edx,edx
00405A4C |. 79 0A jns short _ChinaZi.00405A58 ;符号为正则跳,即为被除数为正整数就跳
00405A4E |. F7DA neg edx
00405A50 |. F7D8 neg eax
00405A52 |. 83DA 00 sbb edx,0 ;为负则取反
00405A55 |. 83CF 01 or edi,1 ;设置为负的标志
00405A58 |> 09C9 or ecx,ecx
00405A5A |. 79 07 jns short _ChinaZi.00405A63 ;符号为正则跳,除数为正整数就跳
00405A5C |. F7D9 neg ecx
00405A5E |. F7DB neg ebx
00405A60 |. 83D9 00 sbb ecx,0 ;为负则取反
00405A63 |> 89CD mov ebp,ecx ;除数变为EBP+EBX,因为ECX要做计数器
00405A65 |. B9 40000000 mov ecx,40 ;设定循环次数为64次,为什么?
;也许很多人搞不懂的就是这里,我开始也就是搞不定这里,我参考了数篇破文,甚至搬出了我那古老的8086汇编教程,搞到深夜一两点,我终于开窍了。
;因为两个32位寄存加起来刚好是64位,也就刚好把EDX+EAX的值移到(当然是要用带进位的)EDI+ESI中(如果不作后面的减法运算的话)
00405A6A |. 57 push edi ;保存这个临时变量到堆栈
00405A6B |. 31FF xor edi,edi
00405A6D |. 31F6 xor esi,esi ;EDI+ESI=0
00405A6F |> D1E0 /shl eax,1
00405A71 |. D1D2 |rcl edx,1
00405A73 |. D1D6 |rcl esi,1
00405A75 |. D1D7 |rcl edi,1 ;左移一位,实际上就是借一位,也等于乘2
00405A77 |. 39EF |cmp edi,ebp ;比较除数的高双字
00405A79 |. 72 0B |jb short _ChinaZi.00405A86 ;小于就直接进行下一次借位,也也就是说不够除。
00405A7B |. 77 04 |ja short _ChinaZi.00405A81 ;大于就减去除数
00405A7D |. 39DE |cmp esi,ebx ;高双字相等的话,继续比较低双字
00405A7F |. 72 05 |jb short _ChinaZi.00405A86 ;小于就直接进行下一次借位,也也就是说不够除。
00405A81 |> 29DE |sub esi,ebx
00405A83 |. 19EF |sbb edi,ebp ;减去除数
00405A85 |. 40 |inc eax ;加1?NO。也许很多人更加搞不懂这里。为什么?答案:这是商!这是该段循环的巧妙之处,因为是二进制,所以:够除,商为1,不够除,又利用EAX左移在后面补0,即商为0,然后充分利用循环将得到一位位商换算成二进制保存在逐渐移出的EDX+EAX中。
00405A86 |>^ E2 E7 \loopd short _ChinaZi.00405A6F ;循环直到除法做完
00405A88 |. 89F0 mov eax,esi
00405A8A |. 89FA mov edx,edi ;出参返回余数,如果没有这两句,则出参返回商。相信大家看到这里就应该豁然开朗了。还是不懂?反复看我的解说,反复琢磨。还是不懂?那说明你对二进制和移位指令的理解有问题
00405A8C |. 5B pop ebx ;弹出堆栈中的标志位到EBX,EBX作为除数在这里已经不需要了。
00405A8D |. F7C3 01000000 test ebx,1
00405A93 |. 74 07 je short _ChinaZi.00405A9C ;为正整数,则跳走
00405A95 |. F7DA neg edx
00405A97 |. F7D8 neg eax
00405A99 |. 83DA 00 sbb edx,0 ;为负则对出参也取反
00405A9C |> 5F pop edi
00405A9D |. 5E pop esi
00405A9E |. 5B pop ebx
00405A9F |. 5D pop ebp ;恢复现场
00405AA0 |. C2 0800 retn 8 ;返回
00405AA3 |> F7F3 div ebx ;32位除法
00405AA5 |. 92 xchg eax,edx ;交换商和余数,有这句则出参为余数,没有这句则出参为商。
00405AA6 |. 31D2 xor edx,edx ;高位清0,调整出参为64位
00405AA8 \.^ EB F2 jmp short _ChinaZi.00405A9C
00405AAA . C3 retn
真正一句一句读懂这个函数的汇编代码后,才觉得妙呀!不多一字,不少一字,有简有繁,有正有负,真是佩服写这段代码的程序员呀!
好了,这篇超详细的教程就先到这里吧,都凌晨03:59 2005-04-22了,明天还要上班呢。大家以后看见了这头痛的64次左移,就知道是在做除数运算了,只要注意一下出参就行了。要写注册机,也就好写得多了,不要再将这汇编代码贴到你的高级语言程序中去了。
这也算逆向吧。jney2好久没这样熬夜了,还有两个小时可睡。本文完
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)