14

[原创]菜鸟啄硬壳(之四)――通用脱壳机习作(软件已修改)

wulje 2007-2-13 13:54 10460
【文章标题】: 菜鸟啄硬壳(之四)――通用脱壳机习作
【文章作者】: Wulje
【下载地址】: 自己搜索下载
【使用工具】: OD
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
          2月13日上传的“通用脱壳机”在设计上有缺陷,致使入口地址大于4FFFFF的软件脱壳后不能运行。现已更改,重新上传。虽然它适用范围不太宽,但对UPX壳,ASPack壳好像还很有效。

【详细过程】
.......当网友读到这篇文章后,可能认为我并不很“菜”,装着“菜鸟”写文章来卖弄自己。其实我跟大家并不认识,没有这个必要,更没有什么本事可卖弄。我只想说一点,软件这个东西既有十分深奥的一面(深奥的是软件作者的思想),技术上却有一通百通的共性。无知者无畏,我从第一篇文章开始接触“壳”来,每解剖一个壳,就有不少收获,每篇文章中都明显地留下了我的足迹,只要能进行交流,菜不菜根本不是问题,我等菜鸟们更不可自卑。好了,言归正传!
.......我说过调用LoadLibrary和GetProcAddress函数是壳的共性,更是它的软肋,跟踪这两个函数并手动修改部分代码,脱壳基本上都是成功的。能不能写个软件,让它自动跟踪这两个函数,把装载API地址的工作改为写函数导入表(IAT)的工作,如果能成功,管它是什么壳,不是都给脱了吗?这就是我的“通用脱壳机”的构思。有了这个想法,但实施起来却困难重重。本着无知者无畏的精神,还居然搞出了一个适应范围很宽的“脱壳机”(见附件),我相信永远不会有脱壳机的终点。你有七算,壳作者的八算,加密与解密永远是“思想”的战争,绝不会有一劳永逸的软件。我的构思是这样的:
.......1.写一个替代LoadLibraryA和GetProcAddress的代码,当壳代码调用这两个API时,自动将它取代;
.......2.由于壳代码千变万化,为了“通用性”,只能以LoadLibraryA和GetProcAddress的调用地址和壳打交道,把壳看成一个“黑匣子”,壳的所有内部信息都从进出这两个地址的参数和寄存器值获取;
.......3.将自编代码嵌入壳中,壳解压时自动完成“导入表”的写入工作;
.......4.最困难的是“双重壳”中取代LoadLibraryA和GetProcAddress的时机掌握,因为它解压第一层壳时还必需使用真正的这两个API函数,只有当它开始解压第二层壳时才可以取代;
.......5.最没有规律的是OEP的搜索,目前还只能根据壳代码特征去设计搜索方案,一个可行的方案是搞一个“壳特征库”,根据不同的壳去查找不同的搜索方法。(对我这个爱好者来说没有这个必要和精力)
.......对具体代码感兴趣的网友,可继续看下面内容:
  
....... 一、替代LoadLibraryA和GetProcAddress的源代码
.......要壳自解压,必需运行壳。运行中,这两个函数地址是惟一和壳交道的通道(静态搜索OEP例外),所以在_GetProcAddress中处理的事件较多(动态处理)。
  ;--------------------------------------------------------
_LoadLibraryA.....uses    edi    _DLL_Addr       
..................xor     eax,eax
..................mov     dw_Flag,eax................;调用LoadLibraryA的标记
...................if     !dwCase....................;若是双重壳,第1次调用kernel32,则IID表不登记
..........................sub   dw_IID,4
...................endif
..................mov     edi,dw_IID.................;dw_IID是自定义的IID地址
..................mov     eax,_DLL_Addr
..................xor     eax,400000h                       
..................mov     [edi],eax..................;写IID表(eax指向库函数地址)
..................add     dw_IID,4                               
..................xor     eax,400000h
..................ret
_LoadLibraryA.....endp
  ;-------------------------------------------------
_GetProcAddress...proc   uses edi esi _DLL_Addr,_API_Name_Addr....;_API_Name_Addr是函数名地址或函数序列号
                                 
............if    _API_Name_Addr == 1.....................;某种壳的特殊结束标记
..................mov   esi,dw_IAT_Start..................;IAT表开始地址
..................xor   ecx,ecx
...................repeat.................................;清除某些IAT表中不规范的结束标记
.........................lods dword ptr [esi]
..........................if   eax >= 7fffffffh
...............................mov dword ptr [esi-4],0       
..........................endif       
.........................inc ecx
...................until ecx >= 80h
..................jmp    dw_Exit..........................;脱壳后入口地址(特殊壳使用)
.............elseif      dw_GetProcAddr < 2...............;开关,调用_GetProcAddress次数
..................push   esi
..................mov    esi,_DLL_Addr
..................mov    eax,[esi]
..................and    eax,0f0f0f0fh
...................if    eax == 0e02050bh.................;kernel32.dll
.........................mov    esi,_API_Name_Addr
.........................mov    eax,[esi]
..........................if eax == 74726956h..............;VirtualAlloc或VirtualFree函数
.............................push   _DLL_Addr
.............................call   A_LoadLibrary_1........;真实的windows地址
.............................push   _API_Name_Addr
.............................push   eax
.............................call   A_GetProcAddress_1.....;真实的windows地址
.............................pop    esi
.............................inc    dw_GetProcAddr               
.............................jmp    step3..................;返回的是Virtual等函数地址
......................... .endif                       
...................endif
..................inc    dw_GetProcAddr
..................pop    esi
.............endif
............cmp   dwCase,0                                 
............jnz   step1....................................;获取各寄存器初值
............mov   dw_ecx,ecx...............................;寻找记录IAT表地址的寄存器
............mov   dw_ebx,ebx
............mov   dw_edx,edx
............mov   dw_esi,esi
............mov   dw_edi,edi
............inc   dwCase
............jmp   step2
step1:......cmp   dwCase,1
............jnz   step2
............pushad......................................... ;若找到需要的寄存器,填IID表
............sub   ecx,dw_ecx
............sub   ebx,dw_ebx
............sub   edx,dw_edx
............sub   esi,dw_esi
............sub   edi,dw_edi
.............if   ecx == 4..................................;两次差值为4的寄存器是写IAT表的寄存器
.....................mov  eax,1
.....................mov  dw_count,eax......................;将该寄存器打上标记
.....................mov  ecx,dw_ecx                       
.....................mov  dw_IAT_Start,ecx..................;上次的寄存器值就是IAT表起始地址
.............elseif  ebx == 4               
.....................mov  eax,2
.....................mov  dw_count,eax
.....................mov  ebx,dw_ebx                                       
.....................mov  dw_IAT_Start,ebx       
.............elseif  edx == 4       
.....................mov  eax,3
.....................mov  dw_count,eax
.....................mov  edx,dw_edx
.....................mov  dw_IAT_Start,edx
.............elseif  esi == 4
.....................mov  eax,4
.....................mov  dw_count,eax
.....................mov  esi,dw_esi
.....................mov  dw_IAT_Start,esi
.............elseif  edi == 4
.....................mov  eax,5
.....................mov  dw_count,eax
.....................mov  edi,dw_edi
.....................mov  dw_IAT_ Start ,edi
.............endif
.....@@:....popad
............mov   edi,dw_IID                                
............mov   eax,dw_IAT_Start       
............xor   eax,400000h
............mov   [edi-10h],eax............................;填写IID数据表中的INT表地址
............inc   dwCase...................................;使用一次后关闭               
step2:                                                   
.............if   dw_Flag == 0.............................;调用一个新库函数后进入
..................inc  dw_Flag
...................if     dw_count == 1
..........................mov dw_IAT,ecx
...................elseif dw_count == 2
..........................mov dw_IAT,ebx
...................elseif dw_count == 3
..........................mov dw_IAT,edx
...................elseif dw_count == 4
..........................mov dw_IAT,esi
...................elseif dw_count == 5
..........................mov dw_IAT,edi
...................endif
..................mov   edi,dw_IAT                           
..................mov   eax,[edi-4]..........................;记录下一个IID表中的IAT开始位置
...................if   eax == -1
........................mov dword ptr [edi-4],0..............;把某些壳IAT结束标记FFFFFFFF清0
...................endif
..................mov   esi,dw_IID
..................xor   edi,400000h
..................mov   [esi],edi
..................add   dw_IID,10h
..................add   dw_IID_IAT,4                        
..................mov   eax,dw_IID_IAT
..................xor   eax,400000h
..................mov   [esi-10h],eax........................;填写IID表中的INT数据表地址
.............endif                                          
............mov   edi,dw_IID_IAT                             
............add   dw_IID_IAT,4
............mov   eax,_API_Name_Addr                        
.............if   eax > 400000h..............................;以函数名地址方式调用GetProcAddress
..................sub  eax,400002h                           
..................mov  [edi],eax.............................;写INT表(某些特殊壳需要)
.............else
..................xor  eax,80000000h.........................;以序列号方式调用GetProcAddress
..................mov  [edi],eax
..................xor  eax,80000000h
.............endif               
...step3:....ret
_GetProcAddress     endp
  
        二、嵌入代码和变量地址重定位(静态处理)
       嵌入前面的两段代码并不困难,困难的是嵌入代码不能使用壳程序的变量地址,因为它很可能被冲掉,所以变量地址必需自带。不同的壳长度是不同的,嵌入地址也是不同的,所以要重定位。嵌入代码总是在壳最后(加入的)1000字节中。
  ;--------------------------------------------------------------
............mov   ebx,PE_oldLench...................;壳原长度
............shr   ebx,08
............or    bx,4000h..........................;bx是重定位值
............mov   edi,lpMemory......................;内存壳映像文件开始地址
............add   edi,PE_oldLench...................;edi指向了添加的1000字节               
............mov   ecx,299h..........................;嵌入代码长度
............mov   esi,401000h.......................;代码起始位置
............add   edi,40h...........................;预留16个变量地址
............push  edi
..@@:.......lodsb...................................;嵌入代码
............stosb
............loop  @B
............pop   edi                        
.............repeat
...................mov   ax,word ptr [edi+ecx]
....................if   ax == 4040h................;需要重定位的地址004040xxh
.........................mov   word ptr [edi+ecx],bx
....................endif
...................inc   ecx
.............until ecx >= 299h
  ;----------------------------------------------------------------------
  
.......三、入口地址设断的时机的掌握(进程调试)
.......怎样寻找LoadLibraryA和GetProcAddress地址并用代码地址替换是一个纯PE文件知识的问题,不是本文要讨论的问题,搜索OEP入口地址是静态分析后处理的技术问题,也不是本文讨论的问题。有兴趣的网友用OD打开附件中的程序就会一目了然。
.......进程调试(动态)中,如何在PE头中写字节(PE头是禁止写入的),至今我都没有解决。我第一篇壳文中提到的那个Keygen.exe壳(该壳叫什么名,我也不知道),整个解压代码都是写在PE头中的,要动态地在入口地址设断都不成功(有谁知道在进程中能对PE头写入,请交流一下)。所以我的设断都是在调用LoadLibraryA和GetProcAddress开始后。设断的目的是准确的把握抓取内存映像的时机,如果一开始就在OEP地址设断,那么壳在自解压的过程中会在该地址写入代码,冲掉你的断点。一般的壳在开始调用GetProcAddress时设置OEP断点地址是设有问题的,双重壳就不一定了,FantaMorph.exe壳(好象是个Aspack壳)它两次在OEP位置写入代码,过早地在OEP设断就会失败。我打开过用Aspack脱壳后的FantaMorph.exe,部分代码好象被重组过,OEP地址都变了,与我的脱壳机脱壳后有较大的不同。我的脱壳机除嵌入了替代LoadLibraryA和GetProcAddress函数代码、重写了IAT表、添加了INT表外(大多数程序不需要该表,但用VB6写的程序却需要),对原程序未作任何改动。
.......对付这种双重壳,我是采用分别在_LoadLibraryA和_GetProcAddress地址设断,两处“对倒”的方式(有点像机构抄股票),适当放行解压第一层壳需要的kernel32和VirtualAlloc和VitualFree函数,当开始解压第二层壳时在OEP设断,一旦进程到达该点并断下后,dump内存映像就一切OK了。
.......FantaMorph.exe壳是不是一个“双重壳”,我并不清楚,有点乱说,因为我并不知道双重壳是什么概念。
.......如何在进程调试中设断点,也只是windows的API应用技术问题,不在此说明。
      
.......四、后记
.......我附件中的“通用脱壳机”让高手见笑了,到底它适用那些壳,我并不清楚,如果手动输入OEP地址(16进制)适应范围会宽很多。我只分析了我电脑中的三个壳文件(更多的我没有),还有一个UPX加壳机。本脱壳机对付UPX壳实在是一瞬间的事。任何脱壳机的生命都是有限的,这个也不例外。我对这个脱壳机并不报好大希望,更不需要报酬,完全免费提供给任何使用者。你要任何改造,如增加搜索OEP等的功能都请随便。它就当我从“看雪论坛”获得知识的一个回报。
.......我的全部源代码写得很烂,不好意思拿出来。原因是开始构思时认为处理的事件不会太多,就按过程顺序去处理,使用了太多的全局变量和转跳。后来插入的事件越来越多,源代码已经绞成了一团乱麻,把自己都看晕了头。想改成按功能模块独立设计,那就要前功尽弃了。算了,搞起玩的,将就!
.......(说明:贴子中怎样使代码按需要对齐,我始终没有解决,只好在前面添上“....”号,给阅读带来不便,甚至掩盖了代码中必需的“.”号。一些贴子对齐得那么好不知是怎样处理的?请指教!)
.......谢谢!
  
  
  
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2007年02月13日 12:52:52
上传的附件:
最新回复 (17)
8
kanxue 2007-2-13 14:06
2
[i]最初由[B] wulje [/B]发布[/i] .......(说明:贴子中怎样使代码按需要对齐,我始终没有解决,只好在前面添上“....”号,给阅读带来不便,甚至掩盖了代码中必需的“.”号。一些贴子对齐得那么好不知是怎样处理的?请指教!)


我们一般这样处理对齐问题,先在记事本里排好版,用空格对齐代码,然后直接帖到论坛就行,论坛显示的效果与记事本显示基本一样。
还有一个方法是用代码[ code ]标签将代码括住(不过一般不需要这样做)
4
skylly 2007-2-13 14:38
3
26
forgot 2007-2-13 15:28
4
没有看懂...晕
14
笨笨雄 2007-2-13 17:23
5
根据壳特征定位OEP,还不如根据不同编译器编译出来的程序的入口特征定位OEP。

翻译版最新的主题里面,FORGOT推荐的其中一篇文章,貌似讲的就是壳把OEP也变成多态的问题

保护壳一般都用多态技术,如果是这样,你要根据特征码识别,就有点难度了。

不过你可以写一个反汇编引擎,搜索形如PUSH XXXXXXXX,CALL XXXXXXXX,MOV REG,DWORD PTR [XXXXXXXX]的机械码(都是5字节以上的指令,误认率较低),然后生成象IDA那样的图表。

根据CALL指令生成调用图。那么处于最上层的CALL(由于程序可能使用TIMER或者各种回调函数,所以你应该附加一些规则以识别OEP,在IAT完美修复的情况下,是可以做到较高的成功率的),便很可能是OEP了。

你也可以使用PAGE GUARD,在指定范围内设成不可运行。这样壳把EIP给OEP时,就会产生异常,那个出错的地方便是OEP了。也就是OD SFX的原理

另外你使用的技术易于模拟(尤其是GETPROCADDRESS,有PE知识的人都可以轻松自己实现)也易于攻击(HOOK和ANTI HOOK的文章不难找)。

你的脱壳机,对付压缩壳,还好了。
14
wulje 2007-2-13 18:43
6
[i]最初由[B] 笨笨雄 [/B]发布[/i] 根据壳特征定位OEP,还不如根据不同编译器编译出来的程序的入口特征定位OEP。 翻译版最新的主题里面,FORGOT推荐的其中一篇文章,貌似讲的就是壳把OEP也变成多态的问题 保护壳一般都用多态技术,如果是这样,你要根据特征码识别,就有点难度了。 ........

你说的这些,目前还没有接触过,慢慢学习研究。谢谢!
发烧cyrix 2007-2-14 11:12
7
楼主历害,很具创新力!脱壳机对付一些标准压缩壳还可以,但对付加密及变形壳的不行。
14
wulje 2007-2-14 12:41
8
[i]最初由[B] 发烧cyrix [/B]发布[/i] 楼主历害,很具创新力!脱壳机对付一些标准压缩壳还可以,但对付加密及变形壳的不行。

其实我对壳了解得很少,若网友有一些“奇怪的壳”,可提供给我研究研究,我真的是无知,才这么无畏。请一定不要带病毒!我只是好玩,多半不会有任何结果,更不能承诺一定答复。
鸡蛋壳 2007-2-14 13:11
9
准确的来说,通用脱壳机应该是壳运行环境监视器.
10
Isaiah 2007-2-14 13:53
10
压缩壳SFX就可以了。保护壳想通用可能不太容易。敏感API都不会用系统的。遇到不得不用的情况可能还会搬运一下再用。而且多态变形很难对于人来说都比较难识别,机器就更难了。
renlikun 2007-2-16 07:14
11
学习学习,谢楼主.
long181 2007-4-12 18:48
12
不是太管用。我使用了以下
purecoffee 2007-4-13 00:00
13
刚才试了一下,对付我这个软件还是不行
jsjcjsjc 2007-4-13 17:33
14
菜鸟来学习啊
hxsoft 2007-4-13 18:19
15
三十个贴子,10篇精华,老兄太牛了。
14
wulje 2007-4-16 00:20
16
第一次上传的“通用脱壳机”由于设计上的原因,对于入口地址大于4FFFFF的软件,脱壳后不能运行。修改后的“Unpacker1.01”已解决了这一问题。对压缩壳UPX或ASPpack的有效率还可以。
4
andy00 2007-4-19 14:15
17
楼主加油,关注的你的文章,从你的文章里学到很多东西,看来你是个很有创意的人,多出好文章
duxiaolei 2007-4-21 18:19
18
天文。。。天文呀。。我是看不懂了。
返回