61阅读

脱壳教程-ACProtect Professional 1.3C 主程序脱壳(3)(图)

发布时间:2017-12-27 所属栏目:脱壳教程

一 : ACProtect Professional 1.3C 主程序脱壳(3)(图)


运行程序,crashedL。直接用修复完stolen code的dumped_.exe看看。从EP的第1个call进去就有问题。







在OllyDbg中可以看到:


有部分IAT在壳中。这部分代码前面是跟到了的(在第6次int 3以后),原来认为这只是loader自己需要的API。实际上正常的程序代码也使用了这个IAT。

如果用ImportRec把这部分也取出来,属于不同dll的api混排了,ImportRec不能处理。
这些地址是在736643处理的。


而且有一个不能识别(这个实际上是Hooked MessageBoxA)。



估计这是加壳时做的手脚。当发现主程序使用了与壳代码同样的API时,修改了对应的调用代码,使其调用到壳中的IAT。这样可以加强与壳的联系。反过来看,这里大部分API(除了只有壳使用的),在前面避开IAT加密处理后,都已经import了。

在ACProtect_Fixed中已经有解好的api名。


紧临的函数指针:



注意函数名及指针与7339A9附近的代码并不完全对应。有的函数名或指针在别处。

可以在程序入口自己调用LoadLibrary,GetProcAddress进行处理,填入相应的地址。或者把Loader的代码搬过来。也可以修改主程序的代码,使其调用前面得到的干净的IAT而不是壳中的IAT(这样要处理的地方太多,很麻烦)。

注意ACProtected_Fixed中,import了几个基本的API:

在加载API时壳代码会使用这些函数。这几个地址直接填入007300D8开始的IAT中了。修改dumped_.exe的EP,从壳代码开始(可以看到dump出来的代码,这部分已经解开了),





注意Loader代码中用的几个API(GetModuleHandleA,GetProcAddress)改为使用避开IAT加密后得到的主程序的IAT。


原来在壳代码import的4个API,从主程序的IAT直接取。结束处理后跳回前面的OEP 409DE4。用pushad,pushad保存寄存器值。



注意最后一项7301CC实际是MessageBoxA,被ACProtect用做SDK的接口。 不要填入MessageBox的真正地址。

下面的73013C在跟原程序的过程中始终为0,可能是注册版才有?


处理完后,位于壳空间的IAT已经赋上了正确值。


7. 修复Replaced code (1)

用前面的OllyScript脚本,停下后对Code section设内存访问断点。忽略所有异常。断下后,在抽取代码的共同入口722416设断,运行。第1次调用在004047E5。


里面还有SMC,解出后贴到ACProtect_Fixed中对照跟踪。这部分的处理与低版本基本相同。

1) 查返回地址表



7225CC后ebx指向返回地址表,里面是RVA:



找到匹配地址后,ecx等于表项的offset,第1项为0x2A。

2) 查opcode表
用1中的到的offset查opcode数据表(007262ED):

密文opcode表:


counter表(记录每个地址的调用次数,超出0x20次将使用新的地址解码):


第1次解出的变形码:



3) 调整stack以便正确调用变形码及返回




4) 破坏解出的代码,ret到变形码




5) 执行变形码,返回到原程序




6) Patch

先把解出的代码binary copy到dumped_.exe(直接copy 722416 - 7226B0的代码即可)。

关闭722579的解码(解出7225C1 - 7226B0):


关闭7226A8处对前面代码的破坏:


patch 72260C: jnz->jmp,无论执行多少次都使用同一解码地址



copy正确的ImageBase值,这里为0,改72FC1F为400000





copy 正确的10 bytes key(第1 byte为0)




执行,仍然crashedL。

8. 修复Replaced code (2)

还有replaced code,在这里出异常:


->


将解出的代码贴到ACProtect_Fixed.exe。可以看到,这些实际上是变形的call代码。第1次执行到这里,在buffer中解出的变形码为:




XOR的结果:



目前的dumped_.exe,地址72ED83的值为0(这些值是loader写入的)。

406DF8的变形码原来是call GetKeyboardType。原程序的call API 全部被抽掉了。壳代码的动作与前面相似,用返回地址查表,获取相应的指针,生成jmp dword ptr ds:[xxxxxxxx]指令,该地址则指向类似72124C的变形码,调用正确的API。

变形call API的返回RVA地址表:

