kissy 发表于 2011-3-14 00:30

Shielden2.04主程序脱壳分析

本帖最后由 kissy 于 2011-3-14 00:30 编辑

Shielden主程序使用Safengine NetLicensor v2.0.4.0保护.以下分析可能对SE其他版本也同理.
1.Anti:
S兄贴了篇Bypass anti分析,原理很透彻了.我补充一下.有点啰嗦,大侠们请跳过.:o:
SE反调试的几个选项全部是ANTI,可以直接干掉线程,我看了一下主程序,后面也没发现会出问题,当然我没有点加壳按钮,加壳过程会不会出问题,也无法保证.一开始的时候可
以改CreateThread,直接返回,到OEP的时候再取消修改,否则程序里的线程也无法创建.比如Shielden主程序拖放试练品进入而创建的PE分析线程.如果不取消,将会看到Shielden
界面灰色没反应.
干掉诸如检测REGMON等等线程后,主线程里依然有不少ANTI.体现在对DRX的积极检测.壳会检测TlsValue与DR0~DR3之和是否相当,会mov r32,通过设置r32为DRX来构造
80000004异常,S兄的文章提到过了.当发现不相等或是没出异常,那么壳就获知当前状态为被调试.对第一条S兄采取的办法是在GetThreadContext的时候清掉DRX,并清掉
TlsValue.对第二条则自己构造了一个异常.实际上壳在许多地方会进行第二条比较,比较的地址也不同,所以S兄给出的方法可以顺利到达OEP.但是一旦断下之后,在诸如IAT调用
之类的与壳通信的地方,又会被壳检测到.因为可能这里出现了另外一条mov r32,.而且r32是随机的,不同试练品里不同.DR0~DR3这4个DWORD,不同的试练品也是不同的.
解决办法有两个:
1>.OD在F2下断并断下的时候,会无情的清掉DRX.所以还是改改OD这个执拗习惯吧.
修改OD:
<0042EA22>
mov ecx,dword ptr ds:
mov esi,400
mov eax,dword ptr ds:
xor eax,eax
mov edx,dword ptr ds:
mov edx,4D8D78
mov ecx,dword ptr ds:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop

这么改当然简单了,如此一来当中断的时候,OD将不再清DRX了.不过单步也可能会出问题.比如PUSHFD这样指令单步就飞了.如果不满意,那就判断4个DRX是否都为0即可.

<0042EA22>
jmp 004AF645
<004AF645>
pushfd
cmp dword ptr ds:,0
jnz @L00000001
cmp dword ptr ds:,0
jnz @L00000001
cmp dword ptr ds:,0
jnz @L00000001
cmp dword ptr ds:,0
jnz @L00000001
mov ecx,dword ptr ds:
mov esi,400
mov eax,dword ptr ds:
xor eax,eax
mov edx,dword ptr ds:
mov edx,4D8D78
mov ecx,dword ptr ds:
jmp @L00000002
@L00000001:
mov ecx,dword ptr ds:
mov esi,400
mov dword ptr ds:,ecx
mov eax,dword ptr ds:
mov dword ptr ds:,eax
xor eax,eax
mov edx,dword ptr ds:
mov dword ptr ds:,edx
mov edx,4D8D78
mov ecx,dword ptr ds:
mov dword ptr ds:,ecx
@L00000002:
popfd
jmp 0042EA51

