通过.NET实现Gargoyle(一种内存扫描的对抗技术)

xiaohui 技术 2018年12月14日发布
Favorite收藏

导语:Gargoyle是一种帮助恶意软件逃脱内存扫描的技术,由Josh Lospinoso于2017年对外公布,不过只是作为概念验证发布的,它的强项在于确保注入的代码在大多数情况下是不可执行(隐藏)的,从而使内存扫描技术难以识别出恶意代码。

Gargoyle是一种帮助恶意软件逃脱内存扫描的技术,由Josh Lospinoso于2017年对外公布,不过只是作为概念验证发布的,它的强项在于确保注入的代码在大多数情况下是不可执行(隐藏)的,从而使内存扫描技术难以识别出恶意代码。客观的说,这是一项很棒的技术,因为研究人员曾经使用 Cobalt Strike (一款非常优秀的后渗透平台)进行过测试,除此之外还用WinDBG( 查看和调试windows内核的一些东西难免需要用到WinDbg)和Volatility插件(Volatility是一款开源内存取证框架,能够对导出的内存镜像进行分析,通过获取内核数据结构,使用插件获取内存的详细情况以及系统的运行状态。)来进行过检测。

由于Powershell支持较高版本的.net,所以研究人员发现通过.NET实现Gargoyle也是可能的。

动态.NET代码执行

目前许多攻击性技术都尽可能避免让恶意代码在目标设备上的硬盘上工作,这样做的目的是为了避免传统的杀毒扫描并避免在硬盘留下可以进行取证的痕迹,这就导致了内存取证技术的发展,以帮助高效打击恶意攻击。对于本机代码,传统上使用反射DLL注入(reflective DLL injection)技术,Reflective DLL Injection提出来就是解决这个问题的,完全不需要在硬盘上写入inject.dll,而是直接在内存当中操作;process hollowing(现代恶意软件常用的一种进程创建技术)以及许多其他类似技术来执行。但是,.NET可以通过使用动态程序集加载和反射,以非常简单的逃避机制来实现以上这些技术的目标。

例如,在PowerShell中研究人员可以看到如何在内存中动态加载.NET程序集,并使用反射技术来实例化类并进行调用。

1.png

对于本文的详细背景信息,如果你感兴趣,可以阅读《如何检测恶意使用的.NET》,其中有更多详细信息。此外,本文的一些信息也在Joe Desimone发布的一篇精彩文章中有所介绍,他在其中还演示了如何使用他发布的Get-ClrReflection.ps1 PowerShell脚本检测内存中加载的.NET程序集。

.NET定时器和回调

最开始的Gargoyle技术使用的是本机Windows定时器对象,以便在定义的时间间隔内执行回调。然后,当定时器到期时,使用由内核传送到目标进程的APC执行这些操作。但在.NET的实现过程中,它采用了更准时和高级的定时器实现方法。

2.png

正如研究人员从Timer()类的构造函数中看到的那样,研究人员可以指定.NET回调方法和在定时器上提供的参数。不过其中的一个限制是,回调必须与TimerCallback委托匹配,具体的过程研究人员可以在下面看到。

3-timer-callback-delegate.png

定时器是系统常用的集之一,程序员可以根据自己的需求定制一个定时器类型,也可以使用.net内建的定时器类型。在.net中一共为程序员提供了3种定时器:

· System.Windows.Forms.Timer类型

· System.Threading.Timer类型

· System.Timers.Timer类型

概括来说,这3种类型都实现了定时的功能。程序员通常需要做的是为定时器设置一个间断时间,设置定时器到时的处理方法,然后可以等待定时器不断地定时和出发时间处理。

