首页
论坛
专栏
课程

[原创]菜鸟啄硬壳(之三)――借壳自动修复ITA表和IID表

2007-1-26 12:43 12437

[原创]菜鸟啄硬壳(之三)――借壳自动修复ITA表和IID表

wulje
14
2007-1-26 12:43
12437
[摘要]   
        1.摸拟壳的装载过程,自动修复ITA表和IID表,脱壳变得如此轻松愉快。
        2.在OD中修改PE头,脱壳后程序可以立即运行。
[正文]
        现在市面上有很多脱壳工具,但大多功能有限,只能脱一些非常标准的已知壳。若壳稍加变化它们就无能为力了,正象杀毒软件一样,只能杀已知的病毒。壳是可以千变万化的,万能脱壳工具好象是没有的?有没有十分容易的脱壳方法呢?答案是看你有没有一个很好的脱壳思路。下面以脱Spy.exe 的壳为例谈点体会。
        我很喜欢窗口侦测的Spy.exe软件(见附件),搞软件破解的没有人不知道它的。它用UPX加壳,我说过LoadLibrary和GetProcAddress函数是壳的软肋,这正好来验证一下这个结论是否管用?
        
        1.对Spy.exe壳的认识
        用壳侦查软件测试Spy.exe,都报告说它是UPX壳。但用UPX脱壳工具(一大堆)上阵,要么没反映,要么windows要发“错误报告”,UpxShell更搞笑,说它是一个陈旧的版本不能解壳?没有一个能生成(那怕是不能运行的)脱壳文件,只有ProcDump稍好点,脱出了一个不能运行的Unpacked.exe。Spy.exe的壳肯定是一个老版本了,老版本尚且如此,新版本谈何容易?
        我在《菜鸟啄硬壳(之一)》中有一句话说过头了。我说:“无论是windows或者自己调用GetProcAddress装载API函数,都必须要有IAT表”。这句话错了(资历太浅)!凡事都有例外!这个Spy.exe壳装载API函数就没有现存的IAT表!抢先抓取IAT表的思路受阻了。
        
        2.使用对付壳的杀手锏――拦截“GetProcAddress”函数
        由于有了前面乱说的教训,我不敢说拦截LoadLibrary和GetProcAddress函数一定可以做到什么。拦截的目的是分析代码是如何装载API函数的?从分析中尽量做到下面四点。这四点是脱壳思路的源泉!
        1)获取API函数名(脱壳后)地址;
        2)抓取IAT表或获取IAT表地址(如果没有IAT表,那么装载第1个API的地址就是IAT表地址);
        3)抓取IID表或获取IID表的地址(可能在LoadLibrary访问的地址附近,但不一定有IID表);
        4)获取OEP地址。
        如果你能做到以上4点(无论你用什么方法),那么让程序自动修复ITA表和IID表且脱壳后可以立即运行的程序就离你不远了。下面说具体的对Spy.exe脱壳的操作方法:
        
        (1)用OD打开Spy,用右键打开弹出式菜单找“搜索―全部模块中的名称”找到LoadLibrary,记下地址:4B7154。在OD命令栏键入“HR 4B7154”回车,运行。(按ctrl+F9)来到下面代码:
