2020-11-24(dll注入的N种搞法)
所謂DLL注入,本來是軟件用于向其他程序添加/擴展功能、調(diào)試或逆向工程的一種合法技術(shù)。不過,后來惡意軟件也常用這種方式來干壞事。因此,這意味著從安全的角度來看,我們必須知道DLL注入是如何工作的。
之前,當我開始開發(fā)Red Team的定制工具(為了模擬不同類型的攻擊者)時,我完成了這個小項目的大部分代碼,并將該項目命名為“injectAllTheThings”。如果你想看一些使用DLL注入的具體示例,請訪問這里。如果你想了解DLL注入,該項目也會很有幫助。實際上,我已經(jīng)在一個單一的Visual Studio項目中組合了多種DLL注入技術(shù)(實際上是7種不同的技術(shù)),它們都有32位和64位版本,并且非常便于閱讀和理解:每種技術(shù)都有自己單獨的源文件。
以下是該工具的輸出,給出了所有已經(jīng)實現(xiàn)的方法。
根據(jù)@SubTee的說法,DLL注入是“沒什么大不了”的。我同意這種觀點,但是,DLL注入遠不止加載DLL這么簡單。
你雖然可以使用經(jīng)過Microsoft簽名的二進制代碼來加載DLL,但是卻無法附加到某個進程來利用其內(nèi)存。大多數(shù)滲透測試人員實際上不知道什么是DLL注入以及它是如何工作的,主要是因為Metasploit可以代勞的事情太多了。他們一直在盲目地使用它。我相信,學習這個“怪異的”內(nèi)存操縱技術(shù)的最佳地點,不是安全論壇,而是黑客論壇。如果你加入了紅隊,你可能需要鼓搗這種東西,除非你安于使用他人提供的工具。
大多時候,我們首先會使用一項高度復雜的技術(shù)展開攻擊,如果我們沒有被發(fā)現(xiàn),才會開始降低復雜程度。這就是說,我們會先將二進制文件丟到磁盤上,然后使用DLL注入。
這篇文章將全面介紹DLL注入,同時也是GitHub托管的該項目的“幫助文檔”。
概述
DLL注入簡單來說就是將代碼插入/注入到正在運行的進程中的過程。我們注入的代碼是動態(tài)鏈接庫(DLL)的形式。為什么可以做到這一點?因為DLL(如UNIX中的共享庫)是在運行時根據(jù)需要來進行加載。在這個項目中,我將只使用DLL,但是實際上還可以使用其他各種形式(任何PE文件、shellcode / assembly等)來“注入”代碼,這些在惡意軟件中非常常見。
此外,請記住,您需要具有適當級別的權(quán)限才能鼓搗其他進程的內(nèi)存。但是,這里不會探討受和保護的進程、Windows特權(quán)級別(由Vista引入)有關(guān)的內(nèi)容——這是一個完全不同的主題。
如上所述,DLL注入可以用于合法目的。例如,防病毒和終端安全解決方案就需要使用這些技術(shù)將自己的軟件代碼/掛鉤放置到系統(tǒng)上的“所有”運行的進程中。這使他們能夠在運行過程中監(jiān)視每個進程的行為,從而更好地保護我們。但是,該技術(shù)也可以用于惡意的目的。一般來說,常用技術(shù)是注入“l(fā)sass”進程以獲取密碼哈希值。惡意軟件也廣泛使用代碼注入技術(shù),例如,運行shellcode、運行PE文件或?qū)LL加載到另一個進程的內(nèi)存中以隱藏自身,等等。
基礎知識
我們將使用MS Windows API完成各種注入,因為這個API提供了非常豐富的功能,允許我們連接和操縱其他進程。自從微軟第一個版本的操作系統(tǒng)以來,DLL一直是MS Windows的基石。事實上,MS Windows 所有API都涉及DLL。最重要的一些DLL有“Kernel32.dll”(其中包含用于管理內(nèi)存、進程和線程的函數(shù))、“User32.dll”(主要是用戶界面函數(shù))和“GDI32.dll”(用于繪制圖形和文字顯示)。
您可能奇怪為什么會提供這樣的API,為什么微軟給我們這么好的一套函數(shù)來操作進程的內(nèi)存?實際上,它們的最初用途是擴展應用程序的功能。例如,一家公司創(chuàng)建一個了應用程序,并希望允許其他公司擴展或增強應用程序。所以,DLL最初是用于合法的目的。此外,DLL還可用于項目管理,節(jié)省內(nèi)存,實現(xiàn)資源共享等。
下圖講解DLL注入技術(shù)的流程。
就像上面看到的那樣,DLL注入分為四個步驟:
1.附加到目標/遠程進程
2.在目標/遠程進程內(nèi)分配內(nèi)存
3.將DLL路徑或DLL復制到目標/遠程進程內(nèi)存中
4.讓進程執(zhí)行DLL
所有這些步驟都是通過調(diào)用一組API函數(shù)來實現(xiàn)的。每種技術(shù)都需要一定的設置和選項才能完成。實際上,每種技術(shù)都有自己的優(yōu)點和缺點。
技術(shù)詳解
我們有多種方法來讓進程執(zhí)行我們的DLL。最常見的方法就是使用“CreateRemoteThread()”和“NtCreateThreadEx()”。但是,我們無法將DLL作為參數(shù)傳遞給這些函數(shù)。我們必須提供一個保存執(zhí)行起始點的內(nèi)存地址。為此,我們需要完成內(nèi)存分配,使用“LoadLibrary()”加載我們的DLL,復制內(nèi)存等等。
這個項目我稱之為’injectAllTheThings’(取這個名字,只是因為我討厭‘injector’的名字,加上GitHub上已經(jīng)有太多的‘injector’了),它有7種不同的技術(shù)。當然,這些技術(shù)都不是我發(fā)明的。我只是使用了這七種技術(shù)(是的,還有更多)。一些API具有詳細的文檔說明(如“CreateRemoteThread()”),有些API則沒有相關(guān)的文檔說明(如’NtCreateThreadEx()’)。以下是已經(jīng)實現(xiàn)的技術(shù)的完整列表,它們?nèi)窟m用于32位和64位。
CreateRemoteThread()
NtCreateThreadEx()
QueueUserAPC
SetWindowsHookEx()
RtlCreateUserThread()
通過SetThreadContext()獲取代碼洞
反射型DLL
其中,可能有一些是你早就接觸過的技術(shù)。當然,這不是所有DLL注入技術(shù)的完整列表。如我所說,還有更多的技術(shù),但是并沒有包括在這里。這里給出的,是到目前為止,我在一些項目中使用過的技術(shù)。有些是穩(wěn)定的,有些是不穩(wěn)定的——當然,之所以不穩(wěn)定,可能是由于我的代碼的原因,而不是這些技術(shù)本身的原因。
LoadLibrary()
如MSDN所述,“LoadLibrary()”函數(shù)的作用是將指定的模塊加載到調(diào)用進程的地址空間中。而指定的模塊可能會導致加載其他模塊。
HMODULE WINAPI LoadLibrary(_In_ LPCTSTR lpFileName );lpFileName [in]
The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). (…)
If the string specifies a full path, the function searches only that path for the module.
If the string specifies a relative path or a module name without a path, the function uses a standard search strategy to find the module (…)
If the function cannot find the module, the function fails. When specifying a path, be sure to use backslashes (), not forward slashes (/). (…)
If the string specifies a module name without a path and the file name extension is omitted, the function appends the default library extension .dll to the module name. (…)
換句話說,它只需要一個文件名作為其唯一的參數(shù)。也就是說,我們只需要為DLL的路徑分配一些內(nèi)存,并將執(zhí)行起始點設置為“LoadLibrary()”函數(shù)的地址,將路徑的內(nèi)存地址作為參數(shù)傳遞就行了。
實際上,這里最大的問題是“LoadLibrary()”使用程序來將加載的DLL添加到注冊表中。意思是它可以輕松被檢測到,但是實際上許多終端安全解決方案仍然無法檢測到它們。無論如何,正如我之前所說,DLL注入也有合法的使用情況,所以…另外,請注意,如果DLL已經(jīng)加載了’LoadLibrary()’,它將不會被再次執(zhí)行。如果使用反射型DLL注入,當然沒有這個問題,因為DLL沒有被注冊。如果使用反射DLL注入技術(shù)而不是使用“LoadLibrary()”,會將整個DLL加載到內(nèi)存中。然后找到DLL的入口點的偏移量來加載它。如果你愿意,還可以設法將其隱藏起來。取證人員仍然可以在內(nèi)存中找到你的DLL,只是這不會那么容易而已。Metasploit使用了大量的DLL注入,但是大多數(shù)終端解決方案都能搞定這一切。如果你喜歡狩獵,或者你屬于“藍隊”,可以看看這里和這里。
順便說一句,如果你的終端安全軟件無法搞定所有這一切…你可嘗試使用一些游戲反欺騙引擎。一些反欺詐游戲的反rootkit功能比某些AV更加先進。
連接到目標/遠程進程
首先,我們需要得到要與之進行交互的進程的句柄。為此,我們可以使用API調(diào)用OpenProcess()。
HANDLE WINAPI OpenProcess(_In_ DWORD dwDesiredAccess,_In_ BOOL bInheritHandle,_In_ DWORD dwProcessId );如果您閱讀MSDN上的文檔,就會明白,為此需要具備一定的訪問權(quán)限。訪問權(quán)限的完整列表可以在這里找到。
這些可能因MS Windows版本而異,不過幾乎所有技術(shù)都需要用到以下內(nèi)容。
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_CREATE_THREAD |PROCESS_VM_OPERATION |PROCESS_VM_WRITE,FALSE, dwProcessId);在目標/遠程進程內(nèi)分配內(nèi)存
為了給DLL路徑分配內(nèi)存,我們需要使用VirtualAllocEx()。如MSDN所述,VirtualAllocEx()可以用來預留、提交或更改指定進程的虛擬地址空間內(nèi)的內(nèi)存區(qū)域的狀態(tài)。該函數(shù)將其分配的內(nèi)存初始化為零。
LPVOID WINAPI VirtualAllocEx(_In_ HANDLE hProcess,_In_opt_ LPVOID lpAddress,_In_ SIZE_T dwSize,_In_ DWORD flAllocationType,_In_ DWORD flProtect );我們需要完成類似下面的工作:
// calculate the number of bytes needed for the DLL's pathname DWORD dwSize = (lstrlenW(pszLibFile) + 1) * sizeof(wchar_t); // allocate space in the target/remote process for the pathname LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);此外,您還可以使用“GetFullPathName()”API調(diào)用。但是,我不會在整個項目中使用這個API調(diào)用。不過,這只是個人偏好的問題。
如果要為整個DLL分配空間,則必須執(zhí)行以下操作:
hFile = CreateFileW(pszLibFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); dwSize, = GetFileSize(hFile, NULL); PVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);將DLL路徑或DLL復制到目標/遠程進程的內(nèi)存中
現(xiàn)在只需要使用WriteProcessMemory()API調(diào)用將DLL路徑或完整的DLL復制到目標/遠程進程。
BOOL WINAPI WriteProcessMemory(_In_ HANDLE hProcess,_In_ LPVOID lpBaseAddress,_In_ LPCVOID lpBuffer,_In_ SIZE_T nSize,_Out_ SIZE_T *lpNumberOfBytesWritten );這就像:
DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);如果我們要復制完整的DLL,就像在反射DLL注入技術(shù)中那樣,則還需要另外一些代碼,因為這需要將其讀入內(nèi)存,然后再將其復制到目標/遠程進程。
lpBuffer = HeapAlloc(GetProcessHeap(), 0, dwLength); ReadFile(hFile, lpBuffer, dwLength, &dwBytesRead, NULL); WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL);如前所述,通過使用反射DLL注入技術(shù),并將DLL復制到內(nèi)存中,DLL就不會被注冊到進程中。
這稍微有點復雜,因為需要在內(nèi)存中加載DLL時取得它的入口點。作為反射DLL項目用到的“LoadRemoteLibraryR()”函數(shù)可以為我們完成這些工作。如果你想查看源碼的話,可以訪問這里。
需要注意的一點是,我們要注入的DLL需要使用適當?shù)膇nclude和options進行編譯,使其與ReflectiveDLLInjection方法相適應。'injectAllTheThings’項目包括名為’rdll_32.dll / rdll_64.dll’的DLL,您可以使用它來完成這些工作。
讓進程執(zhí)行DLL
CreateRemoteThread()
CreateRemoteThread()是一種經(jīng)典和最受歡迎的DLL注入技術(shù)。另外,它的說明文檔也是最全面的。
它包括以下步驟:
1.用OpenProcess()打開目標進程
2.通過GetProcAddress()找到LoadLibrary()的地址
3.通過VirtualAllocEx()為目標/遠程進程地址空間中的DLL路徑預留內(nèi)存
4.使用WriteProcessMemory()將DLL路徑寫入前面預留的內(nèi)存空間中
5.使用CreateRemoteThread()創(chuàng)建一個新線程,該線程將調(diào)用LoadLibrary()函數(shù),以DLL路徑名稱作為參數(shù)
如果瀏覽MSDN上的CreateRemoteThread()文檔,會發(fā)現(xiàn)我們需要一個指向由線程執(zhí)行的、類型為LPTHREAD_START_ROUTINE的應用程序定義函數(shù)的指針,它實際上是遠程進程中線程的起始地址。
這意味著為了執(zhí)行我們的DLL,只需要給我們的進程發(fā)出指示,讓它來完成就行了。這樣就簡單了。
完整的步驟如下所示。
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId); // Allocate space in the remote process for the pathname LPVOID pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); // Copy the DLL's pathname to the remote process address space DWORD n = WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, dwSize, NULL); // Get the real address of LoadLibraryW in Kernel32.dll PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); // Create a remote thread that calls LoadLibraryW(DLLPathname) HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);完整的源代碼,請參閱CreateRemoteThread.cpp文件。
NtCreateThreadEx()
另一個選擇是使用NtCreateThreadEx()。這是一個未公開的ntdll.dll函數(shù),在將來它可能會消失或發(fā)生變化。這種技術(shù)實現(xiàn)起來有點復雜,因為我們需要使用一個結(jié)構(gòu)體(見下文)作為參數(shù)傳遞給它,而使用另一個結(jié)構(gòu)體接收來自它的數(shù)據(jù)。
struct NtCreateThreadExBuffer {ULONG Size;ULONG Unknown1;ULONG Unknown2;PULONG Unknown3;ULONG Unknown4;ULONG Unknown5;ULONG Unknown6;PULONG Unknown7;ULONG Unknown8; };這里有一篇對該調(diào)用的詳細說明。這種方法與CreateRemoteThread()方法比較接近
PTHREAD_START_ROUTINE ntCreateThreadExAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx"); LPFUN_NtCreateThreadEx funNtCreateThreadEx = (LPFUN_NtCreateThreadEx)ntCreateThreadExAddr; NTSTATUS status = funNtCreateThreadEx(&hRemoteThread,0x1FFFFF,NULL,hProcess,pfnThreadRtn,(LPVOID)pszLibFileRemote,FALSE,NULL,NULL,NULL,NULL);完整的源代碼,請參閱t_NtCreateThreadEx.cpp文件。
QueueUserAPC()
對于前面的方法,有一個替代:使用QueueUserAPC()函數(shù)。
如MSDN所述,這個調(diào)用“將用戶模式異步過程調(diào)用(APC)對象添加到指定線程的APC隊列。”
下面是具體定義。
DWORD WINAPI QueueUserAPC(_In_ PAPCFUNC pfnAPC,_In_ HANDLE hThread,_In_ ULONG_PTR dwData );pfnAPC [in]
A pointer to the application-supplied APC function to be called when the specified thread performs an alertable wait operation. (…)
hThread [in]
A handle to the thread. The handle must have the THREAD_SET_CONTEXT access right. (…)
dwData [in]
A single value that is passed to the APC function pointed to by the pfnAPC parameter.
所以,如果我們不想創(chuàng)建自己的線程,那么可以使用QueueUserAPC()來劫持目標/遠程進程中的現(xiàn)有線程。也就是說,調(diào)用此函數(shù)將在指定的線程上對異步過程調(diào)用進行排隊。
我們可以使用真正的APC回調(diào)函數(shù)代替LoadLibrary()。這里的參數(shù)實際上可以指向注入的DLL文件名的指針。
DWORD dwResult = QueueUserAPC((PAPCFUNC)pfnThreadRtn, hThread, (ULONG_PTR)pszLibFileRemote);當你嘗試這種技術(shù)的時候,你可能會注意到,這與MS Windows執(zhí)行APC的方式有關(guān)。但是,這里沒有查看APC隊列的調(diào)度器,這意味著,只有當線程變?yōu)榭删緺顟B(tài)時,隊列才會被檢查。
這樣,我們就可以劫持每一個線程了,具體如下。
我們這樣做,主要是想讓一個線程變?yōu)榭删緺顟B(tài)。
順便說一句,很高興看到這種技術(shù)被DOUBLEPULSAR應用。
完整的源代碼,請參見“t_QueueUserAPC.cpp”文件。
SetWindowsHookEx()
為了使用這種技術(shù),我們首先需要了解一下MS Windows鉤子的工作原理。簡單來說,鉤子就是一種攔截事件并采取行動的方式。
你可能會猜到,會有很多不同類型的鉤子。其中,最常見的是WH_KEYBOARD和WH_MOUSE。是的,你可能已經(jīng)猜到了,它們可以用來監(jiān)控鍵盤和鼠標的輸入。
SetWindowsHookEx()的作用是“將應用程序定義的鉤子裝到鉤子鏈中。”
HHOOK WINAPI SetWindowsHookEx(_In_ int idHook,_In_ HOOKPROC lpfn,_In_ HINSTANCE hMod,_In_ DWORD dwThreadId );idHook [in]
Type: int
The type of hook procedure to be installed. (…)
lpfn [in]
Type: HOOKPROC
A pointer to the hook procedure. (…)
hMod [in]
Type: HINSTANCE
A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. (…)
dwThreadId [in]
Type: DWORD
The identifier of the thread with which the hook procedure is to be associated. (…)
MSDN上一個有趣的評論指出:
“SetWindowsHookEx可以用于將DLL注入到另一個進程中。32位DLL不能被注入到64位進程中,同時,64位DLL也不能被注入到32位進程中。如果應用程序需要在其他進程中使用鉤子,則需要使用一個32位應用程序調(diào)用SetWindowsHookEx將32位DLL注入32位進程中,或者使用64位應用程序調(diào)用SetWindowsHookEx來把64位DLL注入64位進程。32位和64位DLL必須具有不同的名稱。”
請大家務必記住這一點。
下面是取自具體實現(xiàn)中的一段代碼。
GetWindowThreadProcessId(targetWnd, &dwProcessId); HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, threadID);我們需要知道,發(fā)生的每個事件都將通過一個鉤子鏈,這是一系列可以在事件中運行的過程。SetWindowsHookExe()要做的基本上就是如何將自己的鉤子放入鉤子鏈中。
上面的代碼需要用到要安裝的鉤子類型(WH_KEYBOARD)、指向過程的指針、具有該過程的DLL的句柄以及將要掛鉤的線程的ID。
為了獲得指向程序的指針,我們需要首先使用LoadLibrary()調(diào)用加載DLL。然后,調(diào)用SetWindowsHookEx(),并等待我們想要的事件發(fā)生(這里而言就是按一個鍵)。一旦相應的事件發(fā)生,我們的DLL就會被執(zhí)行。
完整的源代碼,請參閱t_SetWindowsHookEx.cpp文件。
RtlCreateUserThread()
RtlCreateUserThread()是一個未公開的API調(diào)用。它的設置幾乎與CreateRemoteThread()以及后面介紹的NtCreateThreadE()完全相同。
實際上,RtlCreateUserThread()會調(diào)用NtCreateThreadEx(),這意味著RtlCreateUserThread()是NtCreateThreadEx()的封裝。所以,這里沒有什么新玩意。但是,我們可能只想使用RtlCreateUserThread(),而不是NtCreateThreadEx()。即使后者發(fā)生了變動,我們的RtlCreateUserThread()仍然可以正常工作。
我們知道,mimikatz和Metasploit都使用RtlCreateUserThread()。如果你有興趣的話,可以看看這里和這里。
所以,如果mimikatz和Metasploit正在使用RtlCreateUserThread()…是的,那些家伙都了解自己的代碼…按照他們的“建議”,使用RtlCreateUserThread()——特別是,如果你打算鼓搗一些比簡單的“injectAllTheThings”程序更復雜的事情的時候。
完整的源代碼,請參閱t_RtlCreateUserThread.cpp。
SetThreadContext()
這實際上是一個很酷的方法。通過在目標/遠程進程中分配一大塊內(nèi)存,以便將特制代碼注入目標/遠程進程。而該代碼是負責加載DLL的。
下面給出的是32位的代碼
0x68, 0xCC, 0xCC, 0xCC, 0xCC, // push 0xDEADBEEF (placeholder for return address) 0x9c, // pushfd (save flags and registers) 0x60, // pushad 0x68, 0xCC, 0xCC, 0xCC, 0xCC, // push 0xDEADBEEF (placeholder for DLL path name) 0xb8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, 0xDEADBEEF (placeholder for LoadLibrary) 0xff, 0xd0, // call eax (call LoadLibrary) 0x61, // popad (restore flags and registers) 0x9d, // popfd 0xc3 // ret對于64位代碼,沒有找到任何可用的代碼,只好自己動手了,具體如下。
0x50, // push rax (save rax) 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (placeholder for return address) 0x9c, // pushfq 0x51, // push rcx 0x52, // push rdx 0x53, // push rbx 0x55, // push rbp 0x56, // push rsi 0x57, // push rdi 0x41, 0x50, // push r8 0x41, 0x51, // push r9 0x41, 0x52, // push r10 0x41, 0x53, // push r11 0x41, 0x54, // push r12 0x41, 0x55, // push r13 0x41, 0x56, // push r14 0x41, 0x57, // push r15 0x68,0xef,0xbe,0xad,0xde, // fastcall convention 0x48, 0xB9, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rcx, 0CCCCCCCCCCCCCCCCh (placeholder for DLL path name) 0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, 0CCCCCCCCCCCCCCCCh (placeholder for LoadLibrary) 0xFF, 0xD0, // call rax (call LoadLibrary) 0x58, // pop dummy 0x41, 0x5F, // pop r15 0x41, 0x5E, // pop r14 0x41, 0x5D, // pop r13 0x41, 0x5C, // pop r12 0x41, 0x5B, // pop r11 0x41, 0x5A, // pop r10 0x41, 0x59, // pop r9 0x41, 0x58, // pop r8 0x5F, // pop rdi 0x5E, // pop rsi 0x5D, // pop rbp 0x5B, // pop rbx 0x5A, // pop rdx 0x59, // pop rcx 0x9D, // popfq 0x58, // pop rax 0xC3 // ret在將這個代碼注入目標進程之前,需要填充/修補一些占位符:
返回地址(代碼完成執(zhí)行后線程應該恢復的地址)。
DLL路徑名。
LoadLibrary()的地址。
接下來就是劫持、暫停、注入和恢復線程發(fā)揮作用的時候。
我們首先需要連接到目標/遠程進程,并將內(nèi)存分配到目標/遠程進程中。請注意,我們需要分配具有讀取和寫入權(quán)限的內(nèi)存來保存DLL路徑名,以及存放加載DLL的匯編代碼。
LPVOID lpDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); stub = VirtualAllocEx(hProcess, NULL, stubLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);接下來,我們需要獲得在目標/遠程進程上運行的一個線程的上下文(將要注入我們的匯編代碼的線程)。
為找到線程,我們可以使用函數(shù)getThreadID(),它位于‘a(chǎn)uxiliary.cpp’中。
一旦我們獲得了線程id,就可以設置線程上下文了。
hThread = OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), false, threadID);接下來,我們需要暫停線程來捕獲其上下文。線程的上下文實際上就是其寄存器的狀態(tài)。我們特別感興趣的寄存器是EIP / RIP(有時稱為IP指令指針)。
由于線程被暫停,所以我們可以更改EIP / RIP的值,并強制它繼續(xù)在不同的路徑(我們的代碼洞)中執(zhí)行。
ctx.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &ctx); DWORD64 oldIP = ctx.Rip; ctx.Rip = (DWORD64)stub; ctx.ContextFlags = CONTEXT_CONTROL; WriteProcessMemory(hProcess, (void *)stub, &sc, stubLen, NULL); // write code cave SetThreadContext(hThread, &ctx); ResumeThread(hThread);所以,我們暫停線程,捕獲上下文,從中提取EIP / RIP。當我們注入的代碼運行完成時,保存的這些數(shù)據(jù)將用來恢復現(xiàn)場。新的EIP / RIP設置為我們注入的代碼位置。
然后我們使用返回地址、DLL路徑名地址和LoadLibrary()地址對所有占位符進行填補。
一旦線程開始執(zhí)行,我們的DLL將被加載,它一旦運行完成,將返回到被掛起的位置,并在那里恢復執(zhí)行。
如果你想練習調(diào)試的話,這里有具體的操作指南。啟動要注入的應用程序,如notepad.exe。運行injectAllTheThings_64.exe與x64dbg,如下所示。
也就是說,我們可以使用以下命令行(具體視您的環(huán)境而定):
在調(diào)用WriteProcessMemory()處設置斷點,如下所示。
當代碼運行時,斷點觸發(fā),這時要注意寄存器RDX的內(nèi)存地址。至于為什么要關(guān)注RDX,可以閱讀x64調(diào)用約定方面的資料。
利用單步方式(F8)調(diào)用WriteProcessMemory(),啟動x64dbg的另一個實例,并連接到’notepad.exe’。轉(zhuǎn)到以前復制的地址(RDX中的地址),按“Ctrl + g”,您將看到我們的代碼,如下所示。
太棒了! 現(xiàn)在,請在這個shellcode的開頭設置一個斷點。轉(zhuǎn)到被調(diào)試進程的injectAllTheThings,讓它運行。正如在下面看到的,我們的斷點觸發(fā),現(xiàn)在可以單步調(diào)試代碼,進行仔細研究了。
一旦調(diào)用LoadLibrary()了函數(shù),就可以加載我們的DLL了。
這真是太好了。
我們的shellcode將返回到之前保存的RIP的地址處,notepad.exe將恢復執(zhí)行。
完整的源代碼,請參閱t_suspendInjectResume.cpp。
反射型DLL注射
我還將Stephen Fewer(這種技術(shù)的先驅(qū))代碼引入了這個“injectAllTheThings”項目,此外,我還構(gòu)建了一個反射型DLL項目使用這種技術(shù)。請注意,我們正在注入的DLL必須使用適當?shù)膇nclude和options進行編譯。
由于反射型DLL注入會將整個DLL復制到內(nèi)存中,因此避免了使用進程注冊DLL。我們已經(jīng)完成了一切繁重的工作。要在DLL中加載內(nèi)存時獲取入口點,只需使用Stephen Fewer的代碼即可。他的項目中包含的“LoadRemoteLibraryR()”函數(shù)為我們完成了這些工作。我們使用GetReflectiveLoaderOffset()來確定我們進程內(nèi)存中的偏移量,然后使用該偏移加上目標/遠程進程(我們寫入DLL的位置)中的內(nèi)存的基地址作為執(zhí)行起始點。
有點太復雜了?是的,確實如此。下面是實現(xiàn)這一目標的4個主要步驟。
1.將DLL頭寫入內(nèi)存
2.將每個節(jié)寫入內(nèi)存(通過解析節(jié)表)
3.檢測import并加載所有其他已導入的DLL
4.調(diào)用DllMain入口點
與其他方法相比,這種技術(shù)提供了強大的隱蔽性,并在Metasploit中大量應用。
如果你想了解更多詳情,請訪問官方的GitHub信息庫。此外,請務必閱讀Stephen Fewer的文章。
另外,最好讀一下MemoryModule的作者Joachim Bauch寫的一篇文章,講述了如何從內(nèi)存加載一個DLL,同時,這也是在沒有LoadLibrary()的情況下手動加載Win32 / 64 DLL的好方法。
代碼
當然,還有一些復雜的注入方法,所以后面還會更新injectAllTheThings項目。我最近看到的最有趣的一些是:
DOUBLEPULSAR使用的一種方法
由@zerosum0x0編寫的,使用SetThreadContext()和NtContinue()的反射型DLL注入技術(shù),此處提供了可用的代碼。
我上面描述的所有技術(shù)都是我在GitHub上提供的一個項目中已經(jīng)實現(xiàn)的。此外,我還提供了每種技術(shù)所需的DLL。下表可以了解實際實現(xiàn)的內(nèi)容以及使用方法。
不用說,為了安全起見,最好始終使用injectAllTheThings_32.exe注入32位進程或使用AllTheThings_64.exe注入64位進程。當然,您也可以使用injectAllTheThings_64.exe注入32位進程。其實我還沒有實現(xiàn)這一點,但是我可能稍后會再試一次,你可以試著用WoW64鼓搗一下64位進程。Metasploit的smart_migrate基本上就是這種情況,具體請看這里。
本文涉及的整個項目的所有代碼,包括DLL,都可從GitHub下載。當然,如果您有興致的話,也可以自己試著編譯32位和64位代碼。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的2020-11-24(dll注入的N种搞法)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020-11-23(“花式扫雷” 辅助
- 下一篇: 2020-11-25(多级页表的补充)