0
点赞
收藏
分享

微信扫一扫

C/C++ 手工实现IAT导入表注入劫持

DLL注入有多种方式,今天介绍的这一种注入方式是通过修改导入表,增加一项导入DLL以及导入函数,我们知道当程序在被运行起来之前,其导入表中的导入DLL与导入函数会被递归读取加载到目标空间中,我们向导入表增加导入函数同样可以实现动态加载,本次实验用到的工具依然是上次编写的PE结构解析器。



增加空间插入DLL

1.首先我们先来编写一个简易的DLL文件,这里可以使用C/C++或其他任何一种语言。

2.其次由于要操作导入表,我们需要再次复习一下导入结构的定义 ​​_IMAGE_IMPORT_DESCRIPTOR​​ 该IID结构,是一个数组,默认最后一项以全部为0作为结束符。

要添加IID数组的话,需要修改此处IID数组,增加一块区域,但这块内存一般与IID中的OriginalFirstThunk和FirstThun都有关联,这就导致我们必须整体将其移动到一个新的位置才可以,不能被覆盖。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

IMAGE_THUNK_DATA32

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

默认情况下一个​​_IMAGE_IMPORT_DESCRIPTOR​​​结构的大小为20字节也就是​​0x14​​我们再来看一下数据目录表中导入表的位置与大小。

C/C++ 手工实现IAT导入表注入劫持_重定位


Import RVA = 0x7604
Size = 0xc8


也就是说我们需要找一块或者是自己增加一块空间,该空间的大小应该在 ​​0xc8 + 0x14(一个结构大小) = 0xDC​​的位置。

再来看看文件与节表的对其参数,其中内存对齐 ​​0x00001000​​​ 而文件对其值为 ​​0x00000200​

C/C++ 手工实现IAT导入表注入劫持_数据_02

这里我们就直接扩展最后一个节,并按照文件对齐值为它增加0x200字节的空白区域,使用WinHEX操作,找到文件末尾,右键选择编辑粘贴0字节,然后输入512也就是十六进制的200.

C/C++ 手工实现IAT导入表注入劫持_重定位_03

由于是直接扩展的最后一个rsrc区段,所以定位到这块空白区域只需要:

C/C++ 手工实现IAT导入表注入劫持_重定位_04


文件偏移 0x8400 + 0x8000 = 0x10400
内存偏移 0xB000 + 0x8000 = 0x13000


3.接着找到原IID结构的位置,如上我们知道原始RVA为0x7604,计算 ​​0x7604 - 0x1000 + 0x400 = 0x6A04​​​ 大小为0xC8,也就是从​​0x6A04 - 0x6AB4​​拷贝下来,右键选择复制十六进制数值。

C/C++ 手工实现IAT导入表注入劫持_重定位_05

然后将其粘贴到偏移为0x1040也就是刚才开辟的空间上面,这里需要注意了:由于我们需要增加一个IID结构,所以先要空出黄色部分的空间,其次最后的红色部分全部填充0标志结束符。

C/C++ 手工实现IAT导入表注入劫持_数据_06

在原来的0x6a04出构建新的IID结构,


dllname 文件偏移 = 0x6a14 RVA = 0x6a14 - 0x400 + 0x1000 = 0x7614
import_by_name = 文件偏移 = 0x6a20 RVA = 0x7620


PE文件加载前,OriginalFirstThunk和FirstThunk都指向Import_by_name结构数组,现在可以填充他们了。


OriginalFirstThunk = 文件偏移:0x6a04 相对RVA:0x7604


C/C++ 手工实现IAT导入表注入劫持_数据_07


first Thunk 文件偏移:0x6a0c 相对RVA: 0x760c


C/C++ 手工实现IAT导入表注入劫持_重定位_08

开始填充结构数据,如下所示。

黄色代表:OriginalFirstThunk

淡红色代表:TimeDateStamp

绿色代表:FirstThunk

黄色代表:ForwarderChain

紫色代表:dllname

大红代表:导出msg函数名称

C/C++ 手工实现IAT导入表注入劫持_数组_09

最后转到我们后面导入表的位置,在后面填充这些位置。

7604 = OriginalFirstThunk

7614 = dllname

760c = FirstThunk RVA

C/C++ 手工实现IAT导入表注入劫持_数组_10

接着修正PE文件头,由于改变了PE头中输入表的位置,这里输入表也需要修正,改为13000大小则是dc

C/C++ 手工实现IAT导入表注入劫持_数组_11

C/C++ 手工实现IAT导入表注入劫持_重定位_12

接着修正text节,加上0x8000000写属性,将其属性改为 20000E0 即可。

C/C++ 手工实现IAT导入表注入劫持_数组_13

最后修正输入表的位置,即可实现DLL插入。

C/C++ 手工实现IAT导入表注入劫持_重定位_14

上面这种方式有时可能会出错,例如改完后出现,原因是读取不到节区,没有注册,有时可以有时就会报错,不同系统之间随机出现。

C/C++ 手工实现IAT导入表注入劫持_数组_15


添加新节并插入DLL

解决的办法是自己新建一个节,并设置好属性,先来仿写一个.hack节,我们在 .rsrc 后面直接添加一个新的。

C/C++ 手工实现IAT导入表注入劫持_数据_16


.hack 虚拟偏移:虚拟偏移(.rsrc) + 虚拟大小(.hack) => 0x0001B000 + 0x00001000 = 0001C000 + 2000 = 1e000


C/C++ 手工实现IAT导入表注入劫持_数组_17

上面公式中为什么需要加上2000 ? 这是因为需要凑够整数,字节对齐,不然也是不知别的,例如。

C/C++ 手工实现IAT导入表注入劫持_重定位_18

  1. 0x00011000 + 0x00004337 = 15337 直接取整 16000
  2. 0x0001A000 + 0x00000AB9 = 1AAB9 直接取 1B000
  3. 0x0001B000 + 0x000025A0 = 1D5A0 取整 1E000


.hack 实际偏移:实际偏移(.rsrc) + 实际大小(.rsrc) => 0x00007800 + 0x00002600 = 00009E00


C/C++ 手工实现IAT导入表注入劫持_数据_19

计算得到.hack虚拟偏移:0x0001C000 实际偏移:0x00009E00 找到节表位置,开始仿写。

C/C++ 手工实现IAT导入表注入劫持_重定位_20

在文件末尾,插入1000个0字节填充

C/C++ 手工实现IAT导入表注入劫持_数据_21

最后修正镜像大小,绿色节区数目加1 6改7,蓝色镜像大小加1000,蓝色改为 0001F000,改完保存。

C/C++ 手工实现IAT导入表注入劫持_重定位_22

C/C++ 手工实现IAT导入表注入劫持_数组_23

改完后,运行看看,没问题了,改的很成功,已经识别了。

C/C++ 手工实现IAT导入表注入劫持_数据_24

接着找到导入表,所在位置 0x00006DE0 长度 0x50,winhex跟过去。

C/C++ 手工实现IAT导入表注入劫持_数据_25

选中,拷贝下来。

C/C++ 手工实现IAT导入表注入劫持_重定位_26

放到开辟的空间中,文件偏移 9e00 处。

C/C++ 手工实现IAT导入表注入劫持_重定位_27

修正当前输入表的位置测试是否可以正确识别,新输入表的FOA是 9e00 将其转为RVA = 0x0001E000 大小先保持不变。

C/C++ 手工实现IAT导入表注入劫持_数组_28

C/C++ 手工实现IAT导入表注入劫持_数组_29

改完后,使用工具验证一下,嗯,确实改掉了,程序依旧能打开,这次搬家很成功。

C/C++ 手工实现IAT导入表注入劫持_数组_30

接着在9e90处构建新的IID结构数组,计算得出。

OriginalFirstThunk 文件偏移 = 9e90 相对RVA: 0x0001E090

C/C++ 手工实现IAT导入表注入劫持_重定位_31

TimeDateStamp 单独占用一个存储空间,默认填充为0

TimeDateStamp 文件偏移:9E94 相对RVA: 0x0001E094

C/C++ 手工实现IAT导入表注入劫持_重定位_32

由于默认情况下OriginalFirstThunk和FirstThunk都指向Import_BY_name结构数组,所以两者是一致的。

FirstThunk 文件偏移:9E98 相对RVA: 0x0001E098

C/C++ 手工实现IAT导入表注入劫持_数据_33

ForWarderChain 文件偏移:9E9C 相对RVA: 0x0001E09C

C/C++ 手工实现IAT导入表注入劫持_重定位_34

DllName 文件偏移:9EA0 相对RVA: 0x0001E0A0

C/C++ 手工实现IAT导入表注入劫持_重定位_35

导出函数名称:文件偏移:9EB4 相对RVA: 0x0001E0B4

C/C++ 手工实现IAT导入表注入劫持_数据_36

接着将这些数据填充到指定的位置。我们就在输入表的下方构建这个结构吧。