由此,我猜测此处使用的应该是System.Threading.Timer类,TimerCallback委托就是其中的建构函数,使用TimerCallback委托来指定由调用的方法Timer, 在创建计时器的线程中,此方法不会执行,它在系统提供一个单独的线程池线程中执行。由于这种委托条件的限制,研究人员仅限于使用此签名调用方法。在理想的情况下,研究人员可能希望纯粹指定对Assembly.Load()的回调,将程序集的字节数组作为状态参数,从而确保恶意代码执行。遗憾的是,委托不匹配,并且在加载程序集时自动运行代码并不像在DllMain()函数中放置代码那样简单,就像本机DLL的情况一样。因此,研究人员需要制作一个经过简化的封装器(Wrapper)来处理内存中恶意程序集的加载和执行,然后对计时器进行清理和重新调度。

加载的实现

现在我们有了一个.NET timer原语,它允许我们定义回调,不过要实现加载,

还需要进行以下3步的操作:

1.研究人员需要自定义一些加载代码,且这些代码需要永久加载。因此,研究人员的办法是尽可能使他们简单化且无攻击性,以便将其隐藏起来;

2.研究人员无法将程序集单个的进行卸载(具体原因,请点此查看),因此为了清理植入的设备中的具备全部恶意功能的程序集,研究人员需要将其加载到新的应用程序域(Appdomain)中,然后卸载,AppDomain类似于一个轻量级进程,它是 .net/mono 代码运行时的一个逻辑容器;

3.研究人员需要找到一些方法来加载.NET加载程序集,然后调用一个方法来创建.NET定时器,以便将来加载研究人员用来测试的恶意程序集。

在本文中,“AssemblyLoader”就是研究人员自定义的最简单的无害加载程序集,“DemoAssembly”将是一个PoC程序集,表示在实际使用中具有完整功能的恶意植入。

以下是一段C#代码,涵盖了上述第1点和第2点所需的大部分内容:

public class AssemblyLoaderProxy : MarshalByRefObject, IAssemblyLoader 
{ 
    public void Load(byte[] bytes) 
    { 
        var assembly = AppDomain.CurrentDomain.Load(bytes); 
        var type = assembly.GetType("DemoAssembly.DemoClass"); 
        var method = type.GetMethod("HelloWorld"); 
        var instance = Activator.CreateInstance(type, null); 
        Console.WriteLine("--- Executed from {0}: {1}", AppDomain.CurrentDomain.FriendlyName, method.Invoke(instance, null)); 
    } 
} 
 
public static int StartTimer(string gargoyleDllContentsInBase64) 
{ 
    Console.WriteLine("Start timer function called"); 
    byte[] dllByteArray = Convert.FromBase64String(gargoyleDllContentsInBase64); 
    Timer t = new Timer(new TimerCallback(TimerProcAssemblyLoad), dllByteArray, 0, 0); 
    return 0; 
} 
private static void TimerProcAssemblyLoad(object state) 
{ 
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); 
    Console.WriteLine("Hello from timer!");  
    String appDomainName = "TemporaryApplicationDomain"; 
    AppDomain applicationDomain = System.AppDomain.CreateDomain(appDomainName); 
    var assmblyLoaderType = typeof(AssemblyLoaderProxy); 
    var assemblyLoader = (IAssemblyLoader)applicationDomain.CreateInstanceFromAndUnwrap(assmblyLoaderType.Assembly.Location, assmblyLoaderType.FullName); 
    assemblyLoader.Load((byte[])state); 
    Console.WriteLine("Dynamic assembly has been loaded in new AppDomain " + appDomainName); 
    AppDomain.Unload(applicationDomain); 
    Console.WriteLine("New AppDomain has been unloaded");  
    Timer t = new Timer(new TimerCallback(TimerProcAssemblyLoad), state, 1000, 0); 
}

这是一个相当少量的代码,除了一些名称和调试语句之外,可能还会进行恶意检查,以防止它具有恶意属性。如果使用更大的无害集并且将此代码作为其中的一小部分,则尤其要进行恶意检查。

这是通过定义一个回调函数来实现的,该函数将使用以下流程加载恶意程序集(本文中使用的是DemoAssembly): 

1.从一个新AppDomain中的byte []数组加载DemoAssembly;

2.实例化DemoClass对象;

3.执行HelloWorld()方法;

4.卸载AppDomain来清理内存中的DemoAssembly;