开始:


结束:


寻址变形码数组下标表(每项1字节),用于查变形码指针数组:


变形码地址表(指向变形码的指针):



开始打算写个inline-patch:

用同样的查表动作,把对应的变形码copy出来,得到对应的API地址,与跳过加密而得到的干净IAT对比。查到匹配值后,修改对应的opcode,使其直接call到IAT中的地址。

用OllyScript脚本跳过IAT加密,得不到变形码(此时从变形码地址表中得到的就是API的真正地址,有46项指针无效,为0xCCCCCCCC)。

另一个问题却是难以解决的,replaced code只有5字节:

这里的call是0xE8,调用壳中的绝对地址。InlinePatch写到一定程度才发现,如果要修复代码,使其调用到IAT,需要相对地址调用6 bytesL。真是个低级错误。

现在patch的结果:


真正需要的是:



这样只有保留变形码。把壳中对应的代码copy过来,OEP前生成正确的变形码。而且脱壳后的程序不能直接看到API名字,很不舒服。

只好把壳的相应代码搬过来。再次修改dumped_.exe入口处代码,在把loader空间中的IAT填好后,跳到处理变形码的位置:



loader在处理IAT时需要调用几个API,及判断dll的映射地址、API地址等,先保存需要的数据(我们有干净的IATJ):



由于在前面避开了IAT加密,生成变形码需要的数据已经被正确的API地址覆盖了。用LoadPE把ACProtect的idata section存到文件,然后加到dumped_.exe。



把这个section的密文数据copy到dumped_.exe的idata section,覆盖掉干净的IAT,我们已经不需要它了。现在只要伪造好现场J。



往下执行loader的IAT处理代码,做几处小小的修改,使其使用刚才保存的API地址等数据。


IAT及变形码处理结束后回到OEP。



执行。又挂了L。这次是内存访问异常。跟一跟可以知道,是在Hooked MessageBoxA中。这里面的代码还没有仔细看,有几个switch-case分枝。第1次eax为5。



进去后有几个查表动作:



用调用Hooked MessageBoxA的返回地址查表。这张表在721F25处,dumped_.exe中有,共21项。
注意查表时不是找相等值,而是找大于返回地址值且最接近的值。



继续->



这里出现了另外2张表。7220B5的表中数据为size。Dumped_.exe中有:



问题出在第3张表:



dump出的数据为0。这段代码要把主程序中的一段数据copy到这张表中数据所指的地址。在loader中执行时,这里填入了指向动态分配内存的指针。



显然不能直接复制这些值。有个简单的办法可以骗过loader。从那张size表中可以看到,最大的数据FD5D。用LoadPE再次增加1个section,size为FFFF即可。



修改dumped_.exe,设置21项数据,使其全部指向该地址。



在W2K下运行,显示窗口,但不能响应输入。在WinXP下运行什么也不显示。
下面该与主程序交手了,这需要把板凳坐穿的耐心L。

二 : Shielden2.04主程序脱壳

Shielden2.04主程序脱壳

Shielden主程序施用Safengine NetLicensor v2.0.4.0保护.以下分析可能对SE其他版本也同理.

1.Anti:

S兄贴了篇Bypass anti分析,原理很透辟了.我补充一下.有点烦琐,大侠们请跳过.

SE反调试的几个选项全数是ANTI,可以直接干掉线程,我看了一下主程序,后面也没发现会出问题,当然我没有完整的加一次壳来实验,加壳过程会不会出问题,也没有办法保证.一开始的时候可以改CreateThread,直接归回,到OEP的时候再取消修改,否则程序里的线程也没有办法创建.比如Shielden主程序拖放试练品进入而创建的PE分析线程.如果不取消,将会看到Shielden界面灰色没反应.

干掉诸如检测REGMON等等线程后,主线程里依然有不少ANTI.体此刻对DRX的积极检测.壳会检测TlsValue与DR0~DR3之和是否相当,会mov r32,[r32]经由过程配置r32为DRX来构造80000004异常,S兄的文章提到过了.当发现不相等或是没出异常,那么壳就获知当前状态为被调试.对第一条S兄采取的办法是在GetThreadContext的时候清掉DRX,并清掉TlsValue.对第二条文本身构造了一个异常.实际上壳在许多处所会进行第二条比较,比较的地址也不同,以是S兄给出的方法可以顺利达到OEP.但是一旦断下然后,在诸如IAT调用什么的的与壳通信的处所,又会被壳检测到.因为可能这搭出现了另外一条mov r32,[r32].而且r32是随机的,不同试练品里不同.DR0~DR3这4个DWORD,不同的试练品也是不同的.

解决办法有两个:

1.OD在F2下断并断下的时候,会无情的清掉DRX.以是还是改改OD这个执拗习惯吧. 修改OD: 代码:

0042EA22 mov ecx,dword ptr ds:[4D8D70]

mov esi,400 mov eax,dword ptr ds:[4D8D8C]

xor eax,eax mov edx,dword ptr ds:[4D8DA8]

mov edx,4D8D78 mov ecx,dword ptr ds:[4D8DC4]

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:[4D8D70],0 jnz@L 00000001 cmp dword ptr ds:[4D8D8C],0 jnz@L 00000001 cmp dword ptr ds:[4D8DA8],0 jnz@L 00000001 cmp dword ptr ds:[4D8DC4],0 jnz@L 00000001 mov ecx,dword ptr ds:[4D8D70] mov esi,400 mov eax,dword ptr ds:[4D8D8C]

xor eax,eax mov edx,dword ptr ds:[4D8DA8]

mov edx,4D8D78 mov ecx,dword ptr ds:[4D8DC4]

jmp@L 00000002

@L 00000001:

mov ecx,dword ptr ds:[4D8D70]

mov esi,400 mov dword ptr ds:[ebx+4],ecx mov eax,dword ptr ds:[4D8D8C] mov dword ptr ds:[ebx+8],eax xor eax,eax mov edx,dword ptr ds:[4D8DA8] mov dword ptr ds:[ebx+C],edx mov edx,4D8D78 mov ecx,dword ptr ds:[4D8DC4] mov dword ptr ds:[ebx+10],ecx

@L 00000002:

popfd jmp 0042EA51代码我不优化了,时间名贵.

这样以后,可以F2而不用担心被ANTI了.当然,依然面对被内存校验到.这搭用S兄的方法,搜刮代

shielden Shielden2.04主程序脱壳

码:

9C810C 24000100009D0F31,将rdtsc给N OP掉即可.

2.施用方法1,将可以随意调试而不用担心被SE的"检测调试事件"给ANTI到了.这搭避免了对G(S)etThreadContext的Hook以免不便(不同程序的mov r32,[r32]代码不同引起HOOK 麻烦)和可能的各种崩溃(本机WIN7,HOOK然后会在不明之处挂掉,其他系统未实验.).

这搭依然没有解决硬断和单步的问题.单步不用说了,和谐单步方法多端,躲不完的.对于硬断,SE作者这搭犯了一条大忌.

作者把"检测调试事件"给写进了同一个函数里,而且所有需要检测调试的处所,全数调用的是同一个函数.以是只要一个字节,所有的检测都将失效.

对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[esp+8];6字节的调用有这句,5字节的则没有

popfd retn如此一来,避免了再次调用同一API时,执行前面1,2,3,4这些个重复的操作,避免降低效率.无疑这是此壳的一个亮点.

这搭还有一种情况是针对mov r32,api这种情况的API调用.壳会构造如次结构:代码: 006D41A3 E8 0F 000000 call 006D41B7;006D41B7 006D41A8 90 nop 006D41A9 90 nop

006D41AA 90 nop 006D41AB 90 nop 006D41AC 8B0D A8416D00 mov ecx,dword ptr[6D41A8] 中间的4个NOP是壳填入的API地址,经由过程下面的mov r32,[const]操作,把地址放到寄存器中并归回.在执行前修改进口流程,直接改成JMP跳过中间的过程,跳到这个结构中,同样避免了上面所说的所说的效率问题.

2.模拟

SE是为数不多的在WIN7下依然会模拟API的壳(我见过的唯一一个,也可能是我孤陋寡闻).当然在WIN7下模拟患上比XP下要蕴藉许多.以是调试SE可以的话还是在WIN7下调吧.省下不少事情.

SE的模拟我看到的有两处,第一处是对壳用到的函数和用户函数进行模拟.第二处是对壳本身引入的几个DLL中的API进行模拟.

扩展:shielden 2.3.9.0脱壳 / shielden脱壳 / shielden 脱壳机