C/C++ 手工实现IAT导入表注入劫持_重定位_37

最后我们转到导入表上,将这个结构构造到导入表的最后。

originalFirstThunk = 1e090

dllname = 1e0a0

FirstThunk = 1e098

C/C++ 手工实现IAT导入表注入劫持_重定位_38

由于增加了一个结构,所以需要修正导入表的大小,跳转到160处,修正 50 + 20 = 70

C/C++ 手工实现IAT导入表注入劫持_数据_39

确保最后一个节可读可写可执行。

C/C++ 手工实现IAT导入表注入劫持_数组_40

确保绑定输入解除,也就是将1B0 - 1B8位置的内容全部清除掉。

C/C++ 手工实现IAT导入表注入劫持_数组_41

打开,成功的执行了加载MsgDll.dll 文件,因为根目录没有这个文件所以报错,但已经可以加载成功了

C/C++ 手工实现IAT导入表注入劫持_数据_42

使用工具来验证一下,导入表结构已经可以完美的读取到了,说明我们的计算没问题。

再次分析导入表

我知道你很模糊,这里有必要再深入分析一下,这次我把他们分开来写,这样看起来会更清晰。

首先老样子,定位到节表位置,执行命令 ​​PETools c://win32.exe --ShowDataDirectory​

C/C++ 手工实现IAT导入表注入劫持_数组_43

winhex跳转到 0x00006DE0 这个位置上,总长度是 0x00000050 我们来分析一下第一个导入表结构,从而让你对齐更加清晰。

C/C++ 手工实现IAT导入表注入劫持_数组_44

两个黄色部分,分别是 OriginalFirstThunk 和 FirstThunk 在加载到内存之前其是相同的。

User32.dll存放位置

RVA = 01a54a 转成 --> FOA = 0x0000714A

C/C++ 手工实现IAT导入表注入劫持_数组_45

714a 所对应的是 user32.dll 导入模块。

C/C++ 手工实现IAT导入表注入劫持_重定位_46

IMAGE_IMPORT_DESCRIPTOR 中的 Name 指向了 0x0000714A 里面就是存放着 user32.dll字符串

指向函数LoadIconA

RVA = 1a15c 转成 --> FOA = 0x00006D5C

C/C++ 手工实现IAT导入表注入劫持_数据_47

6d5c里面存放着 1a53e

C/C++ 手工实现IAT导入表注入劫持_数据_48

将其转化为FOA = 713e

C/C++ 手工实现IAT导入表注入劫持_重定位_49

她所对应的是 LoadIconA

C/C++ 手工实现IAT导入表注入劫持_重定位_50

我们来构建这样一个结构单元,如下图。

C/C++ 手工实现IAT导入表注入劫持_重定位_51

将单元填充好

C/C++ 手工实现IAT导入表注入劫持_数组_52

表关系如下,与微软结构定义相同。

C/C++ 手工实现IAT导入表注入劫持_重定位_53

关系如图。

C/C++ 手工实现IAT导入表注入劫持_重定位_54

添加成功了。

C/C++ 手工实现IAT导入表注入劫持_数据_55

反写也可识别,OriginalFirstThunk 和 FirstThunk 一致。

C/C++ 手工实现IAT导入表注入劫持_数据_56

C/C++ 手工实现IAT导入表注入劫持_数据_57

C/C++ 手工实现IAT导入表注入劫持_数组_58

x64dbg 也可以动态加载进来

C/C++ 手工实现IAT导入表注入劫持_重定位_59

调用也没问题。

C/C++ 手工实现IAT导入表注入劫持_数据_60

注意节区属性必须可读可写,改为E0000E0。

导出表解析

导出表默认在DLL中出现较多,EXE文件中出现导出表虽然可以但很奇怪,导出表在数据目录表第一个成员中,IMAGE_EXPORT_DIRECTORY IED结构,使用PETools工具查看如下。

C/C++ 手工实现IAT导入表注入劫持_重定位_61

导出表结构定义如下

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp; // 文件生成时间
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 模块真实名称
DWORD Base;
DWORD NumberOfFunctions; // 导出元素个数
DWORD NumberOfNames; // 导出元素个数
DWORD AddressOfFunctions; // 指向函数地址的数组
DWORD AddressOfNames; // 函数名字的指针地址
DWORD AddressOfNameOrdinals; // 指向输出序列号地址的数组
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

winhex 标号

C/C++ 手工实现IAT导入表注入劫持_重定位_62

第二项 5ba6031a 指向 TimeDateStamp

