SharpSploit 中很有趣的一部分是 SharpSploit.Execution.DynamicInvoke,我通常只需要这一部分并不需要整个库,本文记录一些阅读 SharpSploit 代码并提取其核心部分的过程。
以作者给出的例子为基础:
xxxxxxxxxx//Get a pointer to the NtCreateThreadEx function.IntPtr pFunction = Execution.DynamicInvoke.Generic.GetLibraryAddress(@"ntdll.dll", "NtCreateThreadEx");
//Create an instance of a NtCreateThreadEx delegate from our function pointer.DELEGATES.NtCreateThreadEx createThread = (NATIVE_DELEGATES.NtCreateThreadEx)Marshal.GetDelegateForFunctionPointer(pFunction, typeof(NATIVE_DELEGATES.NtCreateThreadEx));
//Invoke NtCreateThreadEx using the delegatecreateThread(ref threadHandle, Win32.WinNT.ACCESS_MASK.SPECIFIC_RIGHTS_ALL | Win32.WinNT.ACCESS_MASK.STANDARD_RIGHTS_ALL, IntPtr.Zero, process.Handle, baseAddr, IntPtr.Zero, suspended, 0, 0, 0, IntPtr.Zero);从这个例子出发,将依赖的方法依次填充进去。
整理了一下方法间的调用关系和依赖,得到了奇怪的东西,我依赖我自己。

然后重读了 GetLibraryAddress 函数,整理了新的调用流程:
xxxxxxxxxxpublic static IntPtr GetLibraryAddress(string DLLName, string FunctionName, bool CanLoadFromDisk = false){ IntPtr hModule = GetLoadedModuleAddress(DLLName); if (hModule == IntPtr.Zero && CanLoadFromDisk) { hModule = LoadModuleFromDisk(DLLName); if (hModule == IntPtr.Zero) { throw new FileNotFoundException(DLLName + ", unable to find the specified file."); } } else if (hModule == IntPtr.Zero) { throw new DllNotFoundException(DLLName + ", Dll was not found."); }
return GetExportAddress(hModule, FunctionName);}这个函数是判断模块是否已经加载,根据给定参数选择是否从磁盘加载 DLL。
Rtl 和 Ldr 这两个函数是 ntdll 的导出函数,默认加载进每个进程的,所以在调用这两个方法的时候不会用到 LoadModuleFromDisk。
实际的调用逻辑如下:


通常来说,我不需要 LoadModuleFromDisk,我在我自己的版本中去除了这个方法。
然后整理了一些不必要的包装,得到 3 个方法:
GetModuleAddress:获取模块基地址
xxxxxxxxxxpublic static IntPtr GetModuleAddress(string DLLName){ ProcessModuleCollection ProcModules = Process.GetCurrentProcess().Modules; foreach (ProcessModule Mod in ProcModules) { if (Mod.FileName.ToLower().EndsWith(DLLName.ToLower())) { return Mod.BaseAddress; } } return IntPtr.Zero;}GetFunctionAddress:解析导出表获取函数指针
xxxxxxxxxxpublic static IntPtr GetFunctionAddress(IntPtr ModuleBase, string ExportName){ IntPtr FunctionPtr = IntPtr.Zero;
Int32 PeHeader = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + 0x3C)); Int16 OptHeaderSize = Marshal.ReadInt16((IntPtr)(ModuleBase.ToInt64() + PeHeader + 0x14)); Int64 OptHeader = ModuleBase.ToInt64() + PeHeader + 0x18; Int16 Magic = Marshal.ReadInt16((IntPtr)OptHeader); Int64 pExport = 0;
if (Magic == 0x010b) { pExport = OptHeader + 0x60; } else { pExport = OptHeader + 0x70; }
Int32 ExportRVA = Marshal.ReadInt32((IntPtr)pExport); Int32 OrdinalBase = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x10)); Int32 NumberOfFunctions = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x14)); Int32 NumberOfNames = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x18)); Int32 FunctionsRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x1C)); Int32 NamesRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x20)); Int32 OrdinalsRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x24));
for (int i = 0; i < NumberOfNames; i++) { string FunctionName = Marshal.PtrToStringAnsi((IntPtr)(ModuleBase.ToInt64() + Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + NamesRVA + i * 4)))); if (FunctionName.Equals(ExportName, StringComparison.OrdinalIgnoreCase)) { Int32 FunctionOrdinal = Marshal.ReadInt16((IntPtr)(ModuleBase.ToInt64() + OrdinalsRVA + i * 2)) + OrdinalBase; Int32 FunctionRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + FunctionsRVA + (4 * (FunctionOrdinal - OrdinalBase)))); FunctionPtr = (IntPtr)((Int64)ModuleBase + FunctionRVA); break; } }
return FunctionPtr;}GetFunctionDelegate:将函数指针转化为委托
xxxxxxxxxxpublic static Delegate GetFunctionDelegate(string DLLName, string FunctionName, Type FunctionDelegateType){ IntPtr hModule = GetModuleAddress(DLLName); IntPtr FunctionPointer = GetFunctionAddress(hModule, FunctionName); Delegate funcDelegate = Marshal.GetDelegateForFunctionPointer(FunctionPointer, FunctionDelegateType);
return funcDelegate;}用法:
xxxxxxxxxx[UnmanagedFunctionPointer(CallingConvention.StdCall)]public delegate IntPtr HeapCreate(uint flOptions, UIntPtr dwInitialSize, UIntPtr dwMaximumSize);
var hc = (HeapCreate)GetFunctionDelegate("kernel32.dll", "HeapCreate", typeof(HeapCreate));IntPtr heaphandle = hc(0x40000, (UIntPtr)buf.Length, UIntPtr.Zero);局限性:
当我想用分配堆内存的方式代替 VirtualAlloc 时,遇到了 HeapAlloc 始终无法调用成功的问题,在反复检查不是我的代码逻辑问题后,我就把这个问题搁置了。
直到在 http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html 的评论区中找到了问题的原因:

在 DLL 中有一类不同于常规导出函数的函数,叫做 forwarder,这种函数的作用就是指示调用这个函数的代码转到其他 DLL 的导出函数。
https://devblogs.microsoft.com/oldnewthing/20060719-24/?p=30473
当 DInvoke 获取函数地址后,这个地址定位的并不是可执行代码而是字符串。
解决这个问题需要获取 kernel32.dll 的导出信息:dumpbin /EXPORTS kernel32.dll > k32.txt

可以看到 HeapAlloc 后跟着 forwarded to NTDLL.RtlAllocateHeap,用 RtlAllocateHeap 代替 HeapAlloc 即可解决问题。
使用这种技术写了一个 shellcode 加载器:

计划通 ψ(`∇´)ψ
https://github.com/TheWover/DInvoke
https://blog.nviso.eu/2020/11/20/dynamic-invocation-in-net-to-bypass-hooks/