内存加载DLL

#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 ; 
} 


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 在Delphi中,可以使用`LoadLibrary`函数来加载一个动态链接库(DLL)到内存中。以下是一个简单的示例: ```delphi var hDll: THandle; // 动态链接库句柄 begin // 加载动态链接库 hDll := LoadLibrary('path\to\your\dll.dll'); if hDll = 0 then begin ShowMessage('无法加载动态链接库'); Exit; end; // 使用动态链接库中的函数或过程 // ... // 卸载动态链接库 FreeLibrary(hDll); end; ``` 首先,我们声明了一个变量`hDll`,用于存储动态链接库的句柄。然后,使用`LoadLibrary`函数加载指定路径下的DLL文件。如果加载成功,`LoadLibrary`函数会返回该DLL的句柄;如果加载失败,函数会返回0。 加载成功后,我们可以通过该动态链接库的句柄`hDll`来调用其内部的函数或过程。这些函数或过程的声明通常包含在相应的DLL头文件中。通过调用这些函数或过程,我们可以实现与DLL相关的功能。 最后,在不再需要使用动态链接库时,我们调用`FreeLibrary`函数来卸载该DLL并释放其内存。 需要注意的是,在使用动态链接库时,我们应该遵循相应的DLL文件的使用规则和API文档,确保正确使用并处理异常情况。 ### 回答2: 在Delphi中,我们可以使用LoadLibrary函数来加载DLL文件,并使用GetProcAddress函数来获取DLL中的函数地址。 使用LoadLibrary函数加载DLL文件时,我们需要提供DLL文件的完整路径。可以使用函数的返回值来检查是否成功加载DLL。如果返回值为0,表示加载失败;否则,返回一个非零的句柄,表示成功加载DLL加载成功后,我们可以使用GetProcAddress函数来获取DLL中函数的地址。该函数需要传入DLL的句柄和函数的名称,返回一个指向函数的指针。如果函数不存在,则返回空指针。 完成以上步骤后,我们就可以使用获取到的函数地址来调用DLL中的函数。可以通过指针类型转换来将函数指针赋值给相应的函数变量,然后通过调用该函数变量来调用DLL函数。需要注意的是,调用DLL中的函数需要使用stdcall调用约定,以确保参数传递和堆栈清理的正确性。 在使用完DLL后,我们需要使用FreeLibrary函数来释放DLL所占用的内存。该函数需要传入DLL的句柄,释放成功返回非零值,失败返回0。 通过以上步骤,我们可以在Delphi中成功加载和调用DLL函数,实现内存加载DLL的功能。 ### 回答3: Delphi 是一种流行的编程语言,可以通过内存加载 DLL(Dynamic Link Library)来实现一些功能。内存加载 DLL 意味着将 DLL 文件中的程序代码和数据加载内存中,并通过操作内存中的数据来调用 DLL 中的函数和方法。 在 Delphi 中,可以使用 TMemoryStream 类来实现内存加载 DLL。以下是一个简单的例子: 1. 首先,需要在 Delphi 项目中添加一个引用到 "Classes" 单元。 2. 创建一个 TMemoryStream 对象,并加载 DLL 文件的内容到这个对象中。 ```delphi var MemStream: TMemoryStream; begin MemStream := TMemoryStream.Create; try MemStream.LoadFromFile('path_to_dll.dll'); except // 处理加载 DLL 文件失败的情况 end; // 现在,DLL 文件的内容已经加载内存中 // 可以通过 MemStream 来访问内存中的数据 end; ``` 3. 接下来,可以使用 GetProcAddress 函数来获取内存DLL 文件中指定函数的地址,并将其的地址转换为相应的函数指针。然后,就可以调用该函数了。 ```delphi type TMyFunction = function(params): returnType; var MyFunction: TMyFunction; begin MyFunction := TMyFunction(GetProcAddress(MemStream.Memory, 'function_name')); if Assigned(MyFunction) then begin // 调用函数 MyFunction(params); end; end; ``` 通过以上步骤,我们可以在 Delphi 中实现内存加载 DLL 的功能。这种方法可以用于动态加载 DLL,特别是在某些情况下无法直接从文件系统加载 DLL 文件时。需要注意的是,在使用内存加载 DLL 时要小心内存泄漏和错误处理,以保证程序的稳定性和安全性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值