5.重新设置定时器,以无限地重复该过程;

执行Assembly Loader(程序集加载)

为了实现以上提到的第3步,研究人员可以利用本机代码中的COM来触发加载程序集并调用StartTimer()方法,该方法将设置.NET定时器,然后定期在定时器上加载研究人员植入的恶意DemoAssembly。以下代码段显示了此过程的关键代码部分:

// execute managed assembly 
DWORD pReturnValue; 
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain( 
L"AssemblyLoader.dll", 
L"AssemblyLoader", 
L"StartTimer", 
lpcwstr_base64_contents, 
&pReturnValue);

这为研究人员提供了2个触发加载器的选项,研究人员可以运行执行此代码的本机应用程序,也可以将其作为本机DLL注入合法应用程序,然后立即卸载。最终的结果是,研究人员得到了一个.NET加载程序程序集,它看起来无法加载是无害的,但使用.NET可以在未来以固定的时间间隔在内存中加载功能齐全的. net植入程序。

如果研究人员将所有这些功能部分组合在一起,就会得到如下所示的结果。

6.png

然后,如果研究人员使用ProcessHacker之类的工具检查加载的.NET程序集,将只能看到加载程序集,而不会看到临时AppDomain或研究人员植入的恶意DemoAssembly的证据。

7.png

另外,如果研究人员使用像Get-ClrReflection.ps1这样的工具,那么研究人员将看不到恶意痕迹,因为研究人员的设置的加载程序集是通过硬盘加载的,而内存中的DemoAssembly绝对不可能在杀毒软件扫描时加载。

8.png

如何防止通过.NET实现的Gargoyle

通过.NET方法实现的Gargoyle和Gargoyle的原始方法一样,都是基于对某个时间点的内存扫描技术和一般内存取证的绕过。这意味着可以通过实时跟踪解决方案检查通过.NET方法实现的Gargoyle活动。在《如何检测恶意使用的.NET》中,研究人员介绍了如何跟踪.NET程序集的加载活动的具体方法。例如,如果我们使用研究人员发布的PoC ETW跟踪工具只跟踪高风险模块载荷,就可以清楚地看到恶意DemoAssembly的持续重新加载过程。

9.png

不过,实时跟踪解决来预防此恶意活动,仍然存在一些问题,比如攻击者可以在时间点上做文章。所以一个比较好的预防方法就是.NET确实留下了一些可以使用WinDBG的SOS调试扩展来获取的活动痕迹。使用DumpDomain命令,可以显示了大量“TemporaryApplicationDomain”应用程序域和对正在加载的动态模块的引用的残留证据。

10.png

然而,还有更直接的方法,就是是否有可能在系统范围内分析.NET定时器以识别潜在的可疑回调。 Microsoft实际上就为.NET提供了一个名为ClrMD的内存诊断库,我之前使用过这个库,这是我的第一个调用端口。

其实,Criteo Labs实际上已经发布了一篇关于如何使用ClrMD(出于非安全目的)的文章,包括发布代码的细节。不过,研究人员通过进行代码调整,自定义了一个工具,可以对定时器回调进行观察,并识别潜在的可疑实例。

如果研究人员在运行Visual Studio的示例系统上运行此工具就可以找到Visual Studio相关进程中存在的各种定时器回调示例。

11.png

但是,这些回调示例都非常相似,并且在系统或Microsoft名称空间中都存在这些回调,这意味着利用这些名称空间中的回调来发现恶意用例是可能的。不过如上所述,在实际检测中,会遇到许多约束条件,这会导致研究人员根据实际情况,自定义代码来检查各种定时器回调。

12.png

为了应付安全人员的这个检查,攻击者可能会对这种回调进行监测,为此,研究人员要做的就是让回调看起来尽可能合法合理,本文的所有测试代码均可在这里获得。

本文翻译自:https://www.countercept.com/blog/gargoyle-memory-scanning-evasion-for-net/如若转载,请注明原文地址: http://www.4hou.com/technology/15069.html
点赞 2
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论