WINDWOS 系統程式DLL檔案注入。

songjuron發表於2009-07-12

 WINDOWS 系統程式DLL檔案注入。網上資料多如牛毛。我在這邊只是做一個學習筆記作用。

   因為最近有工作需要修改windows系統的程式。所以拿起windows系統核心程式設計來看。需要掌握的知識點比較多。所以做份筆記拾零捕遺。

       修改windows本來的程式需要掌握三個知識點。

       1DLL注入。

       2API攔截

       3windows系統函式。

       其實最主要的是前面兩個技術。而最後一個則是一份文件說明。因為微軟並不是開源的。所以重要的函式入口都沒有公佈。如果得不到這部分函式說明,基本上你也無法對他做出修改。所幸的是我的工作資料都已具備。

       1DLL注入技術,眾所周知,windows系統是由一大堆的DLL檔案組成的。每個系統DLL檔案很有很多的函式提供給程式呼叫。例如kernel32.dll檔案,基本上程式程式都會呼叫他。因為要使用他的LoadLiarbry()函式載入其他的dll檔案。而每個程式要使用dll檔案。就需要先把他載入到記憶體當中。但windows系統了為了安全不要其他程式隨便影響其他程式。以免使系統崩潰。所以程式之間不能通訊。但是windows還是提供了方法讓我們自己的程式可以操作其他程式。只不過這種方法更安全。而且window系統的程式相互之間的程式空間是獨立的。是無法相互訪問的。所以在我們的程式中載入了一個abc.dll檔案。在其他的程式中是無法訪問的。所以我們需要把我們的dll檔案注入到其他的程式中。再呼叫其他程式載入這個dll檔案。這樣就需要使用dll注入技術。

       2API攔截大部分是在做DLL注入之後。才會做這個操作。因為如果你的dll檔案不在另一個程式中。另一個程式是無法訪問的。所以你為了讓替換掉一個程式原有的函式。你必須先把他載入到那個程式空間去。API攔截是為了替換系統的原有功能。替換成我們想要做的。例如為了安全性考慮我們把QQ的每個信包都加一下密再發出去,(我只是打個比方。如果真的這樣做。你還要在QQ的接收方為每個信包解密。)那麼我們可以HOOK到系統的SOCK程式中去。然後替換掉系統的發包函式。系統根據地址空間的地址會呼叫我們的函式。會把要傳送的資訊先傳送給我們的函式。然後我們對每個資訊包進行分析。如果是QQ的資訊包就可以加密後再呼叫原函式發出去。

 

       以上是對兩個技術的概念進行解說。實戰的需要掌握到更多的知識點,這邊羅列一下。並且我對我每API函式都會做說明。以後需要使用時過來一看就會明白。

       Windows程式

       Windows執行緒

       Windows 執行緒同步

       Windows DLL

       Windows 記憶體管理

Unicode

先說我看這部分內容的起點:以前一直做JAVA程式的。上半年才轉到C/C++開發。C++核心技術還比較瞭解。MFCwindows核心技術只是稍微瞭解。而且以上的知識點只是看過DLL技術。其它的都只是瞭解一點。熟悉都沒有。但我把DLL注入技術看完發現並沒有什麼很難理解的。如果大家對那部分不是太瞭解的可以查閱相關的資料。

      

       DLL注入分為很多種。這邊不細細列表。只用相對業說比較容易與比較靈活的(remote thread)遠端執行緒注入DLL的方法。

       我們先理一下DLL注入的思路。其實上面已經說了一部分。這邊再詳細的分細一下,首先說一下程式。程式之間無法通訊。程式之間的記憶體無法相互訪問。程式之間記憶體無法訪問導致B執行緒(我的自己程式的程式)的記憶體裡的東西無法被A程式(其它程式或系統程式)呼叫。這樣我們需要想辦法在A程式中放入我們想載入的DLLWindows提供了LoadLibrary()函式載入DLL檔案。而我們知道LoadLibrary()函式是在kerel32.dll中的所以我們需要定位到A程式的kerel32.dll裡的LoadLibrary()函式地址。然後在B程式中使用CreateRemoteThread()函式呼叫A程式的LoadLibrary()函式。這個地址怎麼獲得。有兩種辦法。Kerel32.dll使用了Dll地址繫結技術。我們可以通過定位自己的kerel32.dll->LoadLibaray()函式來得到遠端程式的地址。因為所有的程式中LoadLibryar()地址是一模一樣的。

       在本程式中使用以下函式獲得函式地址:

       PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)

       GetProcAddress(GetModuleHandle(TEXT(“KERNEL32”)),” LoadLibrary”);

API函式:

       FARPROC WINAPI GetProcAddress(

                      __in  HMODULE hModule,

                           __in  LPCSTR lpProcName

                     );

                     GetProcAddress()得到一個函式地址:

                     Hmodule DLL檔案控制程式碼。

                     LpProcName: 函式名稱。

                     FARPROC:返回一個地址/NULL

PTHREAD_START_ROUTINE 巨集,看一下定義。就是一個整型指標。

 

       typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(

    LPVOID lpThreadParameter

);

 

