diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8d273b8..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "dependencies/SharpMonoInjector"] - path = dependencies/SharpMonoInjector - url = https://github.com/intiface/SharpMonoInjector - branch = master diff --git a/dependencies/SharpMonoInjector/.gitattributes b/dependencies/SharpMonoInjector/.gitattributes new file mode 100644 index 0000000..eba1110 --- /dev/null +++ b/dependencies/SharpMonoInjector/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/.gitignore b/dependencies/SharpMonoInjector/.gitignore new file mode 100644 index 0000000..43d6353 --- /dev/null +++ b/dependencies/SharpMonoInjector/.gitignore @@ -0,0 +1,300 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +build/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/dependencies/SharpMonoInjector/LICENSE b/dependencies/SharpMonoInjector/LICENSE new file mode 100644 index 0000000..54221df --- /dev/null +++ b/dependencies/SharpMonoInjector/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Biney + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/README.md b/dependencies/SharpMonoInjector/README.md new file mode 100644 index 0000000..b6f48f2 --- /dev/null +++ b/dependencies/SharpMonoInjector/README.md @@ -0,0 +1,15 @@ +# SharpMonoInjector +SharpMonoInjector is a tool for injecting assemblies into Mono embedded applications, commonly Unity Engine based games. The target process *usually* does not have to be restarted in order to inject an updated version of the assembly. Your unload method must to destroy all of its resources (such as game objects). + +SharpMonoInjector works by dynamically generating machine code, writing it to the target process and executing it using CreateRemoteThread. The code calls functions in the mono embedded API. The return value is obtained with ReadProcessMemory. + +Both x86 and x64 processes are supported. + +In order for the injector to work, the load/unload methods need to match the following method signature: + + static void Method() + +In [releases](https://github.com/warbler/SharpMonoInjector/releases), there is a console application and a GUI application available. + +![The GUI application](https://i.imgur.com/mPMwlu1.png) +![The console application](https://i.imgur.com/cz8Gyxa.png) diff --git a/dependencies/SharpMonoInjector/src/ExampleAssembly/Cheat.cs b/dependencies/SharpMonoInjector/src/ExampleAssembly/Cheat.cs new file mode 100644 index 0000000..4f265b6 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/ExampleAssembly/Cheat.cs @@ -0,0 +1,10 @@ +namespace ExampleAssembly +{ + public class Cheat : UnityEngine.MonoBehaviour + { + private void OnGUI() + { + UnityEngine.GUI.Label(new UnityEngine.Rect(10, 10, 200, 40), "This is a very useful cheat"); + } + } +} diff --git a/dependencies/SharpMonoInjector/src/ExampleAssembly/ExampleAssembly.csproj b/dependencies/SharpMonoInjector/src/ExampleAssembly/ExampleAssembly.csproj new file mode 100644 index 0000000..c652bec --- /dev/null +++ b/dependencies/SharpMonoInjector/src/ExampleAssembly/ExampleAssembly.csproj @@ -0,0 +1,73 @@ + + + + + Debug + AnyCPU + {251A7FF8-4D39-4F8B-9838-61124BF62F99} + Library + Properties + ExampleAssembly + ExampleAssembly + v4.6.1 + 512 + + + true + full + false + ..\..\build\debug\ + DEBUG;TRACE + prompt + 4 + AnyCPU + + + pdbonly + true + ..\..\build\release\ + TRACE + prompt + 4 + AnyCPU + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + + + ..\..\lib\UnityEngine.dll + False + + + + + + + + + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/ExampleAssembly/Loader.cs b/dependencies/SharpMonoInjector/src/ExampleAssembly/Loader.cs new file mode 100644 index 0000000..3617655 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/ExampleAssembly/Loader.cs @@ -0,0 +1,19 @@ +namespace ExampleAssembly +{ + public class Loader + { + static UnityEngine.GameObject gameObject; + + public static void Load() + { + gameObject = new UnityEngine.GameObject(); + gameObject.AddComponent(); + UnityEngine.Object.DontDestroyOnLoad(gameObject); + } + + public static void Unload() + { + UnityEngine.Object.Destroy(gameObject); + } + } +} diff --git a/dependencies/SharpMonoInjector/src/ExampleAssembly/Properties/AssemblyInfo.cs b/dependencies/SharpMonoInjector/src/ExampleAssembly/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9e57d20 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/ExampleAssembly/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ExampleAssembly")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Biney")] +[assembly: AssemblyProduct("ExampleAssembly")] +[assembly: AssemblyCopyright("Copyright © Biney")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("251a7ff8-4d39-4f8b-9838-61124bf62f99")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/App.config b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/App.config new file mode 100644 index 0000000..8fd53b3 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/CommandLineArguments.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/CommandLineArguments.cs new file mode 100644 index 0000000..e3e30c4 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/CommandLineArguments.cs @@ -0,0 +1,53 @@ +using System.Globalization; +using System.Linq; + +namespace SharpMonoInjector.Console +{ + public class CommandLineArguments + { + private readonly string[] _args; + + public CommandLineArguments(string[] args) + { + _args = args; + } + + public bool IsSwitchPresent(string name) => _args.Any(arg => arg == name); + + public bool GetLongArg(string name, out long value) + { + if (GetStringArg(name, out string str)) + return long.TryParse(str.StartsWith("0x") ? str.Substring(2) : str, NumberStyles.AllowHexSpecifier, null, out value); + + value = default(long); + return false; + } + + public bool GetIntArg(string name, out int value) + { + if (GetStringArg(name, out string str)) + return int.TryParse(str.StartsWith("0x") ? str.Substring(2) : str, NumberStyles.AllowHexSpecifier, null, out value); + + value = default(int); + return false; + } + + public bool GetStringArg(string name, out string value) + { + for (int i = 0; i < _args.Length; i++) { + string arg = _args[i]; + + if (arg == name) { + if (i == _args.Length - 1) + break; + + value = _args[i + 1]; + return true; + } + } + + value = null; + return false; + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Program.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Program.cs new file mode 100644 index 0000000..c2c5d15 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Program.cs @@ -0,0 +1,148 @@ +using System; +using System.IO; + +namespace SharpMonoInjector.Console +{ + internal static class Program + { + private static void Main(string[] args) + { + if (args.Length == 0) { + PrintHelp(); + return; + } + + CommandLineArguments cla = new CommandLineArguments(args); + + bool inject = cla.IsSwitchPresent("inject"); + bool eject = cla.IsSwitchPresent("eject"); + + if (!inject && !eject) { + System.Console.WriteLine("No operation (inject/eject) specified"); + return; + } + + Injector injector; + + if (cla.GetIntArg("-p", out int pid)) { + injector = new Injector(pid); + } else if (cla.GetStringArg("-p", out string pname)) { + injector = new Injector(pname); + } else { + System.Console.WriteLine("No process id/name specified"); + return; + } + + if (inject) + Inject(injector, cla); + else + Eject(injector, cla); + } + + private static void PrintHelp() + { + const string help = + "SharpMonoInjector 2.2\r\n\r\n" + + "Usage:\r\n" + + "smi.exe \r\n\r\n" + + "Options:\r\n" + + "-p - The id or name of the target process\r\n" + + "-a - When injecting, the path of the assembly to inject. When ejecting, the address of the assembly to eject\r\n" + + "-n - The namespace in which the loader class resides\r\n" + + "-c - The name of the loader class\r\n" + + "-m - The name of the method to invoke in the loader class\r\n\r\n" + + "Examples:\r\n" + + "smi.exe inject -p testgame -a ExampleAssembly.dll -n ExampleAssembly -c Loader -m Load\r\n" + + "smi.exe eject -p testgame -a 0x13D23A98 -n ExampleAssembly -c Loader -m Unload\r\n"; + System.Console.WriteLine(help); + } + + private static void Inject(Injector injector, CommandLineArguments args) + { + string assemblyPath, @namespace, className, methodName; + byte[] assembly; + + if (args.GetStringArg("-a", out assemblyPath)) { + try { + assembly = File.ReadAllBytes(assemblyPath); + } catch { + System.Console.WriteLine("Could not read the file " + assemblyPath); + return; + } + } else { + System.Console.WriteLine("No assembly specified"); + return; + } + + args.GetStringArg("-n", out @namespace); + + if (!args.GetStringArg("-c", out className)) { + System.Console.WriteLine("No class name specified"); + return; + } + + if (!args.GetStringArg("-m", out methodName)) { + System.Console.WriteLine("No method name specified"); + return; + } + + using (injector) { + IntPtr remoteAssembly = IntPtr.Zero; + + try { + remoteAssembly = injector.Inject(assembly, @namespace, className, methodName); + } catch (InjectorException ie) { + System.Console.WriteLine("Failed to inject assembly: " + ie); + } catch (Exception exc) { + System.Console.WriteLine("Failed to inject assembly (unknown error): " + exc); + } + + if (remoteAssembly == IntPtr.Zero) + return; + + System.Console.WriteLine($"{Path.GetFileName(assemblyPath)}: " + + (injector.Is64Bit + ? $"0x{remoteAssembly.ToInt64():X16}" + : $"0x{remoteAssembly.ToInt32():X8}")); + } + } + + private static void Eject(Injector injector, CommandLineArguments args) + { + IntPtr assembly; + string @namespace, className, methodName; + + if (args.GetIntArg("-a", out int intPtr)) { + assembly = (IntPtr)intPtr; + } else if (args.GetLongArg("-a", out long longPtr)) { + assembly = (IntPtr)longPtr; + } else { + System.Console.WriteLine("No assembly pointer specified"); + return; + } + + args.GetStringArg("-n", out @namespace); + + if (!args.GetStringArg("-c", out className)) { + System.Console.WriteLine("No class name specified"); + return; + } + + if (!args.GetStringArg("-m", out methodName)) { + System.Console.WriteLine("No method name specified"); + return; + } + + using (injector) { + try { + injector.Eject(assembly, @namespace, className, methodName); + System.Console.WriteLine("Ejection successful"); + } catch (InjectorException ie) { + System.Console.WriteLine("Ejection failed: " + ie); + } catch (Exception exc) { + System.Console.WriteLine("Ejection failed (unknown error): " + exc); + } + } + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Properties/AssemblyInfo.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e76442b --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SharpMonoInjector.Console")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharpMonoInjector.Console")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e677de19-10c7-4981-bc7a-f49ae07e3391")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/SharpMonoInjector.Console.csproj b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/SharpMonoInjector.Console.csproj new file mode 100644 index 0000000..b6c4ab2 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Console/SharpMonoInjector.Console.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {E677DE19-10C7-4981-BC7A-F49AE07E3391} + Exe + SharpMonoInjector.Console + smi + v4.7.1 + 512 + true + true + + + + AnyCPU + true + full + false + ..\..\build\debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + ..\..\build\release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + + {9a2f2e9f-d314-4b7a-ba8d-8522bb91a114} + SharpMonoInjector + + + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.config b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.config new file mode 100644 index 0000000..787dcbe --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml new file mode 100644 index 0000000..6905def --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml.cs new file mode 100644 index 0000000..0000412 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace SharpMonoInjector.Gui +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/InjectedAssemblyToStringConverter.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/InjectedAssemblyToStringConverter.cs new file mode 100644 index 0000000..7fa017a --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/InjectedAssemblyToStringConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using SharpMonoInjector.Gui.Models; + +namespace SharpMonoInjector.Gui.Converters +{ + public class InjectedAssemblyToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + + InjectedAssembly ia = (InjectedAssembly)value; + return $"[{(ia.Is64Bit ? $"0x{ia.Address.ToInt64():X16}" : $"0x{ia.Address.ToInt32():X8}")}] {ia.Name}"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/MonoProcessToStringConverter.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/MonoProcessToStringConverter.cs new file mode 100644 index 0000000..b440923 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Converters/MonoProcessToStringConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using SharpMonoInjector.Gui.Models; + +namespace SharpMonoInjector.Gui.Converters +{ + public class MonoProcessToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + + MonoProcess mp = (MonoProcess)value; + return $"[{mp.Id}] {mp.Name}"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/InjectedAssembly.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/InjectedAssembly.cs new file mode 100644 index 0000000..7115f71 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/InjectedAssembly.cs @@ -0,0 +1,15 @@ +using System; + +namespace SharpMonoInjector.Gui.Models +{ + public class InjectedAssembly + { + public int ProcessId { get; set; } + + public IntPtr Address { get; set; } + + public bool Is64Bit { get; set; } + + public string Name { get; set; } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/MonoProcess.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/MonoProcess.cs new file mode 100644 index 0000000..9445e7f --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Models/MonoProcess.cs @@ -0,0 +1,13 @@ +using System; + +namespace SharpMonoInjector.Gui.Models +{ + public class MonoProcess + { + public IntPtr MonoModule { get; set; } + + public string Name { get; set; } + + public int Id { get; set; } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/AssemblyInfo.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..159e122 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SharpMonoInjector.Gui")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharpMonoInjector.Gui")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.Designer.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.Designer.cs new file mode 100644 index 0000000..32195be --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.Designer.cs @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SharpMonoInjector.Gui.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SharpMonoInjector.Gui.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.resx b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.Designer.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.Designer.cs new file mode 100644 index 0000000..dac5f27 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.Designer.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SharpMonoInjector.Gui.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get { + return defaultInstance; + } + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.settings b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/SharpMonoInjector.Gui.csproj b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/SharpMonoInjector.Gui.csproj new file mode 100644 index 0000000..676b035 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/SharpMonoInjector.Gui.csproj @@ -0,0 +1,114 @@ + + + + + Debug + AnyCPU + {AA64466B-D036-47B1-AA77-DB1DAA026313} + WinExe + SharpMonoInjector.Gui + smi_gui + v4.7.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + ..\..\build\debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + ..\..\build\release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + + + + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {9a2f2e9f-d314-4b7a-ba8d-8522bb91a114} + SharpMonoInjector + + + + \ No newline at end of file diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/MainWindowViewModel.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..7905e04 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.Win32; +using SharpMonoInjector.Gui.Models; + +namespace SharpMonoInjector.Gui.ViewModels +{ + public class MainWindowViewModel : ViewModel + { + public RelayCommand RefreshCommand { get; } + + public RelayCommand BrowseCommand { get; } + + public RelayCommand InjectCommand { get; } + + public RelayCommand EjectCommand { get; } + + public RelayCommand CopyStatusCommand { get; } + + public MainWindowViewModel() + { + RefreshCommand = new RelayCommand(ExecuteRefreshCommand, CanExecuteRefreshCommand); + BrowseCommand = new RelayCommand(ExecuteBrowseCommand); + InjectCommand = new RelayCommand(ExecuteInjectCommand, CanExecuteInjectCommand); + EjectCommand = new RelayCommand(ExecuteEjectCommand, CanExecuteEjectCommand); + CopyStatusCommand = new RelayCommand(ExecuteCopyStatusCommand); + } + + private void ExecuteCopyStatusCommand(object parameter) + { + Clipboard.SetText(Status); + } + + private bool CanExecuteRefreshCommand(object parameter) + { + return !IsRefreshing; + } + + private async void ExecuteRefreshCommand(object parameter) + { + IsRefreshing = true; + Status = "Refreshing processes"; + ObservableCollection processes = new ObservableCollection(); + + await Task.Run(() => + { + int cp = Process.GetCurrentProcess().Id; + + foreach (Process p in Process.GetProcesses()) { + if (p.Id == cp) + continue; + + const ProcessAccessRights flags = ProcessAccessRights.PROCESS_QUERY_INFORMATION | ProcessAccessRights.PROCESS_VM_READ; + IntPtr handle; + + if ((handle = Native.OpenProcess(flags, false, p.Id)) != IntPtr.Zero) { + if (ProcessUtils.GetMonoModule(handle, out IntPtr mono)) { + processes.Add(new MonoProcess + { + MonoModule = mono, + Id = p.Id, + Name = p.ProcessName + }); + } + + Native.CloseHandle(handle); + } + } + }); + + Processes = processes; + + if (Processes.Count > 0) + SelectedProcess = Processes[0]; + + IsRefreshing = false; + Status = "Processes refreshed"; + } + + private void ExecuteBrowseCommand(object parameter) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Filter = "Dynamic Link Library|*.dll"; + ofd.Title = "Select assembly to inject"; + + if (ofd.ShowDialog() == true) + AssemblyPath = ofd.FileName; + } + + private bool CanExecuteInjectCommand(object parameter) + { + return SelectedProcess != null && + File.Exists(AssemblyPath) && + !string.IsNullOrEmpty(InjectClassName) && + !string.IsNullOrEmpty(InjectMethodName) && + !IsExecuting; + } + + private void ExecuteInjectCommand(object parameter) + { + IntPtr handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, SelectedProcess.Id); + + if (handle == IntPtr.Zero) { + Status = "Failed to open process"; + return; + } + + byte[] file; + + try { + file = File.ReadAllBytes(AssemblyPath); + } catch (IOException) { + Status = "Failed to read the file " + AssemblyPath; + return; + } + + IsExecuting = true; + Status = "Injecting " + Path.GetFileName(AssemblyPath); + + using (Injector injector = new Injector(handle, SelectedProcess.MonoModule)) { + try { + IntPtr asm = injector.Inject(file, InjectNamespace, InjectClassName, InjectMethodName); + InjectedAssemblies.Add(new InjectedAssembly + { + ProcessId = SelectedProcess.Id, + Address = asm, + Name = Path.GetFileName(AssemblyPath), + Is64Bit = injector.Is64Bit + }); + Status = "Injection successful"; + } catch (InjectorException ie) { + Status = "Injection failed: " + ie.Message; + } catch (Exception e) { + Status = "Injection failed (unknown error): " + e.Message; + } + } + + IsExecuting = false; + } + + private bool CanExecuteEjectCommand(object parameter) + { + return SelectedAssembly != null && + !string.IsNullOrEmpty(EjectClassName) && + !string.IsNullOrEmpty(EjectMethodName) && + !IsExecuting; + } + + private void ExecuteEjectCommand(object parameter) + { + IntPtr handle = Native.OpenProcess(ProcessAccessRights.PROCESS_ALL_ACCESS, false, SelectedAssembly.ProcessId); + + if (handle == IntPtr.Zero) { + Status = "Failed to open process"; + return; + } + + IsExecuting = true; + Status = "Ejecting " + SelectedAssembly.Name; + + ProcessUtils.GetMonoModule(handle, out IntPtr mono); + + using (Injector injector = new Injector(handle, mono)) { + try { + injector.Eject(SelectedAssembly.Address, EjectNamespace, EjectClassName, EjectMethodName); + InjectedAssemblies.Remove(SelectedAssembly); + Status = "Ejection successful"; + } catch (InjectorException ie) { + Status = "Ejection failed: " + ie.Message; + } catch (Exception e) { + Status = "Ejection failed (unknown error): " + e.Message; + } + } + + IsExecuting = false; + } + + private bool _isRefreshing; + public bool IsRefreshing + { + get => _isRefreshing; + set { + Set(ref _isRefreshing, value); + RefreshCommand.RaiseCanExecuteChanged(); + } + } + + private bool _isExecuting; + public bool IsExecuting + { + get => _isExecuting; + set { + Set(ref _isExecuting, value); + InjectCommand.RaiseCanExecuteChanged(); + EjectCommand.RaiseCanExecuteChanged(); + } + } + + private ObservableCollection _processes; + public ObservableCollection Processes + { + get => _processes; + set => Set(ref _processes, value); + } + + private MonoProcess _selectedProcess; + public MonoProcess SelectedProcess + { + get => _selectedProcess; + set { + _selectedProcess = value; + InjectCommand.RaiseCanExecuteChanged(); + } + } + + private string _status; + public string Status + { + get => _status; + set => Set(ref _status, value); + } + + private string _assemblyPath; + public string AssemblyPath + { + get => _assemblyPath; + set { + Set(ref _assemblyPath, value); + if (File.Exists(_assemblyPath)) + InjectNamespace = Path.GetFileNameWithoutExtension(_assemblyPath); + InjectCommand.RaiseCanExecuteChanged(); + } + } + + private string _injectNamespace; + public string InjectNamespace + { + get => _injectNamespace; + set { + Set(ref _injectNamespace, value); + EjectNamespace = value; + } + } + + private string _injectClassName; + public string InjectClassName + { + get => _injectClassName; + set { + Set(ref _injectClassName, value); + EjectClassName = value; + InjectCommand.RaiseCanExecuteChanged(); + } + } + + private string _injectMethodName; + public string InjectMethodName + { + get => _injectMethodName; + set { + Set(ref _injectMethodName, value); + if (_injectMethodName == "Load") + EjectMethodName = "Unload"; + InjectCommand.RaiseCanExecuteChanged(); + } + } + + private ObservableCollection _injectedAssemblies = new ObservableCollection(); + public ObservableCollection InjectedAssemblies + { + get => _injectedAssemblies; + set => Set(ref _injectedAssemblies, value); + } + + private InjectedAssembly _selectedAssembly; + public InjectedAssembly SelectedAssembly + { + get => _selectedAssembly; + set { + Set(ref _selectedAssembly, value); + EjectCommand.RaiseCanExecuteChanged(); + } + } + + private string _ejectNamespace; + public string EjectNamespace + { + get => _ejectNamespace; + set => Set(ref _ejectNamespace, value); + } + + private string _ejectClassName; + public string EjectClassName + { + get => _ejectClassName; + set { + Set(ref _ejectClassName, value); + EjectCommand.RaiseCanExecuteChanged(); + } + } + + private string _ejectMethodName; + public string EjectMethodName + { + get => _ejectMethodName; + set { + Set(ref _ejectMethodName, value); + EjectCommand.RaiseCanExecuteChanged(); + } + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/RelayCommand.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/RelayCommand.cs new file mode 100644 index 0000000..3f4407c --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/RelayCommand.cs @@ -0,0 +1,26 @@ +using System; +using System.Windows.Input; + +namespace SharpMonoInjector.Gui.ViewModels +{ + public class RelayCommand : ICommand + { + public event EventHandler CanExecuteChanged; + + private readonly Action _execute; + + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute; + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); + + public void Execute(object parameter) => _execute(parameter); + + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/ViewModel.cs b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/ViewModel.cs new file mode 100644 index 0000000..ac5f682 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/ViewModels/ViewModel.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace SharpMonoInjector.Gui.ViewModels +{ + public abstract class ViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void Set(ref T property, T value, [CallerMemberName]string name = null) + { + if (!EqualityComparer.Default.Equals(property, value)) { + property = value; + RaisePropertyChanged(name); + } + } + + protected void RaisePropertyChanged(string name) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } +} diff --git a/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Views/MainWindow.xaml b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Views/MainWindow.xaml new file mode 100644 index 0000000..2e9f114 --- /dev/null +++ b/dependencies/SharpMonoInjector/src/SharpMonoInjector.Gui/Views/MainWindow.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +