分享
 
 
 

JIURL PE 格式学习总结(三)-- PE文件中的输入函数

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

JIURL PE 格式学习总结(三)-- PE文件中的输入函数

作者: JIURL

主页: http://jiurl.yeah.net/

日期: 2003-4-24

关于输入部分,我们将详细介绍关于输入函数的各种结构,通过一个例子来说明输入函数及其相关结构是怎么放在PE文件中的。以及如何在PE文件中找到这些东西。

一 找到输入部分在文件中位置。

1.1 得到PE Header在文件中的位置。

通过DOS Header结构的成员e_lfanew,可以确定PE Header的在文件中的位置。

1.2 得到文件中节的数目。

确定PE Header的在文件中的位置之后,就可以确定PE Header中的成员FileHeader和成员OptionalHeader在文件中的位置。根据 FileHeader 中的 成员NumberOfSections 的值,就可以确定文件中节的数目,也就是节表数组中元素的个数。

1.3 得到节表在文件中的位置。

PE Header在文件中的位置加上PE Header结构的大小就可以得到节表在文件中的开始位置。PE Header结构的大小可以由Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定。其实到目前为止SizeOfOptionalHeade也就是结构Optional Header的大小也是固定的,所以整个PE Header结构的大小也是固定。不过为了安全起见,还是用Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定比较保险。

1.4 得到输入部分在文件中的位置。

第1.2步中我们确定了文件中节的数目,第1.3步中我们确定了节表在文件中的位置。

现在来确定输入部分在文件中的位置。

取得PE Header中的Optional Header中的DataDirectory数组中的第二项,

也就是输入部分项。DataDirectory[]数组的每项都是IMAGE_DATA_DIRECTORY结构,该结构定义如下。

typedef struct _IMAGE_DATA_DIRECTORY {

DWORD VirtualAddress;

DWORD Size;

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

取得DataDirectory数组中的第二项中的成员VirtualAddress的值。这个值就是在内存中资源节的RVA。

如果这个RVA的值为0表示这个PE文件中没有输入部分。

然后根据节的数目,遍历节表数组。也就是从0到(节表数-1)的每一个节表项。

每个节在内存中的RVA的范围是从该节表项的成员VirtualAddress字段的值开始(包括这个值),

到VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。

我们遍历整个节表,看我们取得的输入部分的RVA,在哪个节表项的RVA范围之内。

如果在范围之内,就找到了输入部分所在节的节表项。

这个节表项中的 PointerToRawData 中的值,就是输入部分所在节在文件中的位置。这个节表项中的VirtualAddress 中的值,就是输入部分所在节在内存中的RVA。用输入部分的RVA减去输入部分所在节的RVA,就可以得到输入部分在该节内偏移。用这个偏移加上该节的在文件中的位置,就可以得到输入部分在文件中的位置。即DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - SectionTable[i].VirtualAddress + SectionTable[i].PointerToRawData 。

这样我们就得到了输入部分在文件中开始的位置。

二 PE文件中的输入部分。

输入部分,如果要调用别的PE文件中的输出函数,需要那些东西呢?首先需要知道所需函数在哪个文件中,比如函数 NtRaiseHardError 就在PE文件 ntdll.dll 中。所以我们需要一个文件名。而如何找到某个函数的入口地址呢,我们还需要知道该函数的函数名,或者改函数的序号,通过这两者的任一种,我们就可以找到该函数的入口地址(如果不知道为什么,请看 JIURL PE 格式学习总结(二)-- PE文件中的输出函数)。所以我们还需要函数名或者序号,这两者之一。PE文件的输入部分,有这些内容。我们还可以想到,当一个PE文件被执行的时候,它会把所用的输入函数所在的每一个文件载入内存,并且,根据函数名或者序号,获得每一个输入函数的入口地址,存放起来,在程序执行的时候使用。还有就是,一个可执行文件一般都使用好几个PE文件(通常是dll)的输出函数。所以需要有多个dll(就说成dll吧,提供输出函数的PE文件差不多都是dll,下面就按dll说)的相关信息。

前面我们已经得到了输入部分在文件中开始的位置,在输入部分的最开始,是一个IMAGE_IMPORT_DESCRIPTOR 结构数组,这个数组的最后一个元素内容全为空,标示着这个数组的结束,这个数组的每个元素,保存着一个dll的相关信息。紧跟着这个IMAGE_IMPORT_DESCRIPTOR数组的是几个紧挨着的DWORD数组, 数组的每个元素存有函数名字符串的RVA,或者直接保存序号,每个数组的最后一项为空,标示结束。这几个数组之后,紧跟着的是dll名字的字符串和各个输入函数名结构。

IMAGE_IMPORT_DESCRIPTOR 结构在WINNT.H中定义如下。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

union {

DWORD Characteristics; // 0 for terminating null import descriptor

DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)

};

