美文网首页
Windows下的代码注入

Windows下的代码注入

作者: 一叶障目 | 来源:发表于2018-09-09 18:59 被阅读53次

    木马和病毒的好坏很大程度上取决于它的隐蔽性,木马和病毒本质上也是在执行程序代码,如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现,在编写这类程序的时候可以考虑将代码注入到其他进程中,借用其他进程的环境和资源来执行代码。远程注入技术不仅被木马和病毒广泛使用,防病毒软件和软件调试中也有很大的用途,最近也简单的研究过这些东西,在这里将它发布出来。

    想要将代码注入到其他进程并能成功执行需要解决两个问题:

    1. 第一个问题是如何让远程进程执行注入的代码。原始进程有它自己的执行逻辑,想要破坏原来的执行流程,使EIP寄存器跳转到注入的代码位置基本是不可能的
    2. 第二个问题是每个进程中地址空间是独立的,比如在调用某个句柄时,即使是同一个内核对象,在不同进程中对应的句柄也是不同的,这就需要进行地址转化。

    要进行远程代码注入的要点和难点主要就是这两个问题,下面给出两种不同的注入方式来说明如何解决这两个问题

    DLL注入

    DLL注入很好的解决了第二个问题,DLL被加载到目标进程之后,它里面的代码中的地址就会自动被转化为对应进程中的地址,这个特性是由于DLL加载的过程决定的,它会自己使用它所在进程中的资源和地址空间,所以只要DLL中不存在硬编码的地址,基本不用担心里面会出现函数或者句柄需要进行地址转化的问题。
    那么第一个问题改怎么解决呢?

    要执行用户代码,在Windows中最常见的就是使用回调的方式,Windows采用的是事件驱动的方式,只要发生了某些事件就会调用回调,在众多使用回调的场景中,线程的回调是最简单的,它不会干扰到目标进程的正常执行,也就不用考虑最后还原EIP的问题,因此DLL注入采用的最常见的就是创建一个远程线程,让线程加载DLL代码。

    DLL注入中一般的思路是:使用CreateRemoteThread来在目标进程中创建一个远程的线程,这个线程主要是加载DLL到目标进程中,由于DLL在入口函数(DLLMain)中会处理进程加载Dll的事件,所以将注入代码写到这个事件中,这样就能执行注入的代码了。那么如何在远程进程中执行DLL的加载操作呢?我们知道加载DLL主要使用的是函数LoadLibrary,仔细分析线程的回调函数和LoadLibrary函数的原型,会发现,它们同样都是传入一个参数,而CreateRemoteThread函数正好需要一个函数的地址作为回调,并且传入一个参数作为回调函数的参数。这样就有思路了,我们让LoadLibrary作为线程的回调函数,将对应dll的文件名和路径作为参数传入,这样就可以在对应进程中加载dll了,进一步也就可以执行dllmain中的对应代码了。

    还有一个很重要的问题,我们知道不同进程中,地址空间是隔离的,那么我在注入的进程中传入LoadLibrary函数的地址,这算是一个硬编码的地址,它在目标进程中是否是一样的呢?答案是,二者的地址是一样的,这是由于kernel32.dll在32位程序中加载的基地址是一样的,而LoadLibrary在kernel32.dll中的偏移是一定的(只要不同的进程加载的是同一份kernel32.dll)那么不同进程中的LoadLibrary函数的地址是一样的。其实不光是LoadLibrary函数,只要是kernel32.dll中导出的函数,在不同进程中的地址都是一样的。注意这里只是32位,如果想要使用32位程序往64位目标程序中注入,可能需要考虑地址转换的问题,只要知道kernel32.dll在64位中的偏移,就可以计算出对应函数的地址了。

    LoabLibrary函数传入的代表路径的字符串的首地址在不同进程中同样是不同的,而且也没办法利用偏移来计算,这个时候解决的办法就是在远程进程中申请一块虚拟地址空间,并将目标字符串写入对应的地址中,然后将对应的首地址作为参数传入即可。

    最后总结一下DLL注入的步骤:

    1. 获取LoadLibrary函数的地址
    2. 调用VirtualAllocEx 函数在远程进程中申请一段虚拟内存
    3. 调用WriteProcessMemory 函数将参数写入对应的虚拟内存
    4. 调用CreateRemoteThread 函数创建远程线程,线程的回调函数为LoadLibrary,参数为对应的字符串的地址

    按照这个思路可以编写如下的代码:

    typedef HMODULE(WINAPI *pfnLoadLibrary)(LPCWSTR);
    
    if (!DebugPrivilege()) //提权代码,在Windows Vista 及以上的版本需要将进程的权限提升,否则打开进程会失败
    {
      return FALSE;
    }
    
    //打开目标进程
    HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); //dwPid是对应的进程ID
    if (NULL == hRemoteProcess)
    {
      AfxMessageBox(_T("OpenProcess Error"));
    }
    
    //查找LoadLibrary函数地址
    pfnLoadLibrary lpLoadLibrary = (pfnLoadLibrary)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryW");
    //在远程进程中申请一块内存用于保存对应线程的参数
    PVOID pBuffer = VirtualAllocEx(hRemoteProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
    //在对应内存位置处写入参数值
    DWORD dwWritten = 0;
    WriteProcessMemory(hRemoteProcess, pBuffer, m_csDLLName.GetString(), (m_csDLLName.GetLength() + 1) * sizeof(TCHAR), &dwWritten);
    
    //创建远程线程并传入对应参数
    HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpLoadLibrary, pBuffer, 0, NULL);
    WaitForSingleObject(hRemoteThread, INFINITE);
    VirtualFreeEx(hRemoteProcess, pBuffer, 0, MEM_RELEASE);
    CloseHandle(hRemoteThread);
    CloseHandle(hRemoteProcess);
    

    卸载远程DLL

    上面进行了代码的注入,作为一个文明的程序,自然得考虑卸载dll,毕竟现在提倡环保,谁使用,谁治理。这里既然注入了,自然得考虑卸载。
    卸载的思路与注入的类似,只是函数变为了FreeLibrary,传入的参数变成了对应的dll的句柄了。

    如何获取这个模块的句柄呢?我们可以枚举进程中的模块,根据模块的名称来找到对应的模块并获取它的句柄。枚举的方式一般是使用toolhelp32中对应的函数,下面是卸载的例子代码

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_dwPid);
    if (INVALID_HANDLE_VALUE == hSnapshot)
    {
      AfxMessageBox(_T("CreateToolhelp32Snapshot Error"));
      return;
    }
    
    MODULEENTRY32  me = {0};
    me.dwSize = sizeof(MODULEENTRY32);
    BOOL bRet = Module32First(hSnapshot, &me);
    while (bRet)
    {
      CString csModuleFile = _tcsupr(me.szExePath);
      if (csModuleFile == _tcsupr((LPTSTR)m_csDLLName.GetString()) != -1)
      {
        break;
      }
    
      ZeroMemory(&me, sizeof(me));
      me.dwSize = sizeof(PROCESSENTRY32);
      bRet = Module32Next(hSnapshot, &me);
    }
    
    CloseHandle(hSnapshot);
    
    typedef BOOL (*pfnFreeLibrary)(HMODULE);
    pfnFreeLibrary FreeLibrary = (pfnFreeLibrary)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "FreeLibrary");
    
    HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwPid);
    if (hRemoteProcess == NULL)
    {
        AfxMessageBox(_T("OpenProcess Error"));
        return;
    }
    
    HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, me.modBaseAddr, 0, NULL);
    WaitForSingleObject(hRemoteThread, INFINITE);
    CloseHandle(hRemoteThread);
    CloseHandle(hRemoteProcess);
    

    无DLL的注入

    注入不一定需要使用DLL,虽然使用DLL比较简单一点,无DLL注入在解决上述两个问题的第一个思路是一样的,也是使用CreateRemoteThread来创建一个远程线程来执行目标代码。

    无dll的注入主要麻烦是在进行地址转化上,在调用API的时候,如果无法保证对应的dll的基地址不变的话,就得在目标进程中自行调用LoadLibrary来动态获取函数地址,并调用。

    在动态获取API函数的地址的时候,主要使用的函数是LoadLibrary、GetModuleHandle、GetProcAddress这三个函数,而线程的回调函数只能传入一个参数,所以我们需要将对应的需要传入的参数组成一个结构体,并将结构体对应的数据写入到目标进程的内存中,特别要注意的是,里面不要使用指针或者句柄这种与地址有关的东西。
    例如我们想在目标进程中注入一段代码,让它弹出一个对话框,以便测试是否注入成功。这种情况除了要传入上述三个函数的地址外,还需要MesageBox,而MessageBox是在user32.dll中,user32.dll在每个进程中的基地址并不相同,因此在注入的代码中需要动态加载,因此可以定义下面一个结构

    typedef struct REMOTE_DATA
    {
        DWORD dwLoadLibrary;
        DWORD dwGetProcAddress;
        DWORD dwGetModuleHandle;
    
        DWORD dwGetModuelFileName; //辅助函数
        char szUser32dll[MAX_PATH]; //存储user32dll的路径,以便调用LoadLibrary加载
        char szMessageBox[128]; //存储字符串MessageBoxA 这个字符串,以便使用GetProcAddress加载MesageBox函数
        char szMessage[512]; //弹出对话框上显示的字符
    }
    

    不使用DLL注入与使用DLL注入的另一个区别是,不使用DLL注入的时候需要自己加载目标代码到对应的进程中,这个操作可以借由WriteProcessMemory 将函数代码写到对应的虚拟内存中。
    最后注入的代码主要如下:

    DWORD WINAPI RemoteThreadProc(LPVOID lpParam)
    {
        LPREMOTE_DATA lpData = (LPREMOTE_DATA)lpParam;
        typedef HMODULE (WINAPI *pfnLoadLibrary)(LPCSTR);
        typedef FARPROC (WINAPI *pfnGetProcAddress)(HMODULE, LPCSTR);
        typedef HMODULE (*pfnGetModuleHandle)(LPCSTR);
        typedef DWORD (WINAPI *pfnGetModuleFileName)( HMODULE,LPSTR, DWORD);
    
        pfnGetModuleHandle MyGetModuleHandle = (pfnGetModuleHandle)lpData->dwGetModuleHandle;
        pfnGetModuleFileName MyGetModuleFileName = (pfnGetModuleFileName)lpData->dwGetModuleFileName;
        pfnGetProcAddress MyGetProcAddress = (pfnGetProcAddress)lpData->dwGetProcAddress;
        pfnLoadLibrary MyLoadLibrary = (pfnLoadLibrary)lpData->dwGetProcAddress;
    
        typedef int (WINAPI *pfnMessageBox)(HWND, LPCSTR, LPCSTR, UINT);
        //加载User32.dll
        HMODULE hUser32Dll = MyLoadLibrary(lpData->szUerDll);
        //加载MessageBox函数
        pfnMessageBox MyMessageBox = (pfnMessageBox)MyGetProcAddress(hUser32Dll, lpData->szMessageBox);
        char szTitlte[MAX_PATH] = "";
        MyGetModuleFileName(NULL, szTitlte, MAX_PATH);
    
        MyMessageBox(NULL, lpData->szMessage, szTitlte, MB_OK);
        return 0;
    }
    
    m_dwPid = GetPid(); //获取目标进程ID
    DebugPrivilege(); //进程提权
    HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,  m_dwPid);
    if (NULL == hRemoteProcess)
    {
      AfxMessageBox(_T("OpenProcess Error"));
      return;
    }
    
    LPREMOTE_DATA lpData = new REMOTE_DATA;
    ZeroMemory(lpData, sizeof(REMOTE_DATA));
    
    //获取对应函数的地址
    lpData->dwGetModuleFileName = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetModuleFileNameA");
    lpData->dwGetModuleHandle = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetModuleHandleA");
    lpData->dwGetProcAddress = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetProcAddress");
    lpData->dwLoadLibrary = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");
    
    // 拷贝对应的字符串
    StringCchCopyA(lpData->szMessage, MAX_STRING_LENGTH, "Inject Success!!!");
    StringCchCopyA(lpData->szUerDll, MAX_PATH, "user32.dll");
    StringCchCopyA(lpData->szMessageBox, MAX_PROC_NAME_LENGTH, "MessageBoxA");
    
    //在远程空间中申请对应的内存,写入参数和函数的代码
    LPVOID lpRemoteBuf = VirtualAllocEx(hRemoteProcess, NULL, sizeof(REMOTE_DATA), MEM_COMMIT, PAGE_READWRITE); // 存储data结构的数据
    LPVOID lpRemoteProc = VirtualAllocEx(hRemoteProcess, NULL, 0x4000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 存储函数的代码
    DWORD dwWrittenSize = 0;
    WriteProcessMemory(hRemoteProcess, lpRemoteProc, &RemoteThreadProc, 0x4000, &dwWrittenSize);
    WriteProcessMemory(hRemoteProcess, lpRemoteBuf, lpData, sizeof(REMOTE_DATA), &dwWrittenSize);
    
    HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteProc, lpRemoteBuf, 0, NULL);
    WaitForSingleObject(hRemoteThread, INFINITE);
    VirtualFreeEx(hRemoteProcess, lpRemoteBuf, 0, MEM_RELEASE);
    VirtualFreeEx(hRemoteProcess, lpRemoteProc, 0, MEM_RELEASE);
    CloseHandle(hRemoteThread);
    CloseHandle(hRemoteProcess);
    
    delete[] lpData;
    

    <hr />

    相关文章

      网友评论

          本文标题:Windows下的代码注入

          本文链接:https://www.haomeiwen.com/subject/myydgftx.html