青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

S.l.e!ep.¢%

像打了激速一樣,以四倍的速度運轉,開心的工作
簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

【轉載】一種保護應用程序的方法 模擬Windows PE加載器,從內存資源中加載DLL

編程 2008-07-27 21:23:47 閱讀375 評論0 字號:

來自 老實和尚博客

1、前言

目前很多敏感和重要的DLL(Dynamic-link library) 都沒有提供靜態版本供編譯器進行靜態連接(.lib文件),即使提供了靜態版本也因為兼容性問題導致無法使用,而只提供DLL版本,并且很多專業軟件的授權部分的API,都是單獨提供一個DLL來完成,而主模塊通過調用DLL中的接口來完成授權功能。雖然這些軟件一般都采用了加殼和反調試等保護,但是一旦這些功能失去作用,比如脫殼,反反調試,HOOK API或者干脆寫一個仿真的授權DLL(模擬授權DLL的所有導出函數接口),然后仿真的DLL再調用授權DLL,這樣所有的輸入首先被仿真DLL截獲再傳遞給授權DLL,而授權DLL的輸出也首先傳遞給仿真DLL再傳遞給主程序,這樣就可以輕易的監視二者之間的輸入輸出之間的關系,從而輕易的截獲DLL中的授權信息進行修改再返回給主程序。

2、目前隱式調用敏感DLL中可能存在的安全隱患

以下通過兩個軟件的授權DLL來說明這種問題的嚴重性。如下是兩個軟件中授權DLL的部分信息,如下圖所示:

?

(圖1)

通過工具OllyICE可以輕易的看出IoMonitor.exe調用授權DLL(XKeyAPI.DLL),這樣就很容易在調用這些API的地方設置斷點,然后判斷輸入輸出的關系,從而達到破解的目的。

?

(圖2)

通過工具OllyICE可以輕易的看出sfeng.DLL中導出了很多函數,其中含義也很明顯。GetHDID獲取硬盤的ID,GetCpuId獲取cpu的ID,WinAntiDebug反調試接口。而這些都是主程序需要調用的,比如:主程序通過GetHDID來獲取硬盤編碼,以這個硬盤ID的偽碼來生成授權碼,破解者很容易修改這些接口的輸出值或者干脆寫一個sfeng.DLL來導出跟目標sfeng.DLL一模一樣的導出函數,而主程序卻完全不知曉。只要用戶有一套授權碼就可以讓GetHDID不管什么機器都返回一樣的值,從而達到任何機器都可以使用同一套授權碼。

?

(圖3)

如上圖所示,直接修改DLL中函數GetHDID(RVA地址:0093FF3C開始)的實現,讓它直接返回固定的硬盤ID就可以達到一個授權到處使用的目的。其中:”WD-Z=AM9N086529ksaiy”為需要返回的已經授權的硬盤ID,我們直接返回這個值即可。把原來0093FF3C 部分的代碼用nop替換掉,添加Call 008FFF60,后面添加字符串”WD-Z=AM9N086529ksaiy”,Call 008FFF60之后,ESP=Call后的返回地址(Call指令的下一行),也就是字符串”WD-Z=AM9N086529ksaiy”的首地址,然后pop EAX 后,返回值就是字符串的首地址,通過這種簡單的修改就可以達到破解的目的,說明這種隱式的調用是非常危險的。

3、模擬Windows PE加載器,從資源中加載DLL

本文主要介紹將DLL文件進行加密壓縮后存放在程序的資源段,然后在程序中讀取資源段數據進行解壓和解密工作后,從內存中加載這個DLL,然后模擬PE加載器完成DLL的加載過程。本文主要以Visual C++ 6.0為工具進行介紹,其它開發工具實現過程與此類似。

這樣作的好處也很明顯,DLL文件存放在主程序的資源段,而且經過了加密壓縮處理,破解者很難找到下斷點的地方,也不能輕易修改資源DLL,因為只有主程序完成解壓和解密工作,完成PE加載工作后此DLL才開始工作。

我們知道,要顯式加載一個DLL,并取得其中導出的函數地址一般是通過如下步驟:
(1) 用LoadLibrary加載DLL文件,獲得該DLL的模塊句柄;
(2) 定義一個函數指針類型,并聲明一個變量;
(3) 用GetProcAddress取得該DLL中目標函數的地址,賦值給函數指針變量;
(4) 調用函數指針變量。
這個方法要求DLL文件位于硬盤上面,而我們的DLL現在在內存中。現在假設我們的DLL已經位于內存中,比如通過脫殼、解密或者解壓縮得到,能不能不把它寫入硬盤文件,而直接從內存加載呢?答案是肯定的,方法就是完成跟Windows PE加載器同樣的工作即可。

加載過程大致包括以下幾個部分:

1 、調用 API 讀取 DLL 資源數據拷貝到內存中

2 、調用解壓和解密函數對內存中的DLL 進行處理

3 、檢查DOS 頭和PE 頭判斷是否為合法的PE 格式

4 、計算加載該DLL 所需的虛擬地址空間大小

5 、向操作系統申請指定大小的虛擬地址空間并提交

6 、將DLL 數據復制到所分配的虛擬內存塊中,注意文件段對齊方式和內存段對齊方式

7 、對每個 DLL 文件來說都存在一個重定位節(.reloc) ,用于記錄DLL 文件的重定位信息,需要處理重定位信息

8 、讀取DLL 的引入表部分,加載引入表部分需要的DLL ,并填充需要的函數入口的真實地址

9 、根據DLL 每個節的屬性設置其對應內存頁的讀寫屬性

10 、調用入口函數DLLMain ,完成初始化工作

11 、保存DLL 的基地址(即分配的內存塊起始地址), 用于查找DLL 的導出函數

12 、不需要DLL 的時候,釋放所分配的虛擬內存,釋放所有動態申請的內存

以下部分分別介紹這幾個步驟,以改造過的網上下載的CMemLoadDLL類為例程(原類存在幾個錯誤的地方)

A. 調用 API 讀取 DLL 資源數據拷貝到內存中

// 加載資源 DLL

#define strKey (char)0x15

char DLLtype[4]={'D' ^ strKey ,'l'^ strKey,'l'^ strKey,0x00};

HINSTANCE hinst=AfxGetInstanceHandle();

HRSRC hr=NULL;

HGLOBAL hg=NULL;

// 對資源名稱字符串進行簡單的異或操作,達到不能通過外部字符串參考下斷點

for(int i=0;i<sizeof(DLLtype)-1;i++)

{

DLLtype[i]^=strKey;

}

hr=FindResource(hinst,MAKEINTRESOURCE(IDR_DLL),TEXT(DLLtype));

if (NULL == hr) return FALSE;

// 獲取資源的大小

DWORD dwSize = SizeofResource(hinst, hr);

if (0 == dwSize) return FALSE;

hg=LoadResource(hinst,hr);

if (NULL == hg) return FALSE;

// 鎖定資源

LPVOID pBuffer =(LPSTR)LockResource(hg);

if (NULL == pBuffer) return FALSE;

FreeResource(hg); // 在資源使用完畢后我們不需要使用 UnlockResource FreeResource 來手動地釋放資源,因為它們都是 16 Windows 遺留下來的,在 Win32 中,在使用完畢后系統會自動回收

B. 調用解壓和解密函數對內存總的 DLL 進行處理

對于上面獲取的pBuffer可以進行解壓和解密操作,算法應該跟你加入的資源采取的算法進行逆變換即可,具體算法可以自己選擇,此處省略。

C. 檢查 DOS 頭和 PE 頭判斷是否為合法的 PE 格式

//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; //0*5A4D : 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; //0*00004550 : PE00

if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0*2000 : File is a DLL

return FALSE;

if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0*0002 : 指出文件可以運行

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;

}

D. 計算加載該 DLL 所需的虛擬地址空間大小

計算整個DLL映像文件的尺寸,最大映像尺寸應該為VOffset最大的一個段的VOffset+VSize,然后補齊段對齊即可。如下圖中,最大映像尺寸應該為0x0000D000+0x00000DA6,然后按段對齊(如為:0x1000對齊)則結果為0x0000E000。其中DOS Header和PE Header就占用0x1000字節,代碼段.text從0x1000開始占用了0x7000字節。

段名稱   虛擬地址  虛擬大小  物理地址 物理大小  標志

?

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;

}

// 計算對齊邊界

int CMemLoadDLL::GetAlignedSize(int Origin, int Alignment)

{

return (Origin + Alignment - 1) / Alignment * Alignment;

}

E. 向操作系統申請指定大小的虛擬地址空間并提交

調用操作系統API VirtualAlloc保留指定大小的虛擬內存并提交內存,VirtualAlloc的第一個參數不能指定地址,如果指定地址已經被占用或者指定地址后面沒有足夠的連續的地址空間來滿足提交的大小則會調用失敗,而我們也沒有必要獲取指定地址空間,這樣第一個參數必須保留為NULL(0)。

void *pMemoryAddress=VirtualAlloc((LPVOID)NULL, ImageSize,MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if(pMemoryAddress == NULL)

{

return FALSE;

}

F. DLL 數據復制到所分配的虛擬內存塊中,注意文件段對齊方式和內存段對齊方式

拷貝內存DLL到提交的虛擬地址空間,拷貝的部分包括PE文件的所有部分,DOS Header、 PE Header 、Section Table、Section 1~Section N,如下圖所示:

DOS MZ header

DOS stub

PE header

Section table

Section 1

Section 2

Section ...

Section n

//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 ;

}

G. 每個 DLL 文件來說都存在一個重定位節( .reloc) ,用于記錄 DLL 文件的重定位信息,需要處理重定位信息

Windows加載DLL時就可以按照該節的信息對需要重定位的地址進行修正,在32位代碼中,凡涉及到直接尋址的指令都是需要重定位的,而PE文件的的(.reloc)段則是可選的,因為PE文件一般都可以加載到默認地址(如:0x00400000)。當然系統的DLL其默認加載地址都能滿足要求,因為這些DLL都在系統加載其它程序前首先被加載(如:Kernel32.DLL,User32.DLL)等。

對于操作系統來說,其任務就是在對可執行程序透明的情況下完成重定位操作,在現實中,重定位信息是在編譯的時候由編譯器生成并被保留在可執行文件中的,在程序被執行前由操作系統根據重定位信息修正代碼,這樣在開發程序的時候就不用考慮重定位問題了。

重定位信息在DLL文件中被存放在重定位表中,重定位的算法可以描述為:將直接尋址指令中的雙字地址加上模塊實際裝入地址與模塊建議裝入地址之差。為了進行這個運算,需要有3個數據,首先是需要修正的機器碼地址;其次是模塊的建議裝入地址;最后是模塊的實際裝入地址。

在這3個數據中,模塊的建議裝入地址已經在PE文件頭中定義了(編譯后就已經確定),而模塊的實際裝入地址是Windows裝載器確定的,到裝載文件的時候自然會知道,所以被保存在重定位表中的僅僅是需要修正的代碼的地址。

事實上正是如此,DLL文件的重定位表中保存的就是一大堆需要修正的代碼的地址。

重定位表一般會被單獨存放在一個可丟棄的以“.reloc”命名的節中,但是這并不是必然的,因為重定位表放在其他節中也是合法的,惟一可以肯定的是,假如重定位表存在的話,它的地址肯定可以在DLL文件頭中的數據目錄中找到。重定位表的位置和大小可以從數據目錄中的第6個 IMAGE_DATA_DIRECTORY結構中獲取,雖然重定位表中的有用數據是那些需要重定位機器碼的地址指針,但為了節省空間,DLL文件對存放的方式做了一些優化。

在正常的情況下,每個32位的指針占用4個字節,假如有n個重定位項,那么重定位表的總大小是4×n字節大小。 直接尋址指令在程序中還是比較多的,在比較靠近的重定位表項中,32位指針的高位地址總是相同的,假如把這些相近表項的高位地址統一表示,那么就可以省略一部分的空間,當按照一個內存頁來分割時,在一個頁面中尋址需要的指針位數是12位(一頁等于4096字節,等于2的12次方),假如將這12位湊齊16 位放入一個字類型的數據中,并用一個附加的雙字來表示頁的起始指針,另一個雙字來表示本頁中重定位項數的話,那么占用的總空間會是4+4+2×n字節大 小,計算一下就可以發現,當某個內存頁中的重定位項多于4項的時候,后一種方法的占用空間就會比前面的方法要小。

// 重定向 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 0×600000, 而文件中設置的缺省 ImageBase 0×400000, 則修正偏移量就是 0×200000

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 = 0×1000;

