本文轉自:http://hi.baidu.com/huguosheng/blog/item/1f925438eb826cf1b311c7f0.html 1DLL的進入點函數DllMain BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } DLL_PROCESS_ATTACH:當DLL初次映射到進程的地址空間時,系統將調用DllMain,並為ul_reason_for_call傳遞值DLL_PROCESS_ATTACH。此時DLL可以執行任何與進程相關的初始化。執行個體,如果我將DLL注入到一個新的進程後,如果要自動執行一些任務,則代碼寫在case DLL_PROCESS_ATTACH:後。 DLL_PROCESS_DETACH:當DLL從進程的地址空間被卸載後,系統將調用DllMain,並為ul_reason_for_call傳遞值DLL_PROCESS_DETACH。例如調用FreeLibrary,先調用DllMain完成DLL_PROCESS_DETACH通知後,才從FreeLibrary中返回。 DLL_THREAD_ATTACH:當在一個進程中建立線程時,系統要察看當前映射到該進程的地址空間中的所有DLL檔案映像,並調用每個帶有DLL_THREAD_ATTACH的DllMain函數,此時可以告訴DLL執行線程的初始化操作。注意,系統不為進程的主線程調用帶有DLL_THREAD_ATTACH值的任何DllMain.進程初次啟動進程地址空間的DLL接到的是DLL_PROCESS_ATTACH通知,而不是DLL_THREAD_ATTACH通知。 DLL_THREAD_DETACH:線程終止時調用ExitThread來撤銷線程,此時先調用帶DLL_THREAD_DETACH的DLL的DllMain,在撤銷線程。 2 DLL注入 2.1通過windows 掛鈎訊息來注入DLL 先看下面代碼: HHOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0); WH_GETMESSAGE:指出鉤子的類型,是訊息鉤子。 GetMsgProc:視窗準備處理訊息的時候,系統調用的函數地址?????????? hinstDll:DLL被映射到的進程的地址空間中的RVA。 0:掛鈎所有的GUI進程。 下面我們看看發生了什麼: 1,進程B的一個線程準備把一條訊息發送到一個視窗。 2,系統察看該線程是否安裝WH_GETMESSAGE鉤子。 3,系統察看包含GetMsgProc的DLL是否被映射到進程B的地址空間。 4,如果沒有被映射,則強制映射到B的地址空間,實現了DLL的注入。 5,系統調用進程B空間的GetMsgProc函數(剛被映射)。 2.2使用遠程進程來插入DLL 思路:在你希望的進程中建立一個遠程線程,通過這個線程調用LoadLibrary載入需要的DLL。我們似乎只需要調用 CreateRemoteThread(hRemoteProcess,NULL,0,LoadLibraryA,"c://mylib.dll",0,NULL)即可。但不可以。 錯誤一,LoadLibraryA不能直接作為參數傳入,因為這導致遠程線程執行一些莫名其妙的東西,很可能造成訪問違規。 解決辦法:pfnAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA");然後把 pfnAddr傳入。 錯誤二,不可以直接傳入"c://mylib.dll",因為這段緩衝區在本進程,不在遠程線程內。應該在遠程線程申請空間(VirtualAllocEx),並 把"c://mylib.dll"寫入這段遠程線程空間(WriteProcessMemory)。 範例程式碼: #include <windows.h> #include <stdio.h> void main() { LPCTSTR dllpath="d://print.dll";//將要被注入的DLL,內容是向硬碟上寫記錄檔 DWORD sizedllpath=0,dwWritten=0; HANDLE pRemoteThread,hRemoteProcess; PTHREAD_START_ROUTINE pfnAddr; DWORD pId; void *pFileRemote; BOOL fok; sizedllpath=lstrlenA( dllpath ) + 1; printf("size:%d",sizedllpath); HWND hWinPro=::FindWindow("ProgMan",NULL);//獲得explorer視窗 if(!hWinPro) printf("Exploere沒有啟動"); else { ::GetWindowThreadProcessId(hWinPro,&pId); //獲得explorer控制代碼 hRemoteProcess=::OpenProcess(PROCESS_ALL_ACCESS|PROCESS_VM_WRITE,false,pId); pFileRemote=::VirtualAllocEx(hRemoteProcess,NULL,sizedllpath,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); //在explorer進程分配空間 if(pFileRemote) { printf("分配成功/n"); } fok=::WriteProcessMemory(hRemoteProcess,pFileRemote,(LPVOID)dllpath,sizedllpath,&dwWritten); printf("實際寫入的位元組數:%d/n",dwWritten); printf("需要寫入的位元組數:%d/n",sizedllpath); if (fok==false) { printf("WriteProcessMemory error/n"); return; } pfnAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA"); pRemoteThread=::CreateRemoteThread(hRemoteProcess,NULL,0,pfnAddr,pFileRemote,0,NULL); if(pRemoteThread==NULL) return; else printf("success!/n"); } } 結構化異常處理(SEH) 結構化異常處理包括兩部分:結束處理和異常處理。 4 結束處理 4.1 形式 __try { //被保護部分 } __finally { //結束處理部分 } 無論try部分以如何方式退出(調用return,break,continue,goto等),在退出之前finally部分總會被執行。如果try部分沒有return,break,continue等,則程式的流程是執行完try部分自動流入finally部分(注意,這與異常處理是不同的)。 4.2 分析一個程式 為了加深對結束處理的理解,看如下程式。 DWORD FuncaDoodleDoo() { DWORD dwTemp=0; while(dwTemp<10) { __try{ if(dwTemp==2) continue; if(dwTemp==3) break; } __finally{ dwTemp++; } dwTemp++; } dwTemp+=10; return(dwTemp) ; } 當dwTemp進入while後,順序進入finally中,dwTemp變為1,接著執行dwTemp++,變為2,再次進入迴圈開始部分,遇到了continue指令,這將改變程式流程進入finally塊,dwTemp++變為3,這時返回迴圈開頭,注意不執行finally塊外的dwTemp++。再次進入try塊,遇到break指令,再次進入finally塊,dwTemp變為4,然後退出while,執行dwTemp+=10,所以最終返回14。 4.3 結束處理常式的應用 可以用結束處理常式來簡化編程,但是有一點必須注意:盡量避免在try塊中使用return,break,continue,goto等,因為這會使編譯器產生很多代碼來進行程式流程改變的處理。 先看一個程式 BOOL Fun1() { HANDLE hFile=INVALID_HANDLE_VALUE; PVOID pvBuf=NULL; DWORD dwNumBytesRead; BOOL fok; hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); if(hFile==INVALID_HANDLE_VALUE) return FALSE; pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE); if( pvBuf==NULL) { CloseHandle(hFile); return FALSE; } fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL); if(!fok||dwNumBytesRead==0) { VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT); CloseHandle(hFile); return FALSE; } //do some calculation on the data . . . . VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT); CloseHandle(hFile); return TRUE; } 這個程式的一個缺點是清理代碼VirtualFree,CloseHandle重複出現,羅嗦的很,使程式可讀性降低。fun2對其改進。 BOOL Fun2() { HANDLE hFile=INVALID_HANDLE_VALUE; PVOID pvBuf=NULL; DWORD dwNumBytesRead; BOOL fok; __try { hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); if(hFile==INVALID_HANDLE_VALUE) return FALSE; pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE); if( pvBuf==NULL) { return FALSE; } fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL); if(!fok||dwNumBytesRead==0) { return FALSE; } } //do some calculation on the data . . . . __finally { if( pvBuf!=NULL) VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT); if(hFile!=INVALID_HANDLE_VALUE) CloseHandle(hFile); } return TRUE; } fun2程式碼可讀性有了很大提高,但是try塊中出現了return這會降低程式的效率。fun3使用__leave很好地解決了這個問題。 BOOL Fun3() { HANDLE hFile=INVALID_HANDLE_VALUE; PVOID pvBuf=NULL; DWORD dwNumBytesRead; BOOL fok; BOOL functionok=FALSE; __try { hFile=CreateFile("SOMEDATA.BAT",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); if(hFile==INVALID_HANDLE_VALUE) __leave; pvBuf=VirtualAlloc(NULL,1024,MEM_COOMIT,PAGE_READWRITE); if( pvBuf==NULL) { __leave; } fok=ReadFile(hFile,pvBuf,1024,&dwNumBytesRead,NULL); if(!fok||dwNumBytesRead==0) { __leave } } functionok=TRUE; //do some calculation on the data . . . . __finally { if( pvBuf!=NULL) VirtualFree(pvBuf,MEM_RELEASE|MEM_DECOMMIT); if(hFile!=INVALID_HANDLE_VALUE) CloseHandle(hFile); } return functionok; } fun2達到了理想的效果,只需要增加變數functionok進行控制。 4 總結 結束處理的好處如下: (1)簡化錯誤處理,因所有的清理工作都在一個位置並且保證被執行。 (2)可讀性提高 (3)使用得當,可以寫出健壯卻有很小系統開銷的代碼。 5 異常處理 cpu引發的異常是硬體異常(除0異常,非法記憶體訪問等),作業系統和應用程式也可以自己引發異常,稱為軟體異常。 5.1 形式 __try { //被保護部分 } __except(exception filter) { //異常處理部分 } 注意,如果程式運行正常,則不進入except塊,這與finally是不同的。 5.2 異常過濾器 __except(exception filter)中的filter參數就是異常過濾器,他可以取三個值: EXCEPTION_EXECUTE_HANDLER EXCEPTION_CONTINUE_SEARCH EXCEPTION_CONTINUE_EXECUTION (1)EXCEPTION_EXECUTE_HANDLER 這個值的意思是告訴系統:“我認出了一個異常。即我感覺這個異常可能在某個時刻發生,我已編寫了代碼來處理,現在我想執行這個 代碼”。 一個例子。 char * strcpy(char *strDestionation,char *strSource) 當傳遞了無效地址時,這會導致進程結束。為了使程式更加健壯,可以: char * RobustStrCpy { __try { strcpy(strDestionation,strSource) } __except(EXCEPTION_EXECUTE_HANDLER) { //nothing to do here } return strDestionation; } 再看一個例子。 PBYTE RobustMemDup(PBYTE pbSrc,size_t cb) { PBYTE pbDup=NULL; __try { pbDup=(PBYTE)mallo(cb); memcpy(pbDup,pbSrc,cb); } __except { free(pbDup); pbDup=NULL; } return pbDup; } 但函數失敗的時候,會釋放分配的空間,並以傳回值通知調用函數者知道。 (2)EXCEPTION_CONTINUE_EXECUTION 當代碼產生異常,而過濾器值為EXCEPTION_CONTINUE_EXECUTION時,系統跳回到產生異常的指令,然後再重新執行一次。所以,如果我們確實知道哪條指令產生了什麼什麼異常時,我們可以在except塊進行修正,然後系統自動回去執行發生異常的指令,而此時已經修正了,則程式可以正常執行。這個功能如果好好利用的話會產生很奇妙的效果。後面試算表的例子會看到它的巧妙的應用。 (3) EXCEPTION_CONTINUE_SEARCH 這個值告訴系統尋找前面一個與except塊相匹配的try塊,並調用這個try塊的異常處理代碼。 5.3 一個應用執行個體--試算表 試算表程式(如excel),如果在程式開始執行時就分配空間,則會浪費很多的記憶體(因為通常試算表很大,每個格一個值的話,1024*1024就會浪費1024*1024*sizeof(int)的記憶體)。解決的策略是:先使用VirtualAlloc(.. ,MEM_RESERVE,..)在進程虛擬空間保留(reserve)一個足夠大的空間,而不提交,所以不消耗實體記憶體。當訪問某個格的時候就 VirtualAlloc(.. ,MEM_COMMIT,..)提交哪個格的記憶體。具體實現的時候,當訪問某個表格的時候,而沒有提交記憶體,將會引起EXCEPTION_ACCESS_VIOLATION異常,而在except的過濾器的值設為 EXCEPTION_CONTINUE_EXECUTION,在except塊中為要訪問的記憶體提交實體記憶體,即VirtualAlloc(.. ,MEM_COMMIT,..),然後重新執行存取碼而此時已經分配了實體記憶體則程式正常執行。下面程式採用了windows核心編程中的思想,具體實現的時候參考了網上的很多例子,並進行了改進,最終形成耗記憶體少,效率高的代碼。 //VirtualMatrix.h標頭檔 #include <windows.h> class VirtualMatrix { public: VirtualMatrix(int nRows, int nCols); ~VirtualMatrix(); void setElement(int i, int j, int value); int getElement(int i, int j); LONG ExpFilter(DWORD dwExceptionCode,int i,int j); protected: int nCols; int nRows; LPVOID m_pdata; }; //cpp檔案 #include <stdio.h> #include <windows.h> #include "VirtualMatrix.h" VirtualMatrix::VirtualMatrix(int nRows, int nCols): m_pdata (NULL), nCols(0), nRows(0) { this->nCols = nCols; this->nRows = nRows; m_pdata = VirtualAlloc (NULL, nRows*nCols*sizeof(int), MEM_RESERVE, PAGE_READWRITE ); if (m_pdata == NULL) { MessageBox(NULL, TEXT("reserve failed"), TEXT("virtual matrix"), MB_OK); return; } } VirtualMatrix::~VirtualMatrix(void) { if (m_pdata != NULL) VirtualFree (m_pdata,0,MEM_RELEASE); } void VirtualMatrix::setElement(int i, int j, int value) { if (i < 0 || i >= nRows) return; if (j < 0 || j >= nRows) return; int * p = (int*)(m_pdata); __try { *(p + i*nCols+j) = value; } __except (ExpFilter(GetExceptionCode(),i,j)) { } } LONG VirtualMatrix::ExpFilter(DWORD dwExceptionCode,int i,int j) { if(dwExceptionCode==EXCEPTION_ACCESS_VIOLATION) { VirtualAlloc (LPVOID((long)m_pdata+sizeof(int)*(i*nCols+j)), 10, MEM_COMMIT, PAGE_READWRITE ); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int VirtualMatrix::getElement(int i, int j) { if (i < 0 || i >= nRows) return -1; if (j < 0 || j >= nRows) return -1; __try { int * p = (int*)(m_pdata); int val = *(p + i*nCols+j); return val; } __except (EXCEPTION_EXECUTE_HANDLER) { return -1; } } void main() { VirtualMatrix a(100,100); a.setElement(10,10,3); printf("%d",a.getElement(10,10)); } |