代碼注入技術之Shellcode注入
代碼注入是指在應用程序中注入任意外部代碼的行為,有兩種類型的代碼注入:
1.注入易受攻擊的程序;
2.注入不易受攻擊的程序;
如果代碼注入是在易受攻擊的應用程序中完成的,則可以通過利用在處理無效數據時發生的漏洞來實現。在這種情況下,代碼注入的程度就取決於應用程序中的錯誤,我們也將其稱為「漏洞」。但問題在於,應用程序應該具有可以被利用來獲得代碼執行的漏洞。
本系列針對第二種情況,即不希望或不需要該應用程序具有漏洞。正如所預料的那樣,這為應用程序提供了一個非常廣泛的領域,同時也為濫用操作系統提供了機會。
惡意軟體通常使用此方法,因為執行代碼注入之前不需要滿足任何條件,並且操作系統本身也完全支持該方法。所有主要的操作系統都提供ABI和API,以與其他進程進行交互,控制它們後,在其他程序的進程空間中讀寫內存。
通過CreateRemoteThread API進行代碼注入
CreateRemoteThread是Win32 API提供的用於在另一個進程中創建線程的函數。在另一個應用程序中創建線程之前,必須滿足兩個條件:
1.試圖在另一個進程中創建線程的進程必須具有創建線程的許可權,簡單地說,這意味著與受害進程(目標是指我們要在其中創建線程的進程)具有相同或更高的許可權。
2.兩個進程(目標進程和攻擊者進程)必須駐留在同一個會話中,如果會話標識符不匹配,將不會創建線程。
如果上述任何一個條件不滿足,則操作系統本身將拒絕代碼注入過程。
但這不並是Windows OS體系結構中的安全漏洞,而是由操作系統提供的功能。由於我們無法修改具有比我們更高的許可權的進程,因此不會跨越安全邊界。
演算法
為了通過CreateRemoteThread API實現代碼注入,我們必須遵循以下演算法:
1.選擇一個進程作為受害者進程;
2.使用帶有參數PROCESS_ALL_ACCESS的OpenProcess函數獲得對進程的訪問許可權,以便能夠執行所需的操作;
3.如果OpenProcess失敗,請退出;
4.使用VirtualAllocEx函數在受害者進程的進程空間中分配內存;
5.如果VirtualAllocEx失敗,請退出;
6.將shellcode寫入VirtualAllocEx分配的內存位置;
7.如果Shellcode寫入失敗或只有部分寫入Shellcode,請退出;
8.編寫完shellcode後,請使用必要的參數調用CreateRemoteThread,將shellcode的地址指向LPTHREAD_START_ROUTINE。
運行環境
就本文的示例而言,必須具備以下環境設置:
1.Visual Studio 2019;
2.Windows 10 RS6(x64);
注意:可以從我的github存儲庫訪問代碼。
代碼注入
訪問遠程進程
要對任何進程執行內存操作,我們必須能夠訪問它。可以使用函數OpenProcess來獲得它,該函數的原型為:
它包含三個參數:
1.dwDesiredAccess:它根據受害者進程的安全令牌進行檢查,不過你可以指定一些所需的訪問值,但是指定PROCESS_ALL_ACCESS包含該進程的所有可能的訪問許可權。
2.bInheritHandle:這是一個布爾值,指示該進程創建的進程是否將繼承此句柄。
3.dwProcessId:這是受害者進程的進程標識符;
這個進程返回其他進程的句柄(成功時),其他API函數可以使用它來操作受害進程的內存。如果失敗,則返回NULL。
為Shellcode分配空間
一旦獲得了受害進程的句柄,我們就可以在受害進程內存中為shell代碼分配空間,這是通過使用VirtualAllocEx API調用實現的。
VirtualAllocEx的原型為:
它包含五個參數:
1.hProcess:處理要在其中分配內存的進程;
2.lpAddress:指向受害者進程內存中指定地址的指針,如果將參數指定為NULL,則該函數自動選擇要分配給的內存頁;
3.dwSize:要分配的內存區域的大小,它是以位元組為單位指定的。
4.flAllocationType:指定要分配的內存類型,該參數必須包含以下三個值之一:MEM_COMMIT,MEM_RESERVE,MEM_RESET,MEM_RESET_UNDO。
5.flProtect:指定分配的內存保護。出於我們的目的,由於它將包含要執行的代碼,並且希望其具有可讀性和可寫性,因此將其設置為PAGE_EXECUTE_READWRITE。
該函數在成功時返回分配的基地址,而在失敗時返回NULL。至此,我們已經成功地在受害進程中分配了可執行內存。
在遠程進程中編寫Shellcode
現在,我們需要在分配的區域中編寫我們的shellcode。為此,我們有一個稱為WriteProcessMemory的函數。
WriteProcessMemory是一種由調用者將數據寫入指定進程的內存區域的函數,需要注意的是,整個內存區域必須是可寫的,否則該函數將會失敗,這就是為什麼我們將內存分配為可寫的,以及可讀和可執行的原因。
WriteProcessMemory的原型為:
WriteProcessMemory具有五個參數:
1.hProcess:處理要向其中寫入數據的進程;
2.lpBaseAddress:我們要在其中寫入數據的地址(以指針的形式);
3.lpBuffer:指向必須寫入的數據的指針,該指針必須是const指針。
4.nSize:必須寫入的數據量(以位元組為單位);
5.* lpNumberOfBytesWritten:指向SIZE_T的指針,該指針將存儲在該目標中寫入的位元組數。
如果函數由於某種原因而失敗,它將返回false;如果成功,則將返回true。
至此,所有設置都已準備就緒,只需要在遠程進程中創建一個線程並運行它即可。
執行shellcode
為了在遠程進程中創建線程,我們使用Win32 API提供的函數CreateRemoteThread。
1.CreateRemoteThread是一個用於創建在另一個進程的虛擬空間中運行的線程的函數。2.CreateRemoteThread提供的功能有限,並且可以訪問可以為線程指定的擴展屬性,可以使用CreateRemoteThreadEx,但在本文的示例下,前者就足夠了。
CreateRemoteThread的原型為:
CreateRemoteThread有七個參數,其中只有三個對我們有用。其餘的可以具有默認值,儘管通過調整它們可以對新創建的線程進行更多控制。
我們感興趣的參數是:
hProcess:受害者進程的句柄,我們希望在其中創建線程。
lpStartAddress:它是指向THREAD_START_ROUTINE的指針,THREAD_START_ROUTINE是線程一旦創建即將從其開始執行代碼的位置。
lpParameters:指向LPTHREAD_START_ROUTINE所需參數的指針。由於在本例中,它是一個簡單的shellcode,因此不需要任何參數,因此,我們將其保留為NULL,該參數在DLL注入中有重要意義。
演示
在演示視頻中,你可以看到我使用了一個Shellcode在Windows 10 RS6 (x64)上啟動cal .exe(使用msfvenom生成)。
演示視頻見https://pwnrip.com/wp-content/uploads/2019/06/ShellcodeInjectionDemo.mp4
該方法的優勢
與其他代碼注入技術相比,使用CreateRemoteThread進行代碼注入具有多個優勢,比如:
1.最容易實現;
2.不需要對庫函數進行地址解析;
3.不需要等待事件的發生;
4.不需要操作內存中的文件結構;
該方法的缺點
該方法除了優點之外,還有一些缺點,這些缺點使其無法成為流行的代碼插入技術,比如:
1.只能注入二進位代碼;
2.更改Shellcode時需要進行大量更改;
3.使用shellcode可以實現的數量有限。
本文翻譯自:https://pwnrip.com/demystifying-code-injection-techniques-part-1-shellcode-injection/