// pLocData[i] = 0×313E; 表示本節偏移地址 0×13E 處需要修正

// 因此 pAddress = 基地址 + 0×113E

// 里面的內容是 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);

}

}

H. 讀取 DLL 的引入表部分,加載引入表部分需要的 DLL ,并填充需要的函數入口的真實地址

對引入表中的DLL,通過GetModuleHandle獲得其加載基地址,如果這些DLL在加載本DLL之前還沒有加載,那么先調用LoadLibrary進行加載,如果加載失敗則不能繼續處理直接報錯,說明找不到依賴的DLL。

// 填充引入地址表

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;

// 修改 , 需要將 buf 清零 , 否則 DLL 名稱不對

memset(buf,0,sizeof(buf));

BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);

for(int i=0;i<256;i++)

{

if(pName[i] == 0)break;

buf[i] = pName[i];

}

HMODULE hDLL = GetModuleHandle(buf);

if(hDLL == NULL)

{

hDLL = LoadLibrary (buf); // 有可能依賴的 DLL 還沒有加載 , 如果沒有加載加載后再判斷是否加載成功

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;

}

I. 根據 DLL 每個節的屬性設置其對應內存頁的讀寫屬性

修改段屬性。應該根據每個段的屬性單獨設置其對應內存頁的屬性。這里簡化一下。

統一設置成一個屬性PAGE_EXECUTE_READWRITE,如果代碼段沒有執行屬性,調用的時候會產生異常,頁屬性的設置單位至少為一個頁。

unsigned long old;

VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);

J. 調用入口函數 DLLMain ,完成初始化工作

接下來要調用一下DLL的入口函數,做初始化工作,每個PE文件都有一個OEP, 它就是AddressOfEntryPoint,一切代碼都是從這里開始,OEP+DLL基地址就是其真實入口地址,當然這個入口地址一般都不是你所寫的main或者DLLMain,而是運行庫提供的一段代碼,先完成全局變量的一些初始化和庫函數相關的初始化等,而這段代碼最后會調用真正的main或者DLLMain。

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;

}

K.  保存 DLL 的基地址(即分配的內存塊起始地址) , 用于查找 DLL 的導出函數

// 修正基地址

pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;

//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);

}

}

L. 不需要 DLL 的時候,釋放所分配的虛擬內存,釋放所有動態申請的內存

CMemLoadDll::~CMemLoadDll()

{

if(isLoadOk)

{

ASSERT(pImageBase != NULL);

ASSERT(pDllMain != NULL);

// 脫鉤,準備卸載 dll

pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);

VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);

}

}

4、全部詳細代碼

// 以下代碼經過Win2k Sp4/WinXp Sp2 下測試通過

// MemLoadDll.h: interface for the CMemLoadDll class.

//

//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MEMLOADDLL_H__E1F5150A_B534_4940_9FBF_1E6CA0E50576__INCLUDED_)

#define AFX_MEMLOADDLL_H__E1F5150A_B534_4940_9FBF_1E6CA0E50576__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

typedef BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD, LPVOID );

class CMemLoadDll

{

public:

CMemLoadDll();

virtual ~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;

};

#endif // !defined(AFX_MEMLOADDLL_H__E1F5150A_B534_4940_9FBF_1E6CA0E50576__INCLUDED_)

// MemLoadDll.cpp: implementation of the CMemLoadDll class.

//

//////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "MemLoadDll.h"

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

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 到當前進程的地址空間,缺省位置0×10000000

// 返回值: 成功返回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);

// 修改, 不指定dll 基址申請內存

void *pMemoryAddress = VirtualAlloc((LPVOID)NULL, 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 是0×600000, 而文件中設置的缺省ImageBase 是0×400000, 則修正偏移量就是0×200000

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 = 0×1000;

// pLocData[i] = 0×313E; 表示本節偏移地址0×13E 處需要修正

// 因此 pAddress = 基地址 + 0×113E

// 里面的內容是 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;

// 修改, 需要buf 清零, 否則dll 名稱不對

memset(buf,0,sizeof(buf));

BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);

for(int i=0;i<256;i++)

{

if(pName[i] == 0)break;

buf[i] = pName[i];

}

HMODULE hDll = GetModuleHandle(buf);

