内存免杀

av/edr 检测的方式

静态

  1. 特征值

可以通过甲壳,加密等方式来进行绕过

或者是垃圾代码混淆

动态

动态也是比较难的部分

r3 层

  1. Hook 高危的api
  2. 内存中特征检测
  3. 检测父子关系

r0 层

  1. 内核回调 windows /内核指针 linux
    1. 内核回调指的是使用psSetCreateProcessNotifyRoutine、CmRegisterCallback等API来注册回调函数
    2. 当制定的时间发生的时候,比如创建进程,删除进程,动注册表的时候edr 调用对应的回调函数,进行检测

​ 内核指针是用于linux

Kprobes(Kernel Probes)是Linux提供的一种动态调试机制。它允许用户在几乎任何内核指令处设置断点,并在命中这些断点时执行一些预定义的操作,比如收集数据或执行额外的检查。Kprobes可以分为以下几类:

  • Kprobe:可以在任何内核模块或核心内核函数的入口处插入探测点。
  • Jprobe:允许你在内核函数的入口处放置一个探测点,并且你的处理函数会在这个函数被调用时自动获得相同的参数列表。
  • Kretprobe:用于在内核函数返回时捕获控制权。
  1. 内核钩子 - SSDT系统调用表、全局描述符表GDT、中断描述符表(IDT)钩子会和x64下的patch guard冲突,但依然有绕过方式。

​ patch guard 是一个内核的保护系统,防止未经授权的内核代码修改

  1. 借助ETW实现对底层调用的监控 - ETW是Windows提供的一个强大的消息跟踪机制,允许收集包括内核事件在内的各种系统级事件。通过订阅特定的ETW提供者和事件,EDR可以获得关于系统行为的详细信息。
  2. 硬件辅助 - Intel VT-x或AMD-V,在更低的硬件级别提供对执行环境的控制和监视。

绕过方法

Shellcode 自解密

可以在shellcode 之前添加一个e8 地址的方式

由于 e8 在x86 的汇编当中,意思是call

/xe8 揭秘函数的地址

的这样的方式进行解密后面的代码

当call 执行完了之后下面执行的就是shellcode 原始的代码了

栈区伪造

  1. 重写高风险的api

比如NtAllocateVirtualMemory 用于创建内存

  1. 将返回地址可以放入一个全局的结构体中

这个返回的地址可以使用一个标签,然后直接jmp就可以了

  1. 进入函数之后抬高栈区地址

可以使用sub rsp, N 的方式抬高

  1. Push 0

将栈区进行分割

  1. 在这之上部署一个假栈(伪造一些常见的返回地址制作一个栈底和看上去合理的调用链);
  2. 在假栈 上部署一个Gadget Frame 用来跳转到fixup 函数

跳转回高风险函数调用前的位置,比如预先从内存中找好的JMP [RBX]片段);

  1. 为跳转和堆栈恢复做准备,将真正的返回地址、RBX寄存器值放入结构体暂存,然后将堆栈恢复函数fixup()的地址给RBX,最后JMP到真正的函数调用;

img

整个调用过过程可以理解为下面的代码

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
#include <stdio.h>
#include <stdlib.h>

// 假设这是一个合法的函数原型
void vulnerableFunction(char *buffer);

// 定义全局结构体
struct GlobalStruct {
void* originalReturnAddress;
void* shellcodeAddress;
} globalStruct;

// 这是攻击者准备好的shellcode
unsigned char shellcode[] = "\x90\x90\x90\x90"; // NOP sled + payload

// 修复栈帧并跳转回正常流程的函数
void fixup() {
__asm__ volatile (
"movq %0, %%rbp;" // Restore base pointer
"movq %1, %%rsp;" // Restore stack pointer
"jmp *%2;" // Jump back to the original return address
:
: "r" (globalStruct.originalReturnAddress),
"r" (globalStruct.shellcodeAddress),
"r" (globalStruct.originalReturnAddress)
: "memory"
);
}

void exploit() {
// 将shellcode放置到某个可执行内存区域
void *execBuffer = malloc(sizeof(shellcode));
memcpy(execBuffer, shellcode, sizeof(shellcode));

// 设置全局结构体
globalStruct.originalReturnAddress = &&safePoint; // Save original return address
globalStruct.shellcodeAddress = execBuffer; // Point to shellcode

// 触发vulnerableFunction以改变控制流
char buffer[64];
vulnerableFunction(buffer); // 这里假设vulnerableFunction存在缓冲区溢出漏洞

safePoint:
printf("Exploit completed.\n");
}

int main() {
exploit();
return 0;
}

内存免杀
https://tsy244.github.io/2025/03/10/免杀/内存免杀/
Author
August Rosenberg
Posted on
March 10, 2025
Licensed under