内容简介:Gargoyle是一种帮助恶意软件逃脱内存扫描的技术,由Josh Lospinoso于2017年对外公布,不过只是作为由于Powershell支持较高版本的.net,所以研究人员发现通过.NET实现Gargoyle也是可能的。动态.NET代码执行
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程序集,并使用反射技术来实例化类并进行调用。
对于本文的详细背景信息,如果你感兴趣,可以阅读 《如何检测恶意使用的.NET》 ,其中有更多详细信息。此外,本文的一些信息也在Joe Desimone发布的一篇精彩文章中有所介绍,他在其中还演示了如何使用他发布的 Get-ClrReflection.ps1 PowerShell 脚本检测内存中加载的.NET程序集。
.NET定时器和回调
最开始的Gargoyle技术使用的是本机Windows定时器对象,以便在定义的时间间隔内执行回调。然后,当定时器到期时,使用由内核传送到目标进程的APC执行这些操作。但在.NET的实现过程中,它采用了更准时和高级的定时器实现方法。
正如研究人员从Timer()类的构造函数中看到的那样,研究人员可以指定.NET回调方法和在定时器上提供的参数。不过其中的一个限制是,回调必须与TimerCallback委托匹配,具体的过程研究人员可以在下面看到。
定时器是系统常用的集之一,程序员可以根据自己的需求定制一个定时器类型,也可以使用.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植入程序。
如果研究人员将所有这些功能部分组合在一起,就会得到如下所示的结果。
然后,如果研究人员使用ProcessHacker之类的 工具 检查加载的.NET程序集,将只能看到加载程序集,而不会看到临时AppDomain或研究人员植入的恶意DemoAssembly的证据。
另外,如果研究人员使用像Get-ClrReflection.ps1这样的工具,那么研究人员将看不到恶意痕迹,因为研究人员的设置的加载程序集是通过硬盘加载的,而内存中的DemoAssembly绝对不可能在杀毒软件扫描时加载。
如何防止通过.NET实现的Gargoyle
通过.NET方法实现的Gargoyle和Gargoyle的原始方法一样,都是基于对某个时间点的内存扫描技术和一般内存取证的绕过。这意味着可以通过实时跟踪解决方案检查通过.NET方法实现的Gargoyle活动。在 《如何检测恶意使用的.NET》 中,研究人员介绍了如何跟踪.NET程序集的加载活动的具体方法。例如,如果我们使用研究人员发布的PoC ETW跟踪工具只跟踪高风险模块载荷,就可以清楚地看到恶意DemoAssembly的持续重新加载过程。
不过,实时跟踪解决来预防此恶意活动,仍然存在一些问题,比如攻击者可以在时间点上做文章。所以一个比较好的预防方法就是.NET确实留下了一些可以使用WinDBG的SOS调试扩展来获取的活动痕迹。使用DumpDomain命令,可以显示了大量“TemporaryApplicationDomain”应用程序域和对正在加载的动态模块的引用的残留证据。
然而,还有更直接的方法,就是是否有可能在系统范围内分析.NET定时器以识别潜在的可疑回调。 Microsoft实际上就为.NET提供了一个名为 ClrMD 的内存诊断库,我之前使用过这个库,这是我的第一个调用端口。
其实,Criteo Labs实际上已经发布了一篇关于 如何使用ClrMD (出于非安全目的)的文章,包括发布代码的细节。不过,研究人员通过进行代码调整,自定义了一个工具,可以对定时器回调进行观察,并识别潜在的可疑实例。
如果研究人员在运行Visual Studio的示例系统上运行此工具就可以找到Visual Studio相关进程中存在的各种定时器回调示例。
但是,这些回调示例都非常相似,并且在系统或Microsoft名称空间中都存在这些回调,这意味着利用这些名称空间中的回调来发现恶意用例是可能的。不过如上所述,在实际检测中,会遇到许多约束条件,这会导致研究人员根据实际情况,自定义代码来检查各种定时器回调。
为了应付安全人员的这个检查,攻击者可能会对这种回调进行监测,为此,研究人员要做的就是让回调看起来尽可能合法合理,本文的所有测试代码均可在 这里 获得。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 对抗攻击之利用水印生成对抗样本
- 基于梯度扰动探索对抗攻击与对抗样本
- WriteUp - 2018中国网络安全技术对抗赛阿里安全攻防对抗赛
- 安全之红蓝对抗简介
- 深度有趣 | 07 生成式对抗网络
- 实战生成对抗网络(三):DCGAN
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。