内核前置——注入技术
一共是7种方法
我打算在这里写一下我的最新感悟,有关于前几个方法
关于shellcode
今天分析法四对shellcode有比较好的理解记一下
首先shellcode是一段16进制的汇编代码,简单来讲他是个轴承,一个内存很小的,但是却可以起好作用的,比较独立可以直接运行的,如果要是调用函数啊什么的,又需要复杂的前缀,又不一定十分符合编写者的需求,所以就自己写一个小小的汇编代码直接运行
方法流程
法一、法二流程:
找进程(.exe)->创造新线程->实现注入
原线程——–<(RLT)CreateRemoteThread函数使它进入新线程>|<返回>——-
新线程—————-<返回>
法三流程
找进程->找线程->插入APC->苏醒后实现注入
原线程———<等待状态,QueueUserAPC>|<继续接下来的APC>————
APC: |<苏醒后执行>———–|
法四流程
找进程->找线程->暂停线程->改变eip->完成注入
原线程——–
内存空间 |
1.CreateRemoteThread
实现原理
写一段shellcode,用CreateReoteThread函数注入
Windows C++远程线程(CreateRemoteThread)注入DLL方法、代码示例。-CSDN博客
代码源码
dll代码
1 |
|
- **入口函数 **
**DllMain**:这是 DLL 的入口函数,每当有新的进程或线程加载或卸载此 DLL 时,系统会调用DllMain函数。该函数的返回值是一个BOOL,返回TRUE表示加载成功,返回FALSE表示加载失败。 - 参数说明:
HMODULE hModule:DLL 模块句柄。DWORD ul_reason_for_call:调用原因,可能的值为:DLL_PROCESS_ATTACH:进程加载 DLL 时调用。DLL_THREAD_ATTACH:线程加载 DLL 时调用。DLL_THREAD_DETACH:线程卸载 DLL 时调用。DLL_PROCESS_DETACH:进程卸载 DLL 时调用。
LPVOID lpReserved:保留字段,在一般情况下不需要使用。- 功能实现:
case DLL_PROCESS_ATTACH:当 DLL 被加载时执行的代码。在这里,通过MessageBox函数显示一个弹窗,内容为"Hello from YourDLL!"。- 其他情况(
DLL_THREAD_ATTACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH)下不执行任何操作。 **MessageBox**** 函数**:创建一个带消息的弹窗。传入的参数如下:NULL:弹窗不依赖任何父窗口。L"Hello from YourDLL!":弹窗的消息内容。L"DLL Injection":弹窗的标题。MB_OK | MB_ICONINFORMATION:弹窗显示 “OK” 按钮,并使用信息图标。
被注入dll的进程代码
1 |
|
执行注入的代码
1 |
|
代码分析
单段分析
1 | int main() |
首先这套程序的使用方法是,被注入进程的先运行,再运行执行注入的代码,代码在刚开始会找你要注入的程序,找到以后,就调用dll代码,说明注入成功
接下来,先细分析执行注入代码的每一段
首先看到,设置了两个变量名称,一个是目标进程名,另一个是dll代码地址,后面设立了目标进程id为得到目标进程id函数里传入目标进程名,就跟着去分析得到目标进程id函数
获取进程的ID
1 | DWORD GetTargetProcessID(const TCHAR* targetProcessName) |
首先定义,其次定义processID后面用,紧接着函数访问获取一个正在进行的程序返回一个权柄给hSnapshot,if语句判断返回是否成功,紧接着if语句内,用结构体定义快速存储进程这两步是绑定的,作用就是快速遍历所有进程
之后的if语句,Process32First(hSnapshot, &processEntry)这个是初始,把结构体创造的程序空间赋值,赋值的对象是获取的进程,注意此时后面的是指针,给之后是if判定是不是要的程序名,如果是的话,就把此时的id给processid,退出循环,返回id,回到主程序
1 | if (targetProcessID != 0) |
这里先判断是否成功获取id,获取成功了就进函数
远程线程注入DLL
1 | BOOL InjectDll(DWORD processID, const TCHAR* dllPath) |
首先是定义一个句柄hProcess,用函数OpenProcess打开进程,获取其句柄,如果失败返回false
1 |
|
后续if语句就是说如果上文的Open失败的话会返回null,如果null了就返回false结束函数
1 | LPVOID dllPathAddress = VirtualAllocEx(hProcess, NULL, _tcslen(dllPath) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE); |
开辟一处内存空间用于放置需要的dll代码
1 |
|
判断是否成功,失败的话用closehandle函数关闭打开的句柄,并返回失败
1 | SIZE_T bytesWritten; |
创建存储返回实际写入字节的变量,if里调用写入dll代码的函数,如果成功返回ture,再根据“!”反义不进条件,反之进if条件,将开辟的空间释放,然后关闭句柄
1 | HMODULE kernel32Module = GetModuleHandle(_T("kernel32.dll")); |
这里没什么就是比较固定的模板了,前三个函数分别是
获取“kernel32.dll”的句柄
获取线程启动的函数指针,这个函数就是负责后续可以正常让dll注入的代码用的,在kernel32.dll里
CreateRemoteThread这个函数把线程启动函数用了让要注入的dll函数,注入到线程中,并且把远程线程句柄传回
如果失败了就释放创建的地方,关闭句柄
1 |
|
等待远程创建的程序接到下一项指令,这里是因为我的dll程序写的有等待响应所以说这里才会要等待函数,后面就是响应完就没事了就结束释放这几个句柄了
用到的新东西
CreateToolhelp32Snapshot(TH32CS_SNAP...,x)函数
是 Windows API 中用于获取系统进程、线程、模块和堆栈信息的函数,函数需要的
传参:
第一位
表示我们请求创建一个进程快照。该参数指定了快照内容的类型
* `TH32CS_SNAPPROCESS`:表示获取系统中所有正在运行的进程信息。
* `TH32CS_SNAPTHREAD`:获取线程信息。
* `TH32CS_SNAPMODULE`:获取模块(DLL)信息。
* `TH32CS_SNAPHEAPLIST`:获取堆栈信息。
第二位
表示指定程序的ID,如果是0就代表全部都要
返回值
成功返回句柄
如果失败会返回 INVALID_HANDLE_VALUE
OpenProcess(access,bool,processID) 函数
起作用如名,打开进程,获取句柄
传参
第一位
表示你想要获取的权限
* `PROCESS_ALL_ACCESS`:表示全部权限
第二位
表示失败返回bool值
第三位
表示要打开的进程id
返回值
成功返回句柄
失败返回FALSE
CloseHandle函数
这个就没啥说的和上面的OpenProcess刚好相反作用,就是注意传参传入你想关闭的句柄就ok
VirtualAllocEx(access,lpaddress,dwsize,flallocationtype,flprotect) 函数
是 Windows API 中用于在指定的进程地址空间内分配内存的函数
传参
第一位
目标进程的句柄
第二位
空间分配的地方,如果是null就表示系统自己安排
第三位
需要分配的大小
第四位
内存分配的类型,指定如何分配
第五位
内存区域的保护属性,指定该区域的访问权限
返回值
成功返回区域指针(LPVOID)
失败返回FALSE
VirtualFreeEx(hProcess, dllPathAddress, 0, MEM_RELEASE)函数
这个和上一个起相反作用,就是记一下传参,第一个是句柄,第二个是开辟的空间区域指针,第三个第四个如果是为了关闭的话固定传就好,第四个起主关闭作用
WriteProcessMemory(access, dllPathAddress, lpBuffer , lpNumberOfBytesWritten , &bytesWritten)函数
用于将数据写入指定的进程的内存中
传参
第一位
目标进程的句柄
第二位
你要写在哪
第三位
你要写入的东西,传入类型为区域指针
第四位
要写入的字节数
第五位
返回实际写入的字节数。
返回值
成功返回TRUE
失败返回FALSE
GetModuleHandle (model) 函数
此函数用于获取已加载的模块的句柄
返回值
句柄
GetProcAddress (access,process_name) 函数
作用是获取指定模块(DLL 或 EXE)中某个函数的地址
传参
第一个
目标模块的权柄
第二个
目标的名称,如果传入的时函数名称,返回的就是对应函数地址,如果传入的是函数序号,返回与序号 对应的函数地址
返回值
成功时返回函数指针
失败时返回null
CreateRemoteThread(access,NULL,0,loadLibraryFunction,dllPathAddress,0,NULL) 函数
传参
第一个
目标进程的权柄
第二个
线程安全属性(默认传null就好)
第三个
线程栈堆大小(默认传0就好)
第四个
你要在这里启动的过度函数
第五个
过渡函数的参数,也就是传给第四个参数的,在本段函数中我们知道是要调用线程启动函数,目的是把dll函数注入进线程中,所以这个地方传入的应该是dll函数的地址
第六个
线程创建标志(默认传0就好)
第七个
返回线程ID,就是说注入之后需不需要接上别的程序,用的话就传id,不用的话就直接null
返回值
成功了就返回创建的远程线程的句柄(这个函数弄出来的句柄)
失败的话就NULL
PROCESSENTRY32 xxx结构体
是用来存储单个进程信息的结构体
Kernel32.dll模块
是 Windows 系统的核心动态链接库,提供了许多重要的系统函数,如内存管理、进程控制、文件操作等
CreateRemoteThread
WaitForSingleObject(hRemoteThread, INFINITE)函数
用于等待指定的对象(如线程、事件、互斥体等)进入“信号状态”。它通常用于同步操作,等待某个操作完成或某个条件满足
传参
第一个
等待线程的句柄
第二个
等待时间
2.RLTCreateReote Thread
实现原理
写一段shellcode,用RLTCreateReoteThread函数注入
主要用到的函数
此法与法一十分相似,不同的只在dll注入的时候函数用的不一样,所以就主要分析这两个函数啥区别
CreateRemoteThread是一个公开的、稳定的用户模式 API,用于创建远程线程,适用于大多数应用程序。RtlCreateRemoteThread是底层 API,通常用于操作系统内部,或在一些低级别的编程环境(如内核模式或驱动开发)中使用,不推荐普通应用程序直接调用。它可能会直接操作内核级资源,功能上类似于CreateRemoteThread,但不适用于普通用户应用程序。大概如此
源码
1 | /*----------------------------------------------------------------------- |
1 | .CODE |
1 | // stdafx.cpp : source file that includes just the standard includes |
3.QueueUserApc/NtQueueAPCThread APC 法
实现原理
当一个线程运行时,需要有函数来指引,也就是从等待状态被线程调用函数来使其苏醒,所以可以在线程等待的过程中,插入一个APC调用自己的shellcode来实现苏醒后运行注入
主要用到不同的函数
QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpData) ;函数
作用上,就是排队插队
大致是,引用第一个参数(kerenel32.dll模块里可以让某空间代码,插入进线程里的LoadLibraryA函数),把第三个参数(dll代码写在系统里的空间区域指针)放到第二个参数(目标进程的句柄)里
注意的地方是这个函数需要进程在等待状态才可以注入,对于这个等待状态我也有点不太理解后面理解了再记
这个方法书中说,条件苛刻,但是一旦满足成功率接近100%
源代码
1 | /*----------------------------------------------------------------------- |
4.SetThreadContext 法
实现原理
线程被暂停时,系统会将线程的上下文保存起来,恢复后会从之前保存的eip开始执行
所以注入时可以编写一段shellcode之后,暂停线程,将eip变成shellcode的地址,再在shellcode的结尾跳回
用到的函数
SuspendThread(hThread)函数
用于暂停线程,参数传递的是需要暂停线程的句柄
ZeroMemory(&Context, sizeof(CONTEXT));函数
用于内存空间或结构体清零
Context.ContextFlags = CONTEXT_FULL;
这里设置了 Context.ContextFlags 为 CONTEXT_FULL,表示你希望获取完整的线程上下文。
GetThreadContext (hThread, &Context) 函数
GetThreadContext 是 Windows API 函数,用于获取一个指定线程的上下文。
hThread:线程的句柄,表示你要获取上下文的线程,
&Context:CONTEXT 结构体的指针,GetThreadContext 会把线程的上下文信息填充到这个结构体中
uEIP = Context.Rip;
此步骤是把rip找出来,给变量uEIP
ResumeThread(hThread)函数
用于继续线程,参数传递的是需要继续线程的句柄
FIELD_OFFSET(INJECT_DATA, szDllPath);函数
用于查看第二个参数(结构体中的某个元素)在第一个参数(结构体)里的偏移
源码
1 | /*----------------------------------------------------------------------- |
细析部分代码
1 | VOID PrepareShellCode(BYTE* pOutShellCode) |
1 | //结构体声明 |
1 |
|
1 | .CODE |
先将结构体清零,后面的PrepareShellcode函数是自己命名的,用于将shellcode存入结构体
后面是把dll路径复制进结构体,然后是LoadLibraryA在线程中的位置传给结构体,原先的eip给结构体,最后是计算偏移,最后就是结构体填充完毕
5.ProcessNotifyInject 法
实现原理
不创造新的线程,而是直接在原线程进行时,挖一个“坑”,坑里是shellcode dll注入,等待系统执行到“坑”里即可完成注入,因为不创造新的线程,所以是汇编写的
方法详解
首先,注入前要选择好注入前的时机
第一种是在线程开始之前
ntdll!LdrInitializeThunk这个是一个程序开始前的初始化函数,用于完成用户层的初始化工作,紧接着会调用ntdll!Continue继续执行,而再次执行的起始位置就在NtContinue的第一个参数CONTINUE.EIP之中。对进程的第一个线程来说,这个值一般是kernel32!BaseProcessStartThunk,因此,我们可以在这里修改CONTEXT,是本该继续执行的目标变成我们事先写入的用于加载dll的shellcode
第二种是注入到运行中的进程
思路和第一种大差不差,只是注入位置变成了调用频率更高的API
dll加载系列–SetWindowsHookEx
实现原理
首先说一下钩子
”钩子”是一种特殊的消息处理机制,它可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理。我们可以在系统中自定义钩子,用来监视系统中特定事件的发生,完成特定功能,如屏幕取词,监视日志,截获键盘、鼠标输入等等。
上面是官方话语,配图

既然你所输入的会被钩子挂住,且有修改的风险,那么也可以通过钩子,来实现dll注入
核心–SetWindowsHookEx函数
SetWindowsHookEx函数,可以创建一个新的系统钩子,先解析一下这个函数传入的参数
SetWindowsHookEx( idHook , lpfn , hmod , dwThreadId );中
第一个idHook 传入的是你想要定义监视的钩子类型,比如键盘钩子
第二个lpfn 传入的是包含钩子过程的指针,也就是回调函数,通俗讲就是触发了钩子要干什么的函数,比如按下了某个键,发生什么
第三个hmod 传入的是包含钩子过程的模块的句柄,通俗讲就是,你的回调函数在哪,就传入哪,在一个dll文件里,就传入dll文件的句柄,在同一个文件下,就传入当前进程的句柄,比如hInstance是当前进程的句柄
第四个dwThreadId传入的是安装钩子的线程id,你是想装在哪,0就是全局

这里的SetWindowsHookEx函数可以看到传入的第三个是当前模块的句柄,也就说明传入的第二个参数(回调函数)在当前线程里(在这个项目里就能看到)
方法详解
一.不用dll
1 |
|
函数
FindWindow(lpClassName,lpWindowName) 函数
标准传参
**lpClassName**** 参数**:窗口的类名,用于更精确地定位窗口。- 类名是由窗口创建时注册的,通常由开发者指定。
- 如果你对类名不清楚,可以传入
NULL,表示不基于类名查找。
**lpWindowName**** 参数**:窗口的标题。- 标题是窗口当前显示在标题栏的文本内容。
- 如果标题完全匹配,
FindWindow会返回对应窗口的句柄
func proc = (func)GetProcAddress(hDll, "getDllHookProc");函数
作用
- 使用
GetProcAddress从已加载的 DLL 模块hDll中获取名为getDllHookProc的导出函数地址,并将其转换为func类型的函数指针。
标准传参
第一个为dll句柄
第二个为需要定位的函数名
dll加载系列–Applnit_DLLs注册表项注入
加载user32.dll时,会调用一个LoadAppDLLs()函数,他会读取下面这个注册表项
如果发现这个注册表项下面登记了DLL(在windows 7中)只有将LoadAppInit设置为1,AppInit_DLLs才会启用),就会主动加载他。所以,只要把要加载的DLL登记咋爱这里,就可以将其诸如那些加载了user32.dll的进程