得到LoadLibrary()地址後。我們就可以呼叫遠端空間相同的地址載入我們的DLL檔案了。

HANDLE hThread = CreateRemoteThread(hProcessRemote,NULL,0,pfnTHreadRtn,”

C://MyLib.dll”,0,NULL);

 

API函式:

HANDLE hThread = CreateRemoteThread(

HANDLE       hProcess,              //遠端程式控制程式碼.那個程式將會產生這個執行緒。

PSECURITY_ATTRIBUTES psd,            安全屬性。如果使用預設的安全策略,輸入NULL

DWORD dwstackSize,         指定執行緒的使用地址空間。

PTHREAD_START_ROUTHNE       pfnStartAddr,          遠端函式地址。

pVOID pvParam, 傳遞給該執行緒的引數。

DWORD fdwCreate,       執行緒建立後的狀態。Xp支援三種。2000支援兩種。

PDWORD pdwThreadId);       OUT執行緒ID

 

這時候我們去呼叫時會發現是失敗的。返回一個NULL,為什麼呢。。因為在B程式中呼叫A程式的執行緒。該執行緒會在A的程式的地址去找C://MyLib.dll字串的首地址。會發現沒有。因為C://MyLib.dll字串是載入在B程式中的。

所以我們要把這個路徑寫到A程式的地址空間中去。

這時需要使用記憶體管理的知識點:

       PVOID  VirtualAllocEx(

              HANDLE hProcess,       遠端程式控制程式碼

              PVOID pvAddress,            可以指定遠端程式中的地址。但必須是未用過的。

              Size_t dwSize,          空間大小。

              DWORD flAllocationType,       分配遠端空間型別。

DWORD flProtect);       遠端記憶體頁面許可權。可寫的或只讀的。

      

              記憶體成功分配後返回了遠端程式的地址。

              我們要把進路徑寫到該地址中。

              複製本程式的字串到遠端程式中。

              BOOL WriteProcessMemory(

HANDLE hProcess,   in 遠端程式控制程式碼。

PVOID pvAddressRemote,   in 遠端空間地址。

LPCVOID pvBufferLocal,   in 本程式空間地址路徑的首字串地址

SIZE_T dwSize,   in 寫入的位元組數。

SIZE_T* pdwNumBytesWrite) out 實際寫入的位元組數。

 

這時我們再用函式呼叫遠端執行緒載入Dll檔案。就可以成功呼叫了。只不過傳遞路徑的地方改成遠端程式中儲存路徑的地址就可以了。這時候整個DLL就載入成功了。

 

DLL被遠端函式使用結束以後我們需要清理掉該DLL。所涉及的函式也有兩個。

清空路徑記憶體函式。釋放DLL檔案記憶體函式。

       清空分配的記憶體我們使用

BOOL VirtualFreeEx(

       HANDLE hProcess,       遠端程式。

       PVOID pvAddress,              需要清空的地址。

       SIZE_T dwSize,          需要清空的地址大小。

       DWORD dwFreeType);          釋放型別

       釋放遠端DLL檔案,需要通過CreateRmoteThread()函式呼叫遠端的kernel32.dll函式的FreeLibrary()函式。使用該函式的方法同LoadLibrary()

 

總結一下步驟。

1:用VirtualAllocEx在遠端空間申請一塊記憶體。

2:用WriteprocessMemoryDLL路徑寫入到遠端記憶體中。

3:用GetProcessAddress得到遠端和中kenerl32.dll->LoadLibrary()函式地址。

       4:用CreateRemoteThread執行遠端執行緒載入DLL檔案。

       5:用VirtualFreeEx清理第一步驟申請的記憶體。

       6:用GetProcessAddress得到遠端程式中kernel32.dll->FreeLibrary()函式地址。

       7:用CreateRemoteThread釋入遠端執行緒載入的DLL檔案。

      

       在我實踐的過程中遇到的問題:

       1:怎麼得到遠端程式控制程式碼。

              有兩種方式獲得,一種是通過程式名稱。迭代出所有的程式。比對程式名稱。通過              程式名稱獲得程式ID。第二種直接通過程式ID獲得程式控制程式碼。

              第一種(通過程式名稱獲得程式ID):

                     通過CreateToolhelp32Snapshot()函式得到程式快照:

                            HANDLE WINAPI CreateToolhelp32Snapshot(

                                   DWORD dwFlags,        程式快照,拍照的方式。TH32CS_SNAPPROCESS

所有的程式。

                                   DWORD th32ProcessID);       如果是0就拍currentProcess

再用Process32First()函式把第一個程式解析到PROCESSENTRY32程式實體結構函式中。(這裡使用的是32位的機器,如果是64位請用相關函式)

HANDLE  hsnapshot;

hsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);  

PROCESSENTRY32  processentry;  

processentry.dwSize=sizeof(PROCESSENTRY32);  

Process32First(hsnapshot,&processentry);

 

processentry.szExeFile 得到程式名稱與我們需要注入的程式名稱進行比較。相等則通過processentry.th32ProcessID獲得程式ID

 

