Notebook
Search…
⌃K

Injection Method

R3 Dll 注入方式总结

1. SetWindowsHookEx 方法

HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
该方法主要通过注册 指定线程(dwThreadId) 的 Windows 消息事件(idHook)的回调函数(lpfn)来进行注入 当 dwThreadId 设为 0 或者不是由当前进程创建的线程时,该 hook 函数(lpfn)必须存在于 指定的 dll (hMod)中 eg. SetWindowsHookEx(WH_CBT, MyHookProc, MyDll, 0) ,当系统中有线程触发 WH_CBT事件后,系统自动将 MyDll载入该线程中,并执行 MyHookProc,以此实现 Dll 注入

2. CreateRemoteThread 方法

HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
该方法在CreateRemoteThread 在目标进程中创建一个线程,再通过 新创建的线程进行dll注入 获取函数 LoadLibrary 在内存中的地址
loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
在目标进程空间给将要注入的dll路径字符串(形如“c:\my.dll”)分配空间
remoteDllAddr = VirtualAllcoEx (targetProcess, NULL, str(myDllPath)+1, MEM_COMMIT | MEM_READWRITE);
将dll路径 写入目标进程空间
WriteProcesMemory(targetProcess, remoteDllAddr, (LPVOID)myDllPath, strlen(myDllPath)+1, NULL);
创建远程线程
CreateRemoteThread(
targetProcess, // 目标进程
NULL,
0,
// 线程的执行函数(此处是LoadLibrary)
(LPTHREAD_START_ROUTINE)loadLibraryAddr,
// 执行函数的参数(此处是 需要载入的dll路径)
remoteDllPath,
NULL,NULL );
通过该方法创建的线程,其创建进程和父进程均为 目标进程 !
另:还可以通过RtlCreateUserThread 和 NtCreateThread 函数进行注入,但这两个函数没有从ntdll.dll导出,因此需要手动寻找这两个函数的地址。注入原理和 CreateRemoteThread非常相似,只是替换了CreateRemoteThread 来在目标进程中创建线程,因此将它们归为一类

2.5 Reflective Dll Injection

该方法依然使用线程创建函数在目标进程中创建线程, 但不是通过使线程执行 LoadLibrary 来载入线程
具体过程比较复杂,简述如下:
  1. 1.
    将需要载入的dll的文件内容读入内存中(读入其二进制代码)
  2. 2.
    搜索其PE头,查找该dll 的入口地址(EntryPoint:DllMain) (关键一步)
  3. 3.
    将其入口地址(DllMain)直接作为 线程创建函数的线程起始地址(lpStartAddress)
  4. 4.
    创建远程线程

3. QueueUserApc 方法

DWORD QueueUserAPC(
PAPCFUNC pfnAPC,
HANDLE hThread,
ULONG_PTR dwData
);
该方法和 CreateRemoteThread 类似,将实现注入功能的函数(pfnAPC)加入目标线程的 APC 队列中,当目标线程(hThread)通过 SleepEx、WaitForSingleObject 、WaitForMultipleObject 等过程 变为 Alertable 状态 时,该线程将依次调用其APC队列中的函数,并以dwData作为传入参数,以此实现dll注入
和 CreateRemoteThread相同,pfnAPC 指向 LoadLibrary 的内存地址,dwData则为需要注入的dll的 路径
(目标线程的状态必须为 Alertable,否则不会执行 APC )

4. SetThreadContext 方法

该方法原理是 通过 SetThreadContext 函数更改 目标进程上下文中的指令指针(EIP for x86),跳转到目标进程中我们写入的内存区域,并开始执行,以此实现dll注入或任意代码执行。
由于该方法无法传递参数,因此需要在目标进程中写入一段 汇编代码 用于处理参数并调用 LoadLibrary,然后将EIP指向该段代码起始地址,汇编码如下所示:
(写入内容实际是汇编代码对应的机器码)
// 0xAAAAAAAA 为占位符,以后会被替换
unsigned char shellcode[] = {
0x68, 0xef, 0xbe, 0xad, 0xde, // push 0xAAAAAAAA, 将原EIP值压栈
0x9c, // pushfd,通用寄存器压栈
0x60, // pushad,标志寄存器压栈
0x68, 0xef, 0xbe, 0xad, 0xde, // push 0xAAAAAAAA, 将dll路径压栈(传参)
0xb8, 0xef, 0xbe, 0xad, 0xde, // mov eax, 0xAAAAAAAA
0xff, 0xd0, // call eax, 调用 LoadLibrary
0x61, // popad
0x9d, //popfd
0xc3 //ret
};
注入流程简述如下:
获取 LoadLibrary 内存地址 - loadLibraryAddr
在目标进程中申请内存,将dll路径写入 - remoteDllAddr
在目标进程中申请内存,用于保存shellcode - remoteShellcodeAddr
使用 SuspendThread(htargetThread) 挂起目标线程,并使用GetThreadContext(targetThread, &context) 获取线程上下文 context
存储 context中的原EIP并更新 EIP
oldEip = context.Eip;
context = (DWORD)remoteShellCode;
context.ContextFlags = CONTEXT_CONTROL;
替换 shellcode中的相关地址参数
// 修改内存页为可写可执行属性
VirtualProtect(shellcode, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((void*)(shellcode + 1), &oldEip);
memcpy((void*)(shellcode + 8), &remoteDllAddr);
memcpy((void*)(shellcode + 13, &loadLibraryAddr));
将shellcode写入目标进程并开始执行
// 写入shellcode
WriteProcessMemory(targetProcess, remoteShellcodeAddr, shellcode, NULL);
// 更改线程上下文
SetThreadContext(targetThread, &context);
// 恢复执行
ResumeThread(targetThread);

总结

当前 Windows 平台用户层的注入方法主要为以上4种,其共同点为均需要在目标进程的内存中申请空间来保存相关参数,再通过创建远程线程或注入shellcode的方式直接或间接调用 LoadLibrary 来载入dll (Reflective Injection 除外)
因此涉及到的函数主要有VirtualAlloc(Ex)、WriteProcessMemory、VirtualProtect(Ex), 为保证这些函数成功执行,其传入的目标进程句柄(hProcess)必须拥有相应的权限,通常进程句柄通过 OpenProcess 获取
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 句柄权限, PROCESS_ALL_PROCESS表示请求所有权限
BOOL bInheritHandle,
DWORD dwProcessId // 进程id
);
PROCESS_ALL_ACCESS包括以下 特定权限:
  • PROCESS_TERMINATE
    PROCESS_CREATE_THREAD PROCESS_DUP_HANDLE PROCESS_SET_INFORMATION PROCESS_VM_OPERATION PROCESS_VM_READ PROCESS_VM_WRITE
而以上函数要求进程句柄至少具有 PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_WRITE 权限

防御

在我们的驱动中,对打开进程或线程句柄的操作注册了回调函数,当某一个进程句柄被请求时,回调函数首先查看DesiredAccess参数,如果相应权限位被置1,则将该位置为0(即拒绝该权限的请求行为),因此通过OpenProcess函数返回的句柄将不具有 读写内存或进行内存操作的权限,以此来防御 dll 注入
目前为止,驱动可以防御住所有的用户层 针对特定进程的 dll 注入行为