代码我不优化了,时间宝贵.
这样以后,可以F2而不用担心被ANTI了.当然,依然面临被内存校验到.这里用S兄的方法,搜索
9C810C24000100009D0F31,将rdtsc给NOP掉即可.
2>.使用方法1,将可以随意调试而不用担心被SE的"检测调试事件"给ANTI到了.这里避免了对G(S)etThreadContext的Hook以免不便(不同程序的mov r32,代码不同导致HOOK
麻烦)以及可能的各种崩溃(本机WIN7,HOOK之后会在不明之处挂掉,其他系统未测试.).
这里依然没有解决硬断以及单步的问题.单步不用说了,和谐单步方法多种多样,躲不完的.对于硬断,SE作者这里犯了一条大忌.
作者把"检测调试事件"给写进了同一个函数里,而且所有需要检测调试的地方,全部调用的是同一个函数.所以只要一个字节C3,所有的检测都将无效.
对Shielden2.04主程序而言,只需将006C0874改为C3即可.在看到SE中对乱序中的JCC都给展开了虚假分支执行相同代码之后,再看到这里,有点说不出的奇怪感觉.
可以看出,使用方法2也可以避免修改OD了.更不用HOOK API了.
二.修复输入表:
不知道从什么时候起,导入表已经没有"表"的概念了.尤其在加密壳里面,更是千方百计把API都拆散,避免放在一起方便自己也方便脱壳者.所以现在的脱壳技术也不执着于重建
一张完整的表了,只需要在程序调用API的时候,给他一个正确的API让他调用就是了.而SE的API加密很有特色.
1>.一个完整的API调用,加壳后分为以下几个过程:
1.通过GetModuleHandleA获得当前API所在的DLL基址.(Dll名在加壳时写入)
2.将API名称入栈(CALL +X),把1中取到的基址入栈(PUSH EAX)
3.调用同一个ANTI函数(前面说的大忌.)
4.GetProcAddress获得API地址(这里是单独的函数,其中的GetProcAddress部分好像是自己写的,不是用的API,这里也方便进行后续的模拟操作.并且这里也不排除有ANTI.)
5.将获取的地址写入到前面的执行入口附近,并几个字节,构造成

push api
pushfd
inc ;6字节的调用有这句,5字节的则没有
popfd
retn

如此一来,避免了再次调用同一API时,执行前面1,2,3,4这些重复的操作,避免降低效率.无疑这是此壳的一个亮点.
这里还有一种情况是针对mov r32,api这种情况的API调用.壳会构造如下结构:

006D41A3    E8 0F000000   call 006D41B7                            ; 006D41B7
006D41A8    90            nop
006D41A9    90            nop
006D41AA    90            nop
006D41AB    90            nop
006D41AC    8B0D A8416D00   mov ecx,dword ptr

中间的4个NOP是壳填入的API地址,通过下面的mov r32,操作,把地址放到寄存器中并返回.在执行前修改入口流程,直接改成JMP跳过中间的过程,跳到这个结构中,同样避
免了上述所说的效率问题.
2>.模拟
SE是为数不多的在WIN7下依然会模拟API的壳(我见过的唯一一个,也可能是我孤陋寡闻).当然在WIN7下模拟得比XP下要含蓄许多.所以调试SE可以的话还是在WIN7下调吧.省下不
少事情.
SE的模拟我看到的有两处,第一处是对壳用到的函数以及用户函数进行模拟.第二处是对壳自己引入的几个DLL中的API进行模拟.
第二处比较有意思,壳会把无壳试练品中用到的DLL,都放到加壳后的程序里,并挑几个API列进来.时间有限,我没有一一印证,猜测这里可能与前面的获取DLL基址用到的
GetModuleHandleA有关.如果壳不事先把所有DLL装载进去,那么GetModuleHandleA也就无法获取基址了.也可能跟系统有关系,避免特殊情况下的锁死.至于作者为什么如此构造
导入表而不是用LoadLibraryA,是否如此考虑,也不得而知了.
通过跟踪得知,壳会获取API,并通过一个函数来比较是否需要模拟,需要则返回模拟的地址,否则则返回真实地址.
对Shielden2.04主程序而言,第一种模拟,壳的比较函数在

0075F52C    E8 AFCFF9FF   call 006FC4E0                            ; 006FC4E0

执行完毕后,EAX可能出现模拟API地址,而EDI中则为真实API地址.其他试练品中可能在ESI中出现真实API地址.
所以可以HOOK下一条指令,跳到空地后,mov eax,edi,再跳回来即可.
第二种模拟:

0073F1BC    FF56 8B         call dword ptr
0073F1BF    90            nop
0073F1C0    8B06            mov eax,dword ptr
0073F1C2    5E            pop esi
0073F1C3    E8 39D1FBFF   call 006FC301                            ; 006FC301
0073F1C8    8BF8            mov edi,eax
0073F1CA    3B3E            cmp edi,dword ptr
0073F1CC    74 3F         je short 0073F20D                        ; 0073F20D