计算后得到一个时间戳,将其转为十进制 1537606426 转为时间日期则,没啥可说的。

C/C++ 手工实现IAT导入表注入劫持_数据_63

第五项是Name字段:2280 是RVA 将其转为FOA

C/C++ 手工实现IAT导入表注入劫持_数组_64

跳转到1080处发现时该DLL的原始名称,这个名称就算把名字改掉,名称不会变。

C/C++ 手工实现IAT导入表注入劫持_数据_65

第七八项,代表的时导出函数的个数。

C/C++ 手工实现IAT导入表注入劫持_数组_66

第九项,2258 将其转为FOA跳转过去看看。0x00001058 此处指向228C

C/C++ 手工实现IAT导入表注入劫持_数组_67

将228C转为FOA 0x0000108C 跳过去看看,第一个导出函数名字。

C/C++ 手工实现IAT导入表注入劫持_重定位_68

再将2268转为FOA看看 0x00001068 对应2288

C/C++ 手工实现IAT导入表注入劫持_数组_69

再次将2288转为FOA 0x00001088 跳过去看看,同样是第一个函数的字符串。

C/C++ 手工实现IAT导入表注入劫持_数组_70

分析

C/C++ 手工实现IAT导入表注入劫持_数组_71

首先RVA=2258 计算FOA = 0x00001058 其指向函数的RVA地址,程序装入内存后会被展开。

C/C++ 手工实现IAT导入表注入劫持_重定位_72

展开后格式如下,红色指向黑色位置,我们有四个函数所以是四个函数的地址。

C/C++ 手工实现IAT导入表注入劫持_数组_73

RVA = 2268 其FOA = 0x00001068 它指向函数名字的指针地址。

C/C++ 手工实现IAT导入表注入劫持_数据_74

2268 指向了 1068 而1068中存放了四个指针,分别指向四个导出函数的名称。

C/C++ 手工实现IAT导入表注入劫持_数据_75

拿2288为例子,转换后变成FOA = 1088 以此类推。

C/C++ 手工实现IAT导入表注入劫持_数组_76

C/C++ 手工实现IAT导入表注入劫持_重定位_77


重定位表的解析(拓展)

重定位表在PE中是.reloc,通常情况下EXE可执行文件在映射内存时会独占虚拟空间,所以EXE时不需要重定位信息的,只有DLL才会需要,因为DLL内存不固定,很可能一个DLL被注入到多个进程中,此时就需要对DLL中的数据进行重定位,以确保特定函数还能够被调用到。

重定位表的查找是通过数据目录表的 ​​IMAGE_DIRECTORY_ENTRY_BASERELOC​​​ 的条目来查找的,重定位数据采用按页分割的,每个块存放4KB(1000h)的重定位数据,每个重定位块大小为4字节对齐,他们以一个 ​​ IMAGE_BASE_RELOCATION​​ 结构。

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

首先使用PETools解析,来看一下这个结构。

C/C++ 手工实现IAT导入表注入劫持_数组_78

找到位置 FOA = 0x00001800 大小是 0x00000128 也就是重定位表的位置。

如果想要通过手工找到该位置,其位于PE偏移 ​​1a0​​位置,此处的第一个DWORD是重定位的RVA,第二个位置则是重定位大小。

C/C++ 手工实现IAT导入表注入劫持_数组_79

使用PETools计算的出其FOA = 0x00001800 同样可以定位到重定位表的位置上。

C/C++ 手工实现IAT导入表注入劫持_重定位_80

重定位表的分析,第一个黄色代表重定位数据开始的RVA地址,第二个代表重定位块长度,最后一个

TYpeOffset 是一个数组,数组中每项大小为2字节,高四位代表重定位类型,低12位代表重定位地址,他与VirtualAddress相加得到的是需要修正位置的数据指针。

C/C++ 手工实现IAT导入表注入劫持_数组_81

如下,其中后面的WORD类型,每一个都是一个重定位数据。

C/C++ 手工实现IAT导入表注入劫持_数据_82

以第一个300e为例,将其转为FOA = 0x0000140E,定位过去,程序没被装载,这里为空,装在后可能会重定位修复。

C/C++ 手工实现IAT导入表注入劫持_数组_83

PETools解析后,可对比,计算结果无误。

C/C++ 手工实现IAT导入表注入劫持_数据_84

版权声明:本博客文章与代码均为学习时整理的笔记,文章 [均为原创] 作品,转载请 [添加出处] ,您添加出处是我创作的动力!





举报

相关推荐

0 条评论