PE结构体
一:DOS头
1 | IMAGE_DOS_HEADER //DOS头 |
二:DOS存根
就在DOS头下方
大小不固定
作用是当运行在DOS下 会显示无法在DOS下运行并退出
DOS存根是可选项
三:NT头
1 | IMAGE_NT_HEADERS //NT头 |
四:NT头:文件头
1 | IMAGE_FILE_HEADER //NT头:文件头 |
五:NT头:可选头
1 | IMAGE_OPTIONAL_HEADER32 |
六:节区头
1 | IMAGE_SERCTION_HEADER //节区头 |
后记:今天又重新看了遍第十三章,现在是2021.12.8 23:04,第十三章的重点一部分是我上面写的这样,这样写一遍有助我好好理解整个格式,还要两个重点是EAT和IAT的过程,该自己走一遍,但脑子里依然回响着作者的一句话,”先学这么多就好”,虽然挺不喜欢东西放着不去学,但经常控制不住自己,就像平常我不会特意去看AES SM4加密原理等,碰到对应的题目了,我才会老实去看,也许明天就去试试了(现在在书上仿佛看的已经比较明白了)。
第十三章 IAT
第十三章 EAT
奇怪的PE文件探
IAT
IAT的提供机制与隐式链接有关,即程序开始时既一同加载DLL,程序终止时再释放占用内存
IMAGE_IMPORT_DESCRIPTOR
执行一个普通程序往往需要导入多个库,导入多少库就有多少个IMAGE_IMPORT_DESCRIPTOR
1 | typedef struct _IMAGE_IMPORT_DESCRIPTOR |
读取IID的Name的成员,获取库名的字符串 (“kernel32.dll”)
装载相应库 -> LoadLibrary(“kernel32.dll”)
读取IID的OriginalFirstThunk成员,获取INT地址
逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)
使用IMAGE_IMPORT_BY_NAME的Hint (ordinal) 或Name项,获取相应函数的起始地址
用语句表达就是 GetProcAddress(“GetCurrentThreadId”)
读取IID的FirstThunk (IAT) 成员,获取IAT地址
将上面获得的函数地址输入相应的IAT数组值
重复4~7步骤,直到INT结束(遇到NULL)
现在来跟书上看看IAT的步骤看看装载IAT过程
0x00 查找IID数组位置
查找IID,就在可选头的第二个就是IAT,第一个是EAT
第一个4字节是虚拟地址,第二个4字节是Size成员
因为是在文件里,所以RVA公式算出是 0x6A04
(都是在.text节区 内存基地址是0x1000 文件基地址是0x400)
0x01 分析IID数组各成员
跳转到6A04可查看IID数组
直接拿书上的表了
1. 库名称(Name)
从这可以查看库名称
2. OriginalFirstThunk – INT
从库中导入的API函数名称字符串地址
3. IMAGE_IMPORT_BY_NAME
从7A7A转成6E7A跳过去查看一下
一开始的000F是库函数的固有编号 (为ordinal)
后面是字符串以00结尾
4. FirstThunk - IAT
RVA: 12C4
RAW: 6C4
文件偏移6C4~6EB为IAT数组,对应comdlg32.dll库
第一个元素被硬编码成76324906,实际无意义,notpad.exe文件加载到内存时,准确的地址会取代该值(不同的系统值会不一样)
我自己电脑运行notpad.exe的IAT
EAT
通过EAT才能准确求得从相应库中导出函数的起始地址,且PE文件仅有一个结构体来说明库函数的导出信息
IMAGE_EXPORT_DIRECTORY
1 | typedef struct _IMAGE_EXPORT_DIRECTORY |
来个书上的解析图
从库中获取函数地址的API为 GetProcAddress() 函数,该API引用EAT来获取指定API的地址,下面写下获取函数地址的流程
利用 AddressOfNames 成员转到 “函数名称数组”
“函数名称数组” 中存储着字符串地址。通过比较(strcmp)字符串,查找指定的函数名称(此时数组的索引值称为name_index)
利用 AddressOfNameOrdinal 成员,转到 ordinal 数组
在 ordinal 数组中通过 name_index 查找相应 ordinal 值
利用 AddressOfFunctions 成员转到 “函数地址数组”(EAT)
在 “函数地址数组” 中将刚刚求得的 ordinal 用作数组索引,获得指定函数的起始地址
(也就是上面1到5步都是找获得确定索引值,然后从EAT起始地址通过索引值找到正确地址)
PS: 对于没有函数名称的导出函数,可以通过 Ordinal 查找它们的地址。从Ordinal值中减去IMAGE_EXPORT_DIRECTORY.Base成员后得到一个值,使用该值作为“函数地址数组”的索引。
0x00 查找IED数组位置
查找IED,与IID同理
0x01 分析IED数组各成员
转到到IA2C即可查看IED数组成员
书上的表
1. 函数名称数组
书上的和我有点不一样,我的是RVA = 3538h,所以RAW = 2938h
现在查找 ”AddAtomW“ 函数,只要查找到第三个
2. 查找指定函数名称
RVA = 4BB3h -> RAW = 3FB3h
成功查找到,且以00结尾(第三个元素,所以数组索引为2)
3. Ordinal数组
RVA = 441Ch -> RAW = 381Ch
都是两字节组成的数组(ordinal数组中各元素大小为2个字节)
4. ordinal
从步骤2获得的Index值(2)应用到步骤3中的Ordinal数组即可求得Ordinal(2)
AddressOfNameOrdinals[index] = ordinal (index = 2, ordinal = 2)
PS: 还没碰到无函数名称索引,如果碰到到时候ordinal的作用会具象一点
5. 函数地址数组 - EAT
RVA = 2654h -> RAW = 1A54h
于是拿索引值为2,可得到值326D9h
6. AddAtomW函数地址
我的kernel32.dll的imagebase是7C90 0000h
因此AddAtomW的实际地址为(7C80 0000 + 326D9h = 7C83 26D9h)
7. 验证成果!!
olldbg验证一下
没问题!
2022 7.19
没想到过了这么久又回来了, 发现文中几处不对的地方, 这回是要将给别人听了, 自己做PPT理了一遍, 更清楚了!
About this Post
This post is written by P.Z, licensed under CC BY-NC 4.0.