本人接触pe文件格式不久,参考看雪上的一些资料写了一个pe loader,主要是通过把需要加载的文件的所所有section加载到相应的RVA上,然后进行重定位处理、导入表和导出表处理、资源段处理。由于本loader.exe会被加载到0x0f400000处,因此可以把0x400000给被加载的文件预留了,这样可以避免重定位所带来的性能损耗。导入表的处理主要是通过调用GetProcAddress获取导入表中函数的地址。导出表一般仅在dll中使用,而此主要是用于load exe文件,所以不用做任何处理。资源段处理资料不多,我这里好像有些问题,加载console程序可以正常执行,但是当加载gui程序则不能成功。望高手能指教一二。
执行结果:
加载cmd.exe会提示“系统无法在消息文件中为 Application 找到消息号为 0x2378 的消息文本“错误,不知道说何原因,也望高手指点一二。
主要代码:
代码:
// loader.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "LocalFile.h" /*把该loader.exe的加载地址设置为0x0f400000,从而可以把0x00400000地址 *预留给将要被加载的程序,从而可以避免因地址重定位而带来的性能损耗。 */ #pragma comment(linker, "/BASE:0x0f400000") inline int CDECL DebugPrint(const char *fmt,...) { int nLength = 0; #if defined(_DEBUG) va_list ap; va_start(ap, fmt); nLength = vprintf(fmt, ap); va_end(ap); #endif return nLength; } void DumpPeInfo(PeInfo *pInfo) { DebugPrint("------------headers info------------\n" \ "imageBase: 0x%x. \n" \ "entryPoint: 0x%x. \n" \ "sections: 0x%x. \n" \ "imageSize: 0x%x. \n" \ "exportRva: 0x%x. \n" \ "exportSize: 0x%x. \n" \ "importRva: 0x%x. \n" \ "importSize: 0x%x. \n" \ "resourceRva: 0x%x. \n" \ "resourceSize: 0x%x. \n" \ "relocRva: 0x%x. \n" \ "relocSize: 0x%x. \n" \ "debugRva: 0x%x. \n" \ "debugSize: 0x%x. \n" \ "offsetSections: 0x%x. \n" \ "fileType: %s. \n", pInfo->imageBase, pInfo->entryPoint, pInfo->sections, pInfo->imageSize, pInfo->exRva, pInfo->exSize, pInfo->imRva, pInfo->imSize, pInfo->resRva, pInfo->resSize, pInfo->relocRva, pInfo->relocSize, pInfo->dbgRva, pInfo->dbgSize, pInfo->offsetSection, pInfo->fileType == 0 ? "exe":"dll"); } BOOL IsPEFile(CLocalFile& lf) { IMAGE_DOS_HEADER dh; IMAGE_NT_HEADERS nh; lf.Read(0, sizeof(IMAGE_DOS_HEADER), &dh); if (IMAGE_DOS_SIGNATURE != dh.e_magic) { return FALSE; } lf.Read(dh.e_lfanew, sizeof(IMAGE_NT_HEADERS), &nh); if (IMAGE_NT_SIGNATURE != nh.Signature) { return FALSE; } return TRUE; } BOOL ParseNTHeader(CLocalFile &lf, PeInfo &pe) { IMAGE_DOS_HEADER dh; IMAGE_NT_HEADERS nh; PIMAGE_FILE_HEADER pfh; PIMAGE_OPTIONAL_HEADER32 poh; lf.Read(0, sizeof(IMAGE_DOS_HEADER), &dh); pe.offsetSection = dh.e_lfanew + sizeof(IMAGE_NT_HEADERS); lf.Read(dh.e_lfanew, sizeof(IMAGE_NT_HEADERS), &nh); pfh = &nh.FileHeader; poh = &nh.OptionalHeader; assert(IMAGE_FILE_MACHINE_I386 == pfh->Machine); assert(pfh->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32)); pe.sections = pfh->NumberOfSections; pe.imageBase = poh->ImageBase; pe.entryPoint = poh->AddressOfEntryPoint; pe.imageSize = poh->SizeOfImage; pe.exRva = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; pe.exSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; pe.imRva = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; pe.imSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; pe.resRva = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; pe.resSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size; pe.relocRva = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; pe.relocSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; pe.dbgRva = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; pe.dbgSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; pe.fileType = pfh->Characteristics == IMAGE_FILE_DLL ? IMAGE_FILE_DLL : 0; DumpPeInfo(&pe); return TRUE; } BOOL LoadSections(CLocalFile &lf, PeInfo &pe, MODULEINFO &mi) { PIMAGE_SECTION_HEADER psh, ptmp; DWORD protect = 0, oldProtect = 0; char name[9] = "\0"; psh = (PIMAGE_SECTION_HEADER)_alloca(pe.sections * sizeof(IMAGE_SECTION_HEADER)); if (NULL == psh) { printf("Memory not enough!\n"); return FALSE; } lf.Read(pe.offsetSection, pe.sections * sizeof(IMAGE_SECTION_HEADER), psh); ptmp = psh; DebugPrint("\n\n------------sections info------------\n" \ "name\tVA\tSOD\tPTR\tPTR\tPTL\tNOR\tNOL\tCrt\n"); /* 循环从被加载的文件中读取各个section内容,并存放在该section所指定的 * VirtualAddress地址空间中。 */ for (int i = 0; i < pe.sections; ++i, ptmp++) { memcpy(name, ptmp->Name, 8); if (NULL != ptmp->PointerToRawData && 0 != ptmp->SizeOfRawData) { lf.Read(ptmp->PointerToRawData, ptmp->SizeOfRawData, (void *)((DWORD)mi.lpBaseOfDll + ptmp->VirtualAddress)); } /* 此处代码主要功能是根据各个区段的Characteristics值, * 来设置其所在内存的页属性,但因在RelocPeModule机制重定位时还需要对相关内存 * 进行写入,因此此处的代码应该在RelocPeModule函数调用之后才能执行。 */ #if 0 if (ptmp->Characteristics & IMAGE_SCN_MEM_READ) { protect = PAGE_READONLY; } if (ptmp->Characteristics & IMAGE_SCN_MEM_WRITE) { protect = PAGE_READWRITE; } if (ptmp->Characteristics & IMAGE_SCN_MEM_EXECUTE) { if (protect & PAGE_READONLY) { protect = PAGE_EXECUTE_READ; } else if (protect & PAGE_READWRITE) { protect = PAGE_EXECUTE_READWRITE; } else { protect = PAGE_EXECUTE; } } if (!VirtualProtect((LPVOID)(ptmp->VirtualAddress + (DWORD)mi.lpBaseOfDll), ptmp->SizeOfRawData, protect, &oldProtect)) { printf("Set memory protection failed, error: %d\n", GetLastError()); return FALSE; } #endif DebugPrint("%-8s" \ "0x%x\t" \ "0x%x\t" \ "0x%x\t" \ "0x%x\t" \ "0x%x\t" \ "0x%x\t" \ "0x%x\t" \ "0x%x\n", name, ptmp->VirtualAddress, ptmp->SizeOfRawData, ptmp->PointerToRawData, ptmp->PointerToRelocations, ptmp->PointerToLinenumbers, ptmp->NumberOfRelocations, ptmp->NumberOfLinenumbers, ptmp->Characteristics); } return TRUE; } BOOL FixupResource(PIMAGE_RESOURCE_DIRECTORY pRes, DWORD imagebase, int offsetRlc) { PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry; DWORD nEntries; //DebugPrint("------------resource info------------\n" \ // "NumberOfNamedEntries: 0x%d\n" \ // "NumberOfIdEntries: 0x%d\n", // pRes->NumberOfIdEntries, pRes->NumberOfNamedEntries); nEntries = pRes->NumberOfIdEntries + pRes->NumberOfNamedEntries; pEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pRes + sizeof(IMAGE_RESOURCE_DIRECTORY)); for (DWORD i = 0; i < nEntries; ++i, ++pEntry) { //DebugPrint("\tName/Id: 0x%x\n\tOffsetOfData: 0x%x\n", // pEntry->Name, pEntry->OffsetToData); if (IMAGE_RESOURCE_DATA_IS_DIRECTORY & pEntry->OffsetToData) { PIMAGE_RESOURCE_DIRECTORY pRes2; PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry2; DWORD nEntries2; pRes2 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pRes + (~IMAGE_RESOURCE_DATA_IS_DIRECTORY & pEntry->OffsetToData)); nEntries2 = pRes2->NumberOfIdEntries + pRes2->NumberOfNamedEntries; pEntry2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pRes2 + sizeof(IMAGE_RESOURCE_DIRECTORY)); //DebugPrint("num of entries2: 0x%x\n", nEntries2); for (DWORD j = 0; j < nEntries2; ++j, ++pEntry2) { if (IMAGE_RESOURCE_NAME_IS_STRING & pEntry2->Name) { PIMAGE_RESOURCE_DIR_STRING_U pDirStr; pDirStr = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pRes + (~IMAGE_RESOURCE_NAME_IS_STRING & pEntry2->Name)); //DebugPrint("\tresource name: %S\n", pDirStr->NameString); } //DebugPrint("\tName/Id: 0x%x\n\tOffsetOfData: 0x%x\n", // pEntry2->Name, pEntry2->OffsetToData); if (IMAGE_RESOURCE_DATA_IS_DIRECTORY & pEntry2->OffsetToData) { PIMAGE_RESOURCE_DIRECTORY pRes3; PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry3; DWORD nEntries3; pRes3 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pRes + (~IMAGE_RESOURCE_DATA_IS_DIRECTORY & pEntry2->OffsetToData)); nEntries3 = pRes3->NumberOfIdEntries + pRes3->NumberOfNamedEntries; pEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pRes3 + sizeof(IMAGE_RESOURCE_DIRECTORY)); //printf("num of entries3: 0x%x\n", nEntries2); for (DWORD k = 0; k < nEntries3; ++k) { PIMAGE_RESOURCE_DATA_ENTRY pData; //DebugPrint("\tName/Id: 0x%x\n\tOffsetOfData: 0x%x\n", // pEntry3->Name, pEntry3->OffsetToData); assert(~IMAGE_RESOURCE_DATA_IS_DIRECTORY & pEntry3->OffsetToData); pData = (PIMAGE_RESOURCE_DATA_ENTRY)((DWORD)pRes + pEntry3->OffsetToData); pData->OffsetToData += (DWORD)imagebase; //printf("-------0x%x\n", pData->OffsetToData); } } } } } return TRUE; } BOOL RelocPeModule(PIMAGE_BASE_RELOCATION pBlc, DWORD imagebase, int offsetRlc) { DWORD vaddr, count, offset, type; WORD *items = NULL; while (NULL != pBlc->VirtualAddress) { vaddr = imagebase + pBlc->VirtualAddress; count = (pBlc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) >> 1; items = (WORD *)((char *)pBlc + sizeof(IMAGE_BASE_RELOCATION)); for (DWORD i = 0; i < count; ++i) { offset = items[i] & 0x0fff; type = items[i] >> 12; if (type == 3) { *(DWORD *)(vaddr + offset) += offsetRlc; } } pBlc = (PIMAGE_BASE_RELOCATION)(items + count); } return TRUE; } BOOL FixupExport(PIMAGE_EXPORT_DIRECTORY pExp, DWORD imagebase) { return TRUE; } BOOL FixupImport(PIMAGE_IMPORT_DESCRIPTOR pImp, DWORD imagebase) { PIMAGE_THUNK_DATA pOrgThunk, pFirstThunk; PIMAGE_IMPORT_BY_NAME pImportName; DebugPrint("\n\n------------import table info------------\n"); while (NULL != pImp->OriginalFirstThunk) { pImp->Name += imagebase; DebugPrint("DLL: %s\n", pImp->Name); FARPROC fpFun; HINSTANCE hInstance = LoadLibraryA((LPCSTR)pImp->Name); if (NULL == hInstance) { printf("Load library %s failed, error: %d\n", pImp->Name, GetLastError()); return FALSE; } pOrgThunk = (PIMAGE_THUNK_DATA)(imagebase + pImp->OriginalFirstThunk); pFirstThunk = (PIMAGE_THUNK_DATA)(imagebase + pImp->FirstThunk); while (NULL != *(DWORD *)pOrgThunk) { if (pOrgThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32) { fpFun = GetProcAddress(hInstance, (LPCSTR)(pOrgThunk->u1.Ordinal & 0x0000ffff)); //DebugPrint("\t0x%x\n", pOrgThunk->u1.Ordinal); } else { pImportName = (PIMAGE_IMPORT_BY_NAME)(imagebase + pOrgThunk->u1.AddressOfData); fpFun = GetProcAddress(hInstance, (LPCSTR)pImportName->Name); //DebugPrint("\t%s\n", pImportName->Name); } //DebugPrint("\t\t0x%x\n", fpFun); pFirstThunk->u1.Ordinal = (LONG)fpFun; ++pFirstThunk; ++pOrgThunk; } FreeLibrary(hInstance); ++pImp; } return TRUE; } int LoadPeModule(const char * name, int argc, _TCHAR* argv[]) { CLocalFile lf(name); LPVOID addr; PeInfo pe; MODULEINFO mi; /* 验证是否为合法的pe文件 */ assert(IsPEFile(lf)); /* 处理nt header,获取pe文件的相关信息 */ ParseNTHeader(lf, pe); /*为image按照imageBase优先原则分配空间地址。 *如该空间地址已被reserved或commit,则重新分配一个可用的空间地址, *但此种情况下需要做基址重定位处理。*/ addr = VirtualAlloc((LPVOID)(pe.imageBase), pe.imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NULL == addr) { printf("VirtualAlloc failed, error: %d\n", GetLastError()); addr = VirtualAlloc(NULL, pe.imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (NULL == addr) { printf("VirtualAlloc failed, error: %d\n", GetLastError()); return -1; } } memset((void *)addr, 0, pe.imageSize); mi.EntryPoint = (LPVOID)pe.entryPoint; mi.lpBaseOfDll = (LPVOID)addr; mi.SizeOfImage = pe.imageSize; /* 把sections加载到内存 */ LoadSections(lf, pe, mi); /* 如果实际分配的空间地址和pe文件的基址不一样,则需要做基址重定位处理 */ if ((int)mi.lpBaseOfDll != pe.imageBase) { if (0 == pe.relocSize) { printf("Cannot reloc address!\n"); return -1; } PIMAGE_BASE_RELOCATION pBrlc = (PIMAGE_BASE_RELOCATION)((DWORD)mi.lpBaseOfDll + pe.relocRva); RelocPeModule(pBrlc, (DWORD)mi.lpBaseOfDll, (DWORD)mi.lpBaseOfDll - pe.imageBase); } /* 如果该pe文件有导出表,则处理导出表区段 */ if (0 != pe.exSize) { PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)((DWORD)mi.lpBaseOfDll + pe.exRva); FixupExport(pExp, (DWORD)mi.lpBaseOfDll); } /* 如果pe文件有资源文件,则需要处理资源区段 */ if (0 != pe.resSize) { PIMAGE_RESOURCE_DIRECTORY pRes = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)mi.lpBaseOfDll + pe.resRva); FixupResource(pRes, (DWORD)mi.lpBaseOfDll, (DWORD)mi.lpBaseOfDll - pe.imageBase); } /* 如果pe文件有导入表,则需要处理导入表区段 */ if (0 != pe.imSize) { PIMAGE_IMPORT_DESCRIPTOR pImp = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)mi.lpBaseOfDll + pe.imRva); FixupImport(pImp, (DWORD)mi.lpBaseOfDll); } DebugPrint("\n\nentry: 0x%x\n\n", (DWORD)mi.EntryPoint + (DWORD)mi.lpBaseOfDll); /* 进入该pe文件的entry pointer,执行该pe文件 */ __asm { push argc; push argv; mov eax, mi.EntryPoint; add eax, mi.lpBaseOfDll; call eax; } } int _tmain(int argc, _TCHAR* argv[]) { LoadPeModule("cmd.exe", argc - 1, argv + 1); return 0; }