csdn上的http://blog.csdn.net/wwh1004就是我,非冒充
网上流传的注入托管DLL说是纯C#,实际上还是调用了C++编写的DLL用于在远程进程中启动CLR。这样做会让被注入进程多一个已加载DLL,发布的程序也会带一个启动CLR的DLL(比如CLRInjection还带着LoadCLR2.dll和LoadCLR4.dll),看着很不舒服。所以我看了下MegaDumper.exe的注入方式,发现是手写机器码,所以我也手写机器码实现了,并且支持32位/64位,无CLR环境/有CLR环境,不同CLR版本的注入器(MegaDumper.exe是只支持32位且被注入进程必须存在CLRv2环境)。
注入器是这样注入托管程序集的:
先注入对应的mscoree.dll
再注入托管程序集
这2步都是难点,第一步的难点是如果判断DLL是否为托管程序集并且获取到DLL的目标CLR版本。也许有人会说用Assembly.Load系列方法,但是很遗憾,这种方法稳定性极差,如果同时判断同名不同目标CLR版本程序集,第二次判断的结果将与第一次相同。举个例子,有2个同名程序集A和B,A的目标CLR版本是v2,B的目标CLR版本是v4,先用Assembly.Load系列方法判断了A,获取了A的目标CLR版本,我再用Assembly.Load*系列方法判断B并获取B的目标CLR版本,返回的将是A的目标CLR版本。这是一个藏的很深的BUG,我也是很久才发现有这个问题。
我本打算用dnlib.dll直接读取程序集的目标CLR版本,但是dnlib.dll太大了,1MB有多,于是自己解析PE头。
private struct Section
{
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public Section(uint virtualSize, uint virtualAddress, uint sizeOfRawData, uint pointerToRawData)
{
VirtualSize = virtualSize;
VirtualAddress = virtualAddress;
SizeOfRawData = sizeOfRawData;
PointerToRawData = pointerToRawData;
}
}
/// <summary>
/// 获取CLR版本
/// </summary>
/// <param name="binaryReader"></param>
/// <returns></returns>
private static string GetVersionString(BinaryReader binaryReader)
{
uint ntHeaderOffset;
bool is64;
Section[] sections;
uint rva;
Section? section;
GetPEInfo(binaryReader, out ntHeaderOffset, out is64);
binaryReader.BaseStream.Position = ntHeaderOffset + (is64 ? 0xF8 : 0xE8);
rva = binaryReader.ReadUInt32();
//.Net MetaData Directory RVA
if (rva == 0)
throw new BadImageFormatException("文件不是程序集");
sections = GetSections(binaryReader);
section = GetSection(rva, sections);
if (section == null)
throw new InvalidDataException("未知格式的二进制文件");
binaryReader.BaseStream.Position = section.Value.PointerToRawData + rva - section.Value.VirtualAddress + 0x8;
//.Net MetaData Directory FileOffset
rva = binaryReader.ReadUInt32();
//.Net MetaData Header RVA
if (rva == 0)
throw new BadImageFormatException("文件不是程序集");
section = GetSection(rva, sections);
if (section == null)
throw new InvalidDataException("未知格式的二进制文件");
binaryReader.BaseStream.Position = section.Value.PointerToRawData + rva - section.Value.VirtualAddress + 0xC;
//.Net MetaData Header FileOffset
return Encoding.UTF8.GetString(binaryReader.ReadBytes(binaryReader.ReadInt32() - 2));
}
/// <summary>
/// 获取PE信息
/// </summary>
/// <param name="binaryReader"></param>
/// <param name="ntHeaderOffset"></param>
/// <param name="is64"></param>
private static void GetPEInfo(BinaryReader binaryReader, out uint ntHeaderOffset, out bool is64)
{
ushort machine;
binaryReader.BaseStream.Position = 0x3C;
ntHeaderOffset = binaryReader.ReadUInt32();
binaryReader.BaseStream.Position = ntHeaderOffset + 0x4;
machine = binaryReader.ReadUInt16();
if (machine != 0x14C && machine != 0x8664)
throw new InvalidDataException("未知格式的二进制文件");
is64 = machine == 0x8664;
}
/// <summary>
/// 获取节
/// </summary>
/// <param name="binaryReader"></param>
/// <returns></returns>
private static Section[] GetSections(BinaryReader binaryReader)
{
uint ntHeaderOffset;
bool is64;
ushort numberOfSections;
Section[] sections;
GetPEInfo(binaryReader, out ntHeaderOffset, out is64);
numberOfSections = binaryReader.ReadUInt16();
binaryReader.BaseStream.Position = ntHeaderOffset + (is64 ? 0x108 : 0xF8);
sections = new Section[numberOfSections];
for (int i = 0; i < numberOfSections; i++)
{
binaryReader.BaseStream.Position += 0x8;
sections[i] = new Section(binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32());
binaryReader.BaseStream.Position += 0x10;
}
return sections;
}
/// <summary>
/// 获取RVA对应节
/// </summary>
/// <param name="rva"></param>
/// <param name="sections"></param>
/// <returns></returns>
private static Section? GetSection(uint rva, Section[] sections)
{
foreach (Section section in sections)
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + Math.Max(section.VirtualSize, section.SizeOfRawData))
return section;
return null;
}
第一个难点解决了,接下来看第二个难点,如何注入?全手写机器码意味着极大的工作量,为什么不用c++写好函数,提取出编译好的机器码呢?提取出来的机器码是固定了变量地址的,所以还要自己改。手改机器码肯定难,于是写了一段新代码用来帮助修改
public static class AsmCodeHelper
{
public static bool Is64;
public static void SetCode()
{
string codes;
Assembler.NasmPath = "nasm.exe";
Assembler.NdisasmPath = "ndisasm.exe";
codes = string.Join(Environment.NewLine, BytesToCSharpCode(File.ReadAllText("strbytes.txt")));
Clipboard.SetText(codes);
}
private static List<string> BytesToCSharpCode(string strBytes)
{
byte[] bytes;
bytes = strBytes.Split(' ').Select(s => Convert.ToByte(s, 16)).ToArray();
return BytesToCSharpCode(bytes);
}
private static List<string> BytesToCSharpCode(byte[] bytes)
{
IList<AsmData> asmDataList;
List<string> codeList;
File.WriteAllLines("opcodes.txt", Assembler.BytesToOpcodes(bytes, Is64).GetAllOpcodes());
Process.Start(@"C:\Software\Notepad++\notepad++.exe", Path.GetFullPath("opcodes.txt"));
Console.WriteLine("反编译完成,按任意键继续......");
Console.ReadKey();
asmDataList = Assembler.OpcodesToBytes(File.ReadAllLines("opcodes.txt"), Is64);
codeList = new List<string>(asmDataList.Count * 8);
foreach (AsmData asmData in asmDataList)
codeList.AddRange(BytesToCSharpCodeSub(asmData.Bytes, asmData.Opcode));
return codeList;
}
private static List<string> BytesToCSharpCodeSub(byte[] bytes, string opcode)
{
List<string> codeList;
codeList = new List<string>(bytes.Length + 2);
for (int i = 0; i < bytes.Length; i++)
codeList.Add(string.Format("p[{0}] = 0x{1};", i.ToString(), bytes[i].ToString("X2")));
codeList.Add(string.Format("p += {0};", bytes.Length.ToString()));
codeList.Add(string.Format("//{0}", opcode));
return codeList;
}
}
利用ndisasm和nasm轻松反编译与编译,修改好后直接生成c#写机器码的代码。在改的时候会发现,CorBindToRuntimeEx和CLRCreateInstance函数的地址是不确定的,这又要自己获取。GetProcAddress只能获取当前进程的,并不能获取远程进程,所以这又需要我们熟悉PE结构。利用DLL的导出表,可以自己获取,代码如下:
/// <summary>
/// 获取模块句柄,获取失败时返回 <see cref="IntPtr.Zero"/>
/// </summary>
/// <param name="processHandle">进程句柄</param>
/// <param name="first">是否返回第一个模块句柄</param>
/// <param name="moduleName">模块名</param>
/// <param name="flag">过滤标识</param>
/// <returns></returns>
internal static unsafe IntPtr GetHandleInternal(IntPtr processHandle, bool first, string moduleName, uint flag)
{
if (!first && string.IsNullOrEmpty(moduleName))
throw new ArgumentNullException("first为false时moduleName不能为空");
bool is64;
bool isXP;
IntPtr moduleHandle;
uint size;
IntPtr[] moduleHandles;
StringBuilder moduleNameBuffer;
if (processHandle == IntPtr.Zero)
return IntPtr.Zero;
if (!Process32.Is64ProcessInternal(processHandle, out is64))
return IntPtr.Zero;
if (is64 && !Environment.Is64BitProcess)
throw new NotSupportedException("目标进程为64位但当前进程为32位");
moduleHandle = IntPtr.Zero;
isXP = Environment.OSVersion.Version.Major == 5;
if (isXP)
{
//XP兼容
if (!EnumProcessModules(processHandle, &moduleHandle, (uint)IntPtr.Size, out size))
return IntPtr.Zero;
}
else
{
if (!EnumProcessModulesEx(processHandle, &moduleHandle, (uint)IntPtr.Size, out size, flag))
//先获取储存所有模块句柄所需的字节数
return IntPtr.Zero;
}
if (first)
//返回第一个模块句柄
return moduleHandle;
moduleHandles = new IntPtr[size / IntPtr.Size];
fixed (IntPtr* p = &moduleHandles[0])
{
if (isXP)
{
//XP兼容
if (!EnumProcessModules(processHandle, p, size, out size))
return IntPtr.Zero;
}
else
{
if (!EnumProcessModulesEx(processHandle, p, size, out size, flag))
//获取所有模块句柄
return IntPtr.Zero;
}
}
moduleNameBuffer = new StringBuilder((int)MODULENAME_MAX_LENGTH);
for (int i = 0; i < moduleHandles.Length; i++)
{
if (!GetModuleBaseName(processHandle, moduleHandles[i], moduleNameBuffer, MODULENAME_MAX_LENGTH))
return IntPtr.Zero;
if (moduleNameBuffer.ToString().Equals(moduleName, StringComparison.OrdinalIgnoreCase))
return moduleHandles[i];
}
return IntPtr.Zero;
}
/// <summary>
/// 获取远程进程函数地址
/// </summary>
/// <param name="processHandle">进程句柄</param>
/// <param name="moduleName">模块名</param>
/// <param name="functionName">函数名</param>
/// <returns></returns>
internal static unsafe IntPtr GetProcAddressInternal(IntPtr processHandle, string moduleName, string functionName)
{
IntPtr moduleHandle;
int ntHeaderOffset;
bool is64;
int iedRVA;
IMAGE_EXPORT_DIRECTORY ied;
int[] nameOffsets;
string name;
short ordinal;
int addressOffset;
moduleHandle = GetHandleInternal(processHandle, false, moduleName, EnumModulesFilterFlag.ALL);
if (moduleHandle == IntPtr.Zero)
return IntPtr.Zero;
if (!MemoryIO.ReadInt32Internal(processHandle, moduleHandle + 0x3C, out ntHeaderOffset))
return IntPtr.Zero;
if (!Process32.Is64ProcessInternal(processHandle, out is64))
return IntPtr.Zero;
if (is64)
{
if (!MemoryIO.ReadInt32Internal(processHandle, moduleHandle + ntHeaderOffset + 0x88, out iedRVA))
return IntPtr.Zero;
}
else
{
if (!MemoryIO.ReadInt32Internal(processHandle, moduleHandle + ntHeaderOffset + 0x78, out iedRVA))
return IntPtr.Zero;
}
if (!ReadProcessMemory(processHandle, moduleHandle + iedRVA, &ied, 40, null))
return IntPtr.Zero;
nameOffsets = new int[ied.NumberOfNames];
fixed (void* p = &nameOffsets[0])
if (!ReadProcessMemory(processHandle, moduleHandle + (int)ied.AddressOfNames, p, ied.NumberOfNames * 4, null))
return IntPtr.Zero;
for (int i = 0; i < ied.NumberOfNames; i++)
{
if (!MemoryIO.ReadStringInternal(processHandle, moduleHandle + nameOffsets[i], out name, 40, false, Encoding.ASCII))
return IntPtr.Zero;
if (name == functionName)
{
if (!MemoryIO.ReadInt16Internal(processHandle, moduleHandle + (int)ied.AddressOfNameOrdinals + i * 2, out ordinal))
return IntPtr.Zero;
if (!MemoryIO.ReadInt32Internal(processHandle, moduleHandle + (int)ied.AddressOfFunctions + ordinal * 4, out addressOffset))
return IntPtr.Zero;
return moduleHandle + addressOffset;
}
}
return IntPtr.Zero;
}
完整的注入器代码:
using System;
using System.IO;
using System.Text;
using FastWin32.Memory;
using static FastWin32.NativeMethods;
namespace FastWin32.Diagnostics
{
/// <summary>
/// 注入
/// </summary>
public static class Injector
{
#region Constant
private const int AsmSize = 0x2000;
private const int MachineCodeSize = 0x200;
private const int AssemblyPathOffset = 0x200;
private const int TypeNameOffset = 0x800;
private const int MethodNameOffset = 0x980;
private const int ArgumentOffset = 0xA00;
private const int ReturnValueOffset = 0x1200;
private const int CLRVersionOffset = 0x1210;
private const int CLSID_CLRMetaHostOffset = 0x1260;
private const int IID_ICLRMetaHostOffset = 0x1270;
private const int IID_ICLRRuntimeInfoOffset = 0x1280;
private const int CLSID_CLRRuntimeHostOffset = 0x1290;
private const int IID_ICLRRuntimeHostOffset = 0x12A0;
private const int AvailableOffset = 0x12B0;
private readonly static byte[] CLSID_CLRMetaHost = new Guid(0x9280188D, 0x0E8E, 0x4867, 0xB3, 0x0C, 0x7F, 0xA8, 0x38, 0x84, 0xE8, 0xDE).ToByteArray();
private readonly static byte[] IID_ICLRMetaHost = new Guid(0xD332DB9E, 0xB9B3, 0x4125, 0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16).ToByteArray();
private readonly static byte[] IID_ICLRRuntimeInfo = new Guid(0xBD39D1D2, 0xBA2F, 0x486A, 0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91).ToByteArray();
private readonly static byte[] CLSID_CLRRuntimeHost = new Guid(0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02).ToByteArray();
private readonly static byte[] IID_ICLRRuntimeHost = new Guid(0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02).ToByteArray();
#endregion
private struct Section
{
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public Section(uint virtualSize, uint virtualAddress, uint sizeOfRawData, uint pointerToRawData)
{
VirtualSize = virtualSize;
VirtualAddress = virtualAddress;
SizeOfRawData = sizeOfRawData;
PointerToRawData = pointerToRawData;
}
}
/// <summary>
/// 打开进程(注入使用)
/// </summary>
/// <param name="processId">进程句柄</param>
/// <returns></returns>
private static IntPtr OpenProcessInjecting(uint processId)
{
return OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, false, processId);
}
/// <summary>
/// 注入非托管DLL
/// </summary>
/// <param name="processId">要注入的进程ID</param>
/// <param name="assemblyPath">要注入程序集的路径</param>
/// <param name="typeName">类型名(命名空间+类型名,比如NamespaceA.ClassB)</param>
/// <param name="methodName">方法名(比如MethodC),该方法必须具有此类签名static int MethodName(string),比如private static int InjectingMain(string argument)</param>
/// <param name="argument">参数,长度必须小于1024个字符,可传入null。</param>
/// <returns></returns>
public static bool InjectManaged(uint processId, string assemblyPath, string typeName, string methodName, string argument)
{
if (string.IsNullOrEmpty(assemblyPath))
throw new ArgumentNullException();
if (!File.Exists(assemblyPath))
throw new FileNotFoundException();
if (string.IsNullOrEmpty(typeName))
throw new ArgumentNullException();
if (string.IsNullOrEmpty(methodName))
throw new ArgumentNullException();
if (argument != null && argument.Length >= 1024)
throw new ArgumentOutOfRangeException(nameof(argument) + "长度必须小于1024个字符");
IntPtr processHandle;
int returnValue;
processHandle = OpenProcessInjecting(processId);
if (processHandle == IntPtr.Zero)
return false;
try
{
return InjectManagedInternal(processHandle, assemblyPath, typeName, methodName, argument, out returnValue, false);
}
finally
{
CloseHandle(processHandle);
}
}
/// <summary>
/// 注入非托管DLL,并获取被调用方法的返回值(警告:被调用方法返回后才能获取到返回值,<see cref="InjectManaged(uint, string, string, string, string, out int)"/>方法将一直等待到被调用方法返回。如果仅注入程序集而不需要获取返回值,请使用重载版本<see cref="InjectManaged(uint, string, string, string, string)"/>)
/// </summary>
/// <param name="processId">要注入的进程ID</param>
/// <param name="assemblyPath">要注入程序集的路径</param>
/// <param name="typeName">类型名(命名空间+类型名,比如NamespaceA.ClassB)</param>
/// <param name="methodName">方法名(比如MethodC),该方法必须具有此类签名static int MethodName(string),比如private static int InjectingMain(string argument)</param>
/// <param name="argument">参数,长度必须小于1024个字符,可传入null。</param>
/// <param name="returnValue">被调用方法返回的整数值</param>
/// <returns></returns>
public static bool InjectManaged(uint processId, string assemblyPath, string typeName, string methodName, string argument, out int returnValue)
{
if (string.IsNullOrEmpty(assemblyPath))
throw new ArgumentNullException();
if (!File.Exists(assemblyPath))
throw new FileNotFoundException();
if (string.IsNullOrEmpty(typeName))
throw new ArgumentNullException();
if (string.IsNullOrEmpty(methodName))
throw new ArgumentNullException();
if (argument != null && argument.Length >= 1024)
throw new ArgumentOutOfRangeException(nameof(argument) + "长度必须小于1024个字符");
IntPtr processHandle;
returnValue = 0;
processHandle = OpenProcessInjecting(processId);
if (processHandle == IntPtr.Zero)
return false;
try
{
return InjectManagedInternal(processHandle, assemblyPath, typeName, methodName, argument, out returnValue, true);
}
finally
{
CloseHandle(processHandle);
}
}
/// <summary>
/// 注入非托管DLL
/// </summary>
/// <param name="processHandle">进程句柄</param>
/// <param name="assemblyPath">要注入程序集的路径</param>
/// <param name="typeName">类型名(命名空间+类型名,比如NamespaceA.ClassB)</param>
/// <param name="methodName">方法名(比如MethodC),该方法必须具有此类签名static int MethodName(string),比如private static int InjectingMain(string argument)</param>
/// <param name="argument">参数,长度必须小于1024个字符,可传入null。</param>
/// <param name="returnValue">被调用方法返回的整数值</param>
/// <param name="wait">是否等待返回值</param>
/// <returns></returns>
internal static unsafe bool InjectManagedInternal(IntPtr processHandle, string assemblyPath, string typeName, string methodName, string argument, out int returnValue, bool wait)
{
bool isAssembly;
bool is64;
string clrVersion;
IntPtr pFunction;
IntPtr threadHandle;
uint exitCode;
returnValue = 0;
assemblyPath = Path.GetFullPath(assemblyPath);
//获取绝对路径
IsAssembly(assemblyPath, out isAssembly, out clrVersion);
if (!isAssembly)
throw new NotSupportedException("将注入的DLL为不是程序集,应该调用InjectUnmanaged方法而非调用InjectManaged方法");
if (!Process32.Is64ProcessInternal(processHandle, out is64))
return false;
if (!InjectUnmanagedInternal(processHandle, Path.Combine(GetSystemPath(is64), "mscoree.dll")))
return false;
//加载对应进程位数的mscoree.dll
pFunction = WriteAsm(processHandle, clrVersion, assemblyPath, typeName, methodName, argument);
//获取远程进程中启动CLR的函数指针
if (pFunction == IntPtr.Zero)
return false;
threadHandle = CreateRemoteThread(processHandle, null, 0, pFunction, pFunction + ReturnValueOffset, 0, null);
if (threadHandle == IntPtr.Zero)
return false;
if (wait)
{
WaitForSingleObject(threadHandle, INFINITE);
//等待线程结束
if (!GetExitCodeThread(threadHandle, out exitCode))
return false;
if (!MemoryIO.ReadInt32Internal(processHandle, pFunction + ReturnValueOffset, out returnValue))
return false;
//获取程序集中被调用方法的返回值
if (!MemoryManagement.FreeMemoryInternal(processHandle, pFunction))
return false;
return exitCode == 0;
//ICLRRuntimeHost::ExecuteInDefaultAppDomain返回S_OK(0)表示成功
}
return true;
}
/// <summary>
/// 注入非托管DLL
/// </summary>
/// <param name="processId">要注入的进程ID</param>
/// <param name="dllPath">要注入DLL的路径</param>
/// <returns></returns>
public static bool InjectUnmanaged(uint processId, string dllPath)
{
if (string.IsNullOrEmpty(dllPath))
throw new ArgumentNullException();
if (!File.Exists(dllPath))
throw new FileNotFoundException();
IntPtr processHandle;
bool isAssembly;
string clrVersion;
processHandle = OpenProcessInjecting(processId);
if (processHandle == IntPtr.Zero)
return false;
try
{
dllPath = Path.GetFullPath(dllPath);
//获取绝对路径
IsAssembly(dllPath, out isAssembly, out clrVersion);
if (isAssembly)
throw new NotSupportedException("将注入的DLL为程序集,应该调用InjectManaged方法而非调用InjectUnmanaged方法");
return InjectUnmanagedInternal(processHandle, dllPath);
//注入非托管DLL
}
finally
{
CloseHandle(processHandle);
}
}
/// <summary>
/// 注入非托管Dll
/// </summary>
/// <param name="processHandle">进程句柄</param>
/// <param name="dllPath">要注入的Dll的路径</param>
/// <returns></returns>
internal static unsafe bool InjectUnmanagedInternal(IntPtr processHandle, string dllPath)
{
bool is64;
IntPtr pLoadLibrary;
IntPtr pDllPath;
IntPtr threadHandle;
uint exitCode;
if (!Process32.Is64ProcessInternal(processHandle, out is64))
return false;
pLoadLibrary = Module32.GetProcAddressInternal(processHandle, "kernel32.dll", "LoadLibraryW");
//获取LoadLibrary的函数地址
pDllPath = MemoryManagement.AllocMemoryInternal(processHandle, (uint)dllPath.Length * 2 + 2, PAGE_EXECUTE_READ);
try
{
if (pDllPath == IntPtr.Zero)
return false;
if (!MemoryIO.WriteStringInternal(processHandle, pDllPath, dllPath))
return false;
threadHandle = CreateRemoteThread(processHandle, null, 0, pLoadLibrary, pDllPath, 0, null);
if (threadHandle == IntPtr.Zero)
return false;
WaitForSingleObject(threadHandle, INFINITE);
//等待线程结束
GetExitCodeThread(threadHandle, out exitCode);
return exitCode != 0;
//LoadLibrary返回值不为0则调用成功,否则失败
}
finally
{
MemoryManagement.FreeMemoryInternal(processHandle, pDllPath);
}
}
/// <summary>
/// 获取系统文件夹路径
/// </summary>
/// <param name="is64">是否64位</param>
/// <returns></returns>
private static string GetSystemPath(bool is64)
{
if (!Environment.Is64BitOperatingSystem)
return @"C:\Windows\System32";
else if (is64)
return @"C:\Windows\System32";
else
return @"C:\Windows\SysWOW64";
}
/// <summary>
/// 写入启动CLR的机器码,返回函数指针
/// </summary>
/// <param name="processHandle">进程句柄</param>
/// <param name="clrVersion">CLR版本</param>
/// <param name="assemblyPath">程序集路径(绝对路径)</param>
/// <param name="typeName">类型名</param>
/// <param name="methodName">方法名</param>
/// <param name="argument">参数(可空,如果非空,长度必须小于2000)</param>
/// <returns></returns>
private static unsafe IntPtr WriteAsm(IntPtr processHandle, string clrVersion, string assemblyPath, string typeName, string methodName, string argument)
{
bool is64;
byte[] asm;
IntPtr pFunction;
IntPtr pCorBindToRuntimeEx;
IntPtr pCLRCreateInstance;
if (!Process32.Is64ProcessInternal(processHandle, out is64))
return IntPtr.Zero;
asm = GetAsmCommon(clrVersion, assemblyPath, typeName, methodName, argument);
pFunction = MemoryManagement.AllocMemoryInternal(processHandle, AsmSize, PAGE_EXECUTE_READWRITE);
if (pFunction == IntPtr.Zero)
return IntPtr.Zero;
try
{
fixed (byte* p = &asm[0])
{
switch (clrVersion)
{
case "v2.0.50727":
pCorBindToRuntimeEx = Module32.GetProcAddressInternal(processHandle, "mscoree.dll", "CorBindToRuntimeEx");
if (pCorBindToRuntimeEx == IntPtr.Zero)
return IntPtr.Zero;
if (is64)
SetAsm64V2(p, (long)pFunction, (long)pCorBindToRuntimeEx);
else
SetAsm32V2(p, (int)pFunction, (int)pCorBindToRuntimeEx);
break;
case "v4.0.30319":
pCLRCreateInstance = Module32.GetProcAddressInternal(processHandle, "mscoree.dll", "CLRCreateInstance");
if (pCLRCreateInstance == IntPtr.Zero)
return IntPtr.Zero;
if (is64)
SetAsm64V4(p, (long)pFunction, (long)pCLRCreateInstance);
else
SetAsm32V4(p, (int)pFunction, (int)pCLRCreateInstance);
break;
default:
return IntPtr.Zero;
}
}
if (!MemoryIO.WriteBytesInternal(processHandle, pFunction, asm))
return IntPtr.Zero;
}
catch
{
MemoryManagement.FreeMemoryInternal(processHandle, pFunction);
return IntPtr.Zero;
}
return pFunction;
}
/// <summary>
/// 获取设置好参数的机器码
/// </summary>
/// <param name="clrVersion">CLR版本</param>
/// <param name="assemblyPath">程序集路径(绝对路径)</param>
/// <param name="typeName">类型名</param>
/// <param name="methodName">方法名</param>
/// <param name="argument">参数(可空,如果非空,长度必须小于2000)</param>
/// <returns></returns>
private static byte[] GetAsmCommon(string clrVersion, string assemblyPath, string typeName, string methodName, string argument)
{
byte[] bytes;
using (MemoryStream memoryStream = new MemoryStream(AsmSize))
{
bytes = Encoding.Unicode.GetBytes(assemblyPath);
memoryStream.Position = AssemblyPathOffset;
memoryStream.Write(bytes, 0, bytes.Length);
//assemblyPath
bytes = Encoding.Unicode.GetBytes(typeName);
memoryStream.Position = TypeNameOffset;
memoryStream.Write(bytes, 0, bytes.Length);
//typeName
bytes = Encoding.Unicode.GetBytes(methodName);
memoryStream.Position = MethodNameOffset;
memoryStream.Write(bytes, 0, bytes.Length);
//methodName
bytes = argument == null ? new byte[0] : Encoding.Unicode.GetBytes(argument);
memoryStream.Position = ArgumentOffset;
memoryStream.Write(bytes, 0, bytes.Length);
//argument
bytes = Encoding.Unicode.GetBytes(clrVersion);
memoryStream.Position = CLRVersionOffset;
memoryStream.Write(bytes, 0, bytes.Length);
//clrVersion
memoryStream.Position = CLSID_CLRMetaHostOffset;
memoryStream.Write(CLSID_CLRMetaHost, 0, CLSID_CLRMetaHost.Length);
memoryStream.Position = IID_ICLRMetaHostOffset;
memoryStream.Write(IID_ICLRMetaHost, 0, IID_ICLRMetaHost.Length);
memoryStream.Position = IID_ICLRRuntimeInfoOffset;
memoryStream.Write(IID_ICLRRuntimeInfo, 0, IID_ICLRRuntimeInfo.Length);
memoryStream.Position = CLSID_CLRRuntimeHostOffset;
memoryStream.Write(CLSID_CLRRuntimeHost, 0, CLSID_CLRRuntimeHost.Length);
memoryStream.Position = IID_ICLRRuntimeHostOffset;
memoryStream.Write(IID_ICLRRuntimeHost, 0, IID_ICLRRuntimeHost.Length);
memoryStream.SetLength(AsmSize);
return memoryStream.ToArray();
}
}
/// <summary>
/// 设置启动32位CLR V2的机器码
/// </summary>
/// <param name="p">机器码指针</param>
/// <param name="pFunction">函数指针</param>
/// <param name="pCorBindToRuntimeEx">CorBindToRuntimeEx的函数指针</param>
private static unsafe void SetAsm32V2(byte* p, int pFunction, int pCorBindToRuntimeEx)
{
//HRESULT WINAPI LoadCLR2(DWORD *pReturnValue)
#region {
p[0] = 0x55;
p += 1;
//push ebp
p[0] = 0x89;
p[1] = 0xE5;
p += 2;
//mov ebp,esp
p[0] = 0x83;
p[1] = 0xEC;
p[2] = 0x44;
p += 3;
//sub esp,byte +0x44
p[0] = 0x53;
p += 1;
//push ebx
p[0] = 0x56;
p += 1;
//push esi
p[0] = 0x57;
p += 1;
//push edi
p[0] = 0xC7;
p[1] = 0x45;
p[2] = 0xFC;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
#endregion
#region ICLRRuntimeHost *pRuntimeHost = nullptr;
//mov dword [ebp-0x4],0x0
p[0] = 0x8D;
p[1] = 0x45;
p[2] = 0xFC;
p += 3;
#endregion
#region CorBindToRuntimeEx(L"v2.0.50727", nullptr, 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pRuntimeHost);
//lea eax,[ebp-0x4]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + IID_ICLRRuntimeHostOffset;
p += 5;
//push dword PIID_ICLRRuntimeHost
p[0] = 0x68;
*(int*)(p + 1) = pFunction + CLSID_CLRRuntimeHostOffset;
p += 5;
//push dword pCLSID_CLRRuntimeHost
p[0] = 0x6A;
p[1] = 0x00;
p += 2;
//push byte +0x0
p[0] = 0x6A;
p[1] = 0x00;
p += 2;
//push byte +0x0
p[0] = 0x68;
*(int*)(p + 1) = pFunction + CLRVersionOffset;
p += 5;
//push dword pCLRVersion
p[0] = 0xB9;
*(int*)(p + 1) = pCorBindToRuntimeEx;
p += 5;
//mov ecx,pCorBindToRuntimeEx
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region pRuntimeHost->Start();
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xFC;
p += 3;
//mov eax,[ebp-0x4]
p[0] = 0x8B;
p[1] = 0x08;
p += 2;
//mov ecx,[eax]
p[0] = 0x8B;
p[1] = 0x55;
p[2] = 0xFC;
p += 3;
//mov edx,[ebp-0x4]
p[0] = 0x52;
p += 1;
//push edx
p[0] = 0x8B;
p[1] = 0x41;
p[2] = 0x0C;
p += 3;
//mov eax,[ecx+0xc]
p[0] = 0xFF;
p[1] = 0xD0;
p += 2;
//call eax
#endregion
#region return pRuntimeHost->ExecuteInDefaultAppDomain(L"assemblyPath", L"typeName", L"methodName", L"argument", pReturnValue);
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0x08;
p += 3;
//mov eax,[ebp+0x8]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + ArgumentOffset;
p += 5;
//push dword pArgument
p[0] = 0x68;
*(int*)(p + 1) = pFunction + MethodNameOffset;
p += 5;
//push dword pMethodName
p[0] = 0x68;
*(int*)(p + 1) = pFunction + TypeNameOffset;
p += 5;
//push dword pTypeName
p[0] = 0x68;
*(int*)(p + 1) = pFunction + AssemblyPathOffset;
p += 5;
//push dword pAssemblyPath
p[0] = 0x8B;
p[1] = 0x4D;
p[2] = 0xFC;
p += 3;
//mov ecx,[ebp-0x4]
p[0] = 0x8B;
p[1] = 0x11;
p += 2;
//mov edx,[ecx]
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xFC;
p += 3;
//mov eax,[ebp-0x4]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x8B;
p[1] = 0x4A;
p[2] = 0x2C;
p += 3;
//mov ecx,[edx+0x2c]
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region }
p[0] = 0x5F;
p += 1;
//pop edi
p[0] = 0x5E;
p += 1;
//pop esi
p[0] = 0x5B;
p += 1;
//pop ebx
p[0] = 0x89;
p[1] = 0xEC;
p += 2;
//mov esp,ebp
p[0] = 0x5D;
p += 1;
//pop ebp
p[0] = 0xC2;
p[1] = 0x04;
p[2] = 0x00;
p += 3;
//ret 0x4
#endregion
}
/// <summary>
/// 设置启动32位CLR V4的机器码
/// </summary>
/// <param name="p">机器码指针</param>
/// <param name="pFunction">函数指针</param>
/// <param name="pCLRCreateInstance">CLRCreateInstance的函数指针</param>
private static unsafe void SetAsm32V4(byte* p, int pFunction, int pCLRCreateInstance)
{
//HRESULT WINAPI LoadCLR4(DWORD *pReturnValue)
#region {
p[0] = 0x55;
p += 1;
//push ebp
p[0] = 0x89;
p[1] = 0xE5;
p += 2;
//mov ebp,esp
p[0] = 0x83;
p[1] = 0xEC;
p[2] = 0x4C;
p += 3;
//sub esp,byte +0x4c
p[0] = 0x53;
p += 1;
//push ebx
p[0] = 0x56;
p += 1;
//push esi
p[0] = 0x57;
p += 1;
//push edi
#endregion
#region ICLRMetaHost *pMetaHost = nullptr;
p[0] = 0xC7;
p[1] = 0x45;
p[2] = 0xFC;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
//mov dword [ebp-0x4],0x0
#endregion
#region ICLRRuntimeInfo *pRuntimeInfo = nullptr;
p[0] = 0xC7;
p[1] = 0x45;
p[2] = 0xF8;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
//mov dword [ebp-0x8],0x0
#endregion
#region ICLRRuntimeHost *pRuntimeHost = nullptr;
p[0] = 0xC7;
p[1] = 0x45;
p[2] = 0xF4;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
//mov dword [ebp-0xc],0x0
#endregion
#region CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
p[0] = 0x8D;
p[1] = 0x45;
p[2] = 0xFC;
p += 3;
//lea eax,[ebp-0x4]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + IID_ICLRMetaHostOffset;
p += 5;
//push dword pIID_ICLRMetaHost
p[0] = 0x68;
*(int*)(p + 1) = pFunction + CLSID_CLRMetaHostOffset;
p += 5;
//push dword pCLSID_CLRMetaHost
p[0] = 0xB9;
*(int*)(p + 1) = pCLRCreateInstance;
p += 5;
//mov ecx,pCLRCreateInstance
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&pRuntimeInfo);
p[0] = 0x8D;
p[1] = 0x45;
p[2] = 0xF8;
p += 3;
//lea eax,[ebp-0x8]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + IID_ICLRRuntimeInfoOffset;
p += 5;
//push dword pIID_ICLRRuntimeInfo
p[0] = 0x68;
*(int*)(p + 1) = pFunction + CLRVersionOffset;
p += 5;
//push dword pCLRVersion
p[0] = 0x8B;
p[1] = 0x4D;
p[2] = 0xFC;
p += 3;
//mov ecx,[ebp-0x4]
p[0] = 0x8B;
p[1] = 0x11;
p += 2;
//mov edx,[ecx]
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xFC;
p += 3;
//mov eax,[ebp-0x4]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x8B;
p[1] = 0x4A;
p[2] = 0x0C;
p += 3;
//mov ecx,[edx+0xc]
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pRuntimeHost);
p[0] = 0x8D;
p[1] = 0x45;
p[2] = 0xF4;
p += 3;
//lea eax,[ebp-0xc]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + IID_ICLRRuntimeHostOffset;
p += 5;
//push dword pIID_ICLRRuntimeHost
p[0] = 0x68;
*(int*)(p + 1) = pFunction + CLSID_CLRRuntimeHostOffset;
p += 5;
//push dword pCLSID_CLRRuntimeHost
p[0] = 0x8B;
p[1] = 0x4D;
p[2] = 0xF8;
p += 3;
//mov ecx,[ebp-0x8]
p[0] = 0x8B;
p[1] = 0x11;
p += 2;
//mov edx,[ecx]
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xF8;
p += 3;
//mov eax,[ebp-0x8]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x8B;
p[1] = 0x4A;
p[2] = 0x24;
p += 3;
//mov ecx,[edx+0x24]
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region pRuntimeHost->Start();
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xF4;
p += 3;
//mov eax,[ebp-0xc]
p[0] = 0x8B;
p[1] = 0x08;
p += 2;
//mov ecx,[eax]
p[0] = 0x8B;
p[1] = 0x55;
p[2] = 0xF4;
p += 3;
//mov edx,[ebp-0xc]
p[0] = 0x52;
p += 1;
//push edx
p[0] = 0x8B;
p[1] = 0x41;
p[2] = 0x0C;
p += 3;
//mov eax,[ecx+0xc]
p[0] = 0xFF;
p[1] = 0xD0;
p += 2;
//call eax
#endregion
#region return pRuntimeHost->ExecuteInDefaultAppDomain(L"assemblyPath", L"typeName", L"methodName", L"argument", pReturnValue);
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0x08;
p += 3;
//mov eax,[ebp+0x8]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x68;
*(int*)(p + 1) = pFunction + ArgumentOffset;
p += 5;
//push dword pArgument
p[0] = 0x68;
*(int*)(p + 1) = pFunction + MethodNameOffset;
p += 5;
//push dword pMethodName
p[0] = 0x68;
*(int*)(p + 1) = pFunction + TypeNameOffset;
p += 5;
//push dword pTypeName
p[0] = 0x68;
*(int*)(p + 1) = pFunction + AssemblyPathOffset;
p += 5;
//push dword pAssemblyPath
p[0] = 0x8B;
p[1] = 0x4D;
p[2] = 0xF4;
p += 3;
//mov ecx,[ebp-0xc]
p[0] = 0x8B;
p[1] = 0x11;
p += 2;
//mov edx,[ecx]
p[0] = 0x8B;
p[1] = 0x45;
p[2] = 0xF4;
p += 3;
//mov eax,[ebp-0xc]
p[0] = 0x50;
p += 1;
//push eax
p[0] = 0x8B;
p[1] = 0x4A;
p[2] = 0x2C;
p += 3;
//mov ecx,[edx+0x2c]
p[0] = 0xFF;
p[1] = 0xD1;
p += 2;
//call ecx
#endregion
#region }
p[0] = 0x5F;
p += 1;
//pop edi
p[0] = 0x5E;
p += 1;
//pop esi
p[0] = 0x5B;
p += 1;
//pop ebx
p[0] = 0x89;
p[1] = 0xEC;
p += 2;
//mov esp,ebp
p[0] = 0x5D;
p += 1;
//pop ebp
p[0] = 0xC2;
p[1] = 0x04;
p[2] = 0x00;
p += 3;
//ret 0x4
#endregion
}
/// <summary>
/// 设置启动64位CLR V2的机器码
/// </summary>
/// <param name="p">机器码指针</param>
/// <param name="pFunction">函数指针</param>
/// <param name="pCorBindToRuntimeEx">CorBindToRuntimeEx的函数指针</param>
private static unsafe void SetAsm64V2(byte* p, long pFunction, long pCorBindToRuntimeEx)
{
//HRESULT WINAPI LoadCLR2(DWORD *pReturnValue)
#region {
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x08;
p += 5;
//mov [rsp+0x8],rcx
p[0] = 0x55;
p += 1;
//push rbp
p[0] = 0x48;
p[1] = 0x81;
p[2] = 0xEC;
p[3] = 0x80;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
//sub rsp,0x80
p[0] = 0x48;
p[1] = 0x8D;
p[2] = 0x6C;
p[3] = 0x24;
p[4] = 0x30;
p += 5;
//lea rbp,[rsp+0x30]
#endregion
#region ICLRRuntimeHost *pRuntimeHost = nullptr;
p[0] = 0x48;
p[1] = 0xC7;
p[2] = 0x45;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p[7] = 0x00;
p += 8;
//mov qword [rbp+0x0],0x0
#endregion
#region CorBindToRuntimeEx(L"v2.0.50727", nullptr, 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pRuntimeHost);
p[0] = 0x48;
p[1] = 0x8D;
p[2] = 0x45;
p[3] = 0x00;
p += 4;
//lea rax,[rbp+0x0]
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x44;
p[3] = 0x24;
p[4] = 0x28;
p += 5;
//mov [rsp+0x28],rax
p[0] = 0x48;
p[1] = 0xB8;
*(long*)(p + 2) = pFunction + IID_ICLRRuntimeHostOffset;
p += 10;
//mov rax,pIID_ICLRRuntimeHost
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x44;
p[3] = 0x24;
p[4] = 0x20;
p += 5;
//mov [rsp+0x20],rax
p[0] = 0x49;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + CLSID_CLRRuntimeHostOffset;
p += 10;
//mov r9,pCLSID_CLRRuntimeHost
p[0] = 0x45;
p[1] = 0x31;
p[2] = 0xC0;
p += 3;
//xor r8d,r8d
p[0] = 0x31;
p[1] = 0xD2;
p += 2;
//xor edx,edx
p[0] = 0x48;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + CLRVersionOffset;
p += 10;
//mov rcx,pCLRVersion
p[0] = 0x49;
p[1] = 0xBF;
*(long*)(p + 2) = pCorBindToRuntimeEx;
p += 10;
//mov r15,pCorBindToRuntimeEx
p[0] = 0x41;
p[1] = 0xFF;
p[2] = 0xD7;
p += 3;
//call r15
#endregion
#region pRuntimeHost->Start();
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x00;
p += 4;
//mov rax,[rbp+0x0]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x00;
p += 4;
//mov rcx,[rbp+0x0]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x18;
p += 3;
//call [rax+0x18]
#endregion
#region return pRuntimeHost->ExecuteInDefaultAppDomain(L"assemblyPath", L"typeName", L"methodName", L"argument", pReturnValue);
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x00;
p += 4;
//mov rax,[rbp+0x0]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x60;
p += 4;
//mov rcx,[rbp+0x60]
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x28;
p += 5;
//mov [rsp+0x28],rcx
p[0] = 0x48;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + ArgumentOffset;
p += 10;
//mov rcx,pArgument
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x20;
p += 5;
//mov [rsp+0x20],rcx
p[0] = 0x49;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + MethodNameOffset;
p += 10;
//mov r9,pMethodName
p[0] = 0x49;
p[1] = 0xB8;
*(long*)(p + 2) = pFunction + TypeNameOffset;
p += 10;
//mov r8,pTypeName
p[0] = 0x48;
p[1] = 0xBA;
*(long*)(p + 2) = pFunction + AssemblyPathOffset;
p += 10;
//mov rdx,pAssemblyPath
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x00;
p += 4;
//mov rcx,[rbp+0x0]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x58;
p += 3;
//call [rax+0x58]
#endregion
#region }
p[0] = 0x48;
p[1] = 0x8D;
p[2] = 0x65;
p[3] = 0x50;
p += 4;
//lea rsp,[rbp+0x50]
p[0] = 0x5D;
p += 1;
//pop rbp
p[0] = 0xC3;
p += 1;
//ret
#endregion
}
/// <summary>
/// 设置启动64位CLR V4的机器码
/// </summary>
/// <param name="p">机器码指针</param>
/// <param name="pFunction">函数指针</param>
/// <param name="pCLRCreateInstance">CLRCreateInstance的函数指针</param>
private static unsafe void SetAsm64V4(byte* p, long pFunction, long pCLRCreateInstance)
{
//HRESULT WINAPI LoadCLR4(DWORD *pReturnValue)
#region {
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x08;
p += 5;
//mov [rsp+0x8],rcx
p[0] = 0x55;
p += 1;
//push rbp
p[0] = 0x48;
p[1] = 0x81;
p[2] = 0xEC;
p[3] = 0x90;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p += 7;
//sub rsp,0x90
p[0] = 0x48;
p[1] = 0x8D;
p[2] = 0x6C;
p[3] = 0x24;
p[4] = 0x30;
p += 5;
//lea rbp,[rsp+0x30]
#endregion
#region ICLRMetaHost *pMetaHost = nullptr;
p[0] = 0x48;
p[1] = 0xC7;
p[2] = 0x45;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p[7] = 0x00;
p += 8;
//mov qword [rbp+0x0],0x0
#endregion
#region ICLRRuntimeInfo *pRuntimeInfo = nullptr;
p[0] = 0x48;
p[1] = 0xC7;
p[2] = 0x45;
p[3] = 0x08;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p[7] = 0x00;
p += 8;
//mov qword [rbp+0x8],0x0
#endregion
#region ICLRRuntimeHost *pRuntimeHost = nullptr;
p[0] = 0x48;
p[1] = 0xC7;
p[2] = 0x45;
p[3] = 0x10;
p[4] = 0x00;
p[5] = 0x00;
p[6] = 0x00;
p[7] = 0x00;
p += 8;
//mov qword [rbp+0x10],0x0
#endregion
#region CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
p[0] = 0x4C;
p[1] = 0x8D;
p[2] = 0x45;
p[3] = 0x00;
p += 4;
//lea r8,[rbp+0x0]
p[0] = 0x48;
p[1] = 0xBA;
*(long*)(p + 2) = pFunction + IID_ICLRMetaHostOffset;
p += 10;
//mov rdx,pIID_ICLRMetaHost
p[0] = 0x48;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + CLSID_CLRMetaHostOffset;
p += 10;
//mov rcx,pCLSID_CLRMetaHost
p[0] = 0x49;
p[1] = 0xBF;
*(long*)(p + 2) = pCLRCreateInstance;
p += 10;
//mov r15,pCLRCreateInstance
p[0] = 0x41;
p[1] = 0xFF;
p[2] = 0xD7;
p += 3;
//call r15
#endregion
#region pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&pRuntimeInfo);
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x00;
p += 4;
//mov rax,[rbp+0x0]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x4C;
p[1] = 0x8D;
p[2] = 0x4D;
p[3] = 0x08;
p += 4;
//lea r9,[rbp+0x8]
p[0] = 0x49;
p[1] = 0xB8;
*(long*)(p + 2) = pFunction + IID_ICLRRuntimeInfoOffset;
p += 10;
//mov r8,pIID_ICLRRuntimeInfo
p[0] = 0x48;
p[1] = 0xBA;
*(long*)(p + 2) = pFunction + CLRVersionOffset;
p += 10;
//mov rdx,pCLRVersion
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x00;
p += 4;
//mov rcx,[rbp+0x0]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x18;
p += 3;
//call [rax+0x18]
#endregion
#region pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pRuntimeHost);
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x08;
p += 4;
//mov rax,[rbp+0x8]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x4C;
p[1] = 0x8D;
p[2] = 0x4D;
p[3] = 0x10;
p += 4;
//lea r9,[rbp+0x10]
p[0] = 0x49;
p[1] = 0xB8;
*(long*)(p + 2) = pFunction + IID_ICLRRuntimeHostOffset;
p += 10;
//mov r8,pIID_ICLRRuntimeHost
p[0] = 0x48;
p[1] = 0xBA;
*(long*)(p + 2) = pFunction + CLSID_CLRRuntimeHostOffset;
p += 10;
//mov rdx,pCLSID_CLRRuntimeHost
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x08;
p += 4;
//mov rcx,[rbp+0x8]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x48;
p += 3;
//call [rax+0x48]
#endregion
#region pRuntimeHost->Start();
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x10;
p += 4;
//mov rax,[rbp+0x10]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x10;
p += 4;
//mov rcx,[rbp+0x10]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x18;
p += 3;
//call [rax+0x18]
#endregion
#region return pRuntimeHost->ExecuteInDefaultAppDomain(L"assemblyPath", L"typeName", L"methodName", L"argument", pReturnValue);
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x45;
p[3] = 0x10;
p += 4;
//mov rax,[rbp+0x10]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x00;
p += 3;
//mov rax,[rax]
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x70;
p += 4;
//mov rcx,[rbp+0x70]
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x28;
p += 5;
//mov [rsp+0x28],rcx
p[0] = 0x48;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + ArgumentOffset;
p += 10;
//mov rcx,pArgument
p[0] = 0x48;
p[1] = 0x89;
p[2] = 0x4C;
p[3] = 0x24;
p[4] = 0x20;
p += 5;
//mov [rsp+0x20],rcx
p[0] = 0x49;
p[1] = 0xB9;
*(long*)(p + 2) = pFunction + MethodNameOffset;
p += 10;
//mov r9,pMethodName
p[0] = 0x49;
p[1] = 0xB8;
*(long*)(p + 2) = pFunction + TypeNameOffset;
p += 10;
//mov r8,pTypeName
p[0] = 0x48;
p[1] = 0xBA;
*(long*)(p + 2) = pFunction + AssemblyPathOffset;
p += 10;
//mov rdx,pAssemblyPath
p[0] = 0x48;
p[1] = 0x8B;
p[2] = 0x4D;
p[3] = 0x10;
p += 4;
//mov rcx,[rbp+0x10]
p[0] = 0xFF;
p[1] = 0x50;
p[2] = 0x58;
p += 3;
//call [rax+0x58]
#endregion
#region }
p[0] = 0x48;
p[1] = 0x8D;
p[2] = 0x65;
p[3] = 0x60;
p += 4;
//lea rsp,[rbp+0x60]
p[0] = 0x5D;
p += 1;
//pop rbp
p[0] = 0xC3;
p += 1;
//ret
#endregion
}
/// <summary>
/// 判断是否为程序集,如果是,输出CLR版本
/// </summary>
/// <param name="path">路径</param>
/// <param name="isAssembly">是否程序集</param>
/// <param name="clrVersion">CLR版本</param>
private static void IsAssembly(string path, out bool isAssembly, out string clrVersion)
{
try
{
using (BinaryReader binaryReader = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read)))
clrVersion = GetVersionString(binaryReader);
isAssembly = true;
}
catch (BadImageFormatException)
{
clrVersion = null;
isAssembly = false;
}
catch
{
clrVersion = null;
isAssembly = false;
}
}
/// <summary>
/// 获取CLR版本
/// </summary>
/// <param name="binaryReader"></param>
/// <returns></returns>
private static string GetVersionString(BinaryReader binaryReader)
{
uint ntHeaderOffset;
bool is64;
Section[] sections;
uint rva;
Section? section;
GetPEInfo(binaryReader, out ntHeaderOffset, out is64);
binaryReader.BaseStream.Position = ntHeaderOffset + (is64 ? 0xF8 : 0xE8);
rva = binaryReader.ReadUInt32();
//.Net MetaData Directory RVA
if (rva == 0)
throw new BadImageFormatException("文件不是程序集");
sections = GetSections(binaryReader);
section = GetSection(rva, sections);
if (section == null)
throw new InvalidDataException("未知格式的二进制文件");
binaryReader.BaseStream.Position = section.Value.PointerToRawData + rva - section.Value.VirtualAddress + 0x8;
//.Net MetaData Directory FileOffset
rva = binaryReader.ReadUInt32();
//.Net MetaData Header RVA
if (rva == 0)
throw new BadImageFormatException("文件不是程序集");
section = GetSection(rva, sections);
if (section == null)
throw new InvalidDataException("未知格式的二进制文件");
binaryReader.BaseStream.Position = section.Value.PointerToRawData + rva - section.Value.VirtualAddress + 0xC;
//.Net MetaData Header FileOffset
return Encoding.UTF8.GetString(binaryReader.ReadBytes(binaryReader.ReadInt32() - 2));
}
/// <summary>
/// 获取PE信息
/// </summary>
/// <param name="binaryReader"></param>
/// <param name="ntHeaderOffset"></param>
/// <param name="is64"></param>
private static void GetPEInfo(BinaryReader binaryReader, out uint ntHeaderOffset, out bool is64)
{
ushort machine;
binaryReader.BaseStream.Position = 0x3C;
ntHeaderOffset = binaryReader.ReadUInt32();
binaryReader.BaseStream.Position = ntHeaderOffset + 0x4;
machine = binaryReader.ReadUInt16();
if (machine != 0x14C && machine != 0x8664)
throw new InvalidDataException("未知格式的二进制文件");
is64 = machine == 0x8664;
}
/// <summary>
/// 获取节
/// </summary>
/// <param name="binaryReader"></param>
/// <returns></returns>
private static Section[] GetSections(BinaryReader binaryReader)
{
uint ntHeaderOffset;
bool is64;
ushort numberOfSections;
Section[] sections;
GetPEInfo(binaryReader, out ntHeaderOffset, out is64);
numberOfSections = binaryReader.ReadUInt16();
binaryReader.BaseStream.Position = ntHeaderOffset + (is64 ? 0x108 : 0xF8);
sections = new Section[numberOfSections];
for (int i = 0; i < numberOfSections; i++)
{
binaryReader.BaseStream.Position += 0x8;
sections[i] = new Section(binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32());
binaryReader.BaseStream.Position += 0x10;
}
return sections;
}
/// <summary>
/// 获取RVA对应节
/// </summary>
/// <param name="rva"></param>
/// <param name="sections"></param>
/// <returns></returns>
private static Section? GetSection(uint rva, Section[] sections)
{
foreach (Section section in sections)
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + Math.Max(section.VirtualSize, section.SizeOfRawData))
return section;
return null;
}
}
}
注入进程的Demo可以在附件下载