if(hDll == NULL)

{

hDll = LoadLibrary (buf); // 有可能依賴的dll 還沒有加載, 如果沒有加載加載后再判斷是否加載成功

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; //0*5A4D : 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; //0*00004550 : PE00

if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0*2000 : File is a DLL

return FALSE;

if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0*0002 : 指出文件可以運行

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 ;

}

// 加載資源 DLL

#define strKey (char)0x15

char DLLtype[4]={'D' ^ strKey ,'l'^ strKey,'l'^ strKey,0x00};

HINSTANCE hinst=AfxGetInstanceHandle();

HRSRC hr=NULL;

HGLOBAL hg=NULL;

// 對資源名稱字符串進行簡單的異或操作,達到不能通過外部字符串參考下斷點

for(int i=0;i<sizeof(DLLtype)-1;i++)

{

DLLtype[i]^=strKey;

}

hr=FindResource(hinst,MAKEINTRESOURCE(IDR_DLL),TEXT(DLLtype));

if (NULL == hr) return FALSE;

// 獲取資源的大小

DWORD dwSize = SizeofResource(hinst, hr);

if (0 == dwSize) return FALSE;

hg=LoadResource(hinst,hr);

if (NULL == hg) return FALSE;

// 鎖定資源

LPVOID pBuffer =(LPSTR)LockResource(hg);

if (NULL == pBuffer) return FALSE;

// pBuffer 進行處理

pMemLoadDll=new CMemLoadDll();

if(pMemLoadDll->MemLoadLibrary(pBuffer, dwSize)) // 加載 dll 到當前進程的地址空間

{

for(int i=0;i<sizeof(dllname)-1;i++)

{

dllname[i]^=strKey;

}

SENSE3 = (DllSENSE3)pMemLoadDll->MemGetProcAddress(dllname);

if(SENSE3 == NULL)

{

return TRUE;

}

}

