- 在分析/逆向
程序时,如果事先知道这类程序的一些特征,那将会是事半功倍的; - 分析/逆向
程序,和写程序不同,比喻的话:写程序像在作案,分析/逆向
程序就像是在破案,对破案来讲,重在假想和推理;
转载:
Delphi & BC++
使用Detect it
Easy工具确定软件使用的编译器为Delphi或者是BC++,这两个编译器的按钮特征码相同。
OD载入程序,反汇编窗口右键->查找->二进制字符串:
然后在HEX窗口输入特征码,注意Delphi和BC++的特征码是相同的(“740E8BD38B83????????FF93????????”):
注意勾选整个块。
特征码中的问号代表任意数据。
在特征码的下面都会传下一个CALL,然后我们在每个特征码的下面的CALL下断点:
然后CTRL+L,在所有的特征码下面的CALL下断点。因为我们不知道是哪一个按钮,所以需要在每个按钮都下断点,如果程序比较小,很快,如果程序比较大,按钮事件比较多,可以使用脚本来下断点。
断点下完之后,F9运行程序,然后点击关键的注册之类的按钮,程序会停当前按钮的CALL处,然后F7进CALL,就可以找关键CALL和关键跳了。
特征1:VC链接器版本
工作需要要脱一个VMP壳,我是一个从来没接触过脱壳的人。瞬间那种心情遇到的人应该都知道!没办法硬着头皮找教程,7天看完了
《天草的壳的世界》尝试脱壳下面是我的脱壳过程希望大牛多多指正!
1、准备工具,FEID(查壳工具)、DIE(查壳工具)、LordPE(dump工具)、ImpRec(IAT修复工具)、UIF(IAT修复工具)、CFF(文件优化工具)、OD(吾爱破解专版
调试工具)
2、查壳 PEID
区段显示是vmp1,看来是VMP壳编写语言还是不能确定上DIE
显示编程语言是VC++的,VMP壳
3、开OD准备脱壳、OD设置如下
VB
使用同样的方法搜索VB的特征码(“816C24”):
在每个jmp处下断点,然后运行程序,来到断点处,jmp跟过去之后就是按钮事件的处理过程了。
VS版本 | 链接器版本 |
VS2017 | 14.12 |
VS2015 | 14.0, 14.1 |
VS2013 | 12.0 |
VS2012 | 11.0 |
VS2010 | 10.0 |
VS2008 | 9.0 |
VS2005 | 8.0 |
VC2003 | 7.0, 7.1 |
VC6/VB6/E语言 | 6.0 |
VC5/BC++ | 5.0 |
Delphi | 2.25 |
VB5 |
4.20 |
MFC
注意,使用MFC和VC++编写的程序,使用PEiD查壳时,都会显示是Visual
C++6.0的编译器,两者的区分方法是,使用OD载入程序,在E窗口中,MFC的程序会调用一个mfc42.dll的动态链接库文件,而VC++的程序不会调用这个库。
载入MFC程序,在E窗口中双击mfc42.dll,跟进去,然后反汇编窗口中右键->查找->命令:
“sub
eax,0a”,找到之后,在特征码的下面会有一个je跳转,选中je命令行,然后按回车,程序会跟随到跳转处,来到目标地址,找下面的一个CALL,下断点:
如果je跳转之后,没有CALL,那就不用管这个特征码,继续查找下一个特征码,知道找到所有的CALL为止。然后全速运行程序,点击按钮之后,程序会在断点处停止,然后在上下文找关键CALL和关键跳即可。
打开进程到入口看下
4、跑OEP
啥也卡不懂,不过没关系知道是VMP壳就好办,直接CTRL+G 输入 VirtualProtect
下断 如下图(如果提示未知,先在内存窗口找到 kernel32.dll代码段
进去后在转到就到了)
F9跑起来,这是会段下
注意NewProtect 这项 等于 PAGE_READONLY 是停下,否则一直F9
按慢点别跑飞了
到0401000处看看,代码是否已经解码
已经解码, 搜索特征码 EB0B85F375078BC6C1E0100BF0
这是OEP第一个CALL的尾部,根据他找到OEP
OEP 就是 00a6f7c8,不过这才是第一步 这时候DUMP 是没用的 因为VMP加密了IAT
我们需要还原他
5、还原IAT
随便找一个IAT调用函数跟一下,刚好OEP下面就有两个 FF25 型的IAT
调用,跟进去看下
还是啥都看不懂,不过没关系
既然是调用IAT函数,肯定会在某一时刻调到真实函数地址去的
继续跟下面把整个跟的流程贴上来
这是整个流程的实际执行的代码 ,现在分析下
01059D91 53 push ebx 保护环境
01059D92 66:0FB6DB movzx bx,bl 垃圾代码
01059D96 66:BB 5D55 mov bx,0x555D 垃圾代码
01059D9A BB 5AC27200 mov ebx,0072C25A
这才是最终对EBX操作所以上面两条都是没用的代码
00F3BBFF 8B9B 25697400 mov ebx,dword ptr ds:[ebx+0x746925]
计算地址
01196B98 8D9B 5071F632 lea ebx,dword ptr
ds:[ebx+0x32F67150] 计算地址
00EA9FC2 871C24 xchg dword ptr ss:[esp],ebx
和栈顶交换,并还原EBX的原始值
00F9FC9D C2 0400 retn 0x4 还原EBX
并返回函数
这时候看下堆栈 栈顶出现真实函数的地址
<ignore_js_op>
00F9FC9D C2 0400 retn 0x4
分析下这条指令的执行流程,先EIP=栈顶的值 也就是真实函数地址,栈顶+4+4
懂汇编的人就会发现,栈顶+8的位置保存是当前CALL的返回地址
那执行了这条指令后返回地址不是没有了么?因为这个IAT调用 没加密前是
FF25型的 也就是 jmp [????????] 所以不需要返回地址
,而调用这个IAT的时候
是有一个CALL的进CALL的时候就会PUSH返回地址到堆栈,所以这个IAT处理的很巧妙!执行完真实函数后直接就返回到调用IATCALL的下面继续执行了。
看图片发现 这个CALL 下面有一条 RETN指令 看来这条
是VMP加上去的了,但是不要忽略了这条指令 因为这条指令很重要,VMP在获取
IAT地址的最后RETN的地址 会随机JMP到代码他添加的RETN 上,并不完全是在壳段
所以这个RETN 要留到最后处理。
再找一个FF25 型的IAT调用看看
跟进去看下,下面是执行流程代码
00FB7DC7 90 nop
00FB7DC8 0FB7D6 movzx edx,si 垃圾代码
00FB7DCB 66:0FBED1 movsx dx,cl 垃圾代码
00FB7DCF 5A pop edx 出栈
00FB7DD0 871424 xchg dword ptr ss:[esp],edx
交换栈顶的值,还原 EDX原始值
00FB7DD3 52 push edx
保护环境
00FB7DD4 66:0FBED1 movsx dx,cl 垃圾
00FB7DD8 0FB7D6 movzx edx,si 垃圾
00FB7DDB BA B7757D00 mov edx,007D75B7 计算地址
010DFDAD 8B92 3D036900 mov edx,dword ptr ds:[edx+0x69033D]
计算地址
0102B837 8D92 B52FE512 lea edx,dword ptr ds:[edx+0x12E52FB5]
计算地址
00EACB30 871424 xchg dword ptr ss:[esp],edx
函数真实地址给栈顶 并还原环境
0115D36E C2 0400 retn 0x4
这个FF25 CALL 的第四行 有个POP edx 而进这个CALL之前 有一个push edx
,可以看出 这个push edx 也是垃圾代码是 VMP自己添加上去的,看来VMP
会随机在上下 填充一个字节,在上面就是 PUSH 一个寄存器,在下面就是 retn
找一个 FF15型的CALL看看
跟进去看下流程
010B27B9 BE B34E6E0B mov esi,0xB6E4EB3 垃圾代码
00EFFC5B 5E pop esi 出栈
00EEB9B0 873424 xchg dword ptr ss:[esp],esi
交换栈顶的值,还原 ESI原始值
00F437BB 56 push esi 保存环境
00F8D827 BE 4B084800 mov esi,0048084B 计算地址
00FA55B3 8BB6 C005B900 mov esi,dword ptr
ds:[esi+0xB905C0] 计算地址
010E8053 8DB6 3F68AD31 lea esi,dword ptr ds:[esi+0x31AD683F]
计算地址
00F44191 873424 xchg dword ptr ss:[esp],esi
真实函数地址给栈顶,还原环境
0057A761 C3 retn
第二行 pop esi 看来这个CALL 的push esi 是垃圾指令了,但是 发现这个
是retn 为什么不是,retn 04 呢?因为这个是 FF15 型调用,也就是 call
[????????] 需要执行完后返回到call 下面继续执行
再找一个FF15型调用看看
跟进去看下流程
00E77D14 90 nop
00E77D15 51 push ecx 保存环境
00E77D16 66:F7D1 not cx 垃圾代码
00E77D19 8B4C24 04 mov ecx,dword ptr ss:[esp+0x4]
栈顶+4(调用CALL的返回地址)给ECX,
00FB7CFF 8D49 01 lea ecx,dword ptr ds:[ecx+0x1] 取
返回地址+1的值 给ECX
01013FB5 894C24 04 mov dword ptr
ss:[esp+0x4],ecx 重新写入到返回处
01013FB9 B9 A1598A00 mov ecx,008A59A1 计算地址
010566B2 8B89 94C36C00 mov ecx,dword ptr ds:[ecx+0x6CC394]
计算地址
00E95200 8D89 19378077 lea ecx,dword ptr ds:[ecx+0x77803719]
计算地址
00FEA363 870C24 xchg dword ptr
ss:[esp],ecx 真实函数地址给栈顶,还原环境
010ABADE C3 retn
这个 有点不一样啊,很简单 看调用CALL 的下面有一个 RETN啊,这要是正常返回
程序还不得跑飞啊,VMP巧妙的利用 4 5 6三行代码
就搞定了,这写壳的人真是脑洞大开啊!
所有的IAT加密就这样完了吗??? NO 还有更脑洞大开的 往下看
跟进去看看
0119D768 90 nop
0106B2C9 872C24 xchg dword ptr ss:[esp],ebp
交换栈顶的值,还原栈顶
00F3E26F 55 push ebp
保存返回地址
00F3E270 F7D5 not ebp
垃圾代码
00F3E272 50 push eax
保存环境
00F3E273 B8 D41D4300 mov eax,00431DD4 计算地址
00F3E278 66:8BEB mov bp,bx 垃圾代码
00F3E27B 8B80 41F7BE00 mov eax,dword ptr
ds:[eax+0xBEF741] 计算地址
00F3E281 66:0F4FE8 cmovg bp,ax 垃圾代码
00F3E285 66:8BE8 mov bp,ax 垃圾代码
00F3E288 8D80 903F170C lea eax,dword ptr
ds:[eax+0xC173F90] 计算地址
00F3E28E 0FB7E8 movzx ebp,ax 垃圾代码
00F3E291 8BE8 mov ebp,eax 真实函数地址给 EBP
00F3E293 58 pop eax 还原 寄存器
011B5D71 C3 retn
这是在干啥呢?? 咋把真实函数地址 给了 EBP呢? 看调用处的下面 有一个
call ebp 明白了吧 这是把 mov
xx,[????????] 整成了一个CALL哦,再来看下堆栈 调用处pop EBP , CALL内
第2行
有把栈顶的值还原了,然后把函数的返回地址重新PUSH进去,这里处理的很巧妙,所有
调用处的 pop ebp也是垃圾指令,这就完了???NO还有往下看
跟进去看下
00E97E5D 90 nop
00E97E5E 0FBFDB movsx ebx,bx 垃圾代码
010C1083 50 push eax 保存环境
010C1084 8B4424 04 mov eax,dword ptr ss:[esp+0x4]
获取当前CALL的返回地址给EAX
00E2EB3F 8D40 01 lea eax,dword ptr ds:[eax+0x1]
获取当前CALL返回地址+1给EAX
公海710登录网址 ,00E2EB42 0F45DB cmovne ebx,ebx 垃圾代码
00E2EB45 66:0FB6DB movzx bx,bl 垃圾代码
00E2EB49 894424 04 mov dword ptr
ss:[esp+0x4],eax 保存返回地址
00E2EB4D B8 012C4300 mov eax,00432C01 计算地址
00E2EB52 66:0FBEDB movsx bx,bl 垃圾代码
00E2EB56 66:8BDF mov bx,di 垃圾代码
01170378 8B80 F58CC000 mov eax,dword ptr ds:[eax+0xC08CF5]
计算地址
0117037E 0FB7DC movzx ebx,sp 垃圾代码
01170381 0F41DE cmovno ebx,esi 垃圾代码
01170384 F6D7 not bh 垃圾代码
01170386 8D80 145A2C32 lea eax,dword ptr ds:[eax+0x322C5A14]
计算地址
0117038C B3 85 mov bl,0x85 垃圾代码
0117038E 8ADE mov bl,dh 垃圾代码
01170390 8BD8 mov ebx,eax 真实函数地址给 EBX
01170392 0FBFC2 movsx eax,dx 垃圾代码
01170395 C6C4 E2 mov ah,0xE2 垃圾代码
www.710.com ,01170398 0FC8 bswap eax 垃圾代码
0117039A 58 pop eax 还原环境
0117039B C3 retn
认真看 了上面的部分,这里不难理解了吧, 4 5 8行处理调用处的 下面 retn
的,函数是获取 EBX的调用值的
易语言
注意,易语言编写的程序,使用PEiD查壳时,显示的编译器也是Visual
C++6.0,使用OD载入程序,直接在反汇编窗口中右键->查找->二进制字符串,输入易语言的特征码:“FF
25”,如果能够找到,就说明是易语言编写的程序。
易语言体:
可以看到有一堆的jmp指令,以上就能判断是易语言程序。
下一步查找易语言的按钮事件:二进制字符串“FF 55 FC 5F
5E»ò(e-debug)”,找到之后,会来到CALL处,直接下断:
查找剩下的,所有地方都下断点,然后F9运行程序开始调试。
易语言程序还可以使用另一种方式来查找按钮事件,使用E-debug工具:
使用OPEN载入易语言程序,然后点击START,运行程序,点击按钮,会显示事件发生的地址:
记下事件发生的地址,然后使用OD载入程序,跟踪到这个地址下断点:
Ctrl+G,输入要跟随的表达式,输入刚才记录的地址,下断点:
F9运行程序,触发断点事件,即可断在此处。
一共6种 IAT相关的处理,所以我是 一边写脚本一边骂写
VMP壳的人,要不要这么坑啊,我要的脱的程序特别大 跑一遍要
1个小时,我也不记得我跑了多少遍了!哎!说来都是泪,新手就是这样啊!不多说了下面贴上脚本!需要的自己去下!
VC++
使用PEiD查壳,查找的编译器为VC++6.0,如果找不到易语言和MFC的标志,那就可以判断是vc++写的程序,按钮事件查找方法,直接OD载入程序,在反汇编窗口右键->查找->所有命令,输入特征码:“sub
eax,0a”:
双击跟进去,在下面会有一个je:
选中之后,回车,跟过去,在下面的CALL下断点:
按照上面的方式找到所有的CALL下断点。
然后F9运行程序,点击按钮,程序来到断点处,F7跟进去,注意,进去之后还不是核心按键处理程序,按两次F8之后,才来到核心代码处:
6、用UIf 修复IAT
7、用lodepe dump
8、用imprec 修复IAT
9、用cff 去掉壳段
至此VMP脱壳完成!
万能断点
不需要知道程序使用的编译器和编译语言,直接OD载入程序,直接运行程序,输入假码之后,不要点击按钮:
查看user32模块:
来到user32模块之后,搜索特征码,右键->查找->二进制字符串:“F3A58BC883E103F3A4E8”
然后下断点,找到所有的都要下断点。然后回到程序,点击按钮,程序暂停,返回OD,alt+F9,执行到用户代码:
然后就到了按键事件处理的代码了。
特征2:OEP
a0) VB5:
【VB5】的OEP平衡堆栈是 sub
esp,0x54
【VB5】的OEP第一个API调用是GetStartupInfoA
【VB5】程序的IAT引用,都是FF15型的
《天草的壳的世界》尝试脱壳下面是我的脱壳过程希望大牛多多指正,这两个编译器的按钮特征码相同。a1) VB6
【VB6】的OEP平衡堆栈是
sub esp,0x4C
【VB6】的OEP第一个API调用是GetStartupInfoA
【VB6】程序的IAT引用,都是FF15型的
b0) Delphi
【Delphi】OEP上面是一个地址
【Delphi】OEP处
有5个CALL
【Delphi】OEP
5个CALL之后,全是0
【Delphi】OEP处第一个CALL有GetModuleHandleA调用
【Delphi】的IAT调用是
FF25形式的
b1) BC++
【BC++】
二进制特征:EB1066623A432B2B484F4F4B90
【BC++】
OEP的第一个API调用是 GetModuleHandleA
【BC++】 IAT调用是
FF25形式的
c)
VC6/E语言(通过分析,发现二者特征一致,可以判定E语言和VC6如出一辙)
【VC6】的OEP平衡堆栈是
sub esp,0x58 或 sub esp,0x68或add esp, -0x5C
【VC6】的OEP第一个API调用是GetVersion
【VC6】程序的IAT引用,都是FF15型的
d) VS2013
【VS2013】开始处,call
xxx; jmp xxx;
【VS2013】的OEP平衡堆栈是sub
esp, 0x44
【VS2013】的OEP第一个API调用是GetStartupInfoW
【VS2013】程序的IAT引用,都是FF15型的
这些特征,会对分析程序起到一定的帮助作用,这里,并没有完全列出全部的主流程序,希望抛砖引玉吧;