发新话题
打印

[技术文章] 【看雪精华】脱壳基础知识入门

【看雪精华】脱壳基础知识入门

  第一课 PE格式

  要想学脱壳,第一步就得掌握PE格式,PE是Portable Executable File Format(可移植的执行体)简写,它是目前Windows平台上的主流可执行文件格式。

Microsoft Visual C++提供的WINNT.H里有PE数据结构的完整定义。
推荐文档:
  ah007翻译的“PE文件格式”1.9版  
  qduwg翻译的PE文件格式  
  Iczelion's 的PE文件格式
  PE结构各字段偏移参考  
  微软官方提供的PE文档(Revision 8.0 - May 16, 2006)  


  学习PE格式的方法是自己先准备一个十六进制工具,如HexWorkshop,WinHex,用这些工具打开一个EXE文件对照着学。强烈推荐你用
Stud_PE v.2.2.0.5这款工具辅助学习PE格式。PE格式学习的重点是在输入表(Import Table)这块。
Stud_PE工具界面:


PE结构图:


第二课 SEH技术
结构化异常处理(Structured Exception Handling,SEH)是Windows操作系统处理程序错误或异常的技术。SEH是Windows操作系统的一种系统机制,与特定的程序设计语言无关。
  外壳程序里大量地使用了SEH,如果不了解SEH,将会使你跟踪十分困难。

  SEH in ASM 研究(一)by hume  
  SEH in ASM 研究(二)by hume  
  Structured Exception Handling  
  加密与解密二版菜鸟学习笔记(2) - SEH 结构化异常处理 by ytcswb  

由于 Ollydbg  对SEH处理异常灵活,因此脱壳用Ollydbg会大大提高效率。

附CONTEXT结构环境:

代码:--------------------------------------------------------------------------------typedef struct _CONTEXT {

/*000*/ DWORD     ContextFlags;

/*004*/ DWORD     Dr0;
/*008*/ DWORD     Dr1;
/*00C*/ DWORD     Dr2;
/*010*/ DWORD     Dr3;
/*014*/ DWORD     Dr6;
/*018*/ DWORD     Dr7;

/*01C*/ FLOATING_SAVE_AREA FloatSave;

/*08C*/ DWORD     SegGs;
/*090*/ DWORD     SegFs;
/*094*/ DWORD     SegEs;
/*098*/ DWORD     SegDs;

/*09C*/ DWORD     Edi;
/*0A0*/ DWORD     Esi;
/*0A4*/ DWORD     Ebx;
/*0A8*/ DWORD     Edx;
/*0AC*/ DWORD     Ecx;
/*0B0*/ DWORD     Eax;

/*0B4*/ DWORD     Ebp;
/*0B8*/ DWORD     Eip;
/*0BC*/ DWORD     SegCs;
/*0C0*/ DWORD     EFlags;
/*0C4*/ DWORD     Esp;
/*0C8*/ DWORD     SegSs;

/*0CC*/    BYTE   ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

/*2CC*/ } CONTEXT;
鸽子爱好者的家园,灰鸽子爱好者论坛欢迎您! 灰鸽子爱好者论坛,因为专注所以专业! 灰鸽子爱好者论坛,因为有你所以精彩!

TOP

第三课 认识壳


1. 什么是壳?

  在一些计算机软件里也有一段专门负责保护软件不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。由于这段程序和自然界的壳在功能上有很多相同的地方,基于命名的规则,就把这样的程序称为“壳”了。
  
推荐文档:
  一切从“壳”开始  

描述壳的示意图:



2. 壳的加载过程

   这里谈的加壳工具不是WinZIP、WinRAR等数据压缩工具,而是谈压缩可执行文件EXE或DLL的工具。加壳过的EXE文件是可执行文件,它可以同正常的EXE文件一样执行。用户执行的实际上是外壳程序,这个外壳程序负责把用户原来的程序在内存中解压缩,并把控制权交还给解开后的真正程序,这一切工作都是在内存中运行的,整个过程对用户是透明的。
  壳和病毒在某些方面比较类似,都需要比原程序代码更早的获得控制权。壳修改了原程序的执行文件的组织结构,从而能够比原程序的代码提前获得控制权,并且不会影响原程序的正常运行。
  这里简单说说一般壳的装载过程。(参考了Ljtt以前写过的一篇文章)
  1)获取壳自己所需要使用的API地址
  如果用PE编辑工具查看加壳后的文件,会发现未加壳的文件和加壳后的文件的输入表不一样,加壳后的输入表一般所引入的DLL和API函数很少,甚至只有Kernel32.dll以及GetProcAddress这个API函数。
  壳实际上还需要其他的API函数来完成它的工作,为了隐藏这些API,它一般只在壳的代码中用显式链接方式动态加载这些API函数:3个脱壳相关的重要函数介绍  
  2)解密原程序的各个区块(Section)的数据
  壳出于保护原程序代码和数据的目的,一般都会加密原程序文件的各个区块。在程序执行时外壳将会对这些区块数据解密,以让程序能正常运行。 壳一般按区块加密的,那么在解密时也按区块解密,并且把解密的区块数据按照区块的定义放在合适的内存位置。
  如果加壳时用到了压缩技术,那么在解密之前还有一道工序,当然是解压缩。这也是一些壳的特色之一,比如说原来的程序文件未加壳时1~2M大小,加壳后反而只有几百K。
  3)重定位
  文件执行时将被映像到指定内存地址中,这个初始内存地址称为基地址(ImageBase)。当然这只是程序文件中声明的,程序运行时能够保证系统一定满足其要求吗?
  对于EXE的程序文件来说,Windows系统会尽量满足。例如某EXE文件的基地址为0x400000,而运行时Windows系统提供给程序的基地址也同样是0x400000。在这种情况下就不需要进行地址“重定位”了。由于不需要对EXE文件进行“重定位”,所以加壳软件把原程序文件中用于保存重定位信息的区块干脆也删除了,这样使得加壳后的文件更加小巧。有些工具提供“Wipe Reloc”的功能,其实就是这个作用。
  不过对于DLL的动态链接库文件来说,Windows系统没有办法保证每一次DLL运行时提供相同的基地址。这样“重定位”就很重要了,此时壳中也需要提供进行“重定位”的代码,否则原程序中的代码是无法正常运行起来的。从这点来说,加壳的DLL比加壳的EXE更难修正。
  4)HOOK-API
  程序文件中的输入表的作用是让Windows系统在程序运行时提供API的实际地址给程序使用。在程序的第一行代码执行之前,Windows系统就完成了这个工作。
  壳一般都修改了原程序文件的输入表,然后自己模仿Windows系统的工作来填充输入表中相关的数据。在填充过程中,外壳就可填充HOOK-API的代码的地址,这样就可间接地获得程序的控制权。
  5)跳转到程序原入口点(OEP)
   从这个时候起壳就把控制权交还给原程序了,一般的壳在这里会有明显的一个“分界线”。但现在的猛壳己没这界限了,壳里有肉,肉里有壳。



3. 压缩引擎  
  
   各类加壳软件,其压缩算法一般不是自己实现的,大多是调用其他的压缩引擎。目前压缩引擎种类比较多,不同的压缩引擎有不同特点,如一些对图像压缩效果好,一些对数据压缩效果好。而加壳软件选择压缩引擎有一个特点,在保证压缩比的条件下,压缩速度慢些关系不是太大,但解压速度一定要快,这样加了壳的EXE文件运行起来速度才不会受太大的影响。例如下面几个压缩引擎就能满足这要求:

1. aPLib压缩引擎 http://www.ibsensoftware.com/,这个库对于低于64K的文件压缩效果较好,速度较快。
2. JCALG1压缩引擎,相对于aPlib,JCALG1对于大文件效果好些。
3. LZMA压缩引擎 http://www.7-zip.org/zh-cn/sdk.html,LZMA 是 7-Zip 程序中 7z 格式 的默认压缩算法,压缩率很高。
鸽子爱好者的家园,灰鸽子爱好者论坛欢迎您! 灰鸽子爱好者论坛,因为专注所以专业! 灰鸽子爱好者论坛,因为有你所以精彩!

TOP

第四课 常见压缩壳与加密壳

   加壳软件按照其加壳目的和作用,可分为两类:一是压缩(Packers),二是保护(Protectors)。压缩这类壳主要目的是减小程序体积,如ASPacK、UPX和PECompact等。另一类是保护程序,用上了各种反跟踪技术保护程序不被调试、脱壳等,其加壳后的体积大小不是其考虑的主要因素,如ASProtect、Armadillo、EXECryptor等。随着加壳技术的发展,这两类软件之间的界线越来越模糊,很多加壳软件除具有较强的压缩性能,同时也有了较强的保护性能。


1. ASPacK  
主页:http://www.aspack.com/
  ASPack是款Win32可执行文件压缩软件,可压缩Windows 32位可执行文件(.exe)以及库文件(.dll、.ocx),文件压缩比率高达40%~70%。


2. UPX
主页:http://upx.sourceforge.net/
  UPX是一个以命令行方式操作的可执行文件经典免费压缩程序,压缩算法自己实现,速度极快,压缩比极高。(开源)

3. PECompact
主页:http://www.bitsum.com/
  PECompact同样也是一款能压缩可执行文件的工具(支持EXE、DLL、SCR、OCX等文件)。相比同类软件,PECompact提供了多种压缩项目的选择,用户可以根据需要确定哪些内部资源需要压缩处理。同时,该软件还提供了加解密的插件接口功能。



4. ASProtect
主页:http://www.aspack.com/
  ASProtect是一款非常强大的Windows 32位保护工具,这4、5年来,其一直在更新进步。其开发者是俄国人Alexey Solodovnikov。它拥有压缩、加密、反跟踪代码、反-反汇编代码、CRC校验和花指令等保护措施。它使用Blowfish、Twofish、TEA等强劲的加密算法,还用RSA1024作为注册密钥生成器。它还通过API钩子(API hooks,包括Import hooks(GPA hook)和Export hooks)与加壳的程序进行通信。甚至用到了多态变形引擎(Polymorphic Engine)。反Apihook代码(Anti-Apihook Code)和BPE32的多态变形引擎(BPE32的Polymorphic Engine)。并且ASProtect为软件开发人员提供SDK,实现加密程序内外结合。



更多与壳有关的描述参考:
   纵横间谁能相抗—论壳与加壳技术 作者:forgot

常见加密壳官方站点

ASProtect        http://www.aspack.com/  
ACProtect        http://www.ultraprotect.com/
Armadillo        http://www.siliconrealms.com
EXECryptor       http://www.strongbit.com/
Obsidium        http://www.obsidium.de/  
PESpin          http://pespin.w.interia.pl/  
VMProtect        http://www.polytech.ural.ru/  
Xtreme-Protector   http://www.oreans.com/xprotector/
Themida/WinLicense  http://www.oreans.com

本站的工具栏目:   http://www.pediy.com/tools/packers.htm
鸽子爱好者的家园,灰鸽子爱好者论坛欢迎您! 灰鸽子爱好者论坛,因为专注所以专业! 灰鸽子爱好者论坛,因为有你所以精彩!

TOP

第五课 文件类型分析

  拿到一个壳,第一步就是用相关工具分析一下是什么壳,然后就可心中有数地跟踪分析。文件分析工具有PEID,FileInfo等。

1.PEiD

  PEiD的GUI界面操作非常方便直观。它的原理是利用查特征串搜索来完成识别工作的。各种开发语言都有固定的启动代码部分,利用这点就可识别出是何种语言编编译的。同样,不同的壳也有其特征码,利用这点就可识别是被何种壳所加密。PEiD提供了一个扩展接口文件userdb.txt ,用户可以自定义一些特征码,这样就可识别出新的文件类型。
  有些外壳程序为了欺骗PEiD等文件识别软件,会伪造启动代码部分,例如将入口代码改成与Visual C++ 6.0所编程程序入口处类似代码,即可达到欺骗目的。所以,文件识别工具所给出的结果只是个参考,文件是否被加壳处理过,还得跟踪分析程序代码才可得知。 可参考这个文档了解如何伪装:让侦测工具把壳识别为VC++7.0的源代码  。目前Hying的壳PE-Armor伪装能力是最强的:



PEiD分析不出类型的文件就报告是“Nothing found *”,如出现这情况一般都是未知壳或新版的壳。

下面PEiD识别出这个软件是用Asprotect 1.2x加的壳。



2.FileInfo

  FileInfo(简称Fi)另一款不错的文件检测工具。Fi运行时是DOS界面,在DOS窗口中运行程序相当不便,建议采用下面的技巧:
1.用鼠标将文件拖到Fi主文件上。
2.将Fi快捷方放进Windows的SendTo文件夹里.以后要分析某文件,只需右击“发送到”功能就可打开Fi。  
  FileInfo升级慢,其识别库不能自定义。而PEiD升级比较频繁,用户也可自定义特征码,因此PEiD用的比较普遍。

  有时,FileInfo和PEID会报“PE Win GUI”,Win GUI就是Windows图形用户界面程序统称,表明程序可能没加壳。但不排除也有加壳的可能性,下图是一个ExeCryptor 2.2x的壳,FileInfo报“*PE Win GUI *section* ??”,其不能识别出来。识别信息中带了个问号,表明FI对给出的结果不是太肯定。
鸽子爱好者的家园,灰鸽子爱好者论坛欢迎您! 灰鸽子爱好者论坛,因为专注所以专业! 灰鸽子爱好者论坛,因为有你所以精彩!

TOP

第六课 寻找OEP  

  一般的压缩壳,如Aspack等都有专用的脱壳机  。而加密壳(如ASProtect,Armadillo) 一般很少有脱壳机,必须手工脱壳。手工脱壳一般情况是分三步:一是查找程序的真正入口点(OEP);二是抓取内存映像文件;三是输入表重建。(当然现在的加密壳复杂些,要考虑更多的东西)
  OEP是Original Entry Point缩写,即程序加壳前的真正的入口点。
  外壳初始化的现场环境(各寄存器值)与原程序的现场环境是相同的。加壳程序初始化时保存各寄存器的值,外壳执行完毕,会恢复各寄存器内容。其代码形式一般如下:

   PUSHFD      ; 将标志寄存器入栈保存
   PUSHAD      ; push eax, ecx, edx, ebx, esp, ebp, esi, edi
   ……        ; 外壳代码部分
   POPAD       ; pop edi, esi, ebp, esp, ebx, edx, ecx, eax
   POPFD       ; 恢复标志寄存器
   JMP OEP      ;
OEP: ……       ; 解压后的程序原代码

  为了讲述方便,本节用UPX加壳的Win98记事本来演示。首先用PEid查看加壳前的记事本:



PEid显示Notepad.exe程序是用Microsoft Visual C++ 6.0编译的,接下来用UPX来加壳,方法是开个DOS窗口,用命令upx notepad.exe。如下图所示:


这时再用PEid查看加壳的文件,PEid会给出如下信息:UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo


UPX的壳可以用UPX.exe自身来脱,命令是:upx -d 文件名 。一些变种的UPX壳用UPX.EXE自身脱不了,这时可以试试UPX ShellEx 这款工具。
脱壳前建议用PE工具LordPE打开目标文件查看一下区块,以尽可能地多了解一些信息,对脱壳有帮助,如下图:


1.根据跨段指令寻找OEP

  推荐用Ollydbg来调试脱壳,比SoftICE和TRW2000方便多了。运行Ollydbg,点击菜单“选项/调试设置”,将第一次暂停设在WinMain函数上。再用Ollydbg打开实例notepad.upx.exe就可中断在外壳的入口点处了:


上图相关代码如下:
0040E8C0 >  60          pushad    //一开始Ollydbg就会中断这行,这个就是外壳的入口点,注意这个pushad指令

   绝大多数加壳程序在被加密的程序中加上一个或多个段,所以依据跨段的转移指令(JMP)就可找到真正的入口点,此时就会有POPAD/POPFD指令出现。UPX 用了一次跨段的转移指令(JMP),在跳到OEP处会看到虚拟地址的值有一个突变,此时就能确定OEP了。
   UPX壳比较简单,大家不必要跟踪去找这个跨段的转移指令,中断WinMain后,只需要在Ollydbg里往下翻屏,就会发现这个跨段转移指令:

上图相关代码如下:
0040EA0E   61          popad        //注意这里的popad指令,和开始的pushad对应         
0040EA0F  - E9 B826FFFF    jmp    004010CC  //这里跳到OEP,将光标移到这,按F4执行到这行

  这一句0040EA0F  jmp  004010CC 就是跳到OEP的指令,执行到这,UPX外壳己将程序解压完毕,并模拟Windows加载器的将原始程序加载到内存,004010CC 就是映射到内存目标程序的入口点,此时就可抓取内存映像文件了。


2.根据堆栈平衡原理找OEP

  这个堆栈平衡原理其找OEP原理这篇文档描述的比较详细:寻找真正的入口(OEP)--广义ESP定律 作者enus
