title: 程序脱壳篇
date: {{ date }}
tags: [‘程序壳’,‘程序脱壳篇’]
categories: [‘程序壳’,‘程序脱壳篇’]
excerpt: 壳是最早出现的专用加密软件技术!程序的壳可以是开发者未来保护自己的代码不被借鉴、破解、逆向;也可用来病毒、木马、蠕虫隐藏恶意代码,不被杀毒如软件查杀!所以我们每次逆向破解的时候遇到带壳程序会非常头疼,这篇文章主要是有关程序脱壳的技术的讲解,内容详细,深入学习者一定不能错过,带你深入研究程序脱壳技术,一定有所收获!

声明

首先要感谢的是我在2022年为了进阶逆向工程学习这门技术的老师,是鱼C工作室的老师小甲鱼,先学会感恩,是为人处世的基本准则。这是我看了它的脱壳篇后的课后自己实践过一遍的笔记总结。
其次这篇文章我最早发布于我临时搭建的博客网站,随后发布现在才搭建的正式网站

最后,抄一百篇不如自己实践操作写一篇,尊重知识产权,写笔记辛苦,禁止转载!!!引用注明出处!!!写了几个月,因为都是实践过的,如今分享给大家学习,但是尊重劳动成果。
我是同一作者!

一· 什么是壳?

1 . 初步认识壳

历史:壳是最早出现的专用加密软件技术!

作用:可以是开发者未来保护自己的代码不被借鉴、破解、逆向;也可用来病毒、木马、蠕虫隐藏恶意代码,不被杀毒如软件查杀!

预习:vmp纯虚拟机壳,目前公认认为公认最强。

温馨提示:脱壳上瘾,可不要随意传播哦!

2 . 壳的执行过程

加壳压缩,壳先执行,还原源程序,执行源程序

3 . OEP(Original Entry Point)

1·EP(Entry Point)

EP(Entry Point),意即程序的入口点。而OEP是程序的原始入口点,一个正常的程序只有EP,

例如:C语言main函数、winAPI函数、VC编译的入口地址0x40000000

只有入口点被修改的程序(加壳等),才会拥有OEP。

2·OEP

OEP:(Original Entry Point),程序的原始入口点,软件加壳就是隐藏了EP, 只要我们找到程序的OEP,就可以立刻脱壳。

PUSHAD (压栈) 代表程序的入口点,POPAD (出栈) 代表程序的出口点,与PUSHAD相对应,一般找到这个OEP就在附近啦。

4 . 壳的装载过程

模拟windows加载器:

壳获取自己所需要使用的API地址、解压或解密源程序的各个区块、进行必要的重定位、跳转到程序原入口点(OPE)

5 . 压缩引擎

1·aPLib

低于64kb的文件压缩速度较快

2·JCALG1

对大文件效果较好

3·LZMA

使用目的:开源,稳定

特点:保证压缩比的情况下解压速度一定要快

6 . 壳的分类

1·压缩壳

  • ASPack
  • UPX
  • PECompact

2·加密壳

  • ASProtect
  • Armadillo
  • EXECryptor
  • Themida
  • VMProtect 缺点慢

二· 实践是检验真理的唯一标准

接下来我们尝试破解这样一个老程序壳:image

1 . 查看壳

1·PEiD查看

image-20220413

2·Exeinfo PE查看

image-20220413

咳咳咳,咋就是说,这找不到这个程序壳的特征码,咋们看不出什么来,试试别的,是个老古董壳!

3·脱壳后的程序查看

image

可见它是由VC5.0编译的程序!

image-20220413

可见无壳!

2 . OD脱壳

用od把程序打开,刚进来情况是这样的:

image-20220413

刚进来的时候是要提示是壳的EP,如果不是,要在选项/调制选项/事件中设置第一次暂停于主模块入口:

image-20220413

点击OD里面的M查看进程内存:

image-20220413

试着分析对比,可见代码的入口地址是0x00401000,按C回到查看汇编代码,我们现在的入口地址是0x46B6DE,可见它在包含SFX的地址0x46B00000中。这壳有这三个区块(资源、SFX、重定向)。.text段就是程序的代码段块!