第二处比较有意思,壳会把无壳试练品中用到的DLL,都放到加壳后的程序里,并挑几个API列进来.时间有限,我没有一一印证,猜测这搭可能与前面的获取DLL基址用到的GetModuleHandleA涉及.如果壳不事先把所有DLL装载进去,那么GetModuleHandleA也就没有办法获取基址了.也可能跟系统涉及系,避免特殊情况下的锁死.至于作者为何如此构造导入表而不是用LoadLibraryA,是否如此考虑,也不患上而知了.

经由过程跟踪患上知,壳会获取API,并经由过程一个函数来比较是否需要模拟,需要则归回模拟

shielden Shielden2.04主程序脱壳

的地址,否则则归回真实地址.

对Shielden2.04主程序而言,第一种模拟,壳的比较函数在代码:

0075F52C E8AFCFF9FF call 006FC4E0;006FC4E0执行完毕后,EAX可能出现模拟API地址,而EDI中则为真实API地址.其他试练品中可能在ESI中出现真实API地址.

以是可以HOOK下一条指令,跳到空地后,mov eax,edi,再跳归来即可.

第二种模拟:代码:

0073F1BC FF56 8B call dword ptr[esi-75]

0073F1BF 90 nop 0073F1C0 8B06 mov eax,dword ptr[esi]

0073F1C2 5E pop esi 0073F1C3 E8 39D1FBFF call 006FC301;006FC301 0073F1C8 8BF8 mov edi,eax 0073F1CA 3B3E cmp edi,dword ptr[esi]

0073F1CC 74 3F je short 0073F20D;0073F20D

0073F1C3归回EAX中可能是模拟API,[ESI]中放的是真实API,这搭只消把此处NOP或者把

006FC301改成RETN即可.为避免壳其他处所模拟,显然后一种方法为佳.上面所说的第一种模拟 也可以试试直接把CALL改成MOV EAX,EDI.

我没实验,写这个的时候胡思乱想的.o(∩_∩)o

至此模拟和ANTI和自校验都过了,可以脱壳了.

3.脱壳

找个OEP相近的API下断,本例为GetSystemTimeAsFileTime.断下后配置EIP为代码: 0050852 E$E8 4D 950000 call 00511A80;00511A80第一个API代码:

00511AB7 E8 0F 635800 call 00A97DCB;00A97DCB这搭被执行过了,壳已经改成PUSH API了,以是要重开个OD,再适当的时候下断,记录下原始代码,贴回去,避免被初始化而没有办法跨平台. 看下这个CALL.代码:

00A9806F FF90 2A 639484 call dword ptr[eax+8494632 A]这搭实际调用的是代码:

006AD168-GetModuleHandleA在适才NOP的处所写如次PATCH代码:

00A97DCC FF35B4A01101 push dword ptr[111A0B4];kernel32.GetModuleHandleA 00A97DD2 8F05 68D16A00 pop dword ptr[6AD168];kernel32.GetModuleHandleA 00A97DD8 90 nop 00A97DD9 90 nop 00A97DDA 90 nop 00A97DDB 90 nop 00A97DDC 90 nop代码:

00A97DCC FF35B4A01101 push dword ptr[111A0B4];kernel32.GetModuleHandleA要记录1下代码:

00A98180 E8EF86C2FF call 006C0874;006C0874 00A98185 E8DC67C2FF call 006BE966;006BE966

这搭就是上面说的1 _3和1 _4了.把6C0874改为RETN过掉ANTI.

006BE966这搭主要目的是获取API地址,而这个函数里可能有其他不和谐的东西,以是还是帮他重写下的好.

改为:代码:

006BE966 8B0424 mov eax,dword ptr[esp]

006BE969 A3 7EE96B00 mov dword ptr[6BE97E],eax 006BE96E 83C4 04 add esp,4 006BE971 FF15B0A01101 call dword ptr[111A0B0];kernel32.GetProcAddress 006BE977 FF35 7EE96B00 push dword ptr[6BE97E]

006BE97D C3 retn

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

同样的代码:

006BE971 FF15B0A01101 call dword ptr[111A0B0];kernel32.GetProcAddress我们也要记录一下.

shielden Shielden2.04主程序脱壳

在壳本身的导入表下面添加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,某个处所再跑出来调戏人.明天还要上班,不看了,该睡觉觉了.

Shielden2.04_uNpacked_bY_KisSy.rar原因:改错共2位会员