Process32Next(hsnapshot,&processentry);解析下一條程式實體。

             

通過程式ID獲得程式控制程式碼。(開啟程式管理器,選擇檢視,勾選UUID。就可以看到程式ID。)這個比較簡單。OpenProcess()函式可以做到。

HANDLE WINAPI OpenProcess(

       DWORD dwDesiredAccess,程式開啟許可權。這個如果是我們的程式是管理員許可權,而被注入的程式許可權是System許可權就無法開啟,需要提權。

       BOOL bInheritHandle,  是否以繼承方式開啟。一般為FLASE

       DWORD processId);       程式ID

 

2DLL檔案路徑問題,你明明把DLL路徑寫到遠端程式中了。但是還是載入不成功。我們先來看一下LoadLibrary()搜尋DLL檔案的演算法。如果是絕對路徑他會直接去該位置載入如果檔案不存在,就會直接返回NULL。如果是相對路徑他會依照下面的位置去搜尋該DLL

1:程式的當前目錄,

2windowssystem/system32目錄

3iwndows目錄

4PATH環境變數中列出的目錄。

 

還原我當的載入失敗的環境。我的Mylib.dll是放在我的程式的的相同路徑下。寫入的dll路徑是/mylib.dll 也就是當前目錄下找。這時候寫到遠端程式中後。遠端程式也會按照上面的順序進行搜尋。而這個DLL檔案是在我的程式中的當前目錄。所以找不到。載入失敗。

解決方案:

我們把該DLL檔案換成絕對路徑,或放在系統目錄中即可以。

 

3:程式訪問許可權。

       可以把DLL檔案注入程式了。這時候我發現在注入System程式的時候是注入失敗的。除錯發現在OpenProcess()獲得程式控制程式碼時就失敗了。在網上查詢發現是程式訪問許可權的問題。我們的許可權只有Administrator()(你必須是以管理員身份登陸的呼叫程式時程式才可以獲得這個許可權)。他是沒有許可權訪問System許可權的。

也可以通過兩個方法解決這個問題:

1:把我們的程式做成服務,他會自己轉換成System許可權。暫時我還沒實踐。

2:把我們的程式提權。提升來Debug許可權。這個許可權是可以操作System許可權的程式的。(相關程式碼)

HANDLE hToken;

       BOOL bOk = FALSE;

       TOKEN_PRIVILEGES tkp;       //TOKEN_PRIVILEGES 令牌許可權

       ::OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|

              TOKEN_QUERY,&hToken);

       if(hToken == NULL) return bOk;

 

       ::LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid);

       tkp.PrivilegeCount = 1;

       tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

       ::AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);

       CloseHandle(hToken);

       return !bOk;

 

BOOL WINAPI OpenProcessToken( //開啟程式的令牌

       HANDLE processHandle, //被開啟令牌程式

       DWORD desiredAccess,  //開啟令牌的型別。

       PHANDEL TokenHandle); //out 令牌控制程式碼。

 

    BOOL WINAPI LookupPrivilegeValue( //獲得系統的某個許可權的LUID。例如上面我們是獲得當前系統的SE_DEUBG_NAME許可權的LUID。

              __in_opt  LPCTSTR lpSystemName, //系統名稱。如果為NULL,則用本地系統名稱。

              __in      LPCTSTR lpName, //許可權型別。

             __out     PLUID lpLuid //MSDN看不懂。大家可以嘗試自己看一下。

              );

 

AdjustTokenPrivileges()程式賦權

BOOL WINAPI AdjustTokenPrivileges(
  __in       HANDLE TokenHandle, //程式令牌控制程式碼。
  __in       BOOL DisableAllPrivileges, //是否應用該許可權。
  __in_opt   PTOKEN_PRIVILEGES NewState, //許可權
  __in       DWORD BufferLength, //PreviousState引數長度,如果為NULL則設為0。
  __out_opt  PTOKEN_PRIVILEGES PreviousState, //餓了。沒精力看那麼長的英文了。
  __out_opt  PDWORD ReturnLength //實際上寫入的長度。
);

 

程式賦權成功後我們就可以對System許可權的程式進行操作了。

 

4:如果你載入的DLL檔案呼叫了MFC會產生一個對話方塊或者那些與使用者互動的介面時。一般的程式提供了與介面互動的介面就沒有問題。但有些程式沒有提供該介面。當呼叫CreateRemoteThread()程式後載入了有MFC程式的DLL檔案就會使該程式進入堵塞狀態。例如我注入的是系統的Lsass.exe程式。使系統崩潰N次後。才發現這個問題。在網上查詢了相關解決方案。找到了一部分程式碼。他是自己定位到桌面。然後再呼叫我們的MFC程式碼。程式碼比較長。大家可以到我們的程式原始碼中查閱。

 

源程式說明:

       imgWalk 被注入的DLL源程式。

              DllDIalog.rc       MFC資源

              ImgWalk.cpp       DLL原始碼。

       InjLib  注入DLL源程式。

              Dialog.rc           MFC資源

              Ing.cpp     注入DLL原始碼。

              InjLib.cpp MFC入口。

 

相關文章