编写PE文件解析器(三)
下面有几个表网上资料比较少,因为几乎用不到,我查文档写写吧,这篇写得比较久很抱歉。
7、IMAGE_DIRECTORY_ENTRY_EXCEPTION【异常处理表】
CPU特定的并且基于表的异常处理。用于除x86之外的其它CPU上。偏移到一个IMAGE_XXX_RUNTIME_FUNCTION_ENTRY数组,根据文件头的Machine来确定结构,64位用IMAGE_IA64_RUNTIME_FUNCTION_ENTRY,详细介绍在pecoff文档5.5节。但是只写定义不写用来干嘛的,这个以后再补充了。
8、IMAGE_DIRECTORY_ENTRY_SECURITY【证书属性表 - WIN_CERTIFICATE】
这是一个数组结构,根据前一个结构定义的大小可以偏移到下一个位置。其结构定义在WinTrust.h里,WIN_CERTIFICATE结构记录了证书长度、版本、类型、二进制内容。,版本是由WIN_CERT_REVISION_XXX定义,类型是WIN_CERT_TYPE_XXX定义。因为证书不会被映射到内存里,所以这里的VirtualAddress存的是文件偏移。另外dwLength如果与可选头中记录的大小总和不同的话,说明证书损坏。
其介绍在pecoff文档60页和5.7节,一般使用win api可以解决大部分事情,现在暂时只能用来解析,读的方法没什么特别的,就不写代码了。
9、IMAGE_DIRECTORY_ENTRY_BASERELOC【重定位表 - IMAGE_BASE_RELOCATION】
当pe加载到内存时,如果可选头记录的ImageBase地址被占用后会换一个位置映射,因为代码里有时候会有调用某地址或者jmp到某些确定地址等等情况,这时候这些地址在编译时用的是硬编码;如果程序不能映射到基址,这个值由于是固定的关系所以不能定位到原来的目标。因为程序可能会在任何位置调用这些值,所以需要一个表记录程序在哪里调用了这些东西。所以就有了重定位表,表里记录的就是偏移到那些位置的rva。
IMAGE_BASE_RELOCATION结构记录了分块的rva和整个分块的大小,紧跟着这个结构的是记录了偏移位置的一个WORD类型数组。那个分块就是按页大小(0x1000)来分的,有时候这个块里没有要重定位的东西就会省掉,IMAGE_BASE_RELOCATION中的VirtualAddress是每个分块的起始rva,计算后面的数组长度使用:(整个块大小SizeOfBlock - 头大小sizeof(IMAGE_BASE_RELOCATION)) / 数组元素大小sizeof(WORD),因为WORD大小为2,直接除以2可以少打一些字= =。通过IMAGE_BASE_RELOCATION头偏移一个SizeOfBlock可以直接到下一个重定位块。整个表以0000结尾,或者使用整个表的大小作为限制来判断结尾也行。
因为是按块来分的,这个块已经记录了rva,所以后面的数组就不记录块的rva了,而记录着重定位方式和相对块rva偏移量。所以这个数组的高4位用来记录重定位类型,低12位记录计算需要的偏移量。计算方式定义有,不写了。
常见的是32位程序的IMAGE_REL_BASED_HIGHLOW和64位程序的IMAGE_REL_BASED_DIR64。IMAGE_REL_BASED_HIGHLOW就是把计算得出的新地址的全部32位覆盖到那个位置上;关于计算方式一会再说,IMAGE_REL_BASED_DIR64就是把计算出的64位覆盖到上面。其它的方式有,ABSOLUTE跳过不处理,作用是用来使块对齐;HIGH、LOW是只应用高位或低位;HIGHADJ用奇怪的方式;其它方式少见而且我懒得写...见文档5.6.2节。
计算出偏移rva直接以分块记录的rva加上低12位就行了。最后修改那个位置的时候需要计算出一个值,计算的时候用这个式子:原来程序中对应位置的值 + 新base - 旧base = 现在程序中在该位置的值。原来程序中对应位置的值等同于文件中的值,写的这个意思是说刚把程序映射到内存没做重定位的时候,那个值还保留着文件中记录的值,这个今后会使用到。
举个栗子!
读到0x1000分块数组第一项为0x3641,高4为3,是HIGHLOW类型,取其低12位,偏移可算出,0x1000 + (0x3641 & 0x0fff) = 0x1641,加上base后可以得到需要重定位的位置;比如内存中base为0xb70000,最终位置为0xb71641,使用OD查看该位置:
00B71640 . A1 6440BC00 mov eax,dword ptr ds:[0xBC4064]
用WinHex打开查看文件,对应位置(要把rva转一下)的值为0x01054064,文件可选头记录的ImageBase为0x01000000,于是这样计算,0x01054064 + 0xb70000 - 0x01000000 = 0xBC4064。
如果想把已经载入内存的代码移位,使用重定位表重定位一下就行了,今后再说。
代码参考:
bool PE::ReadRelocation(IN OUT PIMAGE_BASE_RELOCATION& tmpBaseRelocation, OUT PWORD& relocArray, bool isReadRelocArray, bool isReadOnceRelocation, IN OUT PDWORD stat)
{static DWORD relocArraySize;switch (*stat){default:case 0:if (tmpBaseRelocation == NULL && BaseRelocation == NULL){return false;}if (tmpBaseRelocation == NULL){tmpBaseRelocation = BaseRelocation;}relocArraySize = 0;while (tmpBaseRelocation->VirtualAddress != NULL){*stat = 2;if (isReadRelocArray){*stat = 1;relocArray = PWORD((DWORD)tmpBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));relocArraySize = (tmpBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);while (relocArraySize--){return true;case 1:if (!isReadRelocArray){break;}relocArray++;}}else{return true;case 2:if (isReadRelocArray){continue;}}if (isReadOnceRelocation){return false;}tmpBaseRelocation = PIMAGE_BASE_RELOCATION((DWORD)tmpBaseRelocation + tmpBaseRelocation->SizeOfBlock);}break;}return false;
}
10、IMAGE_DIRECTORY_ENTRY_DEBUG【调试目录 - IMAGE_DEBUG_DIRECTORY】
描述了一些调试信息,其中的Type 类型定义为IMAGE_DEBUG_TYPE_XXX,根据Type的类型来确定偏移到的结构。文档5.1节写得比较详细。
11、IMAGE_DIRECTORY_ENTRY_ARCHITECTURE【IMAGE_ARCHITECTURE_HEADER】
指向特定架构数据,它是一个IMAGE_ARCHITECTURE_HEADER结构数组。不用于x86或IA-64,但看来已用于DEC/Compaq Alpha。数组的FirstEntryRVA指向的结构是IMAGE_ARCHITECTURE_ENTRY数组,这个数组以0xffffffffL结尾。在文档第6节,不过在文档23页写的是保留,必须置0。
12、IMAGE_DIRECTORY_ENTRY_GLOBALPTR
网上的介绍:在某些架构体系上VirtualAddress域是一个RVA,被用来作为全局指针(gp)。不用于x86,而用于IA-64。Size域没有被使用。文档23页有描述,但是没有详细介绍。
13、IMAGE_DIRECTORY_ENTRY_TLS【线程本地存储表 - IMAGE_TLS_DIRECTORY32/64】
当线程们使用同一个变量并且不想让线程们共享这个变量时使用。可以用WinApi TlsAlloc, TlsFree, TlsSetValue, 和 TlsGetValue来使用,在Vc++中直接用__declspec(thread)声明就行。具体在5.7节。因为是静态的关系,会影响使用LoadLibrary的延迟导入。
这个结构分为32位和64位版本,AddressOfCallBacks类型为PIMAGE_TLS_CALLBACK。具体使用方式就不说了,会跑题。
14、IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG【加载配置目录 - IMAGE_LOAD_CONFIG_DIRECTORY32/64】
介绍在文档5.8节。区分32和64位,在过去是用来描述一些复杂或者大型的文件头或可选头结构的,现在用来在xp之后的系统保留SEH技术用的(看文档大概是这个意思,我的渣翻译)。具体介绍在文档里,这里不讨论用法(实际这个我也没用过)。贴一段网上的介绍:IMAGE_LOAD_CONFIG_DIRECTORY中的信息是特定于Windows NT、Windows 2000和 Windows XP的(例如 GlobalFlag 值)。要把这个结构放到你的可执行文件中,你必须用名字__load_config_used 定义一个全局结构,类型是IMAGE_LOAD_CONFIG_DIRECTORY。对于非x86的其它体系,符号名是_load_config_used (只有一个下划线)。如果你确实要包含一个IMAGE_LOAD_CONFIG_DIRECTORY,那么在 C++ 中要得到正确的名字比较棘手。链接器看到的符号名必须是__load_config_used (两个下划线)。C++ 编译器会在全局符号前加一个下划线。另外,它还用类型信息修饰全局符号名。因此,要使一切正常,在 C++ 中就必须像下面这样使用:
extern "C"
IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = {...}
15、IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT【绑定导入表 - IMAGE_BOUND_IMPORT_DESCRIPTOR】
NumberOfModuleForwarderRefs记录了结构之后跟着的IMAGE_BOUND_FORWARDER_REF数组的大小。对应于这个映像绑定的每个DLL。数组元素中的时间戳允许加载器快速判断绑定是否是新的。如果不是,加载器忽略绑定信息并且按正常方式解决导入API。主要是用以加快载入。
16、IMAGE_DIRECTORY_ENTRY_IAT【导入表开头 - IMAGE_THUNK_DATA32/64】
这个取到的是iat的开头,跟用firstThunk的不同是:第一个ImportDirectory导入模块的firstThunk取到的地址不一定在这个表的开头。这个可以快速定位到Thunk数组的起始位置, 作用是让loader快速的定位到该位置设置可读写属性。
17、IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT【延迟导入表 - IMAGE_DELAYLOAD_DESCRIPTOR】
数组结构,可以通过下标偏移。创建有延迟导入表的程序需要在链接器选项/DEALYLOAD那里填入想延迟导入的dll名称(除了一些必要的dll),这样原本应该在导入表的内容就会到这里来了。需要注意的是,延迟加载是第一次使用那个dll的时候才会载入,但是这个载入是由链接器和运行时库实现的。其定义在delayimp.h里也有,这里我用winnt.h里的,其实都一样。
第一个字段Attributes在8.3版的文档(我从官网下到的英文版,应该是最新的)中是置0的,介绍是还没有属性定义,还作为保留字段。不过在winnt.h里的定义是这样:
typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {union {DWORD AllAttributes;struct {DWORD RvaBased : 1; // Delay load version 2DWORD ReservedAttributes : 31;};} Attributes;DWORD DllNameRVA; // RVA to the name of the target library (NULL-terminate ASCII string)DWORD ModuleHandleRVA; // RVA to the HMODULE caching location (PHMODULE)DWORD ImportAddressTableRVA; // RVA to the start of the IAT (PIMAGE_THUNK_DATA)DWORD ImportNameTableRVA; // RVA to the start of the name table (PIMAGE_THUNK_DATA::AddressOfData)DWORD BoundImportAddressTableRVA; // RVA to an optional bound IATDWORD UnloadInformationTableRVA; // RVA to an optional unload info tableDWORD TimeDateStamp; // 0 if not bound,// Otherwise, date/time of the target DLL} IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;
按照这个定义,最低1位应该表示为延迟导入表的版本,高31位全保留,在vs2012中编译出的有延迟导入表的程序里,这个值为1,所以文档可能旧了。DllNameRVA顾名思义就是rva,以\0结尾的字符串。程序延迟载入dll时是这样做的:
程序中延迟导入表的信息是这样(我用vs2012调试的,现在用的电脑上没有OD):
+ Attributes {AllAttributes=0x00000001 RvaBased=0x00000001 ReservedAttributes=0x00000000 } _IMAGE_DELAYLOAD_DESCRIPTOR::<unnamed-type-Attributes>DllNameRVA 0x00016860 unsigned long 文件中值为(下同): USER32.dllModuleHandleRVA 0x00019134 unsigned long 00000000ImportAddressTableRVA 0x0001b070 unsigned long D7144100 会被重定位,最后还会被修改,现在这里的地址跳到延迟载入的代码ImportNameTableRVA 0x0001b040 unsigned long A0B00100 以这个为rva再指到 MessageBoxA 字符串,这是用到的apiBoundImportAddressTableRVA 0x0001b1b0 unsigned long 00000000UnloadInformationTableRVA 0x00000000 unsigned longTimeDateStamp 0x00000000 unsigned long
程序基址为0x01250000,当调用到这个dll中的api时:
MessageBoxA(0,"xxx","ccc",0);
01264388 mov esi,esp
0126438A push 0
0126438C push 1266858h
01264391 push 126685Ch
01264396 push 0
01264398 call dword ptr ds:[126B070h]
程序call的地址 (这个值被重定位过了,原来的值在上面写)为:*(基址+ImportAddressTableRVA),跳到了重定位后的0x012614D7,这里代码为:
012614D7 B8 70 B0 26 01 mov eax,126B070h
012614DC E9 2E FB FF FF jmp __tailMerge_USER32_dll (0126100Fh)
跳转2次到这里:
__tailMerge_USER32_dll:
012614E3 51 push ecx
012614E4 52 push edx
012614E5 50 push eax
012614E6 68 00 B0 26 01 push 126B000h
012614EB E8 42 FB FF FF call ___delayLoadHelper2@8 (01261032h)
012614F0 5A pop edx
012614F1 59 pop ecx
012614F2 FF E0 jmp eax
中间call的函数可以取到它的代码,因为延迟导入是靠编译器来实现的,所以相关的代码要编进exe里。这个函数的代码在delayhlp.cpp的205行(vs2012):
extern "C"
FARPROC WINAPI
__delayLoadHelper2(PCImgDelayDescr pidd,FARPROC * ppfnIATEntry) {// Set up some data we use for the hook procs but also useful for// our own use//InternalImgDelayDescr idd = {pidd->grAttrs,PFromRva<LPCSTR>(pidd->rvaDLLName),PFromRva<HMODULE*>(pidd->rvaHmod),PFromRva<PImgThunkData>(pidd->rvaIAT),PFromRva<PCImgThunkData>(pidd->rvaINT),PFromRva<PCImgThunkData>(pidd->rvaBoundIAT),PFromRva<PCImgThunkData>(pidd->rvaUnloadIAT),pidd->dwTimeStamp};DelayLoadInfo dli = {sizeof DelayLoadInfo,pidd,ppfnIATEntry,idd.szName,{ 0 },0,0,0};if (0 == (idd.grAttrs & dlattrRva)) {PDelayLoadInfo rgpdli[1] = { &dli };RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_INVALID_PARAMETER),0,1,PULONG_PTR(rgpdli));return 0;}HMODULE hmod = *idd.phmod;// Calculate the index for the IAT entry in the import address table// N.B. The INT entries are ordered the same as the IAT entries so// the calculation can be done on the IAT side.//const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT);const unsigned iINT = iIAT;PCImgThunkData pitd = &(idd.pINT[iINT]);dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal);if (dli.dlp.fImportByName) {dli.dlp.szProcName = LPCSTR(PFromRva<PIMAGE_IMPORT_BY_NAME>(RVA(UINT_PTR(pitd->u1.AddressOfData)))->Name);}else {dli.dlp.dwOrdinal = DWORD(IMAGE_ORDINAL(pitd->u1.Ordinal));}// Call the initial hook. If it exists and returns a function pointer,// abort the rest of the processing and just return it for the call.//FARPROC pfnRet = NULL;if (__pfnDliNotifyHook2) {pfnRet = ((*__pfnDliNotifyHook2)(dliStartProcessing, &dli));if (pfnRet != NULL) {goto HookBypass;}}// Check to see if we need to try to load the library.//if (hmod == 0) {if (__pfnDliNotifyHook2) {hmod = HMODULE(((*__pfnDliNotifyHook2)(dliNotePreLoadLibrary, &dli)));}if (hmod == 0) {hmod = ::LoadLibraryEx(dli.szDll, NULL, 0);}if (hmod == 0) {dli.dwLastError = ::GetLastError();if (__pfnDliFailureHook2) {// when the hook is called on LoadLibrary failure, it will// return 0 for failure and an hmod for the lib if it fixed// the problem.//hmod = HMODULE((*__pfnDliFailureHook2)(dliFailLoadLib, &dli));}if (hmod == 0) {PDelayLoadInfo rgpdli[1] = { &dli };RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND),0,1,PULONG_PTR(rgpdli));// If we get to here, we blindly assume that the handler of the exception// has magically fixed everything up and left the function pointer in // dli.pfnCur.//return dli.pfnCur;}}// Store the library handle. If it is already there, we infer// that another thread got there first, and we need to do a// FreeLibrary() to reduce the refcount//HMODULE hmodT = HMODULE(InterlockedExchangePointer((PVOID *) idd.phmod, PVOID(hmod)));if (hmodT == hmod) {::FreeLibrary(hmod);}}// Go for the procedure now.//dli.hmodCur = hmod;if (__pfnDliNotifyHook2) {pfnRet = (*__pfnDliNotifyHook2)(dliNotePreGetProcAddress, &dli);}if (pfnRet == 0) {if (pidd->rvaBoundIAT && pidd->dwTimeStamp) {// bound imports exist...check the timestamp from the target image//PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod));if (pinh->Signature == IMAGE_NT_SIGNATURE &&TimeStampOfImage(pinh) == idd.dwTimeStamp &&FLoadedAtPreferredAddress(pinh, hmod)) {// Everything is good to go, if we have a decent address// in the bound IAT!//pfnRet = FARPROC(UINT_PTR(idd.pBoundIAT[iIAT].u1.Function));if (pfnRet != 0) {goto SetEntryHookBypass;}}}pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName);}if (pfnRet == 0) {dli.dwLastError = ::GetLastError();if (__pfnDliFailureHook2) {// when the hook is called on GetProcAddress failure, it will// return 0 on failure and a valid proc address on success//pfnRet = (*__pfnDliFailureHook2)(dliFailGetProc, &dli);}if (pfnRet == 0) {PDelayLoadInfo rgpdli[1] = { &dli };RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND),0,1,PULONG_PTR(rgpdli));// If we get to here, we blindly assume that the handler of the exception// has magically fixed everything up and left the function pointer in // dli.pfnCur.//pfnRet = dli.pfnCur;}}SetEntryHookBypass:*ppfnIATEntry = pfnRet;HookBypass:if (__pfnDliNotifyHook2) {dli.dwLastError = 0;dli.hmodCur = hmod;dli.pfnCur = pfnRet;(*__pfnDliNotifyHook2)(dliNoteEndProcessing, &dli);}return pfnRet;}
上面的代码文件里都有。pidd是延迟导入表的指针,ppfnIATEntry是前面的0x0126b070。这个函数实际上会调用LoadLibrary来载入延迟导入表中记录的Dll名字(所以KERNEL32.dll别想用延迟导入来消除导入表中的记录了,这需要用其它方法),还会调用GetProcAddress取函数地址,然后把取到的地址写回0x0126b070,就是原来导入表的对应位置,最后函数返回的是延迟导入api的地址,因为参数在前面已经压栈,所以直接jmp eax执行。而因为修改的是iat(这里用的方法类似iathook),所以后面函数再调用到这个api的时候会直接call真实函数地址。当载入同dll的另一个api时会直接取到载入的dll基址然后取得函数地址再修改iat,所以定义中需要一个ModuleHandleRVA。
18、IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR【CLI头 - IMAGE_COR20_HEADER】
网上的介绍是:在最近更新的系统头文件中这个值已被改名为IMAGE_DIRECTORY_ENTRY_COMHEADER。它指向可执行文件中.NET信息的最高级别信息,包括元数据。这个信息是一个IMAGE_COR20_HEADER结构。但是我在Windows Kits 8.0中看到的名字还是原来的名,是不是错过了什么...
在.net程序中,pe结构的某些值是固定的,IMAGE_FILE_HEADER的PointerToSymbolTable、NumberOfSymbols固定为0,IMAGE_OPTIONAL_HEADER的ImageBase固定为0x400000,SectionAlignment为0x2000,FileAlignment为0x200或0x1000,数据目录只有导入表、重定位表、iat和现在说的这个结构有值外,其它都为0。有导入表重定位表和iat的原因是程序需要导入mscoree.dll的_CorExeMain(dll为_CorDllMain)。
这个是.Net程序特有的结构,其定义在winnt.h、corhdr.h里。其中cb为IMAGE_COR20_HEADER结构的大小,跟数据目录中的大小写的一样,Flags取值是枚举ReplacesCorHdrNumericDefines的COMIMAGE_FLAGS_XXX,这个结构很像可选头,因为里面内容几乎都为IMAGE_DATA_DIRECTORY结构,只不过不用下标取数据了而已。EntryPointToken和EntryPointRVA同属一个联合体,当COMIMAGE_FLAGS_NATIVE_ENTRYPOINT没被设置时,指的是托管入口点。StrongNameSignature需要设置COMIMAGE_FLAGS_STRONGNAMESIGNED,CodeManagerTable、ExportAddressTableJumps总是为0。下面用到的文档为Partition II Metadata(下载地址)。
下面开始(我看的英文版,可能会理解错误,见谅):
18.1、MetaDataRoot【元数据头(这里的名称取自CFF Explorer汉化版的翻译,下同)】
文档的176页,第24节。不知道在哪个头文件有定义的代码,估计也定义不了,里面有一项是字符串,但是没有使用指针形式而是直接包括在了元数据头中,所以这个头大小是可变的,这样就不好定义了。原本以为可以用C#的BinaryFormatter,但是发现序列化出来的东西只是相似而已,所以解析还是要自己写代码(我不想写啊)。
元数据头以一个magic number开头:0x424A5342(BJSB = BustinJieberShaBi - 开玩笑),这个字段叫Signature。其它定义下面的代码里有了。造成数据头变长的是版本号,这里使用字符串来记录(ntm逗我),而之后还要按四字节对齐。我还是强行“定义”出来了:
typedef struct _METADATA_ROOT
{_METADATA_ROOT(PVOID address):Signature((PDWORD)address) // address为MetaData的指针{MajorVersion = PWORD(Signature + 1);MinorVersion = PWORD(MajorVersion + 1);Reserved = PDWORD(MinorVersion + 1);Length= PDWORD(Reserved + 1);Version = PCHAR(Length + 1);// 后面有补齐的0,长度也在Length里了,所以Flags可以直接根据Length来得到Flags = PWORD(Version + *Length);Streams = PWORD(Flags+1);}PDWORD Signature;PWORD MajorVersion;PWORD MinorVersion;PDWORD Reserved;PDWORD Length;PCHAR Version; PWORD Flags;PWORD Streams;
}METADATA_ROOT,*PMETADATA_ROOT;
为了能偏移到文件中的位置,所以我都是以指针形式声明的,为的是以后能方便地修改。
18.2、StreamHeader【数据流表】
跟在元数据头后,Offset是相对MetaDataRoot的偏移;Size为指向的流的大小,必须为4的整数;Name是以0结尾并且以下一个4字节对齐的Ascii字符串(ntm逗我),最长为32字符。根据文档显示,有5种不同的堆(文档里用的词为heap):#String、#US、#Blob、#GUID、#~。每一种流最多出现一次,文档意思是如果没有这个流的话,这个流就不会出现在表里。下面简单说说几个流。
18.2.1、#String
里面可能会包含不会使用到的字符串(....heaps can contain garbage...),但它所包含的使用到的字符串都是utf8且以0结尾的。第一项是空字符串\0。这文档说的是字符串堆的物理表示法。
18.2.2、#US和#Blob
还是can contain garbage(ntm逗我),#US(user string,Unicode)存的是会用到的一些字符串,当字符串中UTF16字符的高位或者低位有0x01–0x08, 0x0E–0x1F, 0x27, 0x2D, 0x7F这些值的话,字符串最后的字节置1,否则置0(This final byte holds the value 1 if and only if any UTF16 character within the string has any bit set in its top byte, or its low byte is any of the following: 0x01–0x08, 0x0E–0x1F, 0x27, 0x2D, 0x7F. Otherwise, it holds 0.),#Blob是会用到的一些数据,每一个数据的前面有几个字节是用来描述后面的数据有多长的,原文如下:
· If the first one byte of the 'blob' is 0bbbbbbb2, then the rest of the 'blob' contains thebbbbbbb2 bytes of actual data.
· If the first two bytes of the 'blob' are 10bbbbbb2 andx, then the rest of the 'blob' contains the (bbbbbb2<< 8 + x) bytes of actual data.
· If the first four bytes of the 'blob' are 110bbbbb2,x, y, and z, then the rest of the 'blob' contains the (bbbbb2 << 24 + x<< 16 + y << 8 + z) bytes ofactual data.
这是说:
1.如果第一个字节的高1位为0,则后面包含以低7位值作为长度的数据。
2.如果第一字节高2位为10,则后面数据长度为(低6位<<8+第二字节的值)字节。
3.如果第一字节高3位为110,后面长度为(低5位<<24+第二字节值<<16+第三字节值<<8+第四字节值。
<<应该是移位符号,但是第三条算出的数是不是有点太大了
18.2.3、#GUID
保存一个以16字节表示的GUID的序列,里面的一些GUID有可能是不会用到的。
18.2.4、#~
这个结构我放后面说是因为文档介绍是在后面,这个结构其实是紧跟着数据流表的。的前4字节是保留位,置0。HeapSizes占1个字节,根据置位来确定其它几个流的每个堆所占的字长。如果对应位置1,该位所代表的流的堆就为4字节宽否则为2字节;第0位是#String,第1位是#GUID,第2位是#Blob。后面紧接着始终为1的保留字Reserved。接着的Valid和Sorted是64位字长的,前者每一位代表出现了哪些元数据表,后者为排序了哪些表。Valid因为有一些位没定义完作为保留,所以0x2c之上的值都为0。Rows是有n项的DWORD类型数组,这个n的值取决于前面Valid中1的个数(就是有几张表),这个数组内容为每张表的行数,没有的表就略掉。Rows之后就是表的内容了,同样也有n项。每个位的定义太多就不翻译了。
CLI头不好写定义,解析的时候还是边看文档边读出要读的数据。其它一些介绍可以看这篇文章,然后这篇(一、二)是讲pe格式的。
现在基本上pe中要解析的东西都说完了,写个界面把代码套进去就行了,按前面代码的结构应该不难扩展,界面就不上图了。因为C#用得比较多,所以命名规范不是用c++的,看得比较吃力请见谅。另外代码我在64位使用的时候发现有一些字长上的BUG,太麻烦就不改到博客上了。
编写PE文件解析器(三)相关推荐
- 图解VC++版PE文件解析器源码分析
该源码下载自 http://download.csdn.net/download/witch_soya/4979587 1 Understand 分析的图表 2 PE结构解析的主要代码简要分析 首先看 ...
- XML - XML学习/XML文件解析器(C++)实现
XML - XML学习/XML文件解析器(C++)实现 XML概述 XML是一套定义语义标记的规则,这些标记将文档分成许多部件并对这些部件加以标识.它也是元标记语言,用于定义其他与特定领域有关的, ...
- 实验五——手工编写PE文件
[实验名称] 手工编写PE文件 [实验目的] 1.了解PE文件的概念.结构 2.熟悉PE编辑查看工具,详细了解PE文件格式 3.重点分析PE文件文件头.引入表.引出表,以及资源表 [实验原理] 1.P ...
- 使用springMVC提供的CommonsMultipartResolver文件解析器,实现文件轻松上传
springMVC提供的前端控制器,可以拦截所有请求,指挥调度所有后台逻辑资源. 使用传统方式进行文件上传,需要我们手动解析request对象,获取文件上传项,再进行文件的上传. springMVC框 ...
- Win32汇编:PE结构解析器
PE格式是Windows系统下最常用的可执行文件格式,有些应用必须建立在了解PE文件格式的基础之上,如可执行文件的加密与解密,文件型病毒的查杀等,熟练掌握PE文件结构,有助于软件的分析. 在PE文件中 ...
- [翻译]运用文件解析器在任意文件中使用虚拟应用路径(~)
原文出处:http://www.codeproject.com Using the FileResolver to allow virtual application paths ( ~ ) i ...
- Glib学习(17) Key-value文件解析器 Key-value file parser
glib源码下载:http://ftp.gnome.org/pub/gnome/sources/glib/ glib帮助文档:https://developer.gnome.org/glib/ 本节主 ...
- torrent文件解析器
第二步工作是解析torrent文件,有了bencoding编码解析器 解析torrent文件当然是易如反掌的任务了. 实现的封装类CTorrentParser,完成的主要任务有: 1.判断torren ...
- 15.windbg-dds、dps、dqs、PE文件解析
以下默认windbg加载calc程序 d*s dds.dps和dqs命令显示给定范围内存的内容,它们是把内存区域转储出来,并把内存中每个元素都视为一个符号对其进行解析,dds是四字节视为一个符号,dq ...
最新文章
- shell在linux里摇摇晃晃
- 精简JRE第一步 — 精简bin目录
- java 字符串赋值_Java 学习笔记(二)变量
- Session的模拟
- P2P之UDP穿透NAT的原理与实现(转)
- 脚本语言php是什么意思,php是什么脚本语言
- 信息奥赛一本通(1325:【例7.4】 循环比赛日程表)
- php7 mcrypt模块_如何在php7.2/php7.3中安装mcrypt扩展?
- 新增成功到编制为空bug_36 个JS 面试题为你助力金九银10
- seata使用报错no available service found in cluster ‘default‘
- 树莓派开启samba服务
- Android读取电话薄中的电话号码
- snakeyaml jyaml 哪个好_lol手游哪个英雄可玩性高 英雄联盟手游英雄强度排行
- 云课堂智慧职教答案python_云课堂智慧职教答案python
- 最新全国行政区域编码(2018年12月)
- Mysql获取当天用户生日
- 四核64位处理器,MIMX8MQ5DVAJZAB 满足智能设备应用
- Mini CFA 考试练习题 Ethics and Investment Professionalism
- Redis服务入侵记
- 无法启用网络发现和文件共享或共享无法访问
热门文章
- pr系统兼容性报告不支持视频驱动程序有什么影响?怎么解决?
- 计算机经常突然死机重启,家里电脑最近经常会出现重启死机的现象是什么原因?...
- 刺激战场android闪退,《绝地求生刺激战场》老是闪退怎么办 老是闪退解决方法介绍...
- 荣耀V40怎么样 “微光女神”告诉你
- 红警ol服务器维护中1003,【图片】红警ol心灵终结3单位全面解析_红警ol吧_百度贴吧...
- 模电——电源与地之间并联电容的作用
- 小说光看还不够?当然得有美女一样的声音来阅读!
- Socket编程入门C++
- 新浪微博粉丝通推广效果分析
- 云游戏的2022:破局、新生、元宇宙