DWORD TimeDateStamp; // 0 if not bound,

// -1 if bound, and real date\time stamp

// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)

// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders

DWORD Name;

DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)

} IMAGE_IMPORT_DESCRIPTOR;

这个结构长度为20个字节,共有5个字段。

各字段含义如下:

OriginalFirstThunk:(在WINNT.H中Characteristics这个叫法已经不对了)这里实际上保存着一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫做输入查询表。每个数组元素,或者叫一个表项,保存着一个指向函数名的RVA或者保存着一个函数的序号。

TimeDateStamp:当这个值为0的时候,表明还没有bind。不为0的话,表示已经bind过了。有关bind的内容后面介绍。

ForwarderChain:

Name:一个RVA,这个RVA指向一个ascii以空字符结束的字符串,这个字符串就是本结构对应的dll文件的名字。

FirstThunk:一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫输入地址表。如果bind了的话,这个数组的每个元素,就是一个输入函数的入口地址。

输入查询表,就是OriginalFirstThunk所指向的那个DWORD数组,它的每一个元素是一个DWORD值,当最高位为1时,低31位中的值,就是一个序号。当最高位为0时,这个元素的值就是一个指向一个输入函数名结构的RVA。这个数组的最后一个元素值为空,表示数组的结束。

输入函数名结构,在WINNT.H中定义如下。

