脱壳基础知识入门

现在加解密发展己形成2个分支了,一个就是传统的算法,另一个就是加密壳。越来越多的软件采用了密码学相关算法,现在要做出一个软件注册机己不象前几年那么容易,这就要求解密者必须要有一定的数学功底和密码学知识,而这些在短时间内是不容易掌握的。除了密码学的应用,越来越多的软件加壳了,因此要求解密者必须掌握一些脱壳技术,这就使得壳成了解密必须迈过的一个门槛。壳发展到今天,强度越来越高了,将许多人挡在门外,使得大家望壳兴叹。另外,论坛现在两极分化比较严重,高手讨论的脱壳技术新手看不懂,很多人想学脱壳,但看到壳这么难,只好放弃了,造成新手与高手间一个断档,为了鼓励更多新人加入脱壳的行列,很有必要将壳有关知识总结一下。www.pediy.com主页提供的教学确实有点过时了,己到非更新不可了。相对于密码学算法,脱壳并不难,只要肯花时间,短期内还是比较容易取得成绩的。

第一课 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格式的方法是自己先准备一个十六进制工具,如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 {

 

 DWORD       ContextFlags;

 

 DWORD       Dr0;

 DWORD       Dr1;

 DWORD       Dr2;

 DWORD       Dr3;

 DWORD       Dr6;

 DWORD       Dr7;

 

 FLOATING_SAVE_AREA FloatSave;

 

 DWORD       SegGs;

 DWORD       SegFs;

 DWORD       SegEs;

 DWORD       SegDs;

 

 DWORD       Edi;

 DWORD       Esi;

 DWORD       Ebx;

 DWORD       Edx;

 DWORD       Ecx;

 DWORD       Eax;

 

 DWORD       Ebp;

 DWORD       Eip;

 DWORD       SegCs;

 DWORD       EFlags;

 DWORD       Esp;

 DWORD       SegSs;

 

     BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

 

 } CONTEXT;

 

第三课 认识壳

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 格式 的默认压缩算法,压缩率很高。

 

 

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

加壳软件按照其加壳目的和作用,可分为两类:一是压缩(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/srt-news.shtml 
EXECryptor          http://anonymouse.org/cgi-bin/anon-www.cgi/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/downloads.php

 

第五课 文件类型分析

拿到一个壳,第一步就是用相关工具分析一下是什么壳,然后就可心中有数地跟踪分析。文件分析工具有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对给出的结果不是太肯定。

 

第六课 寻找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 这款工具。

 本节实例下载:notepad.upx.rar 

   脱壳前建议用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定律 作者:Lenus
操作方法:多数壳在运行到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

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

小知识: 硬件断点的原理 作者:Lenus  

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:   http://www.popbase.net/ & http://www.pediy.com/
E-mail:[email protected]
--------------------------------------------------
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.实战

  说了这么多,我想大家都越越欲试了吧。
  好吧,来弄一个猛壳怎么样:
注:本节实例有些难度,不适合新手练习,新手可以跳过这个实例的学习,等找到合适的实例会补充上来的 

 

本节实例下载:unpackme.rar 


  这个壳是一个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》这篇文章。

 

第七课 Dump内存映像

外壳程序解压还原后就会跳到OEP处执行,此时内存映像文件是己解压的程序。这时就可抓取内存映像文件了(该过程称为Dump)。当然不一定非要在程序原入口点抓取,只要能保证内存映像文件是己还原的就行了。
    有关Dump技术的原理大家可以参考:浅谈脱壳中的Dump技术  作者:Lenus 
    继续上一个实例notepad.upx.exe,到OEP后就可以Dump取内存映像文件:
004010CC    55             push    ebp
004010CD    8BEC           mov     ebp, esp
004010CF    83EC 44        sub     esp, 44

   运行LordPE.EXE,点击Options,默认选项如下:

 

默认选上“Full dump:paste header from disk”,PE头的信息直接从磁盘文件获得。设置好后,在LordPE的进程窗口选择notepad.upx.exe,点击右键,执行“dump full”菜单命令。如图:



   将内存抓取的文件另存为dumped.exe,此时程序还不能运行,接下来就是重建输入表。

第八课 重建输入表

在脱壳中输入表处理是很关键的一个环节,因此要求脱壳者对PE格式中的输入表概念非常清楚。在磁盘文件中,PE文件的输入表结构如下图所示:


                         图8.1 磁盘文件中的输入表

   PE文件运行时,Windows系统加载器首先搜索OriginalFirstThunk,如果存在,装载程序迭代搜索数组中的每个指针,找到每个IMAGE_IMPORT_BY_NAME结构所指向的输入函数的地址,然后用函数入口地址来替代由FirstThunk指向的 IMAGE_THUNK_DATA 数组里的元素值(即用真实的函数地址填充到IAT里)。因当PE文件装载内存后准备执行时,上图己转换成这种情况了:


                         图8.2 PE文件装载内存后的输入表

   此时输入表中其它部分就不重要了,程序依靠IAT提供的函数地址就可正常运行(图8.2 红圈部分)。如果程序加壳了,那壳自己模仿Windows装载器的工作来填充IAT中相关的数据,此时内存中就一张IAT表,输入表的其他部分是不存的(当然不是绝对的,也有不少壳,如Aspack等,内存中会出现完整的输入表结构),如图8.3所示。

                       
             图8.3 外壳加载程序后的内部IAT

   输入表重建就是根据图8.3这张IAT恢复整个输入表的结构(即图8.1这个结构),ImpREC这款工具就是这个功能。
   一些压缩壳,填充IAT过程中没做什么手脚,用ImpREC工具可以直接重建输入表。而一些加密壳为了防止输入表被还原,就在IAT加密上大作文章,此时壳填充IAT里的不是实际的API地址,而是填充壳中用来HOOK-API的外壳代码的地址。这样壳中的代码一旦完成了加载工作,在进入原程序的代码之后,仍然能够间接地获得程序的控制权。 因为程序总是需要与系统打交道,与系统交道的途径是API,而API的地址已经替换成了壳的HOOK-API的地址,那程序每一次与系统打交道,都会让壳的代码获得一次控制权,这样壳可以进行反跟踪继续保护软件,同时也可完成某些特殊的任务。所以重建输入表的关键是获得没加密的IAT ,一般的做法是跟踪加壳程序对IAT处理过程,修改相关指令,不让外壳加密IAT。

   UPX、ASPack等加壳保护的壳没加密IAT,而ASProtect、tElock等加密保护的壳都对IAT进行了加密处理。这篇先来简单的,即UPX壳。用OD打开上面的notepad.upx.exe实例,运行到OEP。(实际跟踪过程中,不一定要到OEP,只要外壳处理完IAT就可)然后如下操作:

1) 运行ImportREC,在下拉列表框中选择notepad.upx.exe进程,如图:



2) 上面己得知notepad.upx.exe的OEP地址是4010CC,则在左下角OEP处填入OEP的RVA值,这里填上10CC。点击“IAT AutoSearch”按钮,让其自动检测IAT偏移和大小,如出现下图表示ImportREC自己找到IAT的地址与大小了,即IAT地址:000062E0,大小248。



如果ImportREC没找到IAT偏移,则必须手工填入IAT偏移和大小(IAT偏移手动获得以后再讲述)。

3) 点击“Get Import”按钮,让其分析IAT结构得到基本信息,如下图所示:



4)如发现某个DLL显示"valid :NO" ,按"Show Invalids"按钮将分析所有的无效信息,在Imported Function Found栏中点击鼠标右键,选择"Trace Level1 (Disasm)",再按"Show Invalids"按钮。如果成功,可以看到所有的DLL都为"valid:YES"字样; 
5)再次刷新"Show Invalids"按钮查看结果,如仍有无效的地址,继续手动用右键的Level 2或3修复;
6)如还是出错,可以利用"Invalidate function(s)"、"Delete thunk(s)"、编辑Import表(双击函数)等功能手动修复。
7)开始修复已脱壳的程序。选择Add new section (缺省是选上的) 来为Dump出来的文件加一个Section(虽然文件比较大,但避免了许多不必要的麻烦) 。
8)单击"Fix Dump"按钮,并选择刚在前面己Dump出来的文件。如修复的文件名是"Dump.exe",它将创建一个"Dump_.exe",此外OEP也被修正。

    经过这些步骤,这个UPX壳己成功脱掉。此时再用PEID查一下脱壳后的程序dumped_.exe,会显示是“Microsoft Visual C++ 6.0 SPx Method 1”,如下图所示:



再用LordPE查看脱壳后的输入表:



从上图可以看出,输入表己正确修复,此时脱壳后的文件己能成功运行了。

 

 

第九课 手动确定IAT的地址与大小


    在第八课中讲到,点击ImportREC的“IAT AutoSearch”按钮,一般情况下ImportREC可以自动识别出IAT地址与大小。但如果不能自动识别,就必须手动确定IAT地址与大小,然后将IAT的RVA与Size填进ImportREC,点击“Get Import”按钮就可得到输入表。
    还是用上一节实例演示,用OD打开notepad.upx.exe,来到OEP处:


    随便找一个API函数调用语句,如:
004010D3    FF15 E4634000   call    [4063E4]          ; kernel32.GetCommandLineA
   其中地址4063E4就是IAT中的一部分,在数据窗口下命令:D 4063E4,显示如下:



  上图每一组数据都是指向一个API函数,如 8D 2C 81 7C 就是地址:7C812C8D,在OD里按Ctrl+G,输入7C812C8D跳到这个地址就会发现是kernel32.GetCommandLineA函数:



   IAT是一块连续排列的数据,因此在数据窗口向上翻屏,直到出现00数据,寻找IAT起始地址:


然后向下翻屏,寻找IAT结束地址:

为了直观些,你也可以这样让数据窗口直接显示这些API函数,以确定IAT是否正确,在数据窗口点击鼠标右键:

调整显示格式后的数据窗口:

这样就直观了,IAT中每组数据指向一个API函数,各DLL之间是以000000分开的。
因此IAT范围:0x4062E4~0x406524 ,大小为0x406524-0x4062E4=0x240 
如果IAT加密了,此时IAT中的地址不是指向系统DLL中的API函数了,可能指向外壳。这就十分有必要找到外壳处理IAT的代码了,前面己说过,外壳加载时,会模拟Windows加载器,向IAT里填充当前操作系统API函数的实际地址。所以,在IAT里设个内存写断点,就可中断到这段代码处。
重新加载notepad.upx.exe,在IAT某个地址下内存写断点,这里选择0x4062E4这个地址设内存写断点,先在数据窗口下命令:D 4062E4



然后选择一个地址,点击鼠标右键,下“内存写断点”。



此时只要有数据写入4062E4地址处,OD就会中断,按F9运行OD,会中断这里:

0040E96D    > /8A02          mov     al, [edx]
0040E96F    . |42            inc     edx
0040E970    . |8807          mov     [edi], al
0040E972    . |47            inc     edi
0040E973    . |49            dec     ecx
0040E974    .^\75 F7         jnz     short 0040E96D

这段还不是处理IAT,按F9继续执行程序,会中断这里:


0040E9E9    > /8A07          mov     al, [edi]
0040E9EB    . |47            inc     edi
0040E9EC    . |08C0          or      al, al
0040E9EE    .^|74 DC         je      short 0040E9CC
0040E9F0    . |89F9          mov     ecx, edi
0040E9F2    . |57            push    edi                        // 函数名字符串  
0040E9F3    . |48            dec     eax
0040E9F4    .  F2:AE         repne   scas byte ptr es:[edi]
0040E9F6    . |55            push    ebp                        // DLL模块句柄
0040E9F7    .  FF96 A4EC0000 call    [esi+ECA4]                 ;  kernel32.GetProcAddress
0040E9FD    . |09C0          or      eax, eax
0040E9FF    . |74 07         je      short 0040EA08
0040EA01    . |8903          mov     [ebx], eax                // EBX指向IAT,将取得的API地址填充进IAT
0040EA03    . |83C3 04       add     ebx, 4                    // 指向下一个地址
0040EA06    .^\EB E1         jmp     short 0040E9E9
0040EA08    >  FF96 A8EC0000 call    [esi+ECA8]

上面这段就是UPX外壳填充IAT的全过程,感兴趣的,动态跟踪一下就明白了。这里用GetProcAddress函数获得函数地址:

FARPROC GetProcAddress(
  HMODULE hModule,    // DLL模块句柄
  LPCSTR lpProcName   // 函数名
);

 

 

为了方便大家学习脱猛壳技术,根据我自己的经验,将一些比较不错的文章列出:
Obsidium壳: 



Obsidium外壳学习手记  
Obsidium1.2.5.0主程序脱壳记录点滴  (SDK修复部分)

Armadillo壳: 

试玩armadillo3.50a一点心得  (基础知识)
Armadillo COPYMEMEII之DUMP的一个LOADPE小插件  (利用插件Dump取COPYMEMEII保护的程序)
Armadillo 3.6主程序脱壳   (Dump取COPYMEMEII保护的程序另一方法)
Patch 修复 Armadillo 的IAT乱序 (处理IAT乱序)
Armadillo中code splicing的几种处理方法 (手工修复Code Splicing)
Armadillo客户版Code Splicing+Import (利用ArmInline工具修复Code Splicing)
浅谈Armadillo V.3.75 与 V.3.78的保护  (Nanomites原理概念,即CC保护)
Blaze Media Pro5.05脱壳+基本修复CC(int3)+破解  (修复CC)
Armadillo V4.40主程序脱壳  (目前处理CC很好的一个方法)

ASProtect 2.x SKE壳: 

nspack3.5主程序脱壳分析(Aspr SKE 2.X)   (输入表)
Asprotect SKE 2.2 的Advanced Import protection保护技术  (输入表)
莱鸟脱Aspr2.11 SKE+修复stolen   (Dump法对付stolen OEP)
ASProtect.SKE.2.11 stolen code解密  (ASProtect 2.11的stolen与SDK修复技术 )
ASProtect_SKE_2.3Beta_Build0319 stolen code分析 (ASProtect 2.22/2.23的stolen与SDK修复技术 )
Asprotect 2.XX SKE IAT Fixer (ASProtect 脚本,功能非常强大! )
……(ASProtect 2.x部分没完成)


你可能感兴趣的:(windows,加密,api,解密,exe,工具)