0%

《逆向工程核心原理》学习笔记14

逆向工程核心原理第二十五章

通过修改PE文件加载DLL

练习文件
  • https://github.com/reversecore/book下载对应的章节的可执行文件TextView.exe和myhack3.dll,当然也可以选择下载两者的源码,用vs重新编译,这里直接下载可执行文件进行练习
TextView.exe
  • 使用PEView工具查看TextView.exe可执行文件的IDT(Import Directory Table,导入目录表),如下图,可以看到TextView.exe直接导入的DLL文件为KERNEL32.dll、USER32.dll、GDI32.dll、SHELL32.dll

    image-20211223165038607

  • 修改思路:如上图所示,PE文件导入的DLL信息以结构体列表的形式存在IDT中。只要将myhack3.dll添加到列表尾部就可以了。当然在此之前需要确认一下IDT中有无足够空间。

  • 查看IDT是否有足够空间:首先使用PEView查看TextView.exe的IDT地址(PE文件头的IMAGE_OPTIONAL_HEADER结构体中导入表RVA即为IDT的RVA),可以看到IDT的地址(RVA)为84CC,大小为64h

    image-20211224100929941

  • 接下来在PEView中切换RVA为基址视图,查看RVA 84CC的IDT,如下图,可以看到,TextView.exe存在于.rdata节区,

    image-20211224104149591

  • 在PEView工具栏中将视图改为File Offset,可以看到IDT的文件偏移为76CC

    image-20220117234418434

  • 用HEdit找到76CC地址处,如下图所示,IDT的文件偏移为76CC~772F,整个大小为64字节,共有5个结构体,其中最后一个为NULL结构体。IDT尾部存在其他数据,没有足够空间来添加myhack3.dll的IID结构体

    image-20220118001933169

移动IDT
  • 这种情况,我们可以把整个IDT转移到其他更广阔的位置,然后再添加新的IID。确定移动的目标位置时,可以使用下面的三种方式:

    • 查找文件中的空白区域
    • 增加文件最后一个节区的大小
    • 在文件末尾添加新的节区
  • 首先尝试第一种方法,即查找文件中的空白区域。如下图所示,可以看到.rdata节区尾部恰好存在大片空白区域(一般来说,节区或文件末尾都存在空白区域,PE文件中这种空白区域称为Null-Padding区域)

    image-20220118002541976

  • 接下来只要把原IDT移动到该Null-Padding区域(RVA:8C60~8DFF)中合适位置即可。在此之前,先要确认一下该区域(RVA:8C60~8DFF)是否全是空白可用区域(Null-Padding)。如下图所示:从节区头信息中可知,.rdata在磁盘文件与内存中的大小是不同的,.rdata在磁盘文件中的大小为2E00,而文件执行后加载到内存时,程序实际使用的数据大小仅为2C56,剩余未被使用的区域大小为1AA(2E00-2C56)。在这段空白区域创建IDT不会有什么问题。

    image-20220118153343389

  • 接下来,我们将在RVA:8C60(RAW: 7E60)位置创建新的IDT

修改TextView.exe

  • 修改导入表的RVA值,IMAGE_OPTIONAL_HEADER的导入表结构体成员用来指出IDT的位置(RVA)与大小,如下图所示

    image-20220118160616195

  • 使用HEdit将原RVA:84CC改为新的RVA:8C60,并将Size(IDT结构体大小)的64改为78

    image-20220118161019282

删除绑定导入表
  • BOUND IMPORT TABLE(绑定导入表)是一种提高DLL加载速度的技术,如下图所示

    image-20220118161414168

  • 若想要正常导入myhack3.dll,需要向绑定导入表添加信息。但这个绑定导入表是个可选项,不是必须存在的,所以可删除(修改其值为0即可),而且绑定导入表完全不存在也没关系,但若存在,且其信息记录错误,则会在程序运行时引发错误。

创建新的IDT
  • 先使用HEdit复制原IDT(RVW: 76CC~772F),然后粘贴到IDT的新位置(RAW:7E80),如下图所示

    image-20220118162149707

  • 在新IDT尾部(RAW:7EB0)添加与myhack3.dll对应的IID

    image-20220118163832853

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
    DWORD Characteristics; //
    DWORD OriginalFirstThunk; // 00008D00 => RVA to INT
    };
    DWORD TimeDateStamp; // 0

    DWORD ForwarderChain; // 0
    DWORD Name; // 00008D10 => RVA,指向dll名字,该名字以0结尾
    DWORD FirstThunk; // 00008D20 => RVA,指向IMAGE_THUNK_DATA结构数组
    } IMAGE_IMPORT_DESCRIPTOR;
设置Name、INT、IAT
  • 前面添加的IID结构体成员拥有指向其他数据结构(INT、Name、IAT)的RVA值。因此,必须准确设置这些数据才能保证修改后的TextView.exe文件正常运行。由前面设置可知INT、Name、IAT的RVA/RAW的值,整理如下表所示

    RVA RAW
    INT 8D00 7F00
    Name 8D10 7F10
    IAT 8D20 7F20
  • 这些地址(RVA:8D00,8D10,8D20)就位于新的IDT下方(这里这些RVA可以选择在其他位置)。如下图,用HEdit在7F00、7F10、7F20、7F30输入相应的值

    image-20220118172035434

  • 用PEView打开查看该区域,如下图所示

    image-20220118172249101

  • 下面讲解显示的各值意义2022-01-18.5.23.56

  • 8CB0地址处存在着myhack3.dll的IID结构体,其中3个主要成员(RVA of INT、RVA of Name、RVA of IAT)的值分别是实际INT、Name、IAT的指针

  • INT(Import Name Table,导入名称表)是RVA数组,数组的各个元素都是一个RVA地址,该地址由导入函数的Ordinal(2个字节)+ 函数名结构体组成,数组的末尾为NULL。上图中有一个元素,其值为8D30,该地址处是要导入的函数的Ordinal(2个字节)与函数名称字符串("dummy")

  • Name是包含导入函数的DLL文件名称字符串,在8D10地址处可以看到"myhack3.dll"字符串

  • IAT也是RVA数组,各元素既可以拥有与INT相同的值,也可以拥有其他不同值(若INT数据准确,IAT也可以拥有其他不同值)。实际运行时,PE装载器会将虚拟内存中的IAT替换为实际函数的地址。

修改IAT节区的属性值
  • 加载PE文件到内存时,PE装载器会修改IAT,写入函数的实际地址,所以相关节区一定要拥有WRITE(可写)属性。只有这样,PE装载器才能正常进行写入操作。使用PE View查看.rdata节区头,如下图所示

    image-20220118174350752

  • 向原属性值(Characteristics)40000040添加IMAGE_SCN_MEM_WRITE(80000000)属性值。执行bit OR运算,最终属性值变为C0000040。如下图所示,使用HEdit修改该处的值

    image-20220118174728225

  • 至此完成了所有修改,打开TextView.exe可以看到TextView.exe成功加载myhack3.dll,myhack3.dll被加载后会自动下载指定网站的index.html,然后由TextView.exe显示

    image-20220118174933883