Old inject
1.CreateRemoteThread
老生常谈了,不说了
2.不落地
DLL反射
3.PROCESS HOLLOWING
创建进程并挂起,取消映射目标进程内存,重新Alloc进shellcode,然后ResumeThread就行
4.线程劫持
将目标线程挂起,注入后SetThreadContext
5.SETWINDOWSHOOKEX
通过给特殊事件挂钩,在触发时执行恶意代码
6.改注册表
1 | Appinit_DLL、AppCertDlls 和 IFEO(映像文件执行选项)都是恶意软件用于注入和持久化的注册表项。 |
Appinit_DLL
该注册表项下的所有库都会被加载到所有加载 User32.dll 的进程中
因此,当恶意软件修改此子项时,大多数进程都会加载该恶意库
通过调用 RegCreateKeyEx 打开 Appinit_Dlls 注册表项,并通过调用 RegSetValueEx 修改其值
AppCertDlls
这种方法与 AppInit_DLLs 方法非常相似,区别在于此注册表项下的 DLL 会被加载到调用 Win32 API 函数 CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW、CreateProcessWithTokenW 和 WinExec 的每个进程中
IFEO
IFEO 通常用于调试目的。开发人员可以设置此注册表项下的“调试器值”,将一个程序附加到另一个可执行文件进行调试。因此,每当启动可执行文件时,附加到该可执行文件的程序也会启动。要使用此功能,只需提供调试器的路径,并将其附加到要分析的可执行文件即可。恶意软件可以修改此注册表项,从而将自身注入到目标可执行文件中。
7.APC INJECTION AND ATOMBOMBING
如果线程调用了 SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx 或 WaitForSingleObjectEx 函数,则该线程会进入可更改状态
然后调用 OpenThread 和 QueueUserAPC 函数将 APC 添加到该线程的队列中
然后便会排队执行了
QueueUserAPC 函数接受三个参数:1) 目标线程的句柄;2) 指向恶意软件想要运行的函数的指针;3) 传递给该函数指针的参数
8.SETWINDOWLONG
确实不好懂,先要理解explorer(资源管理器)和命名共享节
命名共享节:(其实就是映射节)
一块可以使不同进程访问同一空间的手段,不过想要使两个进程都放到该空间的方法是要都映射过这个共享段
正常流程:
Explorer调用RegisterClassEx创建一个类(通常名为 "Shell_TrayWnd")WNDCLASSEX,并设置好其内部的lpfnWndProc(类级的窗口过程指针)与cbClsExtra/cbWndExtra(类/窗口的额外内存,俗称 EWM),EWM 常用来放少量数据或回调表项
恶意流程:
先用MapViewFile将恶意文件映射在已经被explorer映射过的共享空间中,并将explorer中EWM的回调槽中存储的函数指针替换成共享段中的shellcode指针,这一过程可以通过GetWindowLong和SetWindowLong实现
9.shims
通过hookshims这个工具来做到加载恶意代码,感觉比较少见
10.IAT钩子和内联钩子
修改导入表或者直接改变API,🤔hook的手段去加载恶意代码
Tran/Map/Proc Inject 共享注入
Process_Injection - Reverse - Technology | Coneco = Welcome to coneco’s study room
11.安全dll掩护payload
先正常VirtualAllloc+WriteMemroy+CreateRemoteThread+LoadLibrary注入普通准备的dll
再通过查询目标进程模块,将正常dll的入口点hook为payload,最后再次CreateRemote shellcode,此时运行地址是安全的但运行的是payload
12.Process Hollowing
审了一遍源码,流程:
创建一个进程,得到进程PEB偏移,得到基地址,取消映射,打开payload,写在基地址上,SetConext设置
一下线程上下文,Resume重启线程
这里创建的进程并映射的安全dll,那么检测技术会认为此进程与地址安全,再次解映射并加载shellcode,就
绕过了
看缺点之前插入一段很不错的知识
页属性共享涉及到操作系统的共享内存机制
这里简单描述一下: 为了节省内存,当一个模块加载进内存时,比如 ntdll.dll,操作系统会看该模块是否已被其他进程加载过了(即已经在物理内存中了)
如果在,那么操作系统只需要简单地将该物理区域映射到需要加载模块的进程中。
因此,这些模块对应的内存是共享的,这些模块内存对应的进程 pte (page table entry) 会指向一个叫 prototypePTE 的结构,该结构会指向这些共享的物理内存。
不过,如果我们向模块,比如 ntdll.dll,的数据段写入数据,那么这时操作系统就会对 ntdll.dll 的数据段分配一段物理内存,然后当前进程对应数据段的 pte 就不会再指向 prototypePTE,而是指向操作系统分配的物理内存。
关于内存共享机制的详细描述,读者看参考 Windows Internals 7th,第五章的 Page fault handling 部分。
缺点:
如果用UnMap,那么会被殊检查
如果不用,就要直接覆盖
覆盖如果不用ReMap自己实现,那么,进程对应pte不会指向protecctPTE,会被检测到
如果使用ReMap,需要一个section,那么文件就会落地
13.Process Doppelganging
源码流程:
创建任务 - 创建映射节并写入payload - 回滚 - 创建进程以映射节名义 - 修改创建的进程信息为svchost来隐藏 - 创建线程 - 启动
依据在事务下创建映射节,回滚依然存在的漏洞进行恶意代码注入
section的代码就是创建进程通过NtCreateProcess,创建线程,初始化,唤醒就行
14.Transacted Hollowing
源码流程:
创建任务 - 创建映射节并写入payload - 回滚 - 直接用目标名创建进程 - 注入 - 启动
记得前面说过,Remap需要文件落地,因为创建section的时候需要落地文件,但是在事务中创建映射节回滚后,映射节依然存在,此时Remap,思路用Process的技巧,架空目标进程,调整入口点和环境,进行注入
15.Process Ghosting
源码流程:
创建一个文件并设置删除属性 - 写入payload并映射 - 创建进程 - 得到目标进程PEB,改变自身PEB,并设置好基地址 - 创建线程 - Resume
将文件设置删除属性并映射节,此时文件处于不可访问属性,可以有效反检测
16.Ghostly Hollowing
把Process Ghosting得到目标进程PEB修改变成直接用目标程序打开进程,其他一样
17.Process Herpaderping
如果一个文件被映射之后,在改变文件并保存,那么映射部分是什么,双击文件后是什么,右键文件用记事本打开看到的又是什么
答案是文件被映射后,改变保存,磁盘中文件就已经是新的了,而映射部分是不受影响的,那么就做到注入部分不被发现了
new Inject – PoolParty 线程池注入
18.Attacking Worker Factories
流程:
GetQueryInformationProces()获得进程中的handle - DuplicateHandle()获得目标进程的worker factory - NtQueryInformationWorkerFactory()得到启动例程值 - 覆盖启动历程值 - NtSetWorkerFactoryInformation()增加一个线程来主动引起启动历程 - 实现注入
worker factory是windows负责管理线程池中的工作线程
启动例程是工作线程的入口点,而启动例程值可以被更改掉
19.Attacking Thread Pools
首先有一个工作项的概念,分为三种工作项
常规工作项、异步工作项(写入文件操作成功后)、定时工作项(定时器到点)
对应的有三种工作队列TP_POOL、I/O completion queue、定时器队列
主要关注前两个,TP_POOL位于进程内存空间的用户模式下,可以通过内存写入原句修改
I/O位于内核,要通过系统调用操作
TP_POOL中有TP_WORK结构,其辅助结构是TP_TASK,TP_TASK最终有ToPostTask API添加,由此得线程池结构
最后得到的流程就是:
得到进程信息 - 获取目标进程工厂和进程池信息 - 读一下TP_POOL - CreateThreadpoolWord创建一个工作项 - 创建并写入TP_WORK(shellcode的写入) - 连接TP_WORK和TP_POOL
20.Attacking Thread Pools: TP_IO
从这开始是一个体系,都是关于IO完成队列的,IO完成队列又可称为(IOCP)
结构降级: IOCP - KQUEUE - EntryList(链表结构,基础单元为 I/O completion entry)
IoCompletion结构其中一个值为
PVOID Key; // Completion Key
该结构为Completion Packet - 其中内容一般是 TP_WORK WP_WAIT等
通过NtSetIoCompletion函数可以向IOCP中插入一个IoCompletion
TP_IO是一个工作项,在文件操作完成后执行,TP_IO的辅助结构为TP_DIRECT,辅助结构就是将会在IOCP中排队的
CreateThreadpoolIo函数可以让一个TP_IO结构下的一个指针指向IOCP并使Completion key指向对应TP_DIRECT,类似于绑定TP_IO这个回调把
然后排队等待执行
流程:
得到进程信息确定进程工厂 - 打开一个文件 - CreateThreadpoolIo一个TP_IO(shellcode也在这里) - 远程开空间并写入 - NtSetIoCompletion插入TP_IO - 对文件简单操作后引发TP_IO
21-25 Attacking Thread Pools: TP_XX
是异步完成项的变体,前面那个是针对TP_IO的,同样的工作项还有TP_WAIT TP_ALPC TP_JOB,这三个同样是用TP_DIRECT作为辅助项,同样去做就行
也可以直接注入TP_DIRECT
26.Attacking Thread Pools: TP_TIMER
前面还能勉强理解,最后这个也太高深了,感觉在学漏洞一样
流程:
得到进程和工厂 - 得到具体工厂信息 - 读取TIMER的TP_POOL - 创建一个time工作项 - 远程创建写入 - 连接上TP_POOL - 复制句柄并设置定时器对象超时等待调用
源码分析
正如文章开头所说,防御机制更关注于执行,如监控(CreateRemoteThread)等运行函数,所以此项目里的函数只是在工作线程中添加了shellcode,而运行是windows线程池机制调用的
所有子类:
WorkerFactoryStartRoutineOverwrite 进程工厂注入
RemoteTpTimerInsertion TP_TIME
RemoteTpIoInsertion TP_IO
RemoteTpJobInsertion TP_JOB
RemoteTpAlpcInsertion TP_ALPC
RemoteTpWorkInsertion TP_WORK
RemoteTpWaitInsertion TP_WAIT
RemoteTpDirectInsertion TP_DIRECT
覆写:
HijackHandle WorkerFactory Time TP_WORK
AllocateShellcodeMemory WorkerFactory
SetExecution all
源码流程:
GetTargetProcessHandle - HijackHandles - AllocateShellcodeMemory - WriteShellcode - SetupExecution
1.从不同覆写API上分析
因为注入调用API是一致的,不一样的仅是几个单独覆写的API,所以可以从这方面入手分析
HijackHandle
未覆盖的是空函数,也就是没用到WorkerFactory信息
WorkerFactory方法需要具体的工厂信息
TP_WORK仅需要工厂句柄
TP_TIMER需要线程池句柄
AllocateShellcodeMemory
未覆盖的直接VirtualAllocEx创建,并返回地址
WorkerFacory返回启动例程地址,这也是他注入的核心
SetExecution
执行部分主要不同于每个方法的调用条件
WorkerFacory 手动设置最小运行线程是当前线程+1,工厂使用启动例程创建新线程的时候运行shellcode
TP_WORK 创建回调是shellcode工作项,远程写入并关联起来,是WORK工作项,当工作线程被调用自动回调
TP_WAIT 创建WAIT对象,远程写入WAIT和辅助项DIRECT,设置事件,绑定对象,触发回调
TP_IO 打开文件,创建IO对象,远程写入并绑定一个辅助项,写入文件触发回调
TP_DIRECT 这个最简洁,直接创建一个,回调绑上,跨进程写上,IOCP写里面
TP_TIMER 创建TIMER对象,远程写入并关联,通过w_NtSetTimer2开始计时,超时触发回调
ALL INCLUDE
总结:
注入技术这么看下来,都是在运行和不落地的方向进行优化
共享注入 Tran/Map/Proc主要的不断进化点在于,跨进程且不落地不用关键api的文件共享,注入就是想要实现两个进程的交互,那么至少目标进程要可以得到此运行进程的内容,在共享注入中,主要利用技术就是映射节,通过不断优化不落地,来躲避检测,而启动主要还是在利用ResumeThread或者CreateThread
而线程池注入主要倾向于如何让系统自然的运行代码,毕竟运行的逻辑是系统自然的