感激kunkun发表的文章:很好,进来顶一下今天刚转正,就看见牛贴了,虽然本人水平差,还不太明白!看看,进修下,这壳可不是一般的牛啊跪拜郁闷眼神喵

变成求职格式是这搭的Bug也是特点援用:

以是S兄给出的方法可以顺利达到OEP.但是一旦断下然后,在诸如IAT调用什么的的与壳通信的处所,

又会被壳检测到.因为可能这搭出现了另外一条mov r32,[r32].而且r32是随机的,喔,作者革新了.我那个范例在按键事件的GetWindowTextA还是用那4个DRx

因我当时对DRx的改法是想顾到让我们本身设的硬断随时都是清醒有效的,而不用借F2软断来唤醒.援用:

壳会把无壳试练品中用到的DLL,都放到加壳后的程序里,并挑几个API列进来会不会是为了相容程序施用到具有TLS的DLL?感激sessiondiy

此篇文章之用户:坐在精华下面就是舒服~跪拜Kissy.都是牛人。[www.61k.com)我来凑人数只能跪拜,没有办法进修。

扩展:shielden 2.3.9.0脱壳 / shielden脱壳 / shielden 脱壳机

三 : ACProtect Professional 1.3C 主程序脱壳(2)(图)

4. dump
根据脱US UnpackMe的经验,不能在false OEP处dump,此时BSS section中许多数据已经初始化了。最好在第一句(push ebp)就dump。可是那个push ebp离false OEP很远L。

Packer EP的初始化环境:






先看看发出第1个call的壳代码:


可以看到(跟一下可以证实),在call入原程序空间前,最后的异常是div 0。用现在的OllyDbg脚本(跟到737B63),停下后修改异常拦截选项:


试试能否拦截异常找到合适的dump位置。当前OllyScript脚本停下时的环境:



栈中为pushad的结果。
第7次div 0异常时:




注意ebp的值已经入栈了。Pushad的结果,对应寄存器值为:
esp = 12FFC0 (原来为12FFC4)
ebp = 12FFC0 = esp (原来为12FFF0)
看看BSS section:




全为0,就在这里dump。如果需要仔细跟stolen code,也可以从这里入手(可以从脚本停下的第6次div 0异常处理后开始)。

先用4D9DE4为OEP。



用LoadPE查看节表:


先把CODE的Vsize和Rsize加大,以避免修复stolen codes时空间不够。



重新跟到false OEP(4D9E37)。重建输入表。



用了Add new section,会不会有问题(好象有篇脱文里提到过这个)? 先这样处理。
用IDA编译dumped_.exe,结果不错。
现在剩下stolen code和replaced code。
5. 修复stolen code

既然不打算仔细跟,就只有猜了。对执行第1个call以前的stolen code进行猜测,应该是安全的。后面的4次call需要仔细看看。

1) call 406EDC

在IDA中看dumped_.exe的结果:


先执行脚本,停下后只勾选div 0异常。直到73A9AF。经过一番遮遮掩掩的代码:


在call前的寄存器:


堆栈:


call完后,下面的pushad为分界线,标识stolen code的结束。所以到这里的stolen code为(对于不确定的,可以在Packer EP修改寄存器值,到这里核对):
Call完后,pushad前的环境:





2) call 46261C





到这里的stolen code:
从这里开始,不能图省事了。要追出全部的stolen code,必须跟(必须确保上一次pushad和下一次popad时的环境一致,才不会丢代码)。先试直接拦截div 0异常,注意后面是否有popad。如果两次的环境不一致,则必须单步跟L。

另外,追stolen code不是一次完成的,所以有些图中寄存器和堆栈中的数据对不上,不必管它。

3) 0073BC47


4) 0073C558


5) 第1次call 462634


这个函数有2个参数L。



这是最后一次后面存在popad的div 0异常。看看是如何回到原程序的。
在73D872忽略所有异常,对code section下内存访问断点。中间在kernel32中有一次内存访问异常。



最后一次div ebx在73DBE1。单步跟到这里:


73DE38破坏前面代码。



至此修复所有stolen codes,将前面的6部分和fasle OEP的2个call合在一起。
本文标题:脱壳教程-ACProtect Professional 1.3C 主程序脱壳(3)(图)
本文地址: http://www.61k.com/1113003.html

61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1