FreeResource(hg); // 在資源使用完畢后我們不需要使用 UnlockResource FreeResource 來手動地釋放資源,因為它們都是 16 Windows 遺留下來的,在 Win32 中,在使用完畢后系統會自動回收。

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美在线不卡视频| 欧美一区二区三区视频免费| 国产精品久久久久永久免费观看| 免费看的黄色欧美网站| 欧美激情1区2区| 欧美日韩中文字幕精品| 欧美体内she精视频在线观看| 国产精品jvid在线观看蜜臀| 国产精品美女久久久久久久| 黑丝一区二区三区| 日韩一区二区精品葵司在线| 亚洲一区欧美二区| 久久精品1区| 欧美激情一区二区三区在线视频观看 | 午夜精品国产更新| 老司机67194精品线观看| 亚洲黄色成人| 亚洲欧美偷拍卡通变态| 久久久99久久精品女同性| 欧美顶级艳妇交换群宴| 国产精品视频免费观看www| 在线国产精品播放| 亚洲欧美另类久久久精品2019| 久久久噜噜噜久噜久久| 亚洲精品国产视频| 久久成人综合视频| 国产精品久久波多野结衣| 亚洲国产成人av在线| 欧美一区二区大片| 亚洲毛片在线| 母乳一区在线观看| 国产手机视频一区二区| 日韩视频免费在线| 美日韩精品免费| 亚洲尤物精选| 欧美日韩你懂的| 在线观看亚洲专区| 久久国产加勒比精品无码| 亚洲日韩视频| 免费观看亚洲视频大全| 国产一区观看| 久久超碰97中文字幕| 中日韩在线视频| 欧美日韩国产一区二区三区| 亚洲国产专区| 老鸭窝91久久精品色噜噜导演| 欧美与欧洲交xxxx免费观看| 久久久久久久激情视频| 国产精品久久久久久超碰| 亚洲一区尤物| 免费亚洲网站| 久久国产主播| 国产午夜精品一区二区三区欧美| 中国av一区| 亚洲精品国产精品乱码不99按摩| 美女日韩欧美| 亚洲精品国产精品乱码不99 | 欧美日韩国产影片| 夜夜嗨av色综合久久久综合网| 欧美激情一区二区三区成人| 美国十次成人| 亚洲精品久久在线| 欧美激情一区二区三区在线| 免费成人在线视频网站| 亚洲黄色在线视频| 亚洲精品免费一区二区三区| 欧美日韩久久不卡| 亚洲综合精品一区二区| 亚洲一区二区三区视频播放| 国产精品主播| 久久久一区二区| 久久久久久有精品国产| 亚洲国产精品va在线看黑人| 亚洲国产日韩一区二区| 欧美日韩国产一区精品一区 | 美女国内精品自产拍在线播放| 亚洲国产精品一区二区尤物区| 欧美成人免费小视频| 女主播福利一区| 一区二区欧美日韩视频| 亚洲一区二区三区三| 黄色成人av在线| 亚洲精品社区| 国产日韩在线不卡| 亚洲福利视频在线| 国产精品夜色7777狼人| 欧美成人精品一区| 欧美少妇一区二区| 久久青青草原一区二区| 欧美国产视频一区二区| 欧美一区2区视频在线观看| 久久人人看视频| 亚洲一二三级电影| 久久精品国产免费观看| 99视频有精品| 欧美在线视屏| 中文欧美字幕免费| 久久精品中文字幕免费mv| 亚洲精品乱码久久久久久蜜桃麻豆| 一区二区三区视频观看| 亚洲大胆女人| 欧美一区激情视频在线观看| 亚洲福利专区| 亚洲一区二区三区涩| 亚洲激情在线| 午夜精品亚洲| 99视频超级精品| 久久全球大尺度高清视频| 亚洲欧美韩国| 欧美日韩亚洲国产精品| 欧美激情欧美激情在线五月| 国产一区二区三区日韩欧美| 在线亚洲自拍| av成人毛片| 欧美成人黑人xx视频免费观看| 午夜精品免费视频| 欧美另类一区| 亚洲电影在线观看| 伊人天天综合| 欧美一区激情视频在线观看| 性欧美激情精品| 欧美偷拍另类| 亚洲日本欧美| 一区二区欧美国产| 欧美日韩精品久久| 亚洲黄色影院| 亚洲精品一区二区三区在线观看| 久久久www成人免费精品| 久久久久久久综合日本| 国产欧美精品久久| 亚洲欧美在线一区二区| 午夜精品久久久久影视| 国产精品福利av| 这里只有精品视频在线| 亚洲一区二区在线观看视频| 欧美日韩免费网站| 一区二区国产日产| 亚洲免费在线视频| 国产精品久久久久9999高清 | 亚洲美女中文字幕| 一本色道久久88精品综合| 欧美精品一级| 91久久精品国产91久久性色| 亚洲三级国产| 欧美国产丝袜视频| 99国产精品久久久| 欧美一级成年大片在线观看| 国产欧美视频一区二区三区| 欧美一区二区三区免费大片| 久久精品人人做人人爽电影蜜月| 国产亚洲综合精品| 久久亚洲色图| 亚洲精品久久久久久久久久久| 99在线热播精品免费| 欧美色网一区二区| 新67194成人永久网站| 久久午夜国产精品| 亚洲区国产区| 欧美小视频在线| 欧美一区二区三区婷婷月色| 蜜臀久久久99精品久久久久久| 亚洲国产专区校园欧美| 欧美日韩国产在线观看| 午夜久久久久久| 欧美国产日韩在线| 亚洲综合色在线| 在线播放中文一区| 欧美三级特黄| 久久综合九色九九| 久久躁狠狠躁夜夜爽| 狂野欧美激情性xxxx欧美| 韩日精品在线| 欧美另类极品videosbest最新版本| 一本久久综合亚洲鲁鲁| 久久久999精品| 亚洲美女视频网| 国产区欧美区日韩区| 欧美成人精品一区| 亚洲欧美一区二区原创| 亚洲高清不卡在线| 久久精品免费播放| 亚洲性av在线| 亚洲欧洲精品一区二区精品久久久| 欧美视频一区| 免费观看日韩| 久久久久久久97| 亚洲图片欧美日产| 亚洲国产日韩欧美在线99| 久久成人一区二区| 亚洲一区二区三区在线观看视频| 亚洲激情国产| 一区二区三区在线免费视频| 国产精品二区影院| 欧美日韩精品福利| 欧美激情bt| 欧美电影电视剧在线观看| 久久av免费一区| 午夜欧美大片免费观看| 亚洲一级电影| 中文av字幕一区|