UPack PE文件头详细分析

  1. UPack是一款PE文件的运行时压缩器,其特点是用一种非常独特的方式对PE头进行变形,UPack会引起诸多现有的PE分析程序错误。
  2. 许多恶意代码使用UPack压缩字节的恶意代码并发布,由于这样的恶意代码非常多,现在大部分杀毒软件干脆将所有UPack压缩的文件全部识别为恶意文件并删除。
  3. 使用UPack压缩notepad.exe
  • 将upack.exe与notepad.exe放到同一个文件夹下,输入如下命令:
C:\Users\12586\Downloads\example\02\18\bin>Upack.exe notepad.exe
+-------------------------------------------------------------+
|Upack 0.39 >>>>>>>> Ultimate PE Packer <<<<<<<< Final Version|
|Copyright (c) 2005 by Dwing  All rights reserved  18 Dec 2005|
|!FREEware for non-commercial use with ABSOLUTELY NO WARRANTY!|
|** Homepage: http://dwing.51.net Contact me: dwing@163.com **|
|*** !WARNING: Remember backup before packing any PE file! ***|
+-------------------------------------------------------------+
Reading notepad.exe ...... 67584 bytes OK!
Recompiling and compressing:[01000000-01014000]
        Rebuilding import table [00C8]...... OK!
        Recompiling resource [008304]...... OK!
        Removing debug data [001C]...... OK!
        Transforming code ......[E0-01-0132] OK!
        Compressing data (Use 128 KB dict) ...... OK!
        Building new PE data ...... OK!
Linking new PE file ...... 45096 bytes OK!
Completed!
  • UPack会直接压缩源文件本身,且不会另外备份,压缩重要文件之前一定要先备份。使用PEView查看:
    在这里插入图片描述
  • 可以看到PEView无法正常读取PE文件头,因为没有IMAGE_OPTIONAL_HEADER、IMAGE_SECTION_HEADER等信息。
  1. 使用Stud_PE工具
  1. 比较PE文件头
  • 原notepad.exe的PE文件头,其中数据按照IMAGE_DOS_HEADER、DOS Stub、IMAGE_NT_HEADERS、IMAGE_SECTION_HEADER顺序排列。
    在这里插入图片描述
  • notepad_upack.exe运行时压缩的PE文件头,MZ与PE签名贴得太近了,并且没有DOS存根,出现了大量字符串,中间好像还夹杂着代码。
    在这里插入图片描述

分析UPack的PE文件头

  1. 重叠文件头
  • 重叠文件头也是其他压缩器经常使用的技法,借助该方法可以把MZ文件头(IMAGE_DOS_HEADER)与PE文件头(IMAGE_NT_HEADER)巧妙重叠在一起,并可有效节约文件头空间,当然这回额外增加文件头的复杂性,给分析带来很大困难。
  1. 使用Stud_PE看一下MZ文件头部分,按Headers选项卡的Basic Headers tree view in hexeditor,如图:
    在这里插入图片描述
  • MZ文件头(IMAGE_DOS_HEADER)中有以下2个重要成员:
(offset 0) e_magic: Magic number = 4D5A('MZ')
(offset 3C) e_lfanew: File address of new exe header
  • 根据PE文件格式规范,IMAGE_NT_HEADERS的起始位置是可变的,换言之,IMAGE_NT_HEADER的起始位置由e_lfanew的值决定,一般在一个正常程序中,e_lfanew拥有如下所示的值:
e_lfanew = MZ文件头大小(40)+ DOS存根大小(可变:VC++为A0)= E0
  • UPack的e_lfanew的值为10,并不违反PE规范,这样就可以把MZ文件头与PE文件头重叠在一起。
  1. IMAGE_FILE_HEADER.SizeOfOptionalHeader
  • 修改IMAGE_FILE_HEADER.SizeOfOptionalHeader的值,可以像文件中插入解码代码,SizeOfOptionalHeader表示PE文件头中紧接着在IMAGE_FILE_HEADER下的IMAGE_OPTIONAL_HEADER结构体的长度,UPack将该值更改为0148.
    在这里插入图片描述
  • SizeOfOptionalHeader的另一层含义是确定节区头(IMAGE_SECTION_HEADER)的起始偏移,仅从PE文件头来看,紧接着IMAGE_OPTIONAL_HEADER的好像是IMAGE_SECTION_HEADER,但实际上。从IMAGE_OPTIONAL_HEADER的起始偏移加上SizeOfOptionalHeader值后的位置开始才是IMAGE_SECTION_HEADER。
  • UPack把SizeOfOptionalHeader的值设置为148,比正常值(E0或F0)要更大一些,所以IMAGE_SECTION_HEADER是从偏移170开始的(IMAGE_OPTIONAL_HEADER)的起始地址偏移=28+SizeOfOPtionalHeader(148)=170)。
  • UPack的基本特征就是把PE文件头变形,像扭曲的麻花一样,向文件头适当插入解码需要的代码,增大SizeOfOptionalHeader的值后,就在IMAGE_OPTIONAL_HEADER与IMAGE_SECTION_HEADER之间添加了额外空间,UPack就向这个区域添加解码代码.
  • IMAGE_OPTIONAL_HEADER结束的位置为D7,IMAGE_SECTION_HEADER的起始位置为170,查看中间的区域:
    在这里插入图片描述
  • 使用调试器查看反汇编代码:
    在这里插入图片描述
  • 上图并不是PE文件头中的信息,而是UPack中使用的代码,若PE相关使用工具将其识别为PE文件头信息,就会引发错误,导致程序无法正常运行。
  1. IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes
  • 从IMAGE_OPTIONAL_HEADER结构体中可以看到,其NumberOfRvaAndSizes的值也发生了改变,这样做的目的也是为了向文件头插入自身代码。
  • NumberOfRvaAndSizes值用来指出紧接着在后面的IMAGE_DATA_DIRECTORY结构体数组的元素个数,正常文件中IMAGE_DATA_DIRECTORY数组元素的个数为10,但在Upack中将其更改为了A个。
    在这里插入图片描述
  • IMAGE_DATA_DIRECTORY结构体数组元素的个数已经被确定为10,但PE规范将NumberOfRvaAndSize值作为数组元素的个数,所以UPack中IMAGE_DATA_DIRECTORY结构体数组的后6个元素将被忽略。
    在这里插入图片描述
  • Upack将IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSize的值更改为A,从LOAD_CONFIG项(文件偏移D8以后)开始不再使用,UPack就在这块被忽视的IMAGE_DATA_DIRECTORY区域中复写字节的代码。
  • NumberOfRvaAndSize的值改变后,在OllyDbg中打开该文件就会弹出错误消息框,OllyDbg检查PE文件是会先检查NumberOfRvaAndSizes的值是否为10.
    在这里插入图片描述
  1. IMAGE_SECTION_HEADER
  • IMAGE_SECTION_HEADER结构体中,UPack会把自身数据记录到程序运行不需要的项目,这与UPack向PE文件头中不使用的区域复写自身代码与数据的方法是一样的。
  • 节区数是3个,IMAGE_SECTION_HEADER结构体数组的起始位置为170,查看该区域:
    在这里插入图片描述
  • IMAGE_SECTION_HEADER结构体:
    在这里插入图片描述
  • 上图框选的结构体成员对程序运行没有任何意义,比如文件偏移1B0地址处的offset to relocations值为0100739D,它为原notepad.exe的EP值,此外,节区头中还隐藏着一些秘密。
  1. 重叠节区
  • UPack的主要特征之一艰苦是可以随意重叠PE节区与文件头。
  • 通过Stud_PE提供的简略试图查看UPack的IMAGE_SECTION_HEADER,选择Section选项卡:
    在这里插入图片描述
  • 存在问题的地方:
    1. 第一个与第三个节区的文件起始偏移值都为10,偏移10是文件头区域,UPack中该位置起即为节区部分。
    2. 第一个节区与第三个节区的文件起始偏移与在文件中的大小是完全一直的,但是,节区内存的起始地址RVA项与内存大小值是彼此不同的,根据PE规范,这样做并不会由什么问题。
  • 综合以上两点,UPack会对PE文件头、第一个节区、第三个节区进行重叠。
    在这里插入图片描述
  • 根据节区头(IMGAE_SECTION_HEADER)中定义的值,PE装载器会将文件偏移0~1FF的区域分别映射到3个不同的内存位置(文件头、第一个节区、第三个节区),也就说,用相同的文件映像可以分别创建处于不同位置的、大小不同的内映像。
  • 文件的头(第一/第三个节区)区域的大小为200,其实这是非常小的,相反,第二个节区尺寸(AE28)非常大,占据了文件的大部分区域,原文件(notepad.exe)即压缩于此。
  • 另外一个需要注意的部分是内存中的第一个节区区域,它的内存尺寸为14000,与原文件(notepad.exe)的Size of Image具有相同的值,也就是说,压缩在第二个节区的文件映像会被原样解压缩到第一个节区(notepad的内存映像),另外,原notepad.exe拥有3个节区,它们被解压到一个节区。
  • 解压缩后第一个节区的内容:
    在这里插入图片描述
  • 压缩的notepad在内存的第二个节区,解压缩的同时被记录到第一个节区,重要的是,notepad.exe(源文件)的内存映像会被整体解压,所以程序能够正常运行。
  1. RVA to Raw
  • 变换公式:
RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData
  • 计算一下,EP的文件偏移(RAW),UPack的EP是RVA 1018,将其带入公式 RAW = 1018 -1000 + 10 = 28.
    在这里插入图片描述
  • 查看文件偏移为28的区域:
    在这里插入图片描述
  • RAW 28不是代码区域,而是“LoadLibrary A”字符串区域,现在Upack的这种把戏欺骗了我们,秘密就在于第一个节区的PointerToRawData值为10.
  • 指向节区开始的文件偏移的PoninterToRawData值应用是FileAlignment的整数倍,UPack的FileAlignment为200,故PointerToRawData值应为0、200、400、600等值,PE装载器发现第一个节区的PointerToRawData(10)不是FileAlignment(200)的整数倍时,它会强制将其识别为整数倍,使Upack文件能够正常运行。
  • 正常的RVA -> RAW变换如下:
RAW = 1018 - 1000 + 0 = 18
  • 使用调试器查看相应区域代码:
    在这里插入图片描述
  1. 导入表
    在这里插入图片描述
  • 上图中选中的就是指向导入表IMAGE_DATA_DIRECTORY结构体,前面4个字节为导入表的地址(RVA),后面4个字节为导入表的大小,从图中可以看到导入表的RVA为271EE。
  • 进行RVA -> RAW的变换,首先确定RVA值属于哪个节区,内存地址271EE在内存中是第三个节区:
RAW = RVA(271EE) - VirtualOffset(27000) + RawOffset(0) = 1EE
  • 查看文件偏移1EE中的数据:
    在这里插入图片描述
  • 查看一下IMAGE_IMPORT_DESCRIPTOR结构体的定义:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // 包含指向IMAGE_DATA(输入名称表)RVA 的结构数组
    };
    DWORD   TimeDateStamp;                  //当可执行文件不与被导入的DLL进行绑定时,此字段为0
    DWORD   ForwarderChain;                 //第一个被转向的API索引
    DWORD   Name;                           //指向被导入的DLL 名称
    DWORD   FirstThunk;                     //指向输入地址表(IAT)RVA,IAT是一个IMAGE_THUNK_DATA结构的数组
} IMAGE_IMPORT_DESCRIPTOR;
  • 根据PE规范,导入表是由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成的数组,最后以一个内容为NULL的结构体结束。
  • 根据PE规范,导入表是由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成的数组,最后一个内容NULL的结构体结束。
  • 所选区域就是IMAGE_IMPORT_DESCRIPTOR结构体数组,偏移1EE~201为第一个结构体,其后既不是第二个结构体,也不是NULL结构体,看起来是违反PE规范的。
  • 但是偏移200为第三个节区的结束,运行时偏移在200以下的部分不会映射到第三个节区内存。
    在这里插入图片描述
  • 第三个节区加载到内存时,文件偏移0~1FF 的区域映射到内存的 27000271FF区域,而(第三个节区其余的内存区域)2720028000区域全部填充为NULL,使用调试器查看相同区域:
    在这里插入图片描述
  1. 导入地址表
  • 将IMAGE_IMPORT_DESCRIPTOR结构体与值进行映射:
    在这里插入图片描述
  • Name的RVA值为2,它属于Header区域,因为第一个节区时RVA 1000开始,Header区域中RVA与RAW值是一样,故使用查看文件中偏移(RAW)为2的区域:
    在这里插入图片描述
  • 在偏移为2的区域中可以看到字符串Kernel32.DLL,该位置原本时DOS头部分(IMAGE_DOS_HEADER),属于不使用的区域,UPack将Import DLL名称写入该处,空白区域一点都没浪费,得到DLL名抽,再看一下从中导入了哪些API函数。
  • 跟踪OriginalFirstThunk(INT)能够发现API名称字符串,但是像UPack这样,OrginalFirstThunk(INT)为0时,跟踪FirstThunk(IAT)也无妨,IAT的值为11E8,属于第一个节区,故RVA -> RAW换算如下:
RAW = RVA(11E8) - VirtualOffset(1000)+ RawOffset(0)= 1E8
  • 查看文件偏移为1E8的区域:
    在这里插入图片描述
  • 选中的部分就是IAT域,同时也作为INT来使用,也就说,该处时Name Pointer(RVA)数组,其结束是NULL,此外还可以看到导入了2个API,分别是RVA 28与BE。
  • RVA位置上存在着导入函数的[ordinal + 名称字符串],由于都是header区域,所以RVA与RAW值是一样:
    在这里插入图片描述
    在这里插入图片描述
  • 导入的2个API函数,分别为LoadLibraryA与GetProcAddress,它们在形成原文件的IAT时非常方便,所以普通压缩器也常常导入使用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值