接下来单步执行F8,到jmp的时候跳转到地址0x004271B0:

image

来到这大概这个样子,右键分析/分分析代码:

image

再看进程内存,可知0x004271B0是在包含代码地址0x00401000中,所以它开头是55/8BEC,我们就找到了OEP

在OD的plugin文件夹中加入插件重新打开:

image

右键选择插件dump debugged process把程序dump出来

如下设置:

image

确保勾选Fix Raw Size是修复壳进行的数据偏移,不勾选Rebuil lmport输入表不进行重定位!

点击dump,保存文件命名dumo1,回到上面脱壳后的查看!

3 . 删除壳,减小程序大小

目前查看dump1.exe文件属性/大小:

image

打开软件lordPE,然后点击PE Editor按钮,选择dump1,对照弹出窗口里面显示的出数据和OD里的进程内存对比,然后点击Sections按钮,发现多余的壳区块,通通右键选择wipe section header, 如果是灰色的,那么请确保程序先不处于运行状态。后点击Save按钮保存OK。

image-20220413224740751

最后点击Rebuild PE再选择dump1打开:

image-20220413224850649

咋就知道,程序大小最后变成了原本91%!

image-20220413225205230

此时再用OD打开就发现壳已经无了:

image-20220413225131666

4 . 根据堆栈平衡原理寻找OEP

1·示例:zip压缩壳

实例:看文件名可猜测这是一个压缩壳

image-20220414144957058

用OD打开,发现刚进来第一句就是一堆跳转之中的一个jmp:

image-20220414145244971

这里就是壳的模块入口了,F8执行,可见这格式像是VC程序的入口点:

image-20220414182834762

可是这并不是OEP,这是为什么呢?原因我们来到进程内存来看:

image-20220414183146139

程序目前运行地址在0x004682BC,这大概在进程内存的0x00465000到0x0046A000之间,并不是我们所想的在0x401000里面,所以它其实是一个伪造程序区块OEP的壳!

现在我们重新打开,从最开始就要注意观察ESP寄存器的变化:

刚开始是0012FFC4,当运行到image-20220414191517674

时ESP发生第一次变化,变成0012FFC0,我们要把鼠标放在ESP显示的数据上右键选择数据窗口中跟随:

image-20220414212857900

然后就可以在数窗口看到0012FFC0,这是ESP所在的内存的位置,在该位置右键/断点/硬件访问/word都可以:

image-20220414213357003

此时就设置好了硬件断点,此断点不一般,只能在/调试/硬件断点/查看到哦,也可在此修改,也可以看到一般它只有四个硬件断点,使用的CPU特殊寄存器下断:

image-20220414213713220

F9直接运行到断点的位置image-20220415101819043

此时ESP的值是0012FFC4,刚好是刚才断点的位置!

这里可以记录一下,一般大部分的壳出现jmp eax都是跳转到OEP,但是这会出现一个明显的分界点,所以现在一些的强壳不会这样做!

可以看到EAX的值是004271B0,我们继续看到进程内存,它在0x00401000到0x0044B000之间的代码区块!

然后F8就进到了OEP:image-20220415112909192

此时就可以删除硬件断点,并进行dump操作!

也是先不勾选重定向,然后保存dump2!

此时打开lordPE,点击PE Editor按钮,选择dump2打开:

image-20220415185618813

先点击Sections按钮把壳的区块删除。然后可以看到ImageBase:00040000正确。但是代码段的偏移Base0fCode:00065000指向的还是伪装壳的代码段,所以我们要把他修改偏移成1000,Base0fData:改成4B000。,Save,OK保存!image-20220415201035557

会发现它脱完壳之后竟然变大了原来的85%,因为这是压缩壳!

接下来就可以根据retn所指向链接的黑线找到该区块的入口:image-20220415223054110

根据这个内存地址和连接指向的红线我们可以再往上寻找,发现就是回到了我们刚进来的一堆jmp的地方:image-20220415223502317

也就是这样通过一连串的代码块的跳转对文件不同区块的压缩和加密!

2·作业

1.eXPressor壳

程序:image-20220416103816878

