正常的软件开发者不对开发的软件进行保护的话,会导致开发的软件源代码很容易的被心怀不轨的人窃取到,而程序的壳便是抵抗第一过程的手段,就如自然界中的昆虫壳的作用一样,程序壳便是保护程序不被非法分子获取甚至破坏的。
加壳过程是用加壳程序对源程序进行压缩、加密、转换指令等操作,然后一般是在程序的开头加上一段壳程序。当加壳程序运行时,在程序开头的壳程序会对加壳程序进行解压缩、解密代码或者数据、解释执行相应代码等过程,壳程序运行完之后一般会将加壳程序恢复成源程序,加壳程序便依旧可以执行相应的功能。
压缩壳:UPX 、 ASPack
压缩壳主要是为了让程序文件占用更小的体积,对于程序的加密保护方面侧重性不是很强。
加密壳 : ASProtect 、Armadillo 、EXECryptor 、Themida
加密壳的功能更加侧重安全性,对于加壳后程序的体积大小并不是十分关注,它能够将程序的重要代码、数据等进行加密,甚至可以提供额外的功能,比如注册机制、使用次数、时间限制等。有的加密壳也涉及到了虚拟机保护技术。
虚拟机保护软件: VMProtect
虚拟机保护技术最近经常听到,但是也是刚刚才弄明白是什么意思,简单来说也是软件保护技术的一种,他可以将我们的程序代码转换成字节码,然后跳转到虚拟机解释执行,这样代码保护程序就是相当高的了。《加密与解密》里面的一句话形容的我觉得十分恰当而且有趣,“就好比把一篇文章从英文翻译成中文后,发现文章里的很多段落是用孟加拉语写的一样。”当然这样的破解成本也是相当的高了。但是虚拟机保护技术的运用会使得程序代码的效率降低,对此一般只会对于特定的关键部分代码进行虚拟机保护处理,这是对安全和效率的权衡。
虽然壳的保护效果很好,但是目前大多的商业软件并不会通过壳来保护他们的软件。首先因为加壳程序的兼容性问题,某些程序加壳后可能因为兼容性不能在某些平台上使用;其次也因为目前大多数的杀毒软件会对程序软件进行安全监测,其可以自动识别并脱去公开的商业壳,然后分析程序的安全性,但是如果是杀毒软件都能脱去的壳那么对于软件的保护也就是微乎其微了,如果使用杀毒软件不能识别的自己开发的个人的壳,杀毒软件出于安全考虑会自动识别软件为病毒,因为对于程序加壳似乎并不能实现程序的推广。
因此目前大多数厂商会设计包含复杂算法的序列号、设置多个“暗桩”等技术来实现程序保护。
其实照我说,开源就完了,你开源我开源大家都开源,那样就省的破解了。
1. 保存入口参数(pushad/popad pushfd/popfd)
2. 获取壳本身使用的API地址
关键函数:
LoadLibraryA(W)、LoadLibraryExA(W) : 将制定的DLL文件映射进内存,返回模块的句柄
GetmoduleHandle: 获取已经映射进内存的dll文件的句柄,返回值句柄
GetProcAddress : 获取dll内制定的函数地址,返回函数地址
3.解密源程序的各个区块的数据
4. IAT的初始化
5. 重定位项的处理(DLL文件脱壳)
6. HOOK API
7. 跳转到OEP
1. 寻找EOP
2. 抓取内存映像
3. 重建PE文件
4.1 . 按照跨段指令寻找OEP
加壳后
加壳前
程序开头
00413000 > 60 pushad # 保存寄存器的值 00413001 E8 C2000000 call 004130C8 00413006 2E:3001 xor byte ptr cs:[ecx], al 00413009 0000 add byte ptr [eax], al 0041300B 0000 add byte ptr [eax], al 0041300D 0000 add byte ptr [eax], al 0041300F 0000 add byte ptr [eax], al 00413011 003E add byte ptr [esi], bh 00413013 3001 xor byte ptr [ecx], al 00413015 002E add byte ptr [esi], ch 00413017 3001 xor byte ptr [ecx], al 00413019 0000 add byte ptr [eax], al 0041301B 0000 add byte ptr [eax], al 0041301D 0000 add byte ptr [eax], al 0041301F 0000 add byte ptr [eax], al
申请内存
004130FE 50 push eax 004130FF FF55 2E call dword ptr [ebp+2E] 00413102 8985 B8000000 mov dword ptr [ebp+B8], eax 00413108 6A 04 push 4 0041310A 68 00100000 push 1000 0041310F FFB5 8F000000 push dword ptr [ebp+8F] 00413115 6A 00 push 0 00413117 FF95 B8000000 call dword ptr [ebp+B8] ; KERNEL32.VirtualAlloc 0041311D 50 push eax 0041311E 8985 C4000000 mov dword ptr [ebp+C4], eax 00413124 8B9D 8B000000 mov ebx, dword ptr [ebp+8B]
跳转到壳的第二部分
0041310A 68 00100000 push 1000 0041310F FFB5 8F000000 push dword ptr [ebp+8F] 00413115 6A 00 push 0 00413117 FF95 B8000000 call dword ptr [ebp+B8] 0041311D 50 push eax 0041311E 8985 C4000000 mov dword ptr [ebp+C4], eax 00413124 8B9D 8B000000 mov ebx, dword ptr [ebp+8B] 0041312A 03DD add ebx, ebp 0041312C 50 push eax 0041312D 53 push ebx 0041312E E8 04000000 call 00413137 00413133 5A pop edx 00413134 55 push ebp 00413135 - FFE2 jmp edx
获取oep并跳转
00020263 C742 50 0010000>mov dword ptr [edx+50], 1000 0002026A FF85 59030000 inc dword ptr [ebp+359] 00020270 8B85 89020000 mov eax, dword ptr [ebp+289] 00020276 0385 51030000 add eax, dword ptr [ebp+351] 0002027C 0185 84020000 add dword ptr [ebp+284], eax 00020282 61 popad 00020283 68 30114000 push 401130 00020288 C3 retn
跳转到oep开启正常程序逻辑
00401130 /. 55 push ebp 00401131 |. 8BEC mov ebp, esp 00401133 |. 6A FF push -1 00401135 |. 68 B8504000 push 004050B8 0040113A |. 68 FC1D4000 push 00401DFC ; SE 处理程序安装 0040113F |. 64:A1 0000000>mov eax, dword ptr fs:[0] 00401145 |. 50 push eax 00401146 |. 64:8925 00000>mov dword ptr fs:[0], esp 0040114D |. 83EC 58 sub esp, 58 00401150 |. 53 push ebx
查看程序的整个section信息位于.text内。
Memory map 地址 大小 属主 区段 包含 类型 访问 初始访问 已映射为 00010000 00010000 Map RW RW 00020000 00001000 Priv RW RW 00030000 00001000 Priv RW RW 00040000 00016000 Map R R 00095000 0000B000 Priv RW 保护 RW 0019C000 00002000 Priv RW 保护 RW 0019E000 00002000 堆栈 于 主? Priv RW 保护 RW 001A0000 00004000 Map R R 001B0000 00002000 Priv RW RW 001F5000 0000B000 Priv RW 保护 RW 00274000 00004000 Priv RW RW 00278000 00003000 数据块 于 ? Priv RW RW 0027B000 00003000 数据块 于 ? Priv RW RW 0027E000 00001000 数据块 于 ? Priv RW RW 00400000 00001000 RebPE PE 文件头 Imag R RWE 00401000 00004000 RebPE .text 代码 Imag R RWE 00405000 00001000 RebPE .rdata Imag R RWE 00406000 00003000 RebPE .data 数据 Imag R RWE 00409000 0000A000 RebPE .rsrc 资源 Imag R RWE 00413000 00007000 RebPE .pediy SFX,输入表 Imag R RWE
4.2 用内存访问断点寻找oep
OD可以对程序设置内存访问断点,这样当程序开始读取或者执行相应代码时,程序会中断,并且把断点清除,利用这个办法我们可以寻找oep。
因为普通的压缩、加密壳等是按区段处理数据的,一般的顺序是.text 、 .rdate、.data 、 .rsrc的顺序处理数据,我们可以现在.rsrc处设置按F2设置内存访问断点,当程序断下后可表明.text段已经处理完毕,那么我们可以在接着对.text段设置内存访问断点,这样当程序执行.text代码时便会中断在oep处。
对data端设置内存访问断点
然后F9执行到中断处
00413145 A4 movs byte ptr es:[edi], byte ptr [esi> 00413146 B3 02 mov bl, 2 00413148 E8 6D000000 call 004131BA 0041314D ^ 73 F6 jnb short 00413145 0041314F 33C9 xor ecx, ecx 00413151 E8 64000000 call 004131BA 00413156 73 1C jnb short 00413174 00413158 33C0 xor eax, eax 0041315A E8 5B000000 call 004131BA 0041315F 73 23 jnb short 00413184 00413161 B3 02 mov bl, 2 00413163 41 inc ecx
在对text端设置内存访问断点
按F9执行到中断处,会停在oep的位置。
00401130 /. 55 push ebp 00401131 |. 8BEC mov ebp, esp 00401133 |. 6A FF push -1 00401135 |. 68 B8504000 push 004050B8 0040113A |. 68 FC1D4000 push 00401DFC ; SE 处理程序安装 0040113F |. 64:A1 0000000>mov eax, dword ptr fs:[0] 00401145 |. 50 push eax 00401146 |. 64:8925 00000>mov dword ptr fs:[0], esp 0040114D |. 83EC 58 sub esp, 58 00401150 |. 53 push ebx 00401151 |. 56 push esi 00401152 |. 57 push edi 00401153 |. 8965 E8 mov dword ptr [ebp-18], esp 00401156 |. FF15 28504000 call dword ptr [405028] ; KERNEL32.GetVersion
4.3 根据栈平衡原理寻找OEP
方法一 :壳程序在运行前,会将程序的寄存器的值利用push指令压入栈内,那么我可以对压入的栈地址设置硬件访问断点,当访问这个地址时,说明壳程序正在恢复寄存器的值,也就是说即将跳转到OEP处。
首先运行壳程序的push指令,使得寄存器现场压入栈内。
00413000 > 60 pushad 00413001 E8 C2000000 call 004130C8 00413006 2E:3001 xor byte ptr cs:[ecx], al 00413009 0000 add byte ptr [eax], al 0041300B 0000 add byte ptr [eax], al
此时栈内的信息如下
0019FF64 00413000 ASCII "`杪"
0019FF68 00413000 ASCII "`杪"
0019FF6C 0019FF94
0019FF70 0019FF84
0019FF74 003A9000
0019FF78 00413000 ASCII "`杪"
0019FF7C 00413000 ASCII "`杪"
对0x19ff64设置硬件访问断点
然后F9
0002027C 0185 84020000 add dword ptr [ebp+284], eax 00020282 61 popad 00020283 68 30114000 push 401130 00020288 C3 retn
程序在执行完popad后停下,可以看到已经来到了OEP附近。
方法二 : 我们已经知道壳程序运行前后的esp地址是不变的,而且正常程序的第一条指令通常是push ebp, 那么我们可以在壳程序刚开始时记录stack地址,然后对于stack-4地址处设置硬件写入断点,这样程序运行到 中断时,便会在OEP处停下。
程序刚开始运行时的stack地址如下
0019FF84 747762C4 返回到 KERNEL32.747762C4 0019FF88 00205000 0019FF8C 747762A0 KERNEL32.BaseThreadInitThunk 0019FF90 F648F892 0019FF94 /0019FFDC
我们队0x19ff80地址设置硬件写入断点。
然后F9程序便会停在oep处。
00401130 /. 55 push ebp 00401131 |. 8BEC mov ebp, esp 00401133 |. 6A FF push -1 00401135 |. 68 B8504000 push 004050B8 0040113A |. 68 FC1D4000 push 00401DFC ; SE 处理程序安装
程序直接停在了 mov ebp , esp处。
4.4 根据编译语言特点寻找OEP
各类语言编译的文件入口点都有自己的特点。
比如在VC6的启动部分有GetCommandLineA(W)、GetVersion函数等。
我们对GetVersion函数设置断点,中断两次后,就可以回到OEP附近。
4.5 最后一次异常法
程序在解密或者解压缩时,会产生许多次异常,那么最后一次异常的后面往往不远处就会跳转到OEP。
也称dump,指的是把指定内存地址的映像转存起来。
工具 : LordPE , 将内存中的数据与磁盘中的文件PE头链接起来。
基本用法 :
图中红框框出来的部分要勾选,表示链接磁盘文件中的文件头
然后准备dump的时候 , 选择dump full选项。
针对一些Anti-dump技术:
纠正SizeOfImage
我们可以使用LordPE的corrct ImageSize功能,其功能室直接在PE文件头中的SizeOfImage来实现的。
修改内存属性
某些Anti-dump技术对于文件的PE文件头等做了权限限制,比如不允许访问之类的,这样LordPE便不能正常dump程序,那么我们就可以利用OD加载程序,然后切换到Alt + M窗口, 对于没有指定权限区段赋予指定权限。
然后再利用LordPE正常进行dump即可。
一般做法是指,跟踪加壳程序对IAT的处理过程,修改相关指令,阻止外壳加密API,获得未加密的IAT。
6.1 确定IAT的地址以及大小
随便查看一个call 函数 , 查看[00405020] = 0x7477cfc0 , 相当于elf文件的got表。
然后我们查看数据栏,搜索0x405020 , IAT的结尾处是一个DOWRD的\x00。
所以我们确定IAT的大小是0xB8。
6.2 根据IAT重建输入表(手动)
有点繁琐,没有动手去试,但是原理还是要闹清楚的吗,建议大家去看一下相关的资料,但是要注意的是 , 最好不要把改变FirstThunk指向的IAT的地址。
6.3 用ImportREC重建输入表
前提条件: 目标程序已经被dump,目标程序正在运行,已知OEP或者IAT的偏移量以及大小。
首先我们让程序运行到OEP,然后dump内存映像,记得要修正imageSize。
然后用ImportREC打开程序,输入OEP使得程序自动获取IAT。
然后选择我们dump出来进程进行修复。
6.4 处理不连续的IAT
有些程序的IAT有可能被断成几份,不连续。
例子 : TestWin.exe
查壳
这一个没壳,直接不用脱,直接dump就可以直接用,也没有dump的必要。就是介绍一下不连续的IAT怎么办。
查找IAT的起始地址与size
因为有多个不连续IAT,这里只能找到最开始的IAT,最好的办法就是将size改的足够大,然后让Im.portREC自动分析。
然后点击show invalid , 分析无效信息 , 在无效信息处点击右键cut chunk ,得到正确的IAT表信息,然后选择dump进行fix。
6.5 修复函数
有时会出现函数名称识别出错的问题,利用ImportICE可以修改。