这两天终于有点正经地写点程序了。在考虑如何对付反汇编与跟踪。
突发奇想,想出了在函数中隐藏函数这么一招。嘿嘿,似乎有点好玩。
OK,既然想在函数的隐藏函数,那么这个隐藏函数就无法像正常函数一样地定义了。
手工打造!如此如此...
jmp @F ; 函数开始就跳转,给隐藏函数提供代码空间
push ebp ; 隐藏函数开始
mov ebp , esp
; ...隐藏函数功能实现指令
mov esp , ebp
pop ebp
retn ; 隐藏函数结束
@@: ; 正常函数开始
; ...正常函数功能实现指令
ret
ex_proc endp
作为一个函数的标志,我们在构造隐藏函数的时候需要加入:
mov ebp,esp
;.....
mov esp,ebp
pop ebp
retn
吝啬地说来,如果函数不需要使用堆栈传递参数或构造局部变量的话,只需要给一个"retn"足矣。
当然,这个前提条件很苛刻,所以还是老实点加上那五行才是正经。
嗯,好。函数打造完了。那么,正常过程中我们如何使用它呢?
载入OD。看看内容:
00401001 . 8BEC mov ebp , esp
00401003 . 83C4 F0 add esp , - 10
00401006 . EB 17 jmp short 0040101F
00401008 /. 55 push ebp ; 隐藏函数位置
00401009 |. 8BEC mov ebp , esp
0040100B |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
0040100D |. 6A 00 push 0 ; |Title = NULL
0040100F |. 68 33204000 push 00402033 ; |Text = "pig4210"
00401014 |. 6A 00 push 0 ; |hOwner = NULL
00401016 |. E8 A1000000 call <jmp.&user32.MessageBoxA> ; MessageBoxA
0040101B |. 8BE5 mov esp , ebp
0040101D |. 5D pop ebp
0040101E . C3 retn ; 隐藏函数结束
0040101F > 60 pushad ; 正常函数开始
00401020 . FF75 08 push dword ptr [ ebp+8 ] ; /<%d>
00401023 . 68 30204000 push 00402030 ; |Format = "%d"
00401028 . 8D45 F0 lea eax , dword ptr [ ebp-10 ] ; |
0040102B . 50 push eax ; |s
0040102C . E8 85000000 call <jmp.&user32.wsprintfA> ; wsprintfA
00401031 . 83C4 0C add esp , 0C
00401034 . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401036 . 6A 00 push 0 ; |Title = NULL
00401038 . 8D45 F0 lea eax , dword ptr [ ebp-10 ] ; |
0040103B . 50 push eax ; |Text
0040103C . 6A 00 push 0 ; |hOwner = NULL
0040103E . E8 79000000 call <jmp.&user32.MessageBoxA> ; MessageBoxA
00401043 . 61 popad
00401044 . C9 leave
00401045 . C2 0400 retn 4
嘿嘿,这么一搞,OD的自动分析只能识别出我们的隐藏函数,而正常函数的外壳竟然无法分析出来。
而用IDA反汇编,IDA果然是反汇编界牛哄哄的XX。它识别出了正常函数,并把隐藏函数剥离出来放在另一边。
然而,我们的目的算是达到了,因为至少已经让cracker有点晕。
至少,他不清楚这个多出来的一段是什么意思。
言归正传。我们已经看到,隐藏函数在正常函数的位置偏移8个字节。
但是,请注意,在我们现实使用中,并不是都是8个字节,这个需要计算,最好反汇编自己跟一遍。
因为:
00401001 . 8BEC mov ebp , esp
00401003 . 83C4 F0 add esp , - 10 ; 分配局部变量,指令长度3B。如果分配的局部变量大的话,指令长度会达到6B
00401006 . EB 17 jmp short 0040101F ; 绝对跳转2B,因为是short跳,所以指令短。
; 如果隐藏函数大的话,7F空间不够用,则将变成长跳转,这时指令长度会达到5B
好,现在当前函数偏移8。使用代码如下:
add eax , 8 ; 偏移8B
call eax ; 开始调用
...
在写这篇文章的时候我突然想到一个方法,实践了一下,成功。嘿嘿。下面再分享经验:
其实我们如果在隐藏函数开始前加一个"hidden_proc equ this byte"定义一个标记。
这样,在调用的时候就不需要计算偏移了,让编译器自己帮我们算吧,嘿嘿
add eax , hidden_proc-ex_proc ; 编译器会帮我们计算偏移的
call eax ; 开始调用
好爽!
写成DLL都可以提供给外部程序调用,只是那个偏移值。。。如果你愿意,可以把这个值也exports掉。
这样,C版本如何调用应该不用我说了吧。。
目前,这个思想还有一些需要实践检验的地方。
欢迎拍砖,交流。