查看PEiD:image-20220416103859136

可见这是一个exPressor的壳!

OD打开程序,刚进来就在这个地址:image-20220416104054028

点击M查看进程内存:image-20220416104212052

根据经验,程序本来的.text代码块被壳包含在了0x0046B000开始的区段,达到了“壳里有肉,肉里有壳”的效果!

根据堆栈平衡原理(80%有效)找到OEP,这次进行dump操作的时候,一点要勾选上重定向输入表!image-20220416104837596

这样插件就会搜索重建输入表,搜索里面的jmp跳转到一些API,然后再把这些API依次组成一个输入表!保存为dump3

再用PEiD查看dump3.exe的时候,就原型毕露了,知道它是VC++5.0写的一个程序!

再用OD打开dump3.exe点击M查看进程内存:

image-20220416105442548

发现它多了一个输入表!

我们在进行LordPE的时候修改偏移Base0fcode的时候,首先要看到dump3程序入口点地址:image-20220416105855396

对比上面的进程内存,可见他在0x401000开始的区段,所以Base0fcode要修改成1000!Save

在删除多余的区块的时候,备份一份,尝试后发现,只有.ex_cod可以wipe掉!image-20220416110735593

最后Rebuild PE选择dump3!image-20220416111201710

完成!

2.MEW压缩壳

程序:image-20220416111513837

查看壳:image-20220416111616682

这题只需要注意一点,像MEW系列壳除了可以使用堆栈平衡原理寻找OEP,记得分析代码;还可以在第一个区段网下滑找到retn加断点,F9运行到这F8,就到了OEP。应为这种壳,就是有个大区段,对软件进行解压缩,没有对它进行什么防护措施!

其他的都很简单,直接ReBulid PE就行,不用删除什么区块!

5 . .text区块加断点寻找OEP

程序:image-20220417165724224

查看壳:image-20220417165927233

该程序直接点击运行是没有任何问题的,但是一用OD运行就只能运行出一个空窗口,而且这时使用堆栈平衡法寻找OEP也是不管用,所以这个程序是具有反调试系统的,所以我们之能先一步一步走,看能不能找到关于OD的API函数,直到运行到:

image-20220417171333489

image-20220417171627417

IsDebuggerPresent是windows提供的对调试器判断的一个API

还有一种更快捷的办法,右键/查找/所有模块中的名称/往下滑就能看到IsDebuggerPresent:image-20220417174617927

双击就进了它的动态链接库:image-20220417175339918

在这里我们加断点,运行到这,然后按Alt+F9也可以来到差不多我们刚才单步走到的位置!image-20220417180734491

注意寄存器EAX,当能探测到调试器的时候eax=1,反之不能探测到则为eax=0

因此可见,该程序跳转就不能是实现了:image-20220417181116429

image-20220417181138685

所以我们可以先修改,标准寄存器ZF位的值吧,也可以把它改为jmp,让其实现跳转,此时再按F9运行可见程序就能正常执行了!

此时就是去掉他的反调试功能了,在修改jmp的位置此时右键/复制到可执行文件/选择,然后保存文件,命名2。

继续用OD打开去掉反调试后的该文件2:image-20220417220910299

查看进程内存:image-20220417221014633

但是!后面发现,用堆栈平衡方式寻找OEP并没有用,甚至停不下!所以这里我们要用另外一种方法就是在.text区块加断点!因为壳不论怎么运行终究还是要回到源程序的代码段的,所以我们就直接在原程序代码段加断点:image-20220417221621685

00401000右键/在访问上设置中断,运行:image-20220417221947771image-20220417222117355

但是这并不是我们想要的地址,那由于运行过了,断点取消了,那我们再继续断继续走,多次后发现并不可以,但是我们看到了loopd:image-20220417222350059

可以看到这里灰线的箭头其实是多次循环回去了,所以我们先直接先让它跳出循环再说,在循环外的下一个地址加断/运行/取段,就来到了这:image-20220417222642877

此时再次00401000下断点/运行的时候,又是多次来到了这里:image-20220417222921036

可见这是又有一个循环的,那总是进去很多个循环怎么办,一个一个像上面那样吗?不!我们重新开始,可以Ctrl+F8,或者长按F8,先观察循环都在哪里,再直接找到循环的末端,通过上面的方式下断:image-20220417225402388

看就是这个灰色循环,目前我们已经断在外了,取断后,断00401000执行,然后我们就来到真实的OEP啦,右键/分析/分析代码:image-20220417225655198

然后我们就把它dump5吧:image-20220417225834904

接下来备份dump5就可以开始老的方法wipe去除多余的区块.Prt啦!
图片 p120.png:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtOxDMhv-1687064225509)(https://www.52ying.top/usr/uploads/2023/ketu/图片p120.png)]

成功啦!


三· 重建IAT输入表(Import Address Table)

1 . 啥是输入表?

在各种不同版本的Windows系统中,DLL的版本各不相同,同一函数在不同版本DLL中的位置也可能不同;另外一个原因在于DLL的重定位,DLL文件的ImageBase值一般为10000000,但当两个DLL尝试装载到同一个地址时会发生冲突,因此有一个DLL得寻找另一个地址装载,这就是所谓的DLL重定位。

因此当我们要调用某个DLL中的函数时,需要借助IAT(Import Address Table,导入地址表)作为中间桥梁。PE文件在装载时由PE装载器将导入函数的真实地址写入到IAT时,函数调用时则需要从IAT中获取函数的真实地址。

对于每一个引入的可执行文件(例如dll),有一个镜像引入描述符(IMAGE_IMPORT_DESCRIPTOR)typedef struct _IMAGE_IMPORT_DESCRIPTOR {
     union {
          DWORD Characteristics;         // 0 for terminating null import descriptor
          DWORD OriginalFirstThunk;   // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
     };
     DWORD TimeDateStamp;           // 0 if not bound,
                                                                // -1 if bound, and real date\time stamp
                                                                // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                                                // O.W. date/time stamp of DLL bound to (Old BIND)
      DWORD ForwarderChain;           // -1 if no forwarders
      DWORD Name;                             // RVA,指向字符串,是这个可执行文件的名字。例如"ACE.dll"
      DWORD FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
IAT是一个IMAGE_THUNK_DATA类型的数组。有多少个函数被导入,这个数组就有多少个成员。该数组以0结尾。
typedef struct _IMAGE_THUNK_DATA32 {
     union {
           DWORD ForwarderString;          // 一个RVA地址,指向forwarder string 
           DWORD Function;                       // PDWORD,被导入的函数的入口地址
           DWORD Ordinal;                         // 该函数的序数
           DWORD AddressOfData;           // 一个RVA地址,指向IMAGE_IMPORT_BY_NAME
      } u1;
} IMAGE_THUNK_DATA32;
PIMAGE_IMPORT_BY_NAME是一个非常简单的结构,就两个成员。
typedef struct _IMAGE_IMPORT_BY_NAME {
     WORD Hint;            // 该函数的导出序数
     BYTE Name[1];      // 该函数的名字
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

image-20220418162411370

2 . OD跟踪输入表

例程序:image-20220418163214018

这是个简单的MessgeBoxA对话框程序,用OD打开:image-20220418163346289

虽然OD注释可以识别到这是MessgeBox,但点开这条指令是call 004011EA,那为什么它知道内存地址0x004011EA地址是一个MessgeBoxA的API函数呢?

那我们就在这行按下回车Enter键:image-20220418164053565

可以看到这是个跳转,其实周围还很多都是跳转,查看下面按是跳转到哪里:image-20220418164207582

上面显示的是取[00402048]内存地址里面的值=77D507EA跳转,为了验证一下,我们在内存窗口Ctrl+G输入00402048,就来到了这个地址:image-20220418164606243

根据小端存储,从右往左读,发现正是77D507EA!

所以我们可以知道在这整个程序无论到在个位置调用MessgeBoxA,它都相当于call 004011EA,在这里集中跳转到MessgeBoxA的动态链接库!image-20220418170825475

可以看到上面这些都是OD分析给我的,但是OD为什么会知道呢,其实就是根据IAT得出的!

接下来我们继续跟踪输入表,Alt+M到内存双击PE文件头,也就是00400000区块:

然后我们就可以看到标准PE头文件格式:image-20220418173633788标志:image-20220418173706834Image Base:image-20220418173758790偏移地址,这是输入表的地址Import Table address:image-20220418173909910

回到内存窗口,其实输入表所在地差不多就在这个位置:image-20220418174153805

接下来我们在代码窗口Ctrl+G搜索00402050,我们就来到了这里,但是发现没办法分析,因为它认为我们这不是在代码段:image-20220418174515314

所以我们要像之前一样安装一个新的插件:image-20220418174706485

安装好插件后重新打开,就可以点击Analyze This!image-20220418175424943

可见左边黑色数据是小端存储,右边绿色是OD简化成了我们方便阅读的字节形式,都是相对400000的偏移地址,可见总共有两个IMAGE_IMPORT_DESCRIPTOR,最后一个全是0是作为结束的标识。我们先尝试Ctrl+G搜第一个参数OriginalFilrstThunk地址00402098:image-20220418180222534

然后就走到了INT的第一个参数IMACE_THUNK_DATA又是一个偏移地址00402122,继续搜:image-20220418180509903

可见我们就来到了IMAGE_IMPORT_BY_NAME内,可找到函数的名字(都是由ASCII码转换翻译的)!

然后看到IMAGE_IMPORT_DESCRIPTOR第五个参数FirstThunk,这才是真正指向IAT的偏移地址:image-20220418181002394

搜索到:image-20220418181113132

可见其实这就是IAT输入表!

3 . HOOK-API

壳为了防止输入表被还原,强化壳自身的保护功能,会在IAT大量加密。

例如:源程序要调用一个函数的时候需要找IAT,然后却进到了壳程序进行监控般的跳转才能最终回到IAT!


四· FSG压缩壳和ImportREC的使用

程序可以正常运行:image-20220418184914873

查看壳:image-20220418184815948

可见这是用Delphi7.0写的程序,API比较多,所以IAT修复比较麻烦!

安装壳程序,点击install.exe,或者自行解压fsg.rar:image-20220418185939919

给程序FishC加壳,拖拽即可!这是一个压缩壳,压缩44%image-20220418202739825加壳后程序也可正常运行!

现在查看壳就可看到壳了:image-20220418203113866

把加FSG壳后的程序用OD打开:image-20220418203704260

此时用老方法较为复杂,目前使用一种新方法,步骤如上图1234,选项/调试选项/SFX/字节方式跟踪真正入口处/重新运行!

在这之后,还是回到了原来的地方,然后再带点击选项/调试选项/SFX/停在自解压器的入口/重新运行/,就可以看见左下角在疯狂的运行,这是在模拟程序的运行,自解压:image-20220418204434819

然后就来到了OEP,接下来就是右键/分析/从模块中删除分析:image-20220418204709439

接下来就可以dump操作了。由于SFG壳破坏了IAT,所以不要勾选重建输入表,因为勾选了也没用!我们接下来还要借助ImpREC.exe神奇工具修复IAT!

image-20220418214958019

如图,要确保要修改的程序被OD打开中或者与运行中,ImpREC才能检测到,然后根据我们刚才跟踪到了SFX代码真正入口点的地址,提取偏移地址,修改上面红色框起来的OEP,然后点击IAT AutoSearch就弹出一个提示框:image-20220418215506236

ImportREC会自动搜索所有的IAT!

翻译:好像找到了一些东西,但也有可能不不正确,如果不正确,建议把RAV改成:00001000 Size改成:00094000

接下俩我们就来验证一下,在内存搜索RAV地址,看它是不是IAT的入口点:image-20220418220105690

就看到这里:image-20220418220743104

继续往下滑就可以看见熟悉的API函数:image-20220418220842333

可见00461BEC上面还包含一些数据其实也是,所以我们还要是要把RVA改成:0046B12C,把这些数据包含进去,这才能确保我们的IAT修复的是完整的,才能正常打开程序!

可见IAT最后到了这里:image-20220418221448148

所以Size也要修改,范围大了可以,范围小了就不可以,只要把全部IAT包含到了就可以!我们修改成大概10000吧:image-20220418221928933

然后我们就可以点击Get Imports按钮了!

加载一会儿,可以看到很多的valid:NO这些就都是不合法的,总共有6642条都是不合法:image-20220418222316652

所以我们点击Show Invalid按钮把不合法的都展示出来,可见这些不合法的数据穿插在各个IAT里面,只要程序加载到这里的时候,加载不到,就会报错!所以我们右键/Cut thunk(s),把这些不合法的东西全部都给砍掉!image-20220418222733408

然后就可见我们见到的都是valid:YES的了,然后我们就点击Fix Dump按钮,选择覆盖刚才OD的dump文件:image-20220418222951970

然后它就帮我们多生成了一个文件dump_可以正常运行!

然后再用PEiD查看壳,如果有时候查看不出来image-20220418223541715可以选择选项/深度扫描:image-20220418223618570

就出来了!也可以观察到,脱壳后的文件变大了!


五 . UPX和WinUpacx压缩壳

UPX官网

UPX壳是目前最流行的,最稳定的壳,更新也较块!

1 . upx309w命令行加\脱壳

image-20220419090226751

win+R打开控制台,cd到壳程序目录下,upx.exe加壳,后面的才参数是我们待加壳地程序,加壳后可以看到,压缩到了原来的51.93%,大小从582656变成了302592字节!

其实也可以不用命令行,直接拖拽到upx.exe加壳!

然后我们就能查到壳:image-20220419090621346

然后查阅文档可知,命令加上-d参数,即可脱壳:image-20220419091006607

如下:image-20220419091127168

2 . Free UPX

image-20220419104644009

点击Add files按钮选择程序,COMPRESS是加壳,DECOMPRESS脱壳,下面的Backup file可以选择create可以创建一个副本,备份原程序!

3 . OD脱UPX壳

OD打开加UPX壳后的fishc程序:image-20220419111142757

通过堆栈平衡原理寻找OEP过程中,硬件断点来到这里:image-20220419111400944

由于这是3.09的版本壳,在之前的版本有可能是jmp,但这里jmp在下面,所以我们先取消硬件断点,然后在下面的jmp加断点/运行至此/去掉断点/F8/分析/分析代码,就来到了OEP:image-20220419111758641image-20220419111850970

然后dump操作,注意不要勾选重建输入表,接下来我们要用ImportRE恢复!

5 . 使用ImportREC修复IAT

image-20220419114356233

  1. 先点击小三角选择用OD正在运行的原程序文件
  2. 更具OD找到的OEP,修改ImportREC的OEP里面的偏移地址
  3. 点击IAT AutoSearch按钮确定
  4. 根据给出的RAV偏移地址去OD内存搜索验证,保证输入表没被修改,如果被修改了,就要把RAV的偏移地址调整过来,把IAT包含进去!这里可见上滑上面没有了,是可以的准确的,下面的Size也把IAT包含了,OKimage-20220419115822595
  5. 点击Get Imports按钮,点击Show Invalid按钮,可见没有错误的IATimage-20220419120253262
  6. 就可以直接Fix Dump选择刚才OD的dumpupx文件了!

6 . WinUpacx壳

该壳压缩效率更高,但已不更新!

image-20220419123029712

界面中文,比较友好!建议勾选3.清除导出表/4.清除重定位表

然后压缩之后会出现一个文件:image-20220419123200002这个文件去掉.bak后缀之后就是我们的原程序备份!

然后OD打开:image-20220419124124354

入过一开始不是这里,那也可以Alt+M,双击fishc在数据窗口找到偏移地址,即可搜索:image-20220419124349123

接下来就是根据堆栈平衡寻找OEP:image-20220419124602886

就可进行dump操作,由于输入表已被破坏所以记得不用勾选重建,重建也是错误的!

然后其实和上一个程序一样操作ImportREC,不多赘述!

然后如果还不满意可以再进行LordPE操作:image-20220419125328317

最后这就就完美啦!恭喜结业!!!芜湖!!!

链接欢迎大家关注我的Bilibli
感谢一键三连哦~

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