typedef struct _IMAGE_IMPORT_BY_NAME {

WORD Hint;

BYTE Name[1];

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

这个结构的长度不定,有两个成员。第一个成员是一个WORD类型,长2个字节,保存着输入函数的序号。第二个成员是一个ascii字符串,这个字符串是输入函数的名字。为了保证字对齐,可能会在ascii结束符\0之后再填充一个\0。比如,1b 01 4e 74 54 65 72 6d 69 6e 61 74 65 50 72 6f 63 65 73 73 00 00 ,如果不填充最后一个00的话,长度为21个字节,不是字对齐。所以要填充一个00。

输入地址表,就是FirstThunk所指向的那个DWORD数组,它的每一个元素是一个DWORD值。如果程序已经bind了的话,(判断依据是TimeDateStamp,TimeDateStamp为0则没有bind)那么这里的每个元素的值,就是一个输入函数的入口地址。如果没有bind的话,那么在本pe文件执行时,载入器会载入dll文件,获得每一个输入函数的入口地址,并填入这个输入地址表的每一项中。(这些是我猜的,大家但愿我猜对吧)这个数组的最后一个元素值为空,表示数组的结束。

bind,从上面的介绍中可以看到,如果没有bind的话,每次pe文件被执行时,载入器都要查询一遍每个函数的入口地址,所以为了优化这一点,就有了bind,把入口点直接存在输入地址表中。

载入器会载入所需要的dll。注意一下没有bind的情况下,载入器对输入部分所要做的事情。总之,在载入之后,所需的dll(根据文件名)已经都被载入到内存。并且输入地址表中的每一个元素都是一个输入函数的入口地址了。

下面我们来看一个例子,通过例子就可以明白是怎么回事了。

我们的例子是Win2k中的exe文件csrss.exe。为了防止大家版本不同,本文附带了这个PE文件。

每个结构的不同成员用 / 分开。每行是一个结构。可以用16进制编辑器打开附带的 routetab.dll 对照着看。

括号中内容为注释。

用开始讲到的寻找输入部分在文件中位置的方法,我们找到了输入部分在文件中的位置为000008DCh。

我们来计算一下第一个IMAGE_IMPORT_DESCRIPTOR中的OriginalFirstThunk,Name,FirstThunk。

输入部分所在节的开始rva(由DataDirectory[2]得到)为1000h。输入部分在节在文件中的位置为600h。

Name为rva(值从结构中可以看到是0000135e,如果你不明白为什么是0000135e而不是5e130000的话,请看 《JIURL PE 格式学习总结(一)》中关于 big-endian和little-endian的介绍),则Name相对于所在节开始处的偏移为135e-1000。而Name在文件中的位置为Name在相对于所在节开始的偏移加上所在节开始处在文件中的位置。所以Name在文件中的位置为135eh-1000h+600h=95eh。同样方法我们可以算出, OriginalFirstThunk:

1318-1000+600=918。FirstThunk:1000-1000+600=600。

000008DC: 18 13 00 00 / ff ff ff ff / ff ff ff ff / 5e 13 00 00 / 00 10 00 00

(结构IMAGE_IMPORT_DESCRIPTOR,每个代表一个dll。可以看到两个IMAGE_IMPORT_DESCRIPTOR,所以本PE文件的输入函数,是由两个dll提供的。第三个全为空,表示结束。)

000008F0: 20 13 00 00 / ff ff ff ff / ff ff ff ff / c2 13 00 00 / 08 10 00 00

(结构IMAGE_IMPORT_DESCRIPTOR)

00000904: 00 00 00 00 / 00 00 00 00 / 00 00 00 00 / 00 00 00 00 / 00 00 00 00

(全为空,表示结束IMAGE_IMPORT_DESCRIPTOR数组结束)

00000918: 44 13 00 00 (文件中的地址为1344-1000+600=944,指向一个输入函数名结构)

0000091C: 00 00 00 00 (为空,一个输入查询表结束)

00000920: 84 13 00 00 (文件中的地址为1384-1000+600=984,指向一个输入函数名结构)

00000924: 98 13 00 00 (1398-1000+600=998)

00000928: 6a 13 00 00 (136a-1000+600=96a)

0000092C: ae 13 00 00 (13ae-1000+600=9ae)

00000930: cc 13 00 00 (13cc-1000+600=9cc)

00000934: dc 13 00 00 (13dc-1000+600=9dc)

00000938: ee 13 00 00 (13ee-1000+600=9ee)

0000093C: 0e 14 00 00 (140e-1000+600=a0e)

00000940: 00 00 00 00 (为空,一个输入查询表结束)

00000944: 18 00 / 43 73 72 53 65 72 76 65 72 49 6e 69 74 69 61 6c 69 7a 61 74 69 6f 6e 00

(输入函数名结构 IMAGE_IMPORT_BY_NAME hint为18 Name为 "CsrServerInitialization.")

0000095E: 43 53 52 53 52 56 2e 64 6c 6c 00 00

(第一个IMAGE_IMPORT_DESCRIPTOR的Name指向这里"CSRSRV.dll")

0000096A: 00 01 / 4e 74 53 65 74 49 6e 66 6f 72 6d 61 74 69 6f 6e 50 72 6f 63 65 73 73 00

("NtSetInformationProcess.")

00000984: 1c 01 / 4e 74 54 65 72 6d 69 6e 61 74 65 54 68 72 65 61 64 00

00000998: 1b 01 / 4e 74 54 65 72 6d 69 6e 61 74 65 50 72 6f 63 65 73 73 00 00

000009AE: d8 00 / 4e 74 52 61 69 73 65 48 61 72 64 45 72 72 6f 72 00 00

000009C2: 6e 74 64 6c 6c 2e 64 6c 6c 00

000009CC: 0d 00 / 44 62 67 42 72 65 61 6b 50 6f 69 6e 74 00

000009DC: 4a 01 / 52 74 6c 41 6c 6c 6f 63 61 74 65 48 65 61 70 00

000009EE: 85 02 / 52 74 6c 55 6e 69 63 6f 64 65 53 74 72 69

6e 67 54 6f 41 6e 73 69 53 74 72 69 6e 67 00 00

00000A0E: 30 02 / 52 74 6c 4e 6f 72 6d 61 6c 69 7a 65 50 72 6f 63 65 73 73 50 61 72 61 6d 73 00

00000A2A: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00000A3A: ...

00000600: 38 1f f8 5f

00000604: 00 00 00 00 (为空,一个输入地址表结束)

00000608: 6d f0 f8 77

0000060C: d8 c3 f8 77

00000610: a5 b7 f8 77

00000614: 38 a4 f9 77

00000618: df f9 f9 77

0000061C: 6b 97 fc 77

00000620: ec e5 f8 77

00000624: 18 2c f9 77

00000628: 00 00 00 00 (为空,一个输入地址表结束)

本例比较可惜的是,在两个输入查询表中,都是函数名结构的RVA,没有直接的序号(是序号还是RVA的判别方法为,看最高位是否为1,为1,其余部分表示序号。为0,整个字段表示RVA)。

三 遍历PE文件中的输入

用while循环,遍历IMAGE_IMPORT_DESCRIPTOR数组的每个元素(每个可以找到一个dll的信息和该dll提供的输入函数)。当某元素的值都为空时,表示遍历到了数组的最后。而对于IMAGE_IMPORT_DESCRIPTOR数组的一个元素,再用while循环,遍历IMAGE_IMPORT_DESCRIPTOR中,两个RVA所指的两个DWORD数组,输入查询表和输入地址表。判断结束的条件也是看,是否数组元素的值已经为空了。也就是while(..){..while(..){}..}这样就可获得每一个有关输入的内容。

实现遍历输入的源程序,可以参考 PEDUMP - Matt Pietrek 1995 。《Windows95系统程式设计大奥秘》附书源码中有。

本文所使用的PE文件csrss.exe

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有