通常在分析一些样本时会遇到如下程序
Handle hImageBase=LoadLibraryA("Kernel32.dll");
void* aAddr =hImageBase+0x3c;
void* bAddr=aAddr++0x78;
的调用,为了能更理解在运行时的解析操作,使用010Editor进行分析了exe文件。
这个是整个PE文件的头部,通常加载一个exe或者dll获取的base地址就是这个头
以MZ
标志开头,表示这个一个PE文件。如果要读取Rich的字符串头信息和ntheader,则需要
ptrRich = base+0x40,这里就指向了This is programe cannot be run in DOS mode 的信息
ntheader = base+0x3c ;这里会获得ntheader的地址
这里是基本的特征信息This is program ..
的信息节区,计算方式
ptrRich=base+0x40
,例如这里的ptrRich=0x40
这里需要重点关注,因为整个PE文件的加载和重要信息都保存在这里,包括了子系统,导入表,导出表等,计算方式
ntheader=base+0x3c =>0xF0
前四个字节就是50 40 00 00
对应的PE
在获取到了ntheader之后,就可以读取导入表、导出表等重要信息节区了。
在知道了ntheader=>0xF0
的位置后,计算的option_header
如下
opHeader = ntheader+sizeof(Signature)+sizeof(struct FILE_HEADER)
==>
opHeader = 0xF0+0x4+0x14=>0x108 ==>ntheader+0x18
看出图中得出的结果和计算的保持一致。
要获取导入或者导出目录信息,这里需要获取目录数组地址,依次解析,通常目录数组主要包括了
每个目录大小是8字节,主要包括两个字段
struct directory{
DWORD virturalAddr;
DWORD size;
};
目前在分析阶段主要关注了导入和导出表的偏移计算,其他的暂时不做关注。这里的virtualAddr
只是指向了一个结构体的地址,size
表示了这个目录有多少个条目,在遍历的时候会使用到。
导入表目录,这个主要是包括了当前PE文件导入的库信息。
根据上述的描述中可以知道,opheader=0x108
,则对应的import_directory=0x170 ==>opheader+0x68
导出表目录,主要是PE文件要导出的一些符号的目录。这里的export_directory=0x168 ==> option_header+0x60
根据上述的计算结果,可以得出
base = LoadLibrary(targetLib);
nt_header = base+0x3c
option_headers=base+0x3c+0x18
import_directory= base+0x3c+0x18+0x68 ==>base+0x3c+0x80==>ImportDirectory
export_directory=base+0x3c+0x18+0x60 ==>base+0x3c+0x78 ==>ExportDirectory
通常会在程序内看到如下的计算方法
esi = targeLibHandle;
mov edi, [esi+3Ch]
push esi
add edi, esi
push edi
mov [esp+5Ch+kernel_pe_header1], edi
mov eax, [edi+78h]
push eax
因此可以得知这里的edi =esi+0x3c==>nt_header eax=edi+0x78==>export_directory
通常这个手段主要是用来动态修复导出表和导出表以及一些修复工作。