操作方法:多数壳在运行到OEP的时候ESP=0012FFC4,这就是说程序的第一句是对0012FFC0进行写入操作,只要在0012FFC0下硬件写入断点(命令行里键入HW 12FFC0),我们就能停在OEP的第二句处。

  用OllyDBG重新加载实例程序notepad.upx.exe,在命令行下硬件写断点:

按F9执行程序,就会中断在OEP第二行:

此时如果将光标向上移,会发现第一句代码变乱了:
004010C7   000D 0A000055  add    [5500000A], cl
004010CD   8BEC        mov    ebp, esp

这是因为Ollydbg将数据当汇编代码来分析了,你可以按 Ctrl+ALT+向上光标键 将当前显示的代码向上滚动一个字节就可看到正确的汇编代码了:

004010CC   55          push   ebp
004010CD   8BEC        mov    ebp, esp  //中断在这行
004010CF   83EC 44      sub    esp, 44
004010D2   56          push   esi
004010D3   FF15 E4634000  call   [4063E4]                 ; kernel32.GetCommandLineA

中断后,别忘点击菜单“调试/硬件断点/”打开硬件断点面板,将刚才的硬件断点删除。

3.根据编译语言特点找OEP

  各类语言编译的文件入口点都有一些规律,可以这利用这点来寻找入口点。
1)Delphi程序
执行程序,用LordPE(或Prodump)选dump(full)脱壳,存为dump.exe。接着用Hex Workshop打开dump.exe,搜索文本“runtime”,搜到后,向前查找离“runtime”最近的十六进制数字“55 8B EC”,数字所在的地址就是程序的OEP。
2)Visual C程序
可以利用Visual C启动部分的几个函数GetCommandLineA(W)、GetVersion、GetModuleHandleA(W)、GetStartupInfoA(W) 等来定位程序的OEP。

  常见的各类编译语言的入口汇编代码都要熟悉,因为一些加密强壳会偷OEP处的代码到壳里,一般情况各编译语言入口代码都相同,到时只需要直接引用相关程序的入口代码,这给我们恢复代码带来方便。
4.用内存断点找OEP

Author:Lenus
From:  www.popbase.net & www.pediy.com
E-mail:Lenus_M@163.com
--------------------------------------------------
1.前言
  发现论坛中很多兄弟在询问:什么是二次内存断点,三次内存断点。还有很多人对内存断点的原理不是很明白。其实只要懂得壳是如何解压代码的,那么就完全可以按自己的喜欢来下断。
  
  本文要解决的问题是:
  1.什么是内存断点?
  2.如何在寻找OEP时使用内存断点。
  3.内存断点的局限性。
  
2.内存断点寻找OEP的原理
  i.首先,在OD中内存断点,硬件断点和普通断点(F2下断)是有本质区别的。硬件断点等效与SoftICE命令bpm,他的中断要用到DR0-DR7的调试寄存器,也就是说OD通过这些DR0-DR7的调试寄存器来判断是否断下。
   普通断点(F2下断)等效于bpx,他是在所执行的的代码的当前地址的一个字节修改为CC(int3)。当程序运行到int3的时候就会产生一个异常,而这个异常将交给OD处理,把这个异常的regEIP-1以后就正好停在了需要的中断的地方(这个根据系统不同会不一样),同时OD在把上面的int3修改回原来的代码。
  而内存断点基本上使用的是对代码使用的保护属性来实现中断。

  内存断点分为:内存访问断点,内存写入断点。
  我们知道,在程序运行的时候会有3种基本的状态产生:读取,写入,执行。

004AE242    A1 00104000     mov eax,dword ptr ds:[004AE24C]        //004AE24C处的内存读取
004AE247    A3 00104000     mov dword ptr ds:[004AE24C],eax        //004AE24C处的内存写入
004AE24C    83C0 01        add eax,1                       //004AE24C处的内存执行
  
  那么我们应该如何中断在上面的几行呢?
  1.当我们对004AE24C下内存访问断点的时候,可以中断在004AE242也可以中断在004AE247。
  2.当我们对004AE24C下内存写入断点的时候,只能中断在004AE247。
  3.当我们对004AE24C下内存访问断点的时候,能中断在004AE24C。

  到这里你可能不明白了,为什么内存访问断点能中断在004AE247这一句对004AE24C的写入,而且还能中断在004AE24C的执行呢?

  其实很简单,我们只要仔细体会一下“内存访问”这四个字的含义遍可以知道,当我们对004AE24C进行读取的时候需要“访问”他吧,当我对004AE24C进行写入的时候也需要“访问”他吧!!当然我们要执行内存地址004AE24C的代码的时候也是还是要“访问”他的!

  所以我们不难得出下面的结论:
  1.内存写入中断的地方,一定是也可以用内存访问中断。
  2.内存执行的地方,也可以用内存访问中断。
  如果这时你认为,那么内存写入岂不是没用了。呵呵~那我要告诉你当然不是,如果你想快速的准确的定位到004AE247这一行的时候,那么他就大有作用了!

  总结一下:内存断点不修改改原代码,不会像普通断点那样因为修改代码被程序校验而导致中断失败;对于区段的访问只是区域大了一点,其原理和上面分析的三行代码是一样的。

  ii.如何使用内存断点来寻找OEP呢?
  要回答这个问题首先要回答这一个问题:壳是如何解压代码的?

  正如我们知道的,壳如果要把原来加密或压缩的代码运行起来就必须要解压和解密原来的代码。而这一个过程我们难道不能将他看做是对代码段(code段)的写入吗?好了,解压完毕了。我们要从壳代码的区段JMP到原来的代码段的时候,难道不正是对代码段(code段)的执行吗?
  理清了上面的关系就好办了,那么如果载入OD后,我们直接对code段下内存访问断点的时候,一定会中断在壳对code段的写入的代码的上面,就像上面的004AE247的这一行。而如果当他把code段的代码全部解压解密完毕了以后,JMP到OEP的时候,我们是不是还可以停在OEP的代码上面呢?而且每按下F9都会中断,因为这时code段在执行中哦!

  相信很多人到这里已经明白了,为什么在教程中到达了某一个时候,某一行的时候。牛人们就叫我们对code段下内存访问断点了吧。
  而如果你还要继续问我为什么一定要到那个地方才可以下断呢?我难道不可以一开始就下断吗?
  正入我上面所说的,如果你在前面下断很可能壳对code段还没解压完毕呢,这时如果你不停的按F9,你将会看到OD的下方不断的在提示你“对401000写入中断” “对401002写入中断”“对401004写入中断”.......如果你不介意按F9到他把正个code段写完的话,我除了同情你的“F9”以外,没什么其他的意见!
  
  那么我们就没有别更快一点的办法了吗?
  有的!那就是我们呼之欲出的两次内存断点办法。
  怎么理解两次内存断点呢?

  让我来做一个假设吧,假设我是一个壳的作者。一个EXE文件的有code段,data段,rsrc段.....依次排列在你的内存空间中,那么我会怎么解码呢?呵呵~我比较笨一点,我会先将code段解码,然后再将data段解压,接着是rsrc段......那么聪明的你不难发现,只要你在data断或者rsrc段下内存访问断点,那么中断的时候code段就已经解压完毕了。这时我们再对code段下内存反问断点,不就可以到达OEP了吗?

  这里注意上面虽然下了两次内存访问断点,但是本质是不一样的,目的也是不一样的。

  1.对data段下内存访问断点而中断是因为内存写入中断,目的是断在对对data段的解压时,这时壳要对data段写数据,但是code段已经解压完毕。
  2.对code段下内存访问断点而中断是因为内存执行中断,目的当然就是寻找OEP了。

  总结一下:如果我们知道壳在什么地方对code段解压完毕我们就可以使用内存断点,找到OEP。如果不知道,那么我们就依靠2次内存断点去找,如果还不行就用多次内存断点。总之明白了原理在多次的内存断点其实都一样。从这个过程中我们了解的是壳在对区段解码的顺序!

  iii.实战

  说了这么多,我想大家都越越欲试了吧。
  好吧,来弄一个猛壳怎么样:
这个壳是一个hying的旧版,我们用他来实验一下我们内存断点法。
  
  OD载入以后来到这里

0040D000 u>  56              push esi          //这里
0040D001    52              push edx
0040D002    51              push ecx
0040D003    53              push ebx
0040D004    55              push ebp
0040D005    E8 15010000        call unpackme.0040D11F

  根据跟过一次的经验我们将先设置,除int3异常以外忽略其他异常,SHIFT+F9

第六课 寻找OEP  

  一般的压缩壳,如Aspack等都有专用的脱壳机  。而加密壳(如ASProtect,Armadillo) 一般很少有脱壳机,必须手工脱壳。手工脱壳一般情况是分三步:一是查找程序的真正入口点(OEP);二是抓取内存映像文件;三是输入表重建。(当然现在的加密壳复杂些,要考虑更多的东西)
  OEP是Original Entry Point缩写,即程序加壳前的真正的入口点。
  外壳初始化的现场环境(各寄存器值)与原程序的现场环境是相同的。加壳程序初始化时保存各寄存器的值,外壳执行完毕,会恢复各寄存器内容。其代码形式一般如下:

   PUSHFD      ; 将标志寄存器入栈保存
   PUSHAD      ; push eax, ecx, edx, ebx, esp, ebp, esi, edi
   ……        ; 外壳代码部分
   POPAD       ; pop edi, esi, ebp, esp, ebx, edx, ecx, eax
   POPFD       ; 恢复标志寄存器
   JMP OEP      ;
OEP: ……       ; 解压后的程序原代码

  为了讲述方便,本节用UPX加壳的Win98记事本来演示。首先用PEid查看加壳前的记事本:

PEid显示Notepad.exe程序是用Microsoft Visual C++ 6.0编译的,接下来用UPX来加壳,方法是开个DOS窗口,用命令upx notepad.exe。如下图所示:

这时再用PEid查看加壳的文件,PEid会给出如下信息:UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo

UPX的壳可以用UPX.exe自身来脱,命令是:upx -d 文件名 。一些变种的UPX壳用UPX.EXE自身脱不了,这时可以试试UPX ShellEx 这款工具。
脱壳前建议用PE工具LordPE打开目标文件查看一下区块,以尽可能地多了解一些信息,对脱壳有帮助,如下图:

1.根据跨段指令寻找OEP

  推荐用Ollydbg来调试脱壳,比SoftICE和TRW2000方便多了。运行Ollydbg,点击菜单“选项/调试设置”,将第一次暂停设在WinMain函数上。再用Ollydbg打开实例notepad.upx.exe就可中断在外壳的入口点处了:

上图相关代码如下:
0040E8C0 >  60          pushad    //一开始Ollydbg就会中断这行,这个就是外壳的入口点,注意这个pushad指令

   绝大多数加壳程序在被加密的程序中加上一个或多个段,所以依据跨段的转移指令(JMP)就可找到真正的入口点,此时就会有POPAD/POPFD指令出现。UPX 用了一次跨段的转移指令(JMP),在跳到OEP处会看到虚拟地址的值有一个突变,此时就能确定OEP了。
   UPX壳比较简单,大家不必要跟踪去找这个跨段的转移指令,中断WinMain后,只需要在Ollydbg里往下翻屏,就会发现这个跨段转移指令:

上图相关代码如下:
0040EA0E   61          popad        //注意这里的popad指令,和开始的pushad对应         
0040EA0F  - E9 B826FFFF    jmp    004010CC  //这里跳到OEP,将光标移到这,按F4执行到这行

  这一句0040EA0F  jmp  004010CC 就是跳到OEP的指令,执行到这,UPX外壳己将程序解压完毕,并模拟Windows加载器的将原始程序加载到内存,004010CC 就是映射到内存目标程序的入口点,此时就可抓取内存映像文件了。


2.根据堆栈平衡原理找OEP

  这个堆栈平衡原理其找OEP原理这篇文档描述的比较详细:寻找真正的入口(OEP)--广义ESP定律 作者enus
操作方法:多数壳在运行到OEP的时候ESP=0012FFC4,这就是说程序的第一句是对0012FFC0进行写入操作,只要在0012FFC0下硬件写入断点(命令行里键入HW 12FFC0),我们就能停在OEP的第二句处。

  用OllyDBG重新加载实例程序notepad.upx.exe,在命令行下硬件写断点:

按F9执行程序,就会中断在OEP第二行:

此时如果将光标向上移,会发现第一句代码变乱了:
004010C7   000D 0A000055  add    [5500000A], cl
004010CD   8BEC        mov    ebp, esp

这是因为Ollydbg将数据当汇编代码来分析了,你可以按 Ctrl+ALT+向上光标键 将当前显示的代码向上滚动一个字节就可看到正确的汇编代码了:

004010CC   55          push   ebp
004010CD   8BEC        mov    ebp, esp  //中断在这行
004010CF   83EC 44      sub    esp, 44
004010D2   56          push   esi
004010D3   FF15 E4634000  call   [4063E4]                 ; kernel32.GetCommandLineA

中断后,别忘点击菜单“调试/硬件断点/”打开硬件断点面板,将刚才的硬件断点删除。

3.根据编译语言特点找OEP

  各类语言编译的文件入口点都有一些规律,可以这利用这点来寻找入口点。
1)Delphi程序
执行程序,用LordPE(或Prodump)选dump(full)脱壳,存为dump.exe。接着用Hex Workshop打开dump.exe,搜索文本“runtime”,搜到后,向前查找离“runtime”最近的十六进制数字“55 8B EC”,数字所在的地址就是程序的OEP。
2)Visual C程序
可以利用Visual C启动部分的几个函数GetCommandLineA(W)、GetVersion、GetModuleHandleA(W)、GetStartupInfoA(W) 等来定位程序的OEP。

  常见的各类编译语言的入口汇编代码都要熟悉,因为一些加密强壳会偷OEP处的代码到壳里,一般情况各编译语言入口代码都相同,到时只需要直接引用相关程序的入口代码,这给我们恢复代码带来方便。
4.用内存断点找OEP

Author:Lenus
From:  www.popbase.net & www.pediy.com
E-mail:Lenus_M@163.com
--------------------------------------------------
1.前言
  发现论坛中很多兄弟在询问:什么是二次内存断点,三次内存断点。还有很多人对内存断点的原理不是很明白。其实只要懂得壳是如何解压代码的,那么就完全可以按自己的喜欢来下断。
  
  本文要解决的问题是:
  1.什么是内存断点?
  2.如何在寻找OEP时使用内存断点。
  3.内存断点的局限性。
  
2.内存断点寻找OEP的原理
  i.首先,在OD中内存断点,硬件断点和普通断点(F2下断)是有本质区别的。硬件断点等效与SoftICE命令bpm,他的中断要用到DR0-DR7的调试寄存器,也就是说OD通过这些DR0-DR7的调试寄存器来判断是否断下。
   普通断点(F2下断)等效于bpx,他是在所执行的的代码的当前地址的一个字节修改为CC(int3)。当程序运行到int3的时候就会产生一个异常,而这个异常将交给OD处理,把这个异常的regEIP-1以后就正好停在了需要的中断的地方(这个根据系统不同会不一样),同时OD在把上面的int3修改回原来的代码。
  而内存断点基本上使用的是对代码使用的保护属性来实现中断。

  内存断点分为:内存访问断点,内存写入断点。
  我们知道,在程序运行的时候会有3种基本的状态产生:读取,写入,执行。

004AE242    A1 00104000     mov eax,dword ptr ds:[004AE24C]        //004AE24C处的内存读取
004AE247    A3 00104000     mov dword ptr ds:[004AE24C],eax        //004AE24C处的内存写入
004AE24C    83C0 01        add eax,1                       //004AE24C处的内存执行
  
  那么我们应该如何中断在上面的几行呢?
  1.当我们对004AE24C下内存访问断点的时候,可以中断在004AE242也可以中断在004AE247。
  2.当我们对004AE24C下内存写入断点的时候,只能中断在004AE247。
  3.当我们对004AE24C下内存访问断点的时候,能中断在004AE24C。

  到这里你可能不明白了,为什么内存访问断点能中断在004AE247这一句对004AE24C的写入,而且还能中断在004AE24C的执行呢?

  其实很简单,我们只要仔细体会一下“内存访问”这四个字的含义遍可以知道,当我们对004AE24C进行读取的时候需要“访问”他吧,当我对004AE24C进行写入的时候也需要“访问”他吧!!当然我们要执行内存地址004AE24C的代码的时候也是还是要“访问”他的!

  所以我们不难得出下面的结论:
  1.内存写入中断的地方,一定是也可以用内存访问中断。
  2.内存执行的地方,也可以用内存访问中断。
  如果这时你认为,那么内存写入岂不是没用了。呵呵~那我要告诉你当然不是,如果你想快速的准确的定位到004AE247这一行的时候,那么他就大有作用了!

  总结一下:内存断点不修改改原代码,不会像普通断点那样因为修改代码被程序校验而导致中断失败;对于区段的访问只是区域大了一点,其原理和上面分析的三行代码是一样的。

  ii.如何使用内存断点来寻找OEP呢?
  要回答这个问题首先要回答这一个问题:壳是如何解压代码的?

  正如我们知道的,壳如果要把原来加密或压缩的代码运行起来就必须要解压和解密原来的代码。而这一个过程我们难道不能将他看做是对代码段(code段)的写入吗?好了,解压完毕了。我们要从壳代码的区段JMP到原来的代码段的时候,难道不正是对代码段(code段)的执行吗?
  理清了上面的关系就好办了,那么如果载入OD后,我们直接对code段下内存访问断点的时候,一定会中断在壳对code段的写入的代码的上面,就像上面的004AE247的这一行。而如果当他把code段的代码全部解压解密完毕了以后,JMP到OEP的时候,我们是不是还可以停在OEP的代码上面呢?而且每按下F9都会中断,因为这时code段在执行中哦!

  相信很多人到这里已经明白了,为什么在教程中到达了某一个时候,某一行的时候。牛人们就叫我们对code段下内存访问断点了吧。
  而如果你还要继续问我为什么一定要到那个地方才可以下断呢?我难道不可以一开始就下断吗?
  正入我上面所说的,如果你在前面下断很可能壳对code段还没解压完毕呢,这时如果你不停的按F9,你将会看到OD的下方不断的在提示你“对401000写入中断” “对401002写入中断”“对401004写入中断”.......如果你不介意按F9到他把正个code段写完的话,我除了同情你的“F9”以外,没什么其他的意见!
  
  那么我们就没有别更快一点的办法了吗?
  有的!那就是我们呼之欲出的两次内存断点办法。
  怎么理解两次内存断点呢?

  让我来做一个假设吧,假设我是一个壳的作者。一个EXE文件的有code段,data段,rsrc段.....依次排列在你的内存空间中,那么我会怎么解码呢?呵呵~我比较笨一点,我会先将code段解码,然后再将data段解压,接着是rsrc段......那么聪明的你不难发现,只要你在data断或者rsrc段下内存访问断点,那么中断的时候code段就已经解压完毕了。这时我们再对code段下内存反问断点,不就可以到达OEP了吗?

  这里注意上面虽然下了两次内存访问断点,但是本质是不一样的,目的也是不一样的。

  1.对data段下内存访问断点而中断是因为内存写入中断,目的是断在对对data段的解压时,这时壳要对data段写数据,但是code段已经解压完毕。
  2.对code段下内存访问断点而中断是因为内存执行中断,目的当然就是寻找OEP了。

  总结一下:如果我们知道壳在什么地方对code段解压完毕我们就可以使用内存断点,找到OEP。如果不知道,那么我们就依靠2次内存断点去找,如果还不行就用多次内存断点。总之明白了原理在多次的内存断点其实都一样。从这个过程中我们了解的是壳在对区段解码的顺序!

  iii.实战

  说了这么多,我想大家都越越欲试了吧。
  好吧,来弄一个猛壳怎么样:
这个壳是一个hying的旧版,我们用他来实验一下我们内存断点法。
  
  OD载入以后来到这里

0040D000 u>  56              push esi          //这里
0040D001    52              push edx
0040D002    51              push ecx
0040D003    53              push ebx
0040D004    55              push ebp
0040D005    E8 15010000        call unpackme.0040D11F

  根据跟过一次的经验我们将先设置,除int3异常以外忽略其他异常,SHIFT+F9

003725B1   64:8925 0000000>     mov    fs:[0], esp
003725B8   CC              int3
003725B9    90              nop              //到这里
003725BA    8BCD            mov ecx,ebp

  然后再设置除“除零”异常外,忽略其他异常。SHIFT+F9

00372660    F7F3            div ebx           //到这里
00372662    90              nop
  
  下面是很多的单步异常,太麻烦我们不管他,现在开始用内存断点的方法(记得将所有异常忽略)。

对code段下内存访问断点,希望他已经解压完毕。方法是按ALT+M键打开内存窗口,在.code段按F2设断:

SHIFT+F9执行:


0040D19D    A4              movs byte ptr es:[edi],byte ptr ds:[esi]      //还没解完呢
0040D19E    B3 02            mov bl,2

对data段下内存“写入”断点,试试看他是不是要写data段。

00372712    F3:A4            rep movs byte ptr es:[edi],byte ptr ds:[esi]    //断到这里
00372714    5E              pop esi

下面再对code段下内存访问断点。F9

00372855    8907            mov dword ptr ds:[edi],eax                 ; SHELL32.DragFinish  //这里是对IAT加密

的地方了!!!
00372857    5A              pop edx
00372858    0FB642 FF         movzx eax,byte ptr ds:[edx-1]
0037285C    03D0            add edx,eax
0037285E    42              inc edx
0037285F    83C7 04          add edi,4
00372862    59              pop ecx
00372863  ^ E2 A9            loopd short 0037280E
00372865  ^ E9 63FFFFFF        jmp 003727CD
0037286A    8BB5 93060000      mov esi,dword ptr ss:[ebp+693]               //到这里下断F2

现在如果再对data下访问断点已经是没用了。这时应该格外的小心。

我们现在就想既然这一段是对code解码的,那么我们就绕过他吧!

到0037286A下断F2,然后清除内存断点!!!!

F9以后停在这里,继续对code下内存访问断点。

看看左下角还在解码,哎~真是麻烦!

003728E1   /EB 1D            jmp short 00372900
003728E3   |25 FFFFFF7F        and eax,7FFFFFFF
003728E8   |0385 83060000      add eax,dword ptr ss:[ebp+683]
003728EE   |2B85 8F060000      sub eax,dword ptr ss:[ebp+68F]
003728F4   |8BDE            mov ebx,esi
003728F6   |2BD8            sub ebx,eax
003728F8   |8958 FC          mov dword ptr ds:[eax-4],ebx              //停在这里
003728FB   |83C7 08          add edi,8
003728FE  ^|EB DB            jmp short 003728DB
00372900   \64:FF35 30000000    push dword ptr fs:[30]                  //清除内存断点以后到这里下断,F9

又是一段解码的代码,再次使用上面的办法手动跳出去。

现在继续对code段下内存访问断点!!F9以后到达这里。

004010CC    FFD7            call edi                             ; unpackme.004010CE  //OEP哦
004010CE    58              pop eax
004010CF    83EC 44          sub esp,44
004010D2    56              push esi
004010D3    90              nop
004010D4    E8 B518F7FF        call 0037298E
004010D9    8BF0            mov esi,eax

呵呵~虽然不是我们熟悉的OEP,但是地址是没错了,况且根据我们的步骤,我可以很肯定的说这是code段的第一次“执行”中断!

所以这就是OEP了。

总结一下:当我们在寻找OEP的时候,要多次对code下断“赌”一“赌”他解压完毕,如果不是就对别的段试试~如果程序跑飞了,那就没办法了,重来呗~其实说起来要赌的是:当data段,idata段,rsrc段摆在你的面前,你会好好“珍惜”那个段,不过还好上天还会给我们从来一次的机会(ctrl+F2 ^_^),那么我们会对那个不会跑飞的段说3个字----“先断你”如果非要在上面加一个次数,我希望是“一次内存断点就好了”

  vi.下面来讨论一下内存断点的局限性问题。
  是不是什么壳都可以用内存中断啊?
  不是每个都可以的,一些像UPX和ASPACK就不行。
  为什么?
  呵呵~follew me!
  情况1.
  我们来看看UPX的壳
  
首先,他的壳代码在UPX1段。

这里是他要跳到OEP的地方

0040ED4F   /77 11         ja short NOTEPAD_.0040ED62        
0040ED51   |01C3          add ebx,eax
0040ED53   |8B03          mov eax,dword ptr ds:[ebx]
0040ED55   |86C4          xchg ah,al
0040ED57   |C1C0 10        rol eax,10                  //在解码
0040ED5A   |86C4          xchg ah,al
0040ED5C   |01F0          add eax,esi
0040ED5E   |8903          mov dword ptr ds:[ebx],eax
0040ED60  ^|EB E2         jmp short NOTEPAD_.0040ED44
0040ED62   \24 0F         and al,0F
0040ED64    C1E0 10        shl eax,10
0040ED67    66:8B07        mov ax,word ptr ds:[edi]
0040ED6A    83C7 02        add edi,2
0040ED6D  ^ EB E2         jmp short NOTEPAD_.0040ED51      //回跳解码
0040ED6F    61           popad
0040ED70  - E9 5723FFFF     jmp NOTEPAD_.004010CC         //跳到OEP

我们看到他在对code段解压完毕的时候马上就JMP到OEP去了,那么我们根本就来不及使用内存断点的办法。

你可能说,我可以在

0040ED6F    61           popad //这一句下段然后使用啊

呵呵~~当然可以,不过你把花在下内存断点的时间,多按下几次F8不更好?!

也就是说当一个壳如果他在JMP 到OEP前的一行代码仍在都在对code段解压,那么我们就不能再使用这种办法了!

或者说我们没必要使用内存断点更贴切一点!

  情况2.
  对于一些在OEP处有stolen code的代码
  我们来看看一个OEP

0049E2F4 u>  55           push ebp                     //OEP
0049E2F5    8BEC          mov ebp,esp
0049E2F7    83C4 F4        add esp,-0C
0049E2FA    B8 BCE04900     mov eax,unpack.0049E0BC
0049E2FF    E8 048CF6FF     call unpack.00406F08             //这里调用子程序
0049E304    A1 B8FE4900     mov eax,dword ptr ds:[49FEB8]
0049E309    50           push eax
0049E30A    6A 00         push 0
0049E30C    68 1F000F00     push 0F001F
0049E311    E8 E68EF6FF     call <jmp.&kernel32.OpenFileMappingA>  //API
0049E316    A3 60194A00     mov dword ptr ds:[4A1960],eax
0049E31B    833D 60194A00 00  cmp dword ptr ds:[4A1960],0

这个软件在被PESPIN加壳了以后这些全被偷掉了!

也就是说,壳在模拟OEP代码的时候必然会执行

0049E2FF    E8 048CF6FF     call unpack.00406F08  //这一步

而这个地方是call向code段的。如果我们使用内存访问断点,那么就停在这个子程序的地方

00406F08    50           push eax                           //会停在这里
00406F09    6A 00         push 0
00406F0B    E8 F8FEFFFF     call <jmp.&kernel32.GetModuleHandleA>
00406F10    BA 04F14900     mov edx,unpack.0049F104
00406F15    52           push edx

这里既不是处理stolen code的地方,也不是FOEP的地方。这就会对我们的判断产生误导。

当然你可以alt+F9返回到壳处理stolen的地方,然后用内存断点,或者按几下F8到达FOEP处,但试问如果你拿到一个未知的壳的时候又怎么知道应该这么处理呢?

还有其他一些情况留给大家总结吧!

在下的砖已抛出,各位的玉不久矣。

--------------------------------------------------
3.总结
    好了说了很多,大家应该对内存断点的办法有了全面的了解,如果了解了内存断点的原理就不难明白他的使用方法,不难明白为什么有写壳不能使用内存断点的办法,其实任何的一种办法都需要经验的积累。相信如果大家在回答开篇的3个问题,已经不难了。
    大家可以结合原理再好好的体会一下《手动脱壳进阶第八篇Skvp1.32》这篇文章。
鸽子爱好者的家园,灰鸽子爱好者论坛欢迎您! 灰鸽子爱好者论坛,因为专注所以专业! 灰鸽子爱好者论坛,因为有你所以精彩!

TOP

辛苦了啊  啊

TOP

写得NN好 [s:21]
不过看不懂 郁闷了

TOP

发新话题