内存加载DLL

来源:互联网 发布:字画 网络 编辑:程序博客网 时间:2024/06/08 13:33
#include "stdafx.h"  typedef   BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,  LPVOID );  class CMemLoadDll { public:     CMemLoadDll();     ~CMemLoadDll();     BOOL    MemLoadLibrary( void* lpFileData , int DataLength);  // Dll file data buffer     FARPROC MemGetProcAddress(LPCSTR lpProcName); private:     BOOL isLoadOk;     BOOL CheckDataValide(void* lpFileData, int DataLength);     int  CalcTotalImageSize();     void CopyDllDatas(void* pDest, void* pSrc);     BOOL FillRavAddress(void* pBase);     void DoRelocation(void* pNewBase);     int  GetAlignedSize(int Origin, int Alignment); private:     ProcDllMain pDllMain;  private:     DWORD  pImageBase;     PIMAGE_DOS_HEADER pDosHeader;     PIMAGE_NT_HEADERS pNTHeader;     PIMAGE_SECTION_HEADER pSectionHeader; };  CMemLoadDll::CMemLoadDll() {     isLoadOk = FALSE;     pImageBase = NULL;     pDllMain = NULL; } CMemLoadDll::~CMemLoadDll() {     if(isLoadOk) {         ASSERT(pImageBase != NULL);         ASSERT(pDllMain   != NULL);         //脱钩,准备卸载dll         pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);         VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);     } }  //MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0x10000000 //返回值: 成功返回TRUE , 失败返回FALSE //lpFileData: 存放dll文件数据的缓冲区 //DataLength: 缓冲区中数据的总长度 BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength) {     if(pImageBase != NULL) {         return FALSE;  //已经加载一个dll,还没有释放,不能加载新的dll     } //检查数据有效性,并初始化     if(!CheckDataValide(lpFileData, DataLength))return FALSE; //计算所需的加载空间     int ImageSize = CalcTotalImageSize();     if(ImageSize == 0) return FALSE;  // 分配虚拟内存     void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,                                         MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);     if(pMemoryAddress == NULL) return FALSE;     else {         CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段         //重定位信息         if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress >0                 && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size>0) {             DoRelocation(pMemoryAddress);         }         //填充引入地址表         if(!FillRavAddress(pMemoryAddress)) { //修正引入地址表失败             VirtualFree(pMemoryAddress,0,MEM_RELEASE);             return FALSE;         }         //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。         //统一设置成一个属性PAGE_EXECUTE_READWRITE         unsigned long old;         VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);     } //修正基地址     pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;  //接下来要调用一下dll的入口函数,做初始化工作。     pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);     BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);     if(!InitResult) { //初始化失败         pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);         VirtualFree(pMemoryAddress,0,MEM_RELEASE);         pDllMain = NULL;         return FALSE;     }      isLoadOk = TRUE;     pImageBase = (DWORD)pMemoryAddress;     return TRUE; }  //MemGetProcAddress函数从dll中获取指定函数的地址 //返回值: 成功返回函数地址 , 失败返回NULL //lpProcName: 要查找函数的名字或者序号 FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName) {     if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||             pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)         return NULL;     if(!isLoadOk) return NULL;      DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;     DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;      PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);     int iBase = pExport->Base;     int iNumberOfFunctions = pExport->NumberOfFunctions;     int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions     LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);     LPWORD  pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);     LPDWORD pAddressOfNames  = (LPDWORD)(pExport->AddressOfNames + pImageBase);      int iOrdinal = -1;      if(((DWORD)lpProcName & 0xFFFF0000) == 0) { //IT IS A ORDINAL!         iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;     } else { //use name         int iFound = -1;          for(int i=0; i<iNumberOfNames; i++) {             char* pName= (char* )(pAddressOfNames[i] + pImageBase);             if(strcmp(pName, lpProcName) == 0) {                 iFound = i;                 break;             }         }         if(iFound >= 0) {             iOrdinal = (int)(pAddressOfOrdinals[iFound]);         }     }      if(iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) return NULL;     else {         DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];         if(pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart+Size))//maybe Export Forwarding             return NULL;         else return (FARPROC)(pFunctionOffset + pImageBase);     }  }  // 重定向PE用到的地址 void CMemLoadDll::DoRelocation( void *NewBase) {     /* 重定位表的结构:     // DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)     // 例如 1000节需要修正5个重定位数据的话,重定位表的数据是     // 00 10 00 00   14 00 00 00      xxxx xxxx xxxx xxxx xxxx 0000     // -----------   -----------      ----     // 给出节的偏移  总尺寸=8+6*2     需要修正的地址           用于对齐4字节     // 重定位表是若干个相连,如果address 和 size都是0 表示结束     // 需要修正的地址是12位的,高4位是形态字,intel cpu下是3     */ //假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000     DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;  //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址     PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase                                   + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);     while((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) { //开始扫描重定位表         WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));         //计算本节需要修正的重定位项(地址)的数目         int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);         for( int i=0 ; i < NumberOfReloc; i++) {             if( (DWORD)(pLocData[i] & 0xF000) == 0x00003000) { //这是一个需要修正的地址                 // 举例:                 // pLoc->VirtualAddress = 0x1000;                 // pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正                 // 因此 pAddress = 基地址 + 0x113E                 // 里面的内容是 A1 ( 0c d4 02 10)  汇编代码是: mov eax , [1002d40c]                 // 需要修正1002d40c这个地址                 DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));                 *pAddress += Delta;             }         }         //转移到下一个节进行处理         pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);     } }  //填充引入地址表 BOOL CMemLoadDll::FillRavAddress(void *pImageBase) { // 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是0表示结束 // 数组定义如下: //     // DWORD   OriginalFirstThunk;         // 0表示结束,否则指向未绑定的IAT结构数组     // DWORD   TimeDateStamp;     // DWORD   ForwarderChain;             // -1 if no forwarders     // DWORD   Name;                       // 给出dll的名字     // DWORD   FirstThunk;                 // 指向IAT结构数组的地址(绑定后,这些IAT里面就是实际的函数地址)     unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;     if(Offset == 0) return TRUE; //No Import Table     PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);     while(pID->Characteristics != 0 ) {         PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);         PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);         //获取dll的名字         char buf[256]; //dll name;         BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);         for(int i=0; i<256; i++) {             if(pName[i] == 0)break;             buf[i] = pName[i];         }         if(i>=256) return FALSE;  // bad dll name         else buf[i] = 0;         HMODULE hDll = GetModuleHandle(buf);         if(hDll == NULL)return FALSE; //NOT FOUND DLL         //获取DLL中每个导出函数的地址,填入IAT         //每个IAT结构是 :         // union { PBYTE  ForwarderString;         //   PDWORD Function;         //   DWORD Ordinal;         //   PIMAGE_IMPORT_BY_NAME  AddressOfData;         // } u1;         // 长度是一个DWORD ,正好容纳一个地址。         for(i=0; ; i++) {             if(pOriginalIAT[i].u1.Function == 0)break;             FARPROC lpFunction = NULL;             if(pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) { //这里的值给出的是导出序号                 lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));             } else { //按照名字导入                 //获取此IAT项所描述的函数名称                 PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)                                                 ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData)); //    if(pByName->Hint !=0) //     lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint); //    else                 lpFunction = GetProcAddress(hDll, (char *)pByName->Name);             }             if(lpFunction != NULL) { //找到了!                 pRealIAT[i].u1.Function = (PDWORD) lpFunction;             } else return FALSE;         }          //move to next         pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));     }     return TRUE; }  //CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件 //返回值: 是一个可执行的dll则返回TRUE,否则返回FALSE。 //lpFileData: 存放dll数据的内存缓冲区 //DataLength: dll文件的长度 BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength) { //检查长度     if(DataLength < sizeof(IMAGE_DOS_HEADER)) return FALSE;     pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;  // DOS头 //检查dos头的标记     if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;  //0x5A4D : MZ  //检查长度     if((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE; //取得pe头     pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE头 //检查pe头的合法性     if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return FALSE;  //0x00004550 : PE00     if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000  : File is a DLL         return FALSE;     if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行         return FALSE;     if(pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;  //取得节表(段表)     pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS)); //验证每个节表的空间     for(int i=0; i< pNTHeader->FileHeader.NumberOfSections; i++) {         if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)return FALSE;     }     return TRUE; }  //计算对齐边界 int CMemLoadDll::GetAlignedSize(int Origin, int Alignment) {     return (Origin + Alignment - 1) / Alignment * Alignment; } //计算整个dll映像文件的尺寸 int CMemLoadDll::CalcTotalImageSize() {     int Size;     if(pNTHeader == NULL)return 0;     int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段对齐字节数  // 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小     Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign); // 计算所有节的大小     for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i) {         //得到该节的大小         int CodeSize = pSectionHeader[i].Misc.VirtualSize ;         int LoadSize = pSectionHeader[i].SizeOfRawData;         int MaxSize = (LoadSize > CodeSize)?(LoadSize):(CodeSize);          int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);         if(Size < SectionSize)             Size = SectionSize;  //Use the Max;     }     return Size; } //CopyDllDatas函数将dll数据复制到指定内存区域,并对齐所有节 //pSrc: 存放dll数据的原始缓冲区 //pDest:目标内存地址 void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc) { // 计算需要复制的PE头+段表字节数     int  HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;     int  SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);     int  MoveSize = HeaderSize + SectionSize; //复制头和段信息     memmove(pDest, pSrc, MoveSize);  //复制每个节     for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i) {         if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;         // 定位该节在内存中的位置         void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);         // 复制段数据到虚拟内存         memmove((void *)pSectionAddress,                 (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),                 pSectionHeader[i].SizeOfRawData);     }  //修正指针,指向新分配的内存 //新的dos头     pDosHeader = (PIMAGE_DOS_HEADER)pDest; //新的pe头地址     pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew)); //新的节表地址     pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));     return ; }