0073F1C3返回EAX中可能是模拟API,中放的是真实API,这里只需要把此处NOP或者把006FC301改成RETN即可.为避免壳其他地方模拟,显然后一种方法为佳.上述第一种模拟
也可以试试直接把CALL改成MOV EAX,EDI.
我没测试,写这个的时候胡思乱想的.o(∩_∩)o
至此模拟和ANTI以及自校验都过了,可以脱壳了.
3>.脱壳
找个OEP附近的API下断,本例为GetSystemTimeAsFileTime.断下后设置EIP为
0050852E > $E8 4D950000   call 00511A80                            ;00511A80

第一个API

00511AB7    E8 0F635800   call 00A97DCB                            ; 00A97DCB

这里被执行过了,壳已经改成PUSH API了,所以要重开个OD,再适当的时候下断,记录下原始代码,贴回去,避免被初始化而无法跨平台.
看下这个CALL.

00A9806F    FF90 2A639484   call dword ptr

这里实际调用的是
006AD168->GetModuleHandleA

在刚才NOP的地方写如下PATCH

00A97DCC    FF35 B4A01101   push dword ptr                ; kernel32.GetModuleHandleA
00A97DD2    8F05 68D16A00   pop dword ptr                    ; kernel32.GetModuleHandleA
00A97DD8    90            nop
00A97DD9    90            nop
00A97DDA    90            nop
00A97DDB    90            nop
00A97DDC    90            nop

这里

00A97DCC    FF35 B4A01101   push dword ptr                ; kernel32.GetModuleHandleA
要记录1下

00A98180    E8 EF86C2FF   call 006C0874                            ; 006C0874
00A98185    E8 DC67C2FF   call 006BE966                            ; 006BE966

这里就是上面说的1>_3和1>_4了.把6C0874改为RETN过掉ANTI.
006BE966这里主要目的是获取API地址,而这个函数里可能有其他不和谐的东西,所以还是帮他重写下的好.
改为:

006BE966    8B0424          mov eax,dword ptr
006BE969    A3 7EE96B00   mov dword ptr ,eax
006BE96E    83C4 04         add esp,4
006BE971    FF15 B0A01101   call dword ptr                ; kernel32.GetProcAddress
006BE977    FF35 7EE96B00   push dword ptr
006BE97D    C3            retn

这样就是单纯的取API地址了.
同样的

006BE971    FF15 B0A01101   call dword ptr                ; kernel32.GetProcAddress

我们也要记录一下.
在壳自己的导入表下面添加GetModuleHandleA以及GetProcAddress.
Dump.
Importrec重建壳原来的导入表.
找到添加的两个API的地址,更改之前记录的两处即可.这样就借助壳自己的取API过程来完成了API填充了.
4.资源修复:
对资源处理比较优秀的有:
1:PEP(HOOK 资源相关的许多API到壳自己的资源函数里)
2:TTP(将资源块整个移动到高地址并修改各自索引)
3:ZP(将图标,版本信息提出来单独造一个资源,在壳的Shell里把原始资源地址和大小写回PE头)
SE的资源保护仅仅从强度来说介于PEP和TTP之间.保留了完整的资源树,资源片则明文分散在内存中.但也同时避免了PEP的资源保护较低的效率.
修复方法:可以从树中读地址,然后从内存DUMP贴回去.

附一个Shielden2.04脱壳主程序.可以跑起来.加壳的时候会挂在RtlAllocateHeap.可能是壳里Create了Heap,某个地方再跑出来调戏人.明天还要上班,不看了,该睡觉觉了.
http://u.115.com/file/f76dfecc3f#
Shielden2.04_uNpacked_bY_KisSy.rar

xJ_Juno 发表于 2011-3-14 00:33

前排支持
软件好像是个免费的加壳工具?

CAL 发表于 2011-3-14 00:37

楼主这个太给力了

Kiζs~乄 发表于 2011-3-14 00:41

前排留名 支持大牛

kk1431 发表于 2011-3-14 02:43

前排支持学习.............

李东国 发表于 2011-3-14 02:55

强壳学习下

sztxgg 发表于 2011-3-14 04:46

K大又出手啦,前排支持!

135544 发表于 2011-3-14 06:28

牛人再度出现膜拜{:1_928:}

zhaoyafei19 发表于 2011-3-14 16:22

这个给力
我是菜鸟,看不懂啊
只有慢慢研究了

xiaoxunxun 发表于 2011-3-14 16:24

拖壳啊 这个很厉害啊 学习了
页: [1] 2 3
查看完整版本: Shielden2.04主程序脱壳分析