004BA123      8B07................mov eax,dword ptr ds:[edi]
004BA125      09C0................or eax,eax
004BA127      743D................je short 004BA166                       
004BA129      8B5F04..............mov ebx,dword ptr ds:[edi+4]
004BA12C      8D8430 00600B00.....lea eax,dword ptr ds:[eax+esi+B6000]  ;获取了库函数名
004BA133      01F3................add ebx,esi                           ;取得了IAT表地址
004BA135      50..................push eax
004BA136      83C708..............add edi,8                             ;指向了库中第1个API
004BA139      FF96 54610B00.......call dword ptr ds:[esi+B6154]         ;调用LoadLibrary
004BA13F      92..................xchg eax,edx
004BA140      8A07................mov al,byte ptr ds:[edi]              ;获取API函数名
004BA142      47..................inc edi
004BA143      08C0................or al,al
004BA145      74DC................je short 004BA123                     ;是库函数返回,是API往下                    
004BA147      52..................push edx
004BA148      89F9................mov ecx,edi
004BA14A      7907................jns short 004BA153                    ;必跳,14C―152是花指令
004BA14C      0FB707..............movzx eax,word ptr ds:[edi]
004BA14F      47..................inc edi
004BA150      50..................push eax
004BA151      47..................inc edi
004BA152      B9 5748F2AE.........mov ecx,AEF24857                      ;花指令,153开始才是真指令
004BA157      52..................push edx
004BA158      FF96 58610B00.......call dword ptr ds:[esi+B6158]         ;调用GetProcAddress
004BA15E      5A..................pop edx
004BA15F      8903................mov dword ptr ds:[ebx],eax            ;装载IAT表
004BA161      83C304..............add ebx,4
004BA164      EBDA................jmp short 004BA140                     
         单步走几下,到4BA148,状态栏显示一个函数名,地址是4B1009。切换内存到4B1000一下就看到一连串的函数名,显然API函数名称表地址在4B1000。继续走,在4BA158调用GetProcAddress,eax中返回了API地址,在4BA15F将把该值存入[ebx],状态栏显示ebx=480140,内存切换到这,发现是全空白,没有装载前的IAT表,但该地址就是装载后的IAT表的位置。4BA139是调用LoadLibrary,当某个.dll中的API装完后,自然就会来访问它。当我们来到4BA12C时,显示user32.dll,地址是4B719A,内存切换过去就发现了一堆.dll函数,原来Spy.exe将库函数和普通API是分开放置的。稍往回走就发现了IID表,地址是4B7000。最后我们发现,程序开始的入口处就是一个pushad,显然出口就在popad(满足pushad、popad成对规律)。来到popad处立即发现jmp 47A6C0,该地址为OEP无疑。这样,前面四个问题就圆满解决了。
        
        (2)修复或创建IAT表,IID表的思路:
        ITA表的实质就是其值是指向API函数名字串地址的,当我们知道函数名表开始的地址,并在其中搜索到某函数时将其所在的“地址”依次填入IAT表,则该表就修复或创建好了。(IID表的实质就自己去看有关书籍吧)弄懂后思路就清晰了。现在关键是搞清楚函数名表中函数排列的规律:我们发现每个函数名是以00 01开始的,每个库函数是以00 00开始的。有了这个就够了(其实在每个00 00后面的数字中暗藏着装载API的地址)。这样就有下面的方法了:
        将4BA198的jmp 47A6C0(出口),改为jmp 4B6B30(跳到空白处),OD中键入下列代码:
004B6B30    BD 00004000...........mov ebp,400000                       
004B6B35    BF 3C010800...........mov edi,8013C                 ;80140是IAT表地址
004B6B3A    BE FE0F0B00...........mov esi,0B0FFE                ;B1000是函数名表
004B6B3F    BB 24704B00...........mov ebx,4B7024                ;4B7014是IID表地址
004B6B44    8A042E................mov al,byte ptr ds:[esi+ebp]  ;搜索函数名
004B6B47    46....................inc esi
004B6B48    81FE 90280B00.........cmp esi,0B2890
004B6B4E    7202..................jb short 004B6B52                     
004B6B50    C3....................retn                          ;此处设断
004B6B51    90....................nop
004B6B52    3C00..................cmp al,0
004B6B54    75EE..................jnz short 004B6B44                     
004B6B56    8A042E................mov al,byte ptr ds:[esi+ebp]
004B6B59    3C00..................cmp al,0
004B6B5B    740A..................je short 004B6B67                       
004B6B5D    4E....................dec esi
004B6B5E    89342F................mov dword ptr ds:[edi+ebp],esi   ;填写IAT表
004B6B61    83C704................add edi,4
004B6B64    46....................inc esi
004B6B65    EBDD..................jmp short 004B6B44                     
004B6B67    83C608................add esi,8
004B6B6A    83C704................add edi,4
004B6B6D    893B..................mov dword ptr ds:[ebx],edi       ;填写IID表
004B6B6F    83C314................add ebx,14
004B6B72    EBD0..................jmp short 004B6B44                     
        运行在4B6B50断下后,因为PE头在运行中是禁止写入的,输入表,导入函数表的“登记”只能手工写入了:OD中内存切换到400000,修改400180:为14700B00 40010000;修改4001D8:为40010800 64060000。最后把第2块节表名由:UPX1改名为:.rsrc(这样eXeScope才可以编辑资源)。一切就绪后,dump当前进程,并将“重建输入表”选项去掉,入口依然是7A6C0。怎么样,脱壳出来的程序是不是运行得很正常?
        
        (3)摸拟壳写IAT表的过程,直接写入函数名地址:
        照你这么说,何苦先让壳把IAT表写成了 API的调用地址,再自己来改成函数名地址,不如让壳直接写函数名地址?还省得我小心地去找函数名字串排列的规律,自编程序。这话有理,但可能难度较大,因为壳写IAT表的空间只有41h字节,且壳也没有写IID表。小心地设计程序,调整原可用代码的位置(因为空间太小了)也能实现,在OD中汇编如下代码(对照那些是原有的,那些是添加的):
