文章目录
1.ARM汇编指令
1)ARM指令通常跟一到两个操作数,我们使用如下模板描述:
MNEMONIC{S}{condition} {Rd}, Operand1, Operand2
其中各个字段的作用如下:
MNEMONIC - 指令的助记符如ADD
{S} - 可选的扩展位,如果指令后加了S,将依据计算结果更新CPSR寄存器中相应的FLAG
{condition} - 执行条件,如果没有指定,默认为AL(无条件执行)
{Rd} - 目的寄存器,存储指令计算结果
Operand1 - 第一个操作数,可以是一个寄存器或一个立即数
Operand2 - 第二个(可变)操作数,可以是一个立即数或寄存器甚至带移位操作的寄存器
助记符、S扩展位、目的寄存器和第一个操作数的作用很好理解,不多做解释,这里补充解释一下执行条件和第二个操作数。设置了执行条件的指令在执行指令前先校验CPSR寄存器中的标志位,只有标志位的组合匹配所设置的执行条件指令才会被执行。第二个操作数被称为可变操作数,因为它可以被设置为多种形式,包括立即数、寄存器、带移位操作的寄存器,如下所示:
#123 - 立即数
Rx - 寄存器比如R1
Rx, ASR n - 对寄存器中的值进行算术右移n位后的值
Rx, LSL n - 对寄存器中的值进行逻辑左移n位后的值
Rx, LSR n - 对寄存器中的值进行逻辑右移n位后的值
Rx, ROR n - 对寄存器中的值进行循环右移n位后的值
Rx, RRX - 对寄存器中的值进行带扩展的循环右移1位后的值
1.1.载入和存储
ldr = Load Word 载入字
ldrh = Load unsigned Half Word 载入无符号半字
ldrsh = Load signed Half Word 载入有符号半字
ldrb = Load unsigned Byte 载入无符号字节
ldrsb = Load signed Bytes 载入有符号字节
str = Store Word 储存字
strh = Store unsigned Half Word 储存无符号半字
strsh = Store signed Half Word 储存有符号半字
strb = Store unsigned Byte 储存无符号字节
strsb = Store signed Byte 储存有符号字节
ARM专用的函数调用规则规定了函数的前四个参数应该分别存贮与R0到R3中。
ldm r0, {r4,r5} /* words[3] -> r4 = 0x03; words[4] -> r5 = 0x04 */
使用LDM指令,将R0指向的内存中,取两个字的数据出来。由于之前我们让R0指向了word[3],因此word[3]的值会被存入R4, word[4]的会被存入R5
stm r1, {r4,r5} /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 */
使用STM指令将多个值写入内存。STM指令将R4,R5寄存器的值0x3和0x4存入R1指向的内存空间中。
LDM和STM有多种不同的使用形式。具体是哪一种使用形式由指令的后缀所决定。这个示例列出了后缀的几种形式:-IA (之后增加), -IB (之前增加), -DA (之后减少), -DB (之前减少)。
ldmia r0, {r4-r6} /* words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05; */
stmia r1, {r4-r6} /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 */
执行完上面的两条指令后,寄存器R4-R6分别包含了 0x3,0x4,0x5 ,内存地址0x000100D0, 0x000100D4,和 0x000100D8中也包含了0x3,0x4,0x5
ldmib r0,{r4-r6} /*words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 */
stmib r1,{r4-r6} /* r4-> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 ->array_buff[3] = 0x06 */
LDMIB指令先将源地址增加4字节(一个字),然后开始载入数据。该方法仍能让我们载入一串前向数据,但第一个数据的起始地址相较源地址有4个字节的偏移。这就是为什么我们用LDMIB指令将内存中的第一个元素载入R4后,R4是0x04(word[4]),而不是R0指向的0x3(word[3])
ldmda r0, {r4-r6} /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 */
使用LDMDA向相反方向执行运算。
stmda r2, {r4-r6} /* r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 */
同样的道理
stmdb r2, {r4-r5} /* r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */
1.2.转移指令
当特定条件满足时,借助条件指令, 通过跳转(分支)或执行某些特定指令来控制程序的流动方向。相关条件被描述为CPSR寄存器中的特定位的状态,这些位根据指令计算后的结果实时改变。比如,如果我们比较两个数并且他们相等,就将零标志位置位(Z=1) ,因为在系统底层发生了a-b=0。在这个例子里两个数是相等的,但如果第一个数字比第二个大,会得出大于结论。而相反的情况下得出小于结论。当然还有很多其他的条件,比如小于等于(LE),大于等于(GE)等等。
下表列出了可能的条件指令,他们的含义以及被检测的状态标志位
1.3.跳转指令
b分支指令 简单地跳向一个函数
bl分支连接指令 将(PC+4)保存到LR中并跳转到函数
BX(分支切换指令)和BLX(分支连接切换指令)
a) 和B/BL+交换指令集相同( ARM <-> Thumb )
b) 需要用寄存器作为第一操作数:BX/BLX+具体的寄存器
BX/BLX用来从ARM指令集切换到Thumb指令集
2.PE文件知识
2.1.PE头
PE头分为5个部分,如下表
在一个PE文件的开始处,我们会看到一个MS-DOS可执行体(英语叫“stub”,意为“根,存根”);它使任何PE文件都是一个有效的MS-DOS可执行文件。
在DOS-根之后是一个32位的签名以及魔数0x00004550 (IMAGE_NT_SIGNATURE)(意为“NT签名”,也就是PE签名;十六进制数45和50分别代表ASCII码字母E和P----译者注)。
之后是文件头(按COFF格式),用来说明该二进制文件将运行在何种机器之上、分几个区段、链接的时间、是可执行文件还是DLL、等等。(本文中可执行文件和DLL文件的区别在于:DLL文件不能被启动,但能被别的二进制文件使用,而一个二进制文件则不能链接到另一个可执行文件。)
那些之后,是可选头(尽管它一直都存在,却仍被称作“可选”----因为COFF文件格式仅为库文件使用一个“可选头”,却不为目标文件使用一个“可选头”,这就是为什么它被称为“可选”的原因)。它会告诉我们该二进制文件怎样被载入的更多信息:开始的地址呀、保留的堆栈数呀、数据段的大小呀、等等。
可选头的一个有趣的部分是尾部的“数据目录”数组;这些目录包含许多指向各“节”数据的指针。例如:如果一个二进制文件拥有一个输出目录,那么你就会在数组成员“IMAGE_DIRECTORY_ENTRY_EXPORT”(输出目录项)中找到一个指向那个目录的指针,而该指针指向文件中的某节。
跟在各种头后面我们就发现各个“节”了,它们都由“节头”引导。本质上讲,各节中的内容才是你执行一个程序真正需要的东西,所有头和目录这些东西只是为了帮助你找到它们。
每节都含有和对齐、包含什么样的数据(如“已初始化数据”等等)、是否能共享等有关的一些标记,还有就是数据本身。大多数(并非所有)节都含有一个或多个可通过可选头的“数据目录”数组中的项来参见的目录,如输出函数目录和基址重定位目录等。无目录形式的内容有:例如“可执行代码”或“已初始化数据”等。
2.2.PE头之IMAGE_FILE_HEADER解析
在IMAGE_DOS_HEADER结构中,只关心e_magic以及e_lfanew,分别是第一个字段以及最后一个字段。
1.e_magic字段
e_magic字段类型为WORD,占用两个字节,表示PE文件的一个Magic标志,它的值固定为4D 5A,即PE文件的e_magic值一定就是4D 5A,如果不是4D 5A则表示不是PE文件,因此e_magic通常作为识别一个文件是否是PE文件的标准之一。在小端模式下,4D 5A表示为WORD类型的数据为0x5A4D,在WinNT.h头文件中定义了一个名为IMAGE_DOS_SIGNATURE的宏,它的值就是0x5A4D,如下所示:
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
2.e_lfanew字段
e_lfanew用于指定PE文件头的偏移,因为在IMAGE_DOS_HEADER头和PE头之间隔了一段DosStub数据,而DosStub的大小是可变的,所以需要通过e_lfanew才能找到PE文件头的位置。IMAGE_DOS_HEADER结构体的大小为64字节,而e_lfanew位于该结构的最后四字节,所以e_lfanew对应第61~64四个字节的数据(下标索引为60~63)。
2.3.PE头之IMAGE_FILE_HEADER
IMAGE_NT_HEADERS结构体在WinNT.h头文件中的定义如下所示:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
可以看出,IMAGE_NT_HEADERS由一个DWORD类型的Signature成员、一个IMAGE_FILE_HEADER类型的FileHeader、以及一个IMAGE_OPTIONAL_HEADER32类型的OptionalHeader组成。
Signature字段的类型为DWORD,占用四个字节,也表示PE文件的一个Magic标志(在之前的实验中,讲到了IMAGE_DOS_HEADER的e_magic也是PE文件的Magic标志),同样,Signature的值固定为50 45 00 00,在小端模式下表示为0x00004550,字符串表示为“PE\0\0”。Signature在WinNt.h头文件中的定义如下所示:
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
1.该结构的第二个成员为FileHeader,对应的类型为IMAGE_FILE_HEADER,该结构体在WinNt.h头文件中的定义如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
-
Machine字段,占用2个字节,指明PE文件支持的CPU类型;PE文件可以支持多种CPU类型,不同的CPU对应的机器码是不一样的,几种典型的机器类型标志如下所示:
-
NumberOfSections字段,占用2个字节,指明PE文件包含的节区数量,对notepad.exe而言,这个值为3,因为其包含.text、.data以及.rsrc三个区块;
-
TimeDateStamp字段,占用4个字节,指明文件的创建时间,注意这个“创建”指的是编译器生成这个PE文件的时间;TimeDateStamp的值表示为1970年1月1日以来用格林威治时间计算的秒数,通过一定的换算即可将其转换为当前的时间信息,LordPE工具提供了这样的功能。
打开桌面的LordPE工具,在LordPE的主界面中单击右侧的“PE编辑器”按钮,载入C:\PE\notepad.exe文件,可以看到“日期时间标志”为0x48025287,如下图所示:
复制下48025287这个值,然后单击界面右侧的“时间标志”按钮来打开转换工具,在编码区域内填入48025287,随后单击解码左侧的复选框,即可完成时间转换,可以看到转换后的时间信息为2008年4月13日18时35分51秒,这就是编译器生成notepad.exe的时间,如下图所示:
-
PointerToSymbolTable字段,COFF符号表的文件偏移位置,这个字段跟调试符号相关,这里不作研究;
-
NumberOfSymbols字段,如果存在COFF符号表,这个字段表示其中的符号数目,该字段同样跟调试符号相关,这里不作研究;
-
SizeOfOptionalHeader字段,指明在IMAGE_NT_HEADERS中紧跟在FileHeader后的OptionalHeader的大小,对于32位的PE文件而言,这个值的大小通常为0x00E0;
-
Characteristics字段,表明文件的属性,如果Characteristics & 0x2000 = 0x2000,那么表明这是一个DLL文件(在WinNT.h头文件中定义了#define IMAGE_FILE_DLL 0x2000这样一个宏);
2.4.PE头之IMAGE_OPTIONAL_HEADER解析
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_OPTIONAL_HEADER结构各个成员字段的含义如下:
-
Magic,表明是ROM映像还是普通的可执行映像,普通的可执行文件的值为0x010B,PE32+可执行文件的值为0x020B(PE32+即64位的PE文件,这种文件只能运行在64位的Windows操作系统下);
-
MajorLinkerVersion,链接器的主版本号;
-
MinorLinkerVersion,链接器的次版本号;
-
SizeOfCode,代码区块的大小,通常而言可执行文件仅有一个代码区块.text,所以通常就是.text区块的大小;
-
SizeOfInitializedData,已初始化数据块的大小,不作研究;
-
SizeOfUninitializedData,未初始化数据块的大小,不作研究;
-
AddressOfEntryPoint,重要字段,程序执行的入口点地址,是一个RVA值;
-
BaseOfCode,代码段的起始地址,是一个RVA值,通常而言就是.text区块的起始RVA;
-
BaseOfData,数据段的起始地址,是一个RVA值,通常而言就是.data区块的起始RVA;
-
ImageBase,重要字段,PE文件在内存中首选的装载基地址;
-
SectionAlignment,当PE文件装载到内存时区块的对齐大小,假设.text区块的大小为0x7748,而SectionAlignment的大小为0x1000,那么对齐后的大小为0x8000字节;
-
FileAlignment,磁盘上PE文件中区块的对齐大小,对齐方式类似SectionAlignment;
-
MajorOperatingSystemVersion,要求的操作系统最低的主版本号;
-
MinorOperatingSystemVersion,要求的操作系统最低的次版本号;
-
MajorImageVersion,PE文件本身的主版本号;
-
MinorImageVersion,PE文件本身的次版本号;
-
MajorSubsystemVersion,要求的子系统最低的主版本号;
-
MinorSubsystemVersion,要求的子系统最低的次版本号;
-
Win32VersionValue,保留字段,通常为0;
-
SizeOfImage,PE文件被装载到内存空间后总的大小,指从ImageBase到最后一个区块的大小;
-
SizeOfHeaders,Dos头、DosStub、PE头以及区块头的总大小,并进行FileAlignment对齐后的大小;
-
CheckSum,校验和,一般的EXE文件通常为0,对重要的系统驱动程序而言比较有意义;
-
Subsystem,子系统,EXE最常用的子系统类型有console以及windows,分别对应控制台应用程序以及GUI应用程序;
-
DllCharacteristics,包含有DEP以及ASLR相关的属性位;
-
SizeOfStackReserve,不作研究;
-
SizeOfStackCommit,不作研究;
-
SizeOfHeapReserve,不作研究;
-
SizeOfHeapCommit,不作研究;
-
LoaderFlags,与调试相关,不作研究;
-
NumberOfRvaAndSizes,数据目录项的个数,固定为16,即后面一个成员DataDirectory的数组元素个数;
-
DataDirectory,数据目录表,包含有输入表、输出表等表项的具体信息;
OptionalHeader的末尾是一个数据目录表数组,即类型为IMAGE_DATA_DIRECTORY的数组DataDirectory,数组的元素个数为16,各个元素所对应的表项如下图所示:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
2.5.PE头之节表头解析
紧跟在IMAGE_NT_HEADERS之后的是节表头,即IMAGE_SECTION_HEADER。
每个节区都会有一个对应的节表头,节表头以及节区的数量都是由IMAGE_NT_HEADERS.FileHeader.NumberOfSections字段的值所决定的。节表头对应的结构体为IMAGE_SECTION_HEADER,该结构体在WinNT.h头文件中的定义如下所示:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
其中各个字段的介绍如下:
-
Name,为对应节区的名字,其中IMAGE_SIZEOF_SHORT_NAME的值固定为8。如果名字的长度小于8,则以NULL字符结束;如果名字的长度等于8,则没有NULL字符,因为数组长度为8。节区的名字通常以点号开头,如.text、.data等,通常而言,这个名字可以随便修改。
-
VirtualSize,在未对齐的情况下,区块所有数据的大小。
-
VirtualAddress,区块被装载到内存时的RVA,这个值总是SectionAlignment的整数倍。
-
SizeOfRawData,区块数据在磁盘文件中按照FileAlignment对齐后的大小。
-
PointerToRawData,区块数据在磁盘文件中的偏移地址。
-
PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers:不是很重要的字段,这里不作研究。
-
Characteristics,区块的属性值,表明区块的可读、可写、可执行等相关属性。
2.6.输入表解析
INT能够表示出函数名是什么,IAT能够表示出这个函数的入口地址在哪里。
可是系统不知道那个结构是INT,IAT。这时候就用IID来描述。通过IID可以找到INT,IAT,以及调用的dll名称。
这就是IAT,INT,IID的作用。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
关于IID结构体中各个成员的含义介绍如下:
- OriginalFirstThunk,RVA,指向输入名称表(简称INT),INT是一个类型为IMAGE_THUNK_DATA结构的数组,同样的,通过在数组末尾附加一个空的IMAGE_THUNK_DATA结构来表示数组的结束,每一个输入的函数都有一个对应的IMAGE_THUNK_DATA结构;
- TimeDateStamp,DLL的时间日期标志,通常忽略;
- ForwarderChain,通常忽略,不作讲解;
- Name,RVA,指向DLL的名字,如“User32.dll”;
- FirstThunk,RVA,指向输入地址表(简称IAT),IAT也是一个类型为IMAGE_THUNK_DATA结构的数组,同样的,通过在数组末尾附加一个空的IMAGE_THUNK_DATA结构来表示数组的结束,每一个输入的函数都有一个对应的IMAGE_THUNK_DATA结构;
需要注意的是,OriginalFirstThunk与FirstThunk极其相似,前者指向INT,后者指向IAT,其中INT和IAT都是类型为IMAGE_THUNK_DATA的数组
IMAGE_THUNK_DATA结构:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function;
DWORD Ordinal;
DWORD AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
可以看出,该类型仅有的一个成员u1是一个联合体(union),而联合体内的类型都是DWORD,所以IMAGE_THUNK_DATA的大小是4字节。当该类型的最高位为1时,表示函数以序号的方式进行输入,这时候低31位的值就表示函数的序号;当该类型的最高位为0时,表示函数以名字的方式进行输入,这时候值就表示一个指向IMAGE_IMPORT_BY_NAME结构的RVA。
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
OriginalFirstThunk指向的INT数组和FirstThunk指向的IAT数组到底有什么区别呢?答案是当PE文件被加载时,PE加载器会遍历INT结构中的数组项,通过其指向的IMAGE_IMPORT_BY_NAME结构来找到函数的名字,PE加载器可以通过函数的名字找到函数的地址,随后把得到的函数地址填充到IAT结构中,此后,通过IAT结构中的函数地址就可以进行函数调用了。
2.7.输出表解析
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;
-
Characteristics,表示输出属性,这个值当前没有意义,总是设置为0;
-
TimeDateStamp,输出表创建的时间(格林威治标准时间);
-
MajorVersion,输出表的主版本号,这个值当前没有意义,总是设置为0;
-
MinorVersion,输出表的次版本号,这个值当前没有意义,总是设置为0;
-
Name,RVA,模块的真实名字,指向一个ASCII字符串,这个字符串是与这些输出函数关联的DLL的名字(如User32.dll);
-
Base,输出表的起始序数值;
-
NumberOfFunctions,输出函数的个数;
-
NumberOfNames,以名字输出的函数的个数;
-
AddressOfFunctions,RVA,指向包含输出函数地址的RVA数组;
-
AddressOfNames,RVA,指向包含以名字方式输出的函数的ASCII字符串名字的RVA数组;
-
AddressOfNameOrdinals,RVA,指向每个以名字方式输出的函数对应在AddressOfFunctions指向的数组中的索引;
2.8.资源结构解析
一个PE文件或多或少都会包含有资源数据,如图标(Icon)、对话框(Dialog)、字符串(String)等,相对于输入表、输出表、重定位而言,资源结构相对更加复杂。 资源结构采用类似文件系统的分层目录结构,通常包含三层目录。数据目录表的第三个成员指向资源表,其类型为IMAGE_RESOURCE_DIRECTORY,该结构占用16字节,该结构体在WinNT.h头文件中的定义如下:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
IMAGE_RESOURCE_DIRECTORY结构体中各个字段的含义如下:
-
Characteristics,资源的属性标志,当前没有意义,它的值总是设置为0;
-
TimeDateStamp,资源数据被资源编译器创建的时间;
-
Major Version,资源的主版本号,通常为0;
-
Minor Version,资源的次版本号,通常为0;
-
NumberOfNamedEntries,重要字段,以字符串命名的资源条目的数量;
-
NumberOfIdEntries,重要字段,以整数命名的资源条目的数量;
主要关注最后两个字段:NumberOfNamedEntries与NumberOfIdEntries,两者加起来就是资源表根目录下总的资源条目的数量。
在IMAGE_RESOURCE_DIRECTORY之后,紧跟着NumberOfNamedEntries + NumberOfIdEntries个IMAGE_RESOURCE_DIRECTORY_ENTRY(简称IRDE)结构类型的数据。IMAGE_RESOURCE_DIRECTORY_ENTRY结构体在WinNT.h头文件中的定义如下所示:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
DWORD Name;
DWORD OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
该结构体占用8个字节,结构体的成员的介绍如下:
- Name,这个字段拥有多个不同的含义:当IRDE位于第一层目录时,Name表示资源的类型;当IRDE位于第二层目录时,Name表示资源的名称;当IRDE位于第三层目录时,Name表示代码页的编号。此外,还需要先判断Name的最高位是0还是1,如果是0则表示当做一个值来使用,如果是1,则表示低31位当做指针来使用;
- OffsetToData,指针;当最高位为1时,表示低位数据指向下一层目录;当最高位为0时,表示没有下一层目录,低位数据指向一个IMAGE_RESOURCE_DATA_ENTRY结构。
注意,当Name和OffsetToData当做指针使用时,其值并不是RVA,而是表示相对于资源区块起始位置的偏移值。
前面提到,当IMAGE_RESOURCE_DIRECTORY_ENTRY位于第一层目录时,它的Name字段表示资源类型,系统预定义的资源类型如下图所示:
资源名称字符串使用Unicode编码,其结构类型为IMAGE_RESOURCE_DIR_STRING_U,定义如下:
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length;
WCHAR NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
其中Length表示字符串的长度,NameString数组存储实际的Unicode字符。
IMAGE_RESOURCE_DATA_ENTRY结构体在WinNT.h头文件中的定义如下:
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData;
DWORD Size;
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
IMAGE_RESOURCE_DATA_ENTRY结构体成员的介绍如下:
-
OffsetToData,这是一个RVA,指向真正的资源数据;
-
Size,表示资源的大小;
-
CodePage,代码页,一般为0;
-
Reserved,保留字段,暂时没有意义
2.9.重定位表解析
在上述的例子中,重定位起到的作用就是将第二条指令从push 00402000修改为push 00872000,经过重定位修正之后,程序就能正常运行了。经过重定位操作之后,PE文件被加载到任意一个基地址都可以正常的运行。
重定位操作由操作系统提供的装载器来完成,那么装载器是如何知道哪些数据需要进行重定位操作呢?这就涉及到我们即将介绍的重定位表了。
需要进行重定位的数据通常都会放在PE文件名为.reloc的节区中,数据目录表的第六个成员指向重定位表。重定位表存放着许多类型为IMAGE_BASE_RELOCATION的结构,该结构体在WinNT.h头文件中的定义如下:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
该结构各个成员的介绍如下:
- VirtualAddress,RVA,指向当前这一组重定位数据开始的地址,各个重定项的值加上VirtualAddress计算出实际需要重定位操作的RVA值;
- SizeOfBlock,整个IMAGE_BASE_RELOCATION结构的大小,注意因为后面的TypeOffset数组的大小不固定,所以需要通过SizeOfBlock来指定整个结构的大小;
- TypeOffset,需要重定位的项,数组的项数可以通过(SizeOfBlock - 8)/2计算出来,数组每一项的大小占用2个字节,一共16位,其中高4位表示重定位的类型,低12位表示重定位的地址,低12位加上VirtualAddress得到实际上需要进行重定位操作的数据的RVA值;
常见的重定位类型有:
- IMAGE_REL_BASED_ABSOLUTE,值为0,没有具体意义,用于对齐使用;
- IMAGE_REL_BASED_HIGHLOW,值为3,表示指向的地址需要进行重定位修正;
- IMAGE_REL_BASED_DIR64,值为10,出现在64位的PE文件中,对指向的整个地址修正;
重定位表的结构如下图所示:
3.病毒分析
在线沙箱
网上有许多公开的在线沙箱,使用这些沙箱提供的服务,我们可以方便的观察一个程序的详细行为报告,进而判断一个程序大致的内部逻辑。
在线沙箱通常用于大致判定一个程序的行为是否安全,在逆向分析中,我们可以通过提交一个文件给沙箱程序来判断程序内部的大致逻辑,通过对沙箱报告的分析,有时候可以有效加快我们的逆向分析进程。
常见的在线沙箱包括但不限于:
VirusTotal:https://www.virustotal.com/gui/
VirSCAN:https://virscan.org/
微步云沙箱:https://s.threatbook.cn/
Joe Sandbox Cloud Basic:https://www.joesandbox.com/#windows
布谷鸟沙盒:https://sandbox.pikker.ee/
OPSWAT MetaDefender:https://metadefender.opswat.com/?lang=en
exe还可以用7z解压!