能力值:
( LV8,RANK:130 )
2 楼
问5.7: dll 和 exe 文件都是PE 文件, 在PE 文件中是怎样区分的呢?
答5.7:有一些小差别。
例如, exe 通常被加载到0x400000, 而dll 默认加载是0x10000000
exe 通常不含reloc 段,而dll 包含。
exe 通常不含export 段, 而dll 包含。
exe 属性010f, 最后1bit 说它没有重定位信息
dll 属性210e. 最高位的2 说它是dll, 这个好像是最关键差别了吧。
问5.8:exe 的 entrypoint 是程序执行的起始点,dll 的 entrypoint 是什么呢?
答5.8:这个地方是dll 的入口函数地址,它是不可以省略的。
dll 在加载,卸载以及线程加载,卸载时都会
到这里执行程序。 它的通用结构是这样的。
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved)
{
switch(fswReason)
case DLL_PROCESS_ATTACH
......
case DLL_THREAD_ATTACH
.....
case DLL_THREAD_DETACH
.....
case DLL_PROCESS_DETACH
....
return (TRUE or False) // true , 成功,false 失败, loader 会把它从内存卸掉。
}
count.dll 中没有按这种结构,它只是简单的返回一个TRUE,因为它不需要申请内存和释放内存等初始化操作。
count.dll entrypoint 是10001000, PE 标识后第10 DWORD 地址,记不住用工具查。
问5.9:前面说过dll 有入口函数, 导出函数和非导出函数。又回到上次未讲的问题
导出表是怎样把函数导出的。
答5.9:我们先猜一猜导出函数的关键要素吧。
1. 导出库名称
2. 函数导出序号。(提供序号导出)
3. 导出函数名 (提供函数名导出)
4. 序号或函数名对应的地址。
它向系统报告这些信息已经足够了。
问5.10: 结合例子和结构定义具体说一下吧。
答5.10: 看count.dll 目录项第一项
00000130 60 20 00 00 5C 00 00 00 ` ..\...
位置 RVA 2060 == offset 660
大小 0x5c
00000660 00 00 00 00 F6 34 EB 3C 00 00 00 00 9C 20 00 00 ....??....?..
00000670 01 00 00 00 02 00 00 00 02 00 00 00 88 20 00 00 ............?..
00000680 90 20 00 00 98 20 00 00 46 10 00 00 23 10 00 00 ?..?..F...#...
00000690 A8 20 00 00 B2 20 00 00 00 00 01 00 43 6F 75 6E ?..?......Coun
000006A0 74 65 72 2E 64 6C 6C 00 5F 44 65 63 43 6F 75 6E ter.dll._DecCoun
000006B0 74 00 5F 49 6E 63 43 6F 75 6E 74 00 t._IncCount.
那么这是一个什么样的数据结构呢?我们先猜猜看,这里也叫逆向学习方法吧。
首先函数名称:count.dll(offset 69c), 函数名称_DecCount(offset 6a8), _IncCount(6b2) 都已经看到了。
转换为RVA. offset 69c =RVA 209c
offset 6a8 =RVA 20a8
offset 6b2 =RVA 20b2
很高兴在数据中找到了9c 20, a8 20, b2 20. 关注一下这些地址。
从660 在滤一下,感觉后面的00000002 可能是个数吧。
后边的2088, 2090,2098
RVA 2088 == offset 688 [688] == 46 10 00 00 23 10 00 00 // 像函数地址,赶紧确认一下(在上一篇),正是。
RVA 2090 == offset 690 [690] == A8 20 00 00 B2 20 00 00 // 函数名称地址
RVA 2098 == offset 690 [690] == 00 00 01 00 // 像hint
这样划分后,看不懂的就不多了,学习数据结构是时候了。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
我们分析的是对的,那个base 是什么东西 ?是导出函数的基序号。所以导出函数序号不是0,1,而是1,2 了
很明显,1指的是1046地址,2指的是1023地址
问5.11 再总结一下导出表吧。
答5.11 导出表有两种导出方法,一种是按名称导出,一种是按序号导出。其中序号导出的个数总是大于等于名称导出的个数。
导出的函数地址按4字节一字排开。每一个函数索引号就是+Base 值就是导出序号。
序号不直观,所以有些函数用名称导出,名称导出最终还是要找到函数序号。所以把名称所在的名称数组的位置为索引
去从名称序号数组中拿到序号(此为索引号),由索引号取到函数地址。
问5.12 假如loader 要插桩本函数 _IncCount 地址,它是怎样操作的。
答5.12
1. 它首先要加载我们的dll. 用loadlibrary
2. 找到我们的导出表。
3. 再找到导出表中AddressOfNames。
4. 遍历该表找到_IncCount 函数,记下它的索引
5. 从AddressOfNameOrdinals数组中,取到该索引对应的函数地址序号
6. 从导出表中找到AddressOfFunctions, 用得到的序号去取到它的地址。
7. 将该地址去填充到IAT 的对应位置上。
这样插桩就完成了。哇!这个小插桩要经过这么多步骤哇,有没有办法简化一下啦... 等着你去实现呢!
问5.13 count.dll 中还有一个reloc 节,讲讲它是怎样构成的。
答5.13 reloc 也是为text 段服务的,前面说过,若dll 加载到它默认位置,可以不用reloc 段。
当不能加载到默认地址时, 某些于地址有关的指令需要重新定位。就是说要修改指令中地址
使其指向正确的地址。
看目录项中reloc 表,第6个表(索引号为5):
00000160 00 40 00 00 18 00 00 00 .@......
RVA 4000 = offset 800, size=0x18
哦,纵使不用reloc 目录项,直接目视也看见它了。这个程序很小,是这样的。
00000800 00 10 00 00 18 00 00 00 28 30 2E 30 3E 30 4B 30 ........(0.0>0K0
00000810 51 30 61 30 6C 30 00 00 00 00 00 00 00 00 00 00 Q0a0l0..........
我们也像export 表一样,先猜猜reloc 结构应该有那些重要元素。
1. 内存地址, 4byte, 我们要知道对哪的指令进行重定位。即where 问题
2. 替换方法。 是替换一字节,2字节还是4字节, 是how 的问题,估计有几个bit 就够了。
3. 用什么替换。 是一个what 的问题。 这个问题就不用考虑了,这个what,就是加载地址与默认地址偏移。
这样看起来一个reloc 项至少也要 5 bytes 了。如果有很多项,那这很多项就构成一个数组。
我这里介绍的方法是一种逆向的学习方法,或者是原始的思考方法。因为我想最初设计这个PE 结构的人也会
这么想。
现在使用的PE 结构在这个想法的基础上进行了优化,使得reloc表占用较少的字节,
具体说是它让一个reloc 项占用2byte,下面看它的方法:
1.
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
它的意思是说,它要重定位VirtualAddress=1000这块区域, 该区域所占的重定位信息大小为SizeOfBlock=0x18
后面紧跟的每2 个bytes 构成一个重定位项, 其中低12 bit为重定位地址, 高4bits 为重定位类型。
我这里把重定位类型copy 过来,其中有的在x86 上是用不到的。
//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MIPS_JMPADDR 5
#define IMAGE_REL_BASED_SECTION 6
#define IMAGE_REL_BASED_REL32 7
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define IMAGE_REL_BASED_IA64_IMM64 9
#define IMAGE_REL_BASED_DIR64 10
#define IMAGE_REL_BASED_HIGH3ADJ 11
地址只有12为意味着你只能管理4K 范围,是的,如果超过了4K范围,我们重新定义一个IMAGE_BASE_RELOCATION
变量就可以了,它又能管理下一个4K的范围。后续没16bit 为一个reloc 项, 最尾部以0000 结尾。当然,由于
重定位块大小有定义,纵使不用0000标识结尾也没有问题,把这里的冗余姑且叫双保险吧,就是浪费了2bytes
count.dll 中,我们先算算有几个重定位项。哦,不用算,有7个,一查就查出来了。
但当程序大的时候还是要计算的。(0x18-8)/2-1=7 个。
计算公式:(SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2-1
问:5.14 那这7个重定位项到底是怎样重定位的呢?还是给出具体结果更彻底。
答:5.14 好,做事做到底,现在我们就开始吧。
第一项:28 30 其内容为 0x3028 重定位类型为3, 地址为0x28
其它重定位类型都是3, 地址不同而已。
#define IMAGE_REL_BASED_HIGHLOW 3
那么这个BASE_HIGHLOW 是什么意思呢?我们先看看0x28处的指令。
10001026 FF0500300010 inc [L10003000]
假如我们的dll 不是被加载到0x10000000,而是加载到0x30000000, 即向上提高了0x20000000
我们只要把0x28地址处也加上0x20000000,就可以了。这样这条指令就变成了
10001026 FF0500300030 inc [L30003000]
看明白了吧! 窗户纸就是这样被捅破的 好了,本帖就到这里结束吧!
能力值:
( LV8,RANK:130 )
3 楼
传上附件供参考
能力值:
( LV2,RANK:10 )
4 楼
我是沙发吗?好像不应该是吧
能力值:
( LV2,RANK:10 )
5 楼
我是板凳吧?好象真应该是吧.
感谢楼主的2篇文章.学习中.
能力值:
( LV2,RANK:10 )
6 楼
一楼二楼都被占了,那么三楼是??
哈哈,支持一个.要调用外部EXE或DLL真难啊,看能否做个类似的实例来说PE结构,这样我更喜欢,哈哈哈
好像没有附件
能力值:
( LV2,RANK:10 )
7 楼
好像没有附件
能力值:
( LV8,RANK:130 )
8 楼
能力值:
( LV2,RANK:10 )
9 楼
楼主太好了!顶,顶,顶!
能力值:
( LV2,RANK:10 )
10 楼
提个疑问如下:
答5.3中下一行里的[0x638]=="0207 setDlgItemInt"是不是"0207"应该为"0227"呀?
3. [0x600] == 2038, RVA 2038==offset 638, [0x638]== "0207 setDlgItemInt", 前面是导出序号,后面是导出名称
另外恳请楼主帮看以下问题!
我需要给DLL中.text节的某个SQL语句后加" and d.IfValid=1",其长度为0x20,根据文件头中的
FileAligment为0x1000,于是在.text节尾补加0xFE0个空字符(Zero Block);那么.text节的VirtualSize及VirtualOffset该怎么修改?因增加文件长度再依次将受影响部分进行相应调整,这种思路可行吗?
原问题在这个帖子里http://bbs.pediy.com/showthread.php?t=58965
能力值:
( LV2,RANK:10 )
11 楼
十分感谢楼主
能力值:
( LV8,RANK:130 )
12 楼
出差了,一直未能上网!
[QUOTE=aluocn;461156]提个疑问如下:
答5.3中下一行里的[0x638]=="0207 setDlgItemInt"是不是"0207"应该为"0227"呀?
3. [0x600] == 2038, RVA 2038==offset 638, [0x638]...[/QUOTE]
你说的是对的,是我书写疏忽了。你很仔细!
关于另一个问题:
我需要给DLL中.text节的某个SQL语句后加" and d.IfValid=1",其长度为0x20,根据文件头中的 FileAligment为0x1000,于是在.text节尾补加0xFE0个空字符(Zero Block);那么.text节的VirtualSize及VirtualOffset该怎么修改?因增加文件长度再依次将受影响部分进行相应调整,这种思路可行吗?
这种思路是不可取的,将text 段延长0x1000,意味着将其后的数据段,rsrc 段等的地址依次推后0x1000 字节, 而你的text 段中可能有很多与数据地址有关的指令,也就是说,这些都是需要重定位的,虽然dll 中有重定位表,但你也不应该去遍历这个重定位表,重新修改代码再存盘,这样似乎付出代价太大。我推荐的一种办法,请查看你的原帖子,希望对你有帮助。
能力值:
( LV2,RANK:10 )
13 楼
楼主辛苦,专注一件事情会取得巨大的成就.
能力值:
( LV8,RANK:130 )
14 楼
感谢支持。近期较忙。PE 格式我还想再补充一些应用给大家看,估计要稍微拖后一点了。
能力值:
( LV2,RANK:10 )
15 楼
接着学习下载
能力值:
( LV2,RANK:10 )
16 楼
谢谢楼住,继续学习
能力值:
( LV2,RANK:10 )
17 楼
学习!!!!!!
能力值:
( LV2,RANK:10 )
18 楼
还有第1啊,已经会了
能力值:
( LV2,RANK:10 )
19 楼
继续学习........