004BA123      BA 24704B00.........mov edx,4B7024                ;B7014是IID表地址
004BA128      BD 00004000.........mov ebp,400000               
004BA12D      8B07................mov eax,dword ptr ds:[edi]    ;edi初值指向函数名表
004BA12F      09C0................or eax,eax
004BA131      7433................je short 004BA166              
004BA133      8B5F04..............mov ebx,dword ptr ds:[edi+4]
004BA136      03DE................add ebx,esi
004BA138      33DD................xor ebx,ebp
004BA13A      891A................mov dword ptr ds:[edx],ebx    ;填写IID表
004BA13C      33DD................xor ebx,ebp
004BA13E      83C214..............add edx,14
004BA141      83C708..............add edi,8
004BA144      8A07................mov al,byte ptr ds:[edi]      ;查找函数名
004BA146      47..................inc edi
004BA147      08C0................or al,al
004BA149      74E2................je short 004BA12D                     
004BA14B      8BCF................mov ecx,edi
004BA14D      57..................push edi
004BA14E      48..................dec eax
004BA14F      F2:AE...............repne scas byte ptr es:[edi]  ;指向下个函数名
004BA151      58..................pop eax
004BA152      48..................dec eax
004BA153      48..................dec eax
004BA154      33C5................xor eax,ebp
004BA156      8903................mov dword ptr ds:[ebx],eax    ;填写IAT表
004BA158      83C304..............add ebx,4
004BA15B      EBE7................jmp short 004BA144                     
004BA15D      90..................nop
        在47A6C0设断(壳已经修复好了两个表了),断下后最后的操作同(2),得到的也是一个可以立即运行的程序。
        
        后记:
        将脱壳后的程序用eXeScope打开,发现它是用Delhpi写成的,怪说不得装载了6000多个API函数(绝大多数是不参与运行的,也是垃圾代码,但不装载还不行!)若是用手工来修改IAT表,这几乎是不可能完成的!
        因为PE头的禁止写入,使脱壳还不能实现全自动化。若另写一程序来打开它,并摸拟它的解压并修改PE头,这不就是脱壳机吗?我没有学习过任何脱壳机的写法,不知是不是这个原理,即便如此,写个脱壳机后,它的适应范围会有多宽多久呢?
        谢谢你认真读完!请菜鸟共勉,请高手指教。

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

上传的附件:
最新回复 (32)
kanxue 8 2007-1-26 12:58
2
0
最初由 wulje 发布
不如让壳直接写函数名地址?还省得我小心地去找函数名字串排列的规律,自编程序。


可以用ImportREC重建输入表,只要一张正确的IAT表,剩下的工作ImportREC帮你完成。

wulje学习方法不错,可以加强对PE结构的了解,另附一张输入表结构图,可能更容易理解此文:

fly 85 2007-1-26 13:11
3
0
支持wulje写个UPX的完美脱壳机
发烧cyrix 2007-1-26 13:21
4
0
历害!以我等菜鸟的水平都不好意思顶贴子了~~~~
以后多多发表!谢谢了。
wulje 14 2007-1-26 13:36
5
0
谢谢坛主、版主的器重,支持。
我其实也是个菜鸟,甚至有点不务正业。四楼此话差矣,学习软件思想是会大步前进的!
xserver 2007-1-26 14:59
6
0
莫非阁下是名震江湖,神来杀神,鬼来杀鬼,逢山开路,遇水搭桥,千里独行,前无古人,后无来者,独一无二,万人敬仰的 中原一点红?
skylly 4 2007-1-26 15:14
7
0
学习,
btw:我只知道IAT表,谁能告诉我楼主说的ITA和IID是啥子?
sbright 2 2007-1-26 17:22
8
0
这些文章正是我们菜菜所需要的
wulje 14 2007-1-26 18:13
9
0
不好意思,牙齿有时还要咬住舌头。我不过是才长了几皮还未干透羽毛的菜鸟。
skylly 4 2007-1-26 18:59
10
0
最初由 wulje 发布
不好意思,牙齿有时还要咬住舌头。我不过是才长了几皮还未干透羽毛的菜鸟。

我更菜。
我没有恶意,诚心请教。
laohai 2007-1-27 15:16
11
0
你写的方法是很好。

但我下载后,用PEID查壳是:UPX 0.71 - 0.72 -> Markus & Laszlo壳,OEP是0047A6C0   用OD载入spy.exe,OD选项/调试设置/事件设为“主模块入口点”
重新载入,停在:004BA000 > $  60            PUSHAD
F8一次,ESP为:0012FFA4  在命令行打入hr 0012FFA4后回车(设硬件断点),运行一次,来到:
004BA198   .- E9 2305FCFF   JMP Spy.0047A6C0(跳到OEP处)
把硬件断点删掉,F8一次,就到:
0047A6C0    55              PUSH EBP  (真正的OEP地址)
这时就可以Dump了,脱后直接就可以运行。不知你怎么要用这么麻烦的事来脱壳。
qyc 4 2007-1-27 17:14
12
0
最初由 laohai 发布
你写的方法是很好。

但我下载后,用PEID查壳是:UPX 0.71 - 0.72 -> Markus & Laszlo壳,OEP是0047A6C0 用OD载入spy.exe,OD选项/调试设置/事件设为“主模块入口点”
重新载入,停在:004BA000 > $ 60 PUSHAD
F8一次,ESP为:0012FFA4 在命令行打入hr 0012FFA4后回车(设硬件断点),运行一次,来到:
........


人家是分析,
RegKiller 10 2007-1-27 18:01
13
0
最初由 laohai 发布
你写的方法是很好。

但我下载后,用PEID查壳是:UPX 0.71 - 0.72 -> Markus & Laszlo壳,OEP是0047A6C0 用OD载入spy.exe,OD选项/调试设置/事件设为“主模块入口点”
重新载入,停在:004BA000 > $ 60 PUSHAD
F8一次,ESP为:0012FFA4 在命令行打入hr 0012FFA4后回车(设硬件断点),运行一次,来到:
........


是压缩壳,只能建议LZ把标题改掉了,有误导嫌疑
forgot 26 2007-1-27 22:20
14
0
我也需要静态un upx:)

支持
zhuwg 11 2007-1-27 22:28
15
0
支持
静态分析文章太难得了。。。不支持不行
skylly 4 2007-1-27 22:30
16
0
期待upx-static-unapcker,
forgot 26 2007-1-27 23:10
17
0
顺便问一下,代码里面那些..........是怎么搞得看起来很爽
xygwf 2007-1-29 08:37
18
0
顶一下.
学习中...
skylly 4 2007-1-30 10:39
19
0
想仿照楼主的思路做个UPX的脱壳机
发现做出来的东西通用性不强,最后发现是有个地方弄错乐:
004BA14A      7907................jns short 004BA153                    ;必跳,14C―152是花指令
004BA14C      0FB707..............movzx eax,word ptr ds:[edi]
004BA14F      47..................inc edi
004BA150      50..................push eax
004BA151      47..................inc edi
004BA152      B9 5748F2AE.........mov ecx,AEF24857                      ;花指令,153开始才是真指令

14c-152那段不是花指令,当导入是by 序号的时候就会走这个分支,所以不能把它当作花指令(由于这个壳导入都是 by name,所以不走这个分支)
h_f22 2 2007-2-4 23:29
20
0
LZ写的真是不错。。。

以前脱UPX都是,用OD载入,ctrl-f找popad,下断点,一下F9,两下F8,DUMP,搞定。从来没想过这么多。。。。
学习 2007-2-5 11:33
21
0
学习一下!!!!
kangaroo 6 2007-2-5 13:03
22
0
嘿嘿,把壳解压IAT讲具体点!!!
004BA122   .  5F            pop     edi                          ;  edi指向函数名区块
004BA123   >  8B07          mov     eax, dword ptr [edi]         ;  共8个字节,前四个字节是库名的偏移,后四个字节为IAT表的偏移
004BA125   .  09C0          or      eax, eax
004BA127   .  74 3D         je      short 004BA166               ;  偏移为0,表示结束,调处加载IAT表
004BA129   .  8B5F 04       mov     ebx, dword ptr [edi+4]       ;  读后四个字节
004BA12C   .  8D8430 00600B>lea     eax, dword ptr [eax+esi+B600>;  esi为00401000加上B6000再加上偏移,得到函数名字符串地址
004BA133   .  01F3          add     ebx, esi                     ;  ebx为后四个字节的偏移,加上esi,即指向IAT表中的偏移!!!
004BA135   .  50            push    eax                          ;  库名入栈
004BA136   .  83C7 08       add     edi, 8                       ;  edi加8(跳过那8个字节)指向下一个地址
004BA139   .  FF96 54610B00 call    dword ptr [esi+B6154]        ;  调用LoadLibrary函数
004BA13F   .  92            xchg    eax, edx                     ;  将句柄保存入edx
004BA140   >  8A07          mov     al, byte ptr [edi]           ;  读取1个字节
004BA142   .  47            inc     edi                          ;  指向下一个字节
004BA143   .  08C0          or      al, al                       ;  判断是否为函数,如果为函数值为1,0就是库名
004BA145   .^ 74 DC         je      short 004BA123               ;  为0(库名)跳上去,
004BA147   .  52            push    edx                          ;  kernel32.7C800000
004BA148   .  89F9          mov     ecx, edi
004BA14A   .  79 07         jns     short 004BA153
004BA14C   .  0FB707        movzx   eax, word ptr [edi]
004BA14F   .  47            inc     edi
004BA150   .  50            push    eax
004BA151   .  47            inc     edi
004BA152      B9            db      B9
004BA153   .  57            push    edi
004BA154   .  48            dec     eax
004BA155   .  F2:AE         repne   scas byte ptr es:[edi]       ;  将edi指向下一个函数名
004BA157   .  52            push    edx
004BA158   .  FF96 58610B00 call    dword ptr [esi+B6158]
004BA15E   .  5A            pop     edx
004BA15F   .  8903          mov     dword ptr [ebx], eax
004BA161   .  83C3 04       add     ebx, 4
004BA164   .^ EB DA         jmp     short 004BA140

楼主方法不错值得敬仰,呵呵,基本上脱离了工具,全手工搞定!!!

上传的附件:
honghai 2007-2-8 23:37
23
0
我是菜鸟 看不懂
assistx 2007-2-9 22:16
24
0
没太看懂,俺慢慢学吧
yuqich 2007-2-9 23:29
25
0
谢谢,楼主了,楼主的文章果然很好
gjpx 2007-3-2 22:50
26
0
这些文章正是我们菜菜所需要的转
yyjpcx 2007-3-24 22:48
27
0
1.hr 004B7154

2.F9

3.Ctrl+F9后

内存中的地址 F6B0EAF0 是不易读取的。请尝试更改EIP 或者跳过“
没办法正常继续

不知道为什么会这样?
上传的附件:
yyjpcx 2007-3-24 22:57
28
0
溢出漏洞???
cyjcbx 2007-3-25 22:20
29
0
好难看懂哦/.//
andy00 4 2007-4-19 13:50
30
0
人家是分析一种脱壳思想.你这个是按步就班.
andy00 4 2007-4-19 14:06
31
0
给wulje 提个意见啊,就是"库名"别说成"库函数名",有些个伙子会搞糊涂的
davidhee 2007-6-13 11:10
32
0
不错,但看不懂。。。
mnop 2007-6-15 12:35
33
0
还真是难啊,看不懂啊.
游客
登录 | 注册 方可回帖
返回