diff --git a/Doorstop b/Doorstop deleted file mode 160000 index f9a0d26b..00000000 --- a/Doorstop +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9a0d26bfb2a6f24abd3b438eb6b813a54e3ed4e diff --git a/Doorstop/.gitignore b/Doorstop/.gitignore new file mode 100644 index 00000000..f35b4165 --- /dev/null +++ b/Doorstop/.gitignore @@ -0,0 +1,325 @@ +## 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 + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build folder +Build/ + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +.vscode/ +# 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 + +# .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 + +# 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 +# TODO: 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 + +# 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 + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/Doorstop/DoorstopTest/DoorstopTest.csproj b/Doorstop/DoorstopTest/DoorstopTest.csproj new file mode 100644 index 00000000..77751093 --- /dev/null +++ b/Doorstop/DoorstopTest/DoorstopTest.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C} + Library + Properties + Doorstop + Doorstop + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + true + bin\Verbose\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + bin\Verbose_Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + + + + + \ No newline at end of file diff --git a/Doorstop/DoorstopTest/Loader.cs b/Doorstop/DoorstopTest/Loader.cs new file mode 100644 index 00000000..23780df0 --- /dev/null +++ b/Doorstop/DoorstopTest/Loader.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; + +namespace Doorstop +{ + public static class Loader + { + public static void Main(string[] args) + { + using (TextWriter tw = File.CreateText("doorstop_is_alive.txt")) + { + tw.WriteLine($"Hello! This text file was generated by Doorstop on {DateTime.Now:R}!"); + tw.WriteLine($"I was called with {args.Length} params!"); + + for (int i = 0; i < args.Length; i++) + tw.WriteLine($"{i} => {args[i]}"); + + tw.WriteLine("Replace this DLL with a custom-made one to do whatever you want!"); + + tw.Flush(); + } + } + } +} \ No newline at end of file diff --git a/Doorstop/DoorstopTest/Properties/AssemblyInfo.cs b/Doorstop/DoorstopTest/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..c19ccf3e --- /dev/null +++ b/Doorstop/DoorstopTest/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("PatchLoader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PatchLoader")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[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("62b61ccc-0775-4cf4-b37a-eb5e33cbd08c")] + +// 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/Doorstop/LICENSE b/Doorstop/LICENSE new file mode 100644 index 00000000..3bbbc1ee --- /dev/null +++ b/Doorstop/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + \ No newline at end of file diff --git a/Doorstop/Proxy/Proxy.vcxproj b/Doorstop/Proxy/Proxy.vcxproj new file mode 100644 index 00000000..a8c6a1f9 --- /dev/null +++ b/Doorstop/Proxy/Proxy.vcxproj @@ -0,0 +1,190 @@ + + + + + Release + Win32 + + + Release + x64 + + + Verbose_Release + Win32 + + + Verbose_Release + x64 + + + + {88609E16-731F-46C9-8139-6B1A7A83240D} + Win32Proj + proxy + 10.0 + proxy + + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + + Level4 + + + MinSpace + true + false + NDEBUG;_WINDOWS;_USRDLL;NOGDI;%(PreprocessorDefinitions) + c11 + CompileAsC + CompileAsC + AnySuitable + false + false + false + false + MultiThreadedDLL + MultiThreadedDLL + + + Windows + true + true + true + $(MSBuildProjectDirectory)\proxy.def + %(AdditionalDependencies) + + + DllMain + false + false + false + %(AdditionalOptions) + %(AdditionalOptions) + + + + + Level4 + + + MinSpace + true + false + _VERBOSE;NDEBUG;_WINDOWS;_USRDLL;NOGDI;%(PreprocessorDefinitions) + c11 + CompileAsC + CompileAsC + AnySuitable + false + false + false + false + MultiThreadedDLL + MultiThreadedDLL + + + Windows + true + true + true + $(MSBuildProjectDirectory)\proxy.def + %(AdditionalDependencies) + + + DllMain + false + false + false + %(AdditionalOptions) + %(AdditionalOptions) + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Doorstop/Proxy/Proxy.vcxproj.filters b/Doorstop/Proxy/Proxy.vcxproj.filters new file mode 100644 index 00000000..162f1bac --- /dev/null +++ b/Doorstop/Proxy/Proxy.vcxproj.filters @@ -0,0 +1,61 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {384fe73e-c79a-42c8-8d61-73dc2f7e7432} + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Other + + + \ No newline at end of file diff --git a/Doorstop/Proxy/assert_util.h b/Doorstop/Proxy/assert_util.h new file mode 100644 index 00000000..ec026404 --- /dev/null +++ b/Doorstop/Proxy/assert_util.h @@ -0,0 +1,63 @@ +#pragma once + + +#ifdef _VERBOSE + +#pragma comment(lib, "ucrt.lib") + +#include +#include + +static HANDLE log_handle; +char buffer[8192]; +wchar_t bufferW[8192]; + +inline void init_logger() +{ + log_handle = CreateFileA("doorstop.log", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL); +} + +inline void free_logger() +{ + CloseHandle(log_handle); +} + +#define LOG(message, ...) \ + { \ + int len = _snprintf_s(buffer, sizeof(buffer)/sizeof(char), _TRUNCATE, message, __VA_ARGS__); \ + WriteFile(log_handle, buffer, len, NULL, NULL); \ + } +#else +inline void init_logger() +{ +} + +inline void free_logger() +{ +} + +#define LOG(message, ...) +#endif + +#define ASSERT_F(test, message, ...) \ + if(!(test)) \ + { \ + _snwprintf_s(bufferW, sizeof(bufferW)/sizeof(wchar_t), _TRUNCATE, message, __VA_ARGS__); \ + MessageBoxW(NULL, bufferW, L"Doorstop: Fatal", MB_OK | MB_ICONERROR); \ + ExitProcess(EXIT_FAILURE); \ + } + +// A helper for cleaner error logging +#define ASSERT(test, message) \ + if(!(test)) \ + { \ + MessageBoxW(NULL, message, L"Doorstop: Fatal", MB_OK | MB_ICONERROR); \ + ExitProcess(EXIT_FAILURE); \ + } + +#define ASSERT_SOFT(test, ...) \ + if(!(test)) \ + { \ + return __VA_ARGS__; \ + } diff --git a/Doorstop/Proxy/config.h b/Doorstop/Proxy/config.h new file mode 100644 index 00000000..ca568c38 --- /dev/null +++ b/Doorstop/Proxy/config.h @@ -0,0 +1,137 @@ +#pragma once + +#pragma warning( disable : 4267 ) + +#include +#include +#include "winapi_util.h" +#include "assert_util.h" +#include "crt.h" + +#define CONFIG_NAME L"doorstop_config" +#define DEFAULT_TARGET_ASSEMBLY L"Doorstop.dll" +#define EXE_EXTENSION_LENGTH 4 + +BOOL enabled = FALSE; +BOOL debug = FALSE; +BOOL debug_server = FALSE; +BOOL debug_info = FALSE; +wchar_t *targetAssembly = NULL; + +#define STR_EQUAL(str1, str2) (lstrcmpiW(str1, str2) == 0) + +inline void initConfigFile() +{ + enabled = TRUE; + + WIN32_FIND_DATAW findData; + HANDLE findHandle = FindFirstFileW(L"*_Data", &findData); + if (findHandle == INVALID_HANDLE_VALUE) + { + MessageBoxW(NULL, L"Could not locate game being injected!", L"No files found in current directory matching '*_Data'", + MB_OK | MB_ICONERROR | MB_SYSTEMMODAL | MB_TOPMOST | MB_SETFOREGROUND); + + ExitProcess(GetLastError()); + } + + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { // must be a directory + wchar_t* target = memalloc(MAX_PATH * sizeof(wchar_t)); + + const int EXIT_FAILURE = 1; + ASSERT(target != NULL, L"Address returned by memalloc was NULL!"); + + wmemset(target, 0, MAX_PATH); + + wmemcpy(target, findData.cFileName, wcslen(findData.cFileName)); + wmemcpy(target + wcslen(target), L"/Managed/IPA.Injector.dll", 26); + + targetAssembly = target; + FindClose(findHandle); + break; + } + } + while (FindNextFileW(findHandle, &findData) != 0); + + if (targetAssembly == NULL) + { + MessageBoxW(NULL, L"Could not locate game being injected!", L"No valid directories matching '*_Data'", + MB_OK | MB_ICONERROR | MB_SYSTEMMODAL | MB_TOPMOST | MB_SETFOREGROUND); + + ExitProcess(GetLastError()); + } +} + +inline void initCmdArgs() +{ + wchar_t *args = GetCommandLineW(); + int argc = 0; + wchar_t **argv = CommandLineToArgvW(args, &argc); + +#define IS_ARGUMENT(arg_name) STR_EQUAL(arg, arg_name) && i < argc + + for (int i = 0; i < argc; i++) + { + wchar_t *arg = argv[i]; + /*if (IS_ARGUMENT(L"--doorstop-enable")) + { + wchar_t *par = argv[++i]; + + if (STR_EQUAL(par, L"true")) + enabled = TRUE; + else if (STR_EQUAL(par, L"false")) + enabled = FALSE; + } + else if (IS_ARGUMENT(L"--doorstop-target")) + { + if (targetAssembly != NULL) + memfree(targetAssembly); + const size_t len = wcslen(argv[i + 1]) + 1; + targetAssembly = memalloc(sizeof(wchar_t) * len); + lstrcpynW(targetAssembly, argv[++i], len); + LOG("Args; Target assembly: %S\n", targetAssembly); + } + else */if (IS_ARGUMENT(L"--mono-debug")) + { + debug = TRUE; + debug_info = TRUE; + LOG("Enabled debugging\n"); + } + else if (IS_ARGUMENT(L"--debug")) + { + debug_info = TRUE; + LOG("Enabled loading of debug info\n"); + } + else if (IS_ARGUMENT(L"--server")) + { + debug_server = TRUE; + LOG("Server-mode debugging enabled\n"); + } + } + + LocalFree(argv); +} + +inline void initEnvVars() +{ + if (GetEnvironmentVariableW(L"DOORSTOP_DISABLE", NULL, 0) != 0) + { + LOG("DOORSTOP_DISABLE is set! Disabling Doorstop!\n"); + enabled = FALSE; + } +} + +inline void loadConfig() +{ + initConfigFile(); + initCmdArgs(); + initEnvVars(); +} + +inline void cleanupConfig() +{ + if (targetAssembly != NULL) + memfree(targetAssembly); +} diff --git a/Doorstop/Proxy/crt.h b/Doorstop/Proxy/crt.h new file mode 100644 index 00000000..432f7cda --- /dev/null +++ b/Doorstop/Proxy/crt.h @@ -0,0 +1,65 @@ +#pragma once + +#pragma warning( disable : 4028 28251 6001 ) + +#include + +HANDLE hHeap; + +#define memalloc(size) HeapAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS, size) +#define memcalloc(size) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, size) +#define memfree(mem) HeapFree(hHeap, 0, mem) + +#define STR_LEN(str) (sizeof(str) / sizeof(str[0])) + +#define DEBUG_BREAK { if (IsDebuggerPresent()) { __debugbreak(); } } + +inline void *wmemcpy(wchar_t *dst, const wchar_t *src, size_t n) +{ + wchar_t *d = dst; + const wchar_t *s = src; + while (n--) + *d++ = *s++; + return dst; +} + +inline void *wmemset(wchar_t *dst, wchar_t c, size_t n) +{ + wchar_t *d = dst; + while (n--) + *(d++) = c; + return dst; +} + +void *memset(void *dst, char c, int n) +{ + char *d = dst; + while (n--) + *d = c; + return dst; +} + +inline void *memcpy(void *dst, const void *src, int n) +{ + char *d = dst; + const char *s = src; + while (n--) + *d++ = *s++; + return dst; +} + +inline size_t wcslen(wchar_t const *str) +{ + size_t result = 0; + while (*str++) + result++; + return result; +} + +inline size_t strlen(char const *str) +{ + size_t result = 0; + while (*str++) + result++; + return result; +} diff --git a/Doorstop/Proxy/dll.def b/Doorstop/Proxy/dll.def new file mode 100644 index 00000000..f85893cb --- /dev/null +++ b/Doorstop/Proxy/dll.def @@ -0,0 +1,64 @@ +EXPORTS +SvchostPushServiceGlobals +WinHttpAddRequestHeaders +WinHttpAutoProxySvcMain +WinHttpCheckPlatform +WinHttpCloseHandle +WinHttpConnect +WinHttpConnectionDeletePolicyEntries +WinHttpConnectionDeleteProxyInfo +WinHttpConnectionFreeNameList +WinHttpConnectionFreeProxyInfo +WinHttpConnectionFreeProxyList +WinHttpConnectionGetNameList +WinHttpConnectionGetProxyInfo +WinHttpConnectionGetProxyList +WinHttpConnectionSetPolicyEntries +WinHttpConnectionSetProxyInfo +WinHttpConnectionUpdateIfIndexTable +WinHttpCrackUrl +WinHttpCreateProxyResolver +WinHttpCreateUrl +WinHttpDetectAutoProxyConfigUrl +WinHttpFreeProxyResult +WinHttpFreeProxyResultEx +WinHttpFreeProxySettings +WinHttpGetDefaultProxyConfiguration +WinHttpGetIEProxyConfigForCurrentUser +WinHttpGetProxyForUrl +WinHttpGetProxyForUrlEx +WinHttpGetProxyForUrlEx2 +WinHttpGetProxyForUrlHvsi +WinHttpGetProxyResult +WinHttpGetProxyResultEx +WinHttpGetProxySettingsVersion +WinHttpGetTunnelSocket +WinHttpOpen +WinHttpOpenRequest +WinHttpProbeConnectivity +WinHttpQueryAuthSchemes +WinHttpQueryDataAvailable +WinHttpQueryHeaders +WinHttpQueryOption +WinHttpReadData +WinHttpReadProxySettings +WinHttpReadProxySettingsHvsi +WinHttpReceiveResponse +WinHttpResetAutoProxy +WinHttpSaveProxyCredentials +WinHttpSendRequest +WinHttpSetCredentials +WinHttpSetDefaultProxyConfiguration +WinHttpSetOption +WinHttpSetStatusCallback +WinHttpSetTimeouts +WinHttpTimeFromSystemTime +WinHttpTimeToSystemTime +WinHttpWebSocketClose +WinHttpWebSocketCompleteUpgrade +WinHttpWebSocketQueryCloseStatus +WinHttpWebSocketReceive +WinHttpWebSocketSend +WinHttpWebSocketShutdown +WinHttpWriteData +WinHttpWriteProxySettings diff --git a/Doorstop/Proxy/hook.h b/Doorstop/Proxy/hook.h new file mode 100644 index 00000000..85e5e872 --- /dev/null +++ b/Doorstop/Proxy/hook.h @@ -0,0 +1,162 @@ +/* + * EAT-based hooking for x86/x64. + * + * Big thanks to ez (https://github.com/ezdiy/) for making this! + * + * Creates "hooks" by modifying the module's export address table. + * The procedure works in three main parts: + * + * 1. Reading the module's PE file and getting all exported functions. + * 2. Finding the right function to "hook" by simple address lookup + * 3. Modify the entry to point to the hook. + * + * The idea is based on the fact that the export table allows forwarding imports: + * + * https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Forwarding + * + * You can also find some reference material on the same page to understand this code better. + * + */ + +#pragma once + +#pragma warning( disable : 4267 90 ) + +#include + +// PE format uses RVAs (Relative Virtual Addresses) to save addresses relative to the base of the module +// More info: https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Relative_Virtual_Addressing_(RVA) +// +// This helper macro converts the saved RVA to a fully valid pointer to the data in the PE file +#define RVA2PTR(t,base,rva) ((t)(((PCHAR) base) + rva)) + +// A helper function to write into protected memory +inline int vpmemcpy(void *dst, void *src, size_t sz) +{ + DWORD oldp; + // Make the memory page writeable + if (!VirtualProtect(dst, sz, PAGE_READWRITE, &oldp)) + return 1; + memcpy(dst, src, sz); + // Restore the protection level + VirtualProtect(dst, sz, oldp, &oldp); + return 0; +} + + +/** + * \brief Replaces the specified function entry in the EAT with a forward to a custom one, thus creating the "hook" effect. + * \param hostDll The address of the module to hook. + * \param originalFunction Address of the original function. + * \param forwardFunctionEntry Name of the function to add a forward to. Must be of form `dll.API`. + * \return TRUE, if hooking succeeded, otherwise, FALSE. + */ +inline BOOL ezHook(HMODULE hostDll, void *originalFunction, char *forwardFunctionEntry) +{ + /* + * Note that we are not doing any trampoline magic or editing the assembly! + * + * Instead, we are reading the module's PE file, find the original function's entry in the export address table (EAT), + * and replace it with a forward import. + * + * This ultimately will fool the game/executable to call our hook, while keeping original function intact. + * + * Thus, in order to work, the proxy DLL has to export the hook, because we are essentially + * asking the game to call our hook without ever going to the original function (unlike with trampolines). + */ + + size_t fwdlen = strlen(forwardFunctionEntry); + + // The module always starts with a DOS (or "MZ") header + IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)hostDll; + + // Next, get the NT headers. The offset to them is saved in e_lfanew + IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew); + + // Get the pointer to the data directory of the exports + IMAGE_DATA_DIRECTORY *edirp = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + IMAGE_DATA_DIRECTORY edir = *edirp; + + // Finally, the virtual address in the data direcotry tells the location of the exports table + IMAGE_EXPORT_DIRECTORY *exports = RVA2PTR(PIMAGE_EXPORT_DIRECTORY, mz, edir.VirtualAddress); + + // Read the addrress of the function list and the address of function names + DWORD *addrs = RVA2PTR(DWORD*, mz, exports->AddressOfFunctions); + // DWORD* names = RVA2PTR(DWORD*, mz, exports->AddressOfNames); + + // Iterate through all functions + for (unsigned i = 0; i < exports->NumberOfFunctions; i++) + { + //char* name = RVA2PTR(char*, mz, names[i]); // Name of the exported function + void *addr = RVA2PTR(void*, mz, addrs[i]); // Address of the exported function + + // Check if we have the function we need to modify + if (addr == originalFunction) + { + DWORD fptr = edir.VirtualAddress + edir.Size; + int err = 0; + + // Update the entry to go the the last entry (which we will populate in the next memcpy) + err |= vpmemcpy(&addrs[i], &fptr, sizeof(fptr)); + + // Add the forwarding import to our function at the end of the EAT + err |= vpmemcpy(((char*)exports + edir.Size), forwardFunctionEntry, fwdlen); + + // Increment the size of the export data directory + // and write the new export data directory + edir.Size += fwdlen + 1; + err |= vpmemcpy(edirp, &edir, sizeof(edir)); + return err == 0; + } + } + return FALSE; +} + + +/** + * \brief Hooks the given function through the Import Address Table + * \param dll Module to hook + * \param targetFunction Address of the target function to hook + * \param detourFunction Address of the detour function + * \return TRUE if successful, otherwise FALSE + */ +inline BOOL iat_hook(HMODULE dll, char const* targetDLL, void *targetFunction, void *detourFunction) +{ + IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)dll; + + IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew); + + IMAGE_IMPORT_DESCRIPTOR *imports = RVA2PTR(PIMAGE_IMPORT_DESCRIPTOR, mz, nt->OptionalHeader.DataDirectory[ + IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + for (int i = 0; imports[i].Characteristics; i++) + { + char *name = RVA2PTR(char*, mz, imports[i].Name); + + if(lstrcmpiA(name, targetDLL) != 0) + continue; + + void **thunk = RVA2PTR(void**, mz, imports[i].FirstThunk); + void **nextThunk = RVA2PTR(void**, mz, imports[i+1].FirstThunk); + + for (; thunk && thunk < nextThunk; thunk++) + { + void *import = *thunk; + + if (import != targetFunction) + continue; + + DWORD oldState; + if (!VirtualProtect(thunk, sizeof(void*), PAGE_READWRITE, &oldState)) + return FALSE; + + *thunk = (void*)detourFunction; + + VirtualProtect(thunk, sizeof(void*), oldState, &oldState); + + return TRUE; + } + } + + return FALSE; +} diff --git a/Doorstop/Proxy/main.c b/Doorstop/Proxy/main.c new file mode 100644 index 00000000..1184fc2d --- /dev/null +++ b/Doorstop/Proxy/main.c @@ -0,0 +1,462 @@ +/* + * main.cpp -- The main "entry point" and the main logic of the DLL. + * + * Here, we define and initialize struct Main that contains the main code of this DLL. + * + * The main procedure goes as follows: + * 1. The loader checks that PatchLoader.dll and mono.dll exist + * 2. mono.dll is loaded into memory and some of its functions are looked up + * 3. mono_jit_init_version is hooked with the help of MinHook + * + * Then, the loader waits until Unity creates its root domain for mono (which is done with mono_jit_init_version). + * + * Inside mono_jit_init_version hook: + * 1. Call the original mono_jit_init_version to get the Unity root domain + * 2. Load PatchLoader.dll into the root domain + * 3. Find and invoke PatchLoader.Loader.Run() + * + * Rest of the work is done on the managed side. + * + */ + +#pragma warning( disable : 4267 100 152 6387 4456 6011 ) + +#include "winapi_util.h" +#include + +#include "config.h" +#include "mono.h" +#include "hook.h" +#include "assert_util.h" +#include "proxy.h" +#include + +#include + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; // This is provided by MSVC with the infomration about this DLL + +HANDLE unhandledMutex; +void ownMonoJitParseOptions(int argc, char * argv[]); +BOOL setOptions = FALSE; + +void unhandledException(void* exc, void* data) +{ + WaitForSingleObject(unhandledMutex, INFINITE); + + void* exception = NULL; + void* mstr = mono_object_to_string(exc, &exception); + + if (exception != NULL) + { +#ifdef _VERBOSE + void* monostr = mono_object_to_string(exception, &exception); + if (exception != NULL) + { + DEBUG_BREAK; + LOG("An error occurred while stringifying uncaught error, but the error could not be stringified.\n"); + ASSERT(FALSE, L"Uncaught exception; could not stringify"); + } + else + { + char* str = mono_string_to_utf8(monostr); + DEBUG_BREAK; + LOG("An error occurred stringifying uncaught error: %s\n", str); + + size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wchar_t* wstr = memalloc(sizeof(wchar_t) * len); + MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len); + + ASSERT_F(FALSE, L"Uncaught exception; stringify failed: %s", wstr); + + memfree(wstr); + mono_free(str); + } +#else + ASSERT(FALSE, L"Could not stringify uncaught exception"); +#endif + } + + char* str = mono_string_to_utf8(mstr); + DEBUG_BREAK; + LOG("Uncaught exception: %s\n", str); + + size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wchar_t* wstr = memalloc(sizeof(wchar_t) * len); + MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len); + +#ifdef _VERBOSE + ASSERT(FALSE, L"Uncaught exception; see doorstop.log for details"); +#else + ASSERT_F(FALSE, L"Uncaught exception: %s", wstr); +#endif + + memfree(wstr); + mono_free(str); + + ReleaseMutex(unhandledMutex); +} + +// The hook for mono_jit_init_version +// We use this since it will always be called once to initialize Mono's JIT +void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version) +{ + // Call the original mono_jit_init_version to initialize the Unity Root Domain + if (debug) { + char* opts[1]; + opts[0] = ""; + ownMonoJitParseOptions(0, opts); + } +#ifdef WIN32 + if (debug_info) { + mono_debug_init(MONO_DEBUG_FORMAT_MONO); + } +#endif + + void *domain = mono_jit_init_version(root_domain_name, runtime_version); + + if (debug_info) { +#ifdef WIN64 + mono_debug_init(MONO_DEBUG_FORMAT_MONO); +#endif + mono_debug_domain_create(domain); + } + + size_t len = WideCharToMultiByte(CP_UTF8, 0, targetAssembly, -1, NULL, 0, NULL, NULL); + char *dll_path = memalloc(sizeof(char) * len); + WideCharToMultiByte(CP_UTF8, 0, targetAssembly, -1, dll_path, len, NULL, NULL); + + LOG("Loading assembly: %s\n", dll_path); + // Load our custom assembly into the domain + void *assembly = mono_domain_assembly_open(domain, dll_path); + + if (assembly == NULL) + LOG("Failed to load assembly\n"); + + memfree(dll_path); + ASSERT_SOFT(assembly != NULL, domain); + + // Get assembly's image that contains CIL code + void *image = mono_assembly_get_image(assembly); + ASSERT_SOFT(image != NULL, domain); + + // Note: we use the runtime_invoke route since jit_exec will not work on DLLs + + // Create a descriptor for a random Main method + void *desc = mono_method_desc_new("*:Main", FALSE); + + // Find the first possible Main method in the assembly + void *method = mono_method_desc_search_in_image(desc, image); + ASSERT_SOFT(method != NULL, domain); + + void *signature = mono_method_signature(method); + + // Get the number of parameters in the signature + UINT32 params = mono_signature_get_param_count(signature); + + void **args = NULL; + wchar_t *app_path = NULL; + if (params == 1) + { + // If there is a parameter, it's most likely a string[]. + // Populate it as follows + // 0 => path to the game's executable + // 1 => --doorstop-invoke + + get_module_path(NULL, &app_path, NULL, 0); + + void *exe_path = MONO_STRING(app_path); + void *doorstop_handle = MONO_STRING(L"--doorstop-invoke"); + + void *args_array = mono_array_new(domain, mono_get_string_class(), 2); + + SET_ARRAY_REF(args_array, 0, exe_path); + SET_ARRAY_REF(args_array, 1, doorstop_handle); + + args = memalloc(sizeof(void*) * 1); + _ASSERTE(args != nullptr); + args[0] = args_array; + } + + LOG("Installing uncaught exception handler\n"); + + mono_install_unhandled_exception_hook(unhandledException, NULL); + + unhandledMutex = CreateMutexW(NULL, FALSE, NULL); + + LOG("Invoking method!\n"); + + void* exception = NULL; + mono_runtime_invoke(method, NULL, args, &exception); + + WaitForSingleObject(unhandledMutex, INFINITE); // if the EH is triggered, wait for it + + if (args != NULL) + { + memfree(app_path); + memfree(args); + NULL; + } + +#ifdef _VERBOSE + if (exception != NULL) + { + void* monostr = mono_object_to_string(exception, &exception); + if (exception != NULL) + LOG("An error occurred while invoking the injector, but the error could not be stringified.\n") + else + { + char* str = mono_string_to_utf8(monostr); + LOG("An error occurred invoking the injector: %s\n", str); + mono_free(str); + } + } +#endif + + cleanupConfig(); + + free_logger(); + + ReleaseMutex(unhandledMutex); + + return domain; +} + +void ownMonoJitParseOptions(int argc, char * argv[]) +{ + setOptions = TRUE; + + int size = argc; +#ifdef WIN64 + if (debug) size += 2; +#elif defined(WIN32) + if (debug) size += 1; +#endif + + char** arguments = memalloc(sizeof(char*) * size); + _ASSERTE(arguments != nullptr); + memcpy(arguments, argv, sizeof(char*) * argc); + if (debug) { + //arguments[argc++] = "--debug"; +#ifdef WIN64 + arguments[argc++] = "--soft-breakpoints"; +#endif + if (debug_server) + arguments[argc] = "--debugger-agent=transport=dt_socket,address=0.0.0.0:10000,server=y"; + else + arguments[argc] = "--debugger-agent=transport=dt_socket,address=127.0.0.1:10000,server=n"; + } + + mono_jit_parse_options(size, arguments); + + memfree(arguments); +} + +BOOL initialized = FALSE; + +void init(HMODULE module) +{ + if (!initialized) + { + initialized = TRUE; + LOG("Got mono.dll at %p\n", module); + loadMonoFunctions(module); + } +} + +void * WINAPI hookGetProcAddress(HMODULE module, char const *name) +{ + if (lstrcmpA(name, "mono_jit_init_version") == 0) + { + init(module); + return (void*)&ownMonoJitInitVersion; + } + if (lstrcmpA(name, "mono_jit_parse_options") == 0 && debug) + { + init(module); + return (void*)&ownMonoJitParseOptions; + } + return (void*)GetProcAddress(module, name); +} + +BOOL hookGetMessage( + BOOL isW, + LPMSG msg, + HWND hwnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax +); + +BOOL WINAPI hookGetMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax) +{ + return hookGetMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax); +} +BOOL WINAPI hookGetMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax) +{ + return hookGetMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax); +} + +typedef BOOL(*GetMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax); + +GetMessageHook getMessageHook = NULL; + +__declspec(dllexport) void __stdcall SetGetMessageHook(GetMessageHook hook) { + getMessageHook = hook; +} + +BOOL hookGetMessage( + BOOL isW, + LPMSG msg, + HWND hwnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax +) +{ + BOOL loop = FALSE; + + BOOL result; + + do { + if (isW) { + result = GetMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax); + } else { + result = GetMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax); + } + + if (getMessageHook) { + loop = getMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax); + } + } while (loop); + + return result; +} + +BOOL hookPeekMessage( + BOOL isW, + LPMSG msg, + HWND hwnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg +); + +BOOL WINAPI hookPeekMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) +{ + return hookPeekMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); +} +BOOL WINAPI hookPeekMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) +{ + return hookPeekMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); +} + +typedef BOOL(*PeekMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax, UINT* wRemoveMsg); + +PeekMessageHook peekMessageHook = NULL; + +__declspec(dllexport) void __stdcall SetPeekMessageHook(PeekMessageHook hook) { + peekMessageHook = hook; +} + +BOOL hookPeekMessage( + BOOL isW, + LPMSG msg, + HWND hwnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg +) +{ + BOOL loop = FALSE; + + BOOL result; + + do { + if (isW) { + result = PeekMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + } + else { + result = PeekMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + } + + if (peekMessageHook) { + loop = peekMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax, &wRemoveMsg); + } + } while (loop); + + return result; +} + +BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reasonForDllLoad, LPVOID reserved) +{ + if (reasonForDllLoad != DLL_PROCESS_ATTACH) + return TRUE; + + hHeap = GetProcessHeap(); + + init_logger(); + + LOG("Doorstop started!\n"); + + wchar_t *dll_path = NULL; + size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path, NULL, 0); + + LOG("DLL Path: %S\n", dll_path); + + wchar_t *dll_name = get_file_name_no_ext(dll_path, dll_path_len); + + LOG("Doorstop DLL Name: %S\n", dll_name); + + loadProxy(dll_name); + loadConfig(); + + // If the loader is disabled, don't inject anything. + if (enabled) + { + LOG("Doorstop enabled!\n"); + ASSERT_SOFT(GetFileAttributesW(targetAssembly) != INVALID_FILE_ATTRIBUTES, TRUE); + + HMODULE targetModule = GetModuleHandleA("UnityPlayer"); + + if(targetModule == NULL) + { + LOG("No UnityPlayer.dll; using EXE as the hook target."); + targetModule = GetModuleHandleA(NULL); + } + + LOG("Installing IAT hook\n"); + if (!iat_hook(targetModule, "kernel32.dll", &GetProcAddress, &hookGetProcAddress)) + { + LOG("Failed to install IAT hook!\n"); + free_logger(); + } + + LOG("Hook installed!\n"); + + LOG("Attempting to install GetMessageA and GetMessageW hooks\n"); + + if (!iat_hook(targetModule, "user32.dll", &GetMessageA, &hookGetMessageA)) { + LOG("Could not hook GetMessageA! (not an error)\n"); + } + if (!iat_hook(targetModule, "user32.dll", &GetMessageW, &hookGetMessageW)) { + LOG("Could not hook GetMessageW! (not an error)\n"); + } + + LOG("Attempting to install PeekMessageA and PeekMessageW hooks\n"); + + if (!iat_hook(targetModule, "user32.dll", &PeekMessageA, &hookPeekMessageA)) { + LOG("Could not hook PeekMessageA! (not an error)\n"); + } + if (!iat_hook(targetModule, "user32.dll", &PeekMessageW, &hookPeekMessageW)) { + LOG("Could not hook PeekMessageW! (not an error)\n"); + } + } + else + { + LOG("Doorstop disabled! memfreeing resources\n"); + free_logger(); + } + + memfree(dll_name); + memfree(dll_path); + + return TRUE; +} diff --git a/Doorstop/Proxy/mono.h b/Doorstop/Proxy/mono.h new file mode 100644 index 00000000..a49357f8 --- /dev/null +++ b/Doorstop/Proxy/mono.h @@ -0,0 +1,105 @@ +/* + * mono.h -- Definitions for Mono C API functions. + * + * The file contains definitions for some of functions provided by Mono C API. + * + * Note: Since we don't use any mono headers, all pointers to mono-related structs are + * replaced with void *. + */ + +#pragma once + +#pragma warning( disable : 4152 ) + +#include + +// Creates a MonoString based from a C wide string +#define MONO_STRING(str) mono_string_new_utf16(domain, str, wcslen(str)) + +// Set MonoArray's index to a reference type value (i.e. string) +#define SET_ARRAY_REF(arr, index, refVal) \ + { \ + void **p = (void**) mono_array_addr_with_size(arr, sizeof(void*), index); \ + mono_gc_wbarrier_set_arrayref(arr, p, refVal); \ + } + +// Here we define the pointers to some functions within mono.dll +// Note to C learners: these are not signature definitions, but rather "variable" +// definitions with the function pointer type. + +// Note: we use void* instead of the real intented structs defined in mono API +// This way we don't need to include or define any of Mono's structs, which saves space +// This, obviously, comes with a drawback of not being able to easily access the contents of the structs + +typedef enum { + MONO_DEBUG_FORMAT_NONE, + MONO_DEBUG_FORMAT_MONO, + /* Deprecated, the mdb debugger is not longer supported. */ + MONO_DEBUG_FORMAT_DEBUGGER +} MonoDebugFormat; + +void (*mono_jit_parse_options)(int argc, char * argv[]); +void (*mono_debug_init)(MonoDebugFormat format); +void(*mono_debug_domain_create)(void*); + +void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version); +void *(*mono_domain_assembly_open)(void *domain, const char *name); +void *(*mono_assembly_get_image)(void *assembly); +void *(*mono_runtime_invoke)(void *method, void *obj, void **params, void **exc); + +void *(*mono_method_desc_new)(const char *name, int include_namespace); +void *(*mono_method_desc_search_in_image)(void *desc, void *image); +void *(*mono_method_signature)(void *method); +UINT32 (*mono_signature_get_param_count)(void *sig); + +void *(*mono_array_new)(void *domain, void *eclass, uintptr_t n); +void (*mono_gc_wbarrier_set_arrayref)(void *arr, void *slot_ptr, void *value); +char *(*mono_array_addr_with_size)(void *arr, int size, uintptr_t idx); + +void *(*mono_get_string_class)(); +void *(*mono_string_new_utf16)(void *domain, const wchar_t *text, INT32 len); + +void* (*mono_object_to_string)(void* obj, void** exc); + +char* (*mono_string_to_utf8)(void* str); +void (*mono_free)(void*); + +/* Installs a function which is called when the runtime encounters an unhandled exception. + * This hook isn't expected to return. + * If no hook has been installed, the runtime will print a message before aborting. + */ +typedef void (*MonoUnhandledExceptionFunc)(void* exc, void* user_data); +void (*mono_install_unhandled_exception_hook)(MonoUnhandledExceptionFunc func, void* user_data); + +/** +* \brief Loads Mono C API function pointers so that the above definitions can be called. +* \param monoLib Mono.dll module. +*/ +inline void loadMonoFunctions(HMODULE monoLib) +{ + // Enjoy the fact that C allows such sloppy casting + // In C++ you would have to cast to the precise function pointer type +#define GET_MONO_PROC(name) name = (void*)GetProcAddress(monoLib, #name) + + // Find and assign all our functions that we are going to use + GET_MONO_PROC(mono_debug_domain_create); + GET_MONO_PROC(mono_domain_assembly_open); + GET_MONO_PROC(mono_assembly_get_image); + GET_MONO_PROC(mono_runtime_invoke); + GET_MONO_PROC(mono_debug_init); + GET_MONO_PROC(mono_jit_init_version); + GET_MONO_PROC(mono_jit_parse_options); + GET_MONO_PROC(mono_method_desc_new); + GET_MONO_PROC(mono_method_desc_search_in_image); + GET_MONO_PROC(mono_method_signature); + GET_MONO_PROC(mono_signature_get_param_count); + GET_MONO_PROC(mono_array_new); + GET_MONO_PROC(mono_get_string_class); + GET_MONO_PROC(mono_string_new_utf16); + GET_MONO_PROC(mono_gc_wbarrier_set_arrayref); + GET_MONO_PROC(mono_array_addr_with_size); + GET_MONO_PROC(mono_object_to_string); + GET_MONO_PROC(mono_string_to_utf8); + GET_MONO_PROC(mono_free); + GET_MONO_PROC(mono_install_unhandled_exception_hook); +} diff --git a/Doorstop/Proxy/proxy.c b/Doorstop/Proxy/proxy.c new file mode 100644 index 00000000..0e3bc619 --- /dev/null +++ b/Doorstop/Proxy/proxy.c @@ -0,0 +1,133 @@ +/* ================================== + * COMPUTER GENERATED -- DO NOT EDIT + * ================================== + * + * This file contains the definitions for all proxy functions this DLL supports. + * + * The proxies are very simple functions that should be optimizied into a + * single JMP instruction without editing the stack at all. + * + * NOTE: While this works, this is a somewhat hackish approach that is based on how + * the compiler optimizes the code. That said, the proxy will not work on Debug build currently + * (that can be fixed by changing the appropriate compile flag that I am yet to locate). + */ + +#pragma warning( disable : 4244 ) + +#include + +#define ADD_ORIGINAL(i, name) originalFunctions[i] = GetProcAddress(dll, #name) + +#define PROXY(i, name) \ + __declspec(dllexport) ULONG __stdcall name() \ + { \ + return originalFunctions[i](); \ + } + +FARPROC originalFunctions[50] = {0}; + +void loadFunctions(HMODULE dll) +{ +ADD_ORIGINAL(0, WinHttpAddRequestHeaders); +ADD_ORIGINAL(1, WinHttpAutoProxySvcMain); +ADD_ORIGINAL(2, WinHttpCheckPlatform); +ADD_ORIGINAL(3, WinHttpCloseHandle); +ADD_ORIGINAL(4, WinHttpConnect); +ADD_ORIGINAL(5, WinHttpConnectionDeleteProxyInfo); +ADD_ORIGINAL(6, WinHttpConnectionFreeNameList); +ADD_ORIGINAL(7, WinHttpConnectionFreeProxyInfo); +ADD_ORIGINAL(8, WinHttpConnectionFreeProxyList); +ADD_ORIGINAL(9, WinHttpConnectionGetNameList); +ADD_ORIGINAL(10, WinHttpConnectionGetProxyInfo); +ADD_ORIGINAL(11, WinHttpConnectionGetProxyList); +ADD_ORIGINAL(12, WinHttpConnectionSetProxyInfo); +ADD_ORIGINAL(13, WinHttpCrackUrl); +ADD_ORIGINAL(14, WinHttpCreateProxyResolver); +ADD_ORIGINAL(15, WinHttpCreateUrl); +ADD_ORIGINAL(16, WinHttpDetectAutoProxyConfigUrl); +ADD_ORIGINAL(17, WinHttpFreeProxyResult); +ADD_ORIGINAL(18, WinHttpGetDefaultProxyConfiguration); +ADD_ORIGINAL(19, WinHttpGetIEProxyConfigForCurrentUser); +ADD_ORIGINAL(20, WinHttpGetProxyForUrl); +ADD_ORIGINAL(21, WinHttpGetProxyForUrlEx); +ADD_ORIGINAL(22, WinHttpGetProxyResult); +ADD_ORIGINAL(23, WinHttpGetTunnelSocket); +ADD_ORIGINAL(24, WinHttpOpen); +ADD_ORIGINAL(25, WinHttpOpenRequest); +ADD_ORIGINAL(26, WinHttpProbeConnectivity); +ADD_ORIGINAL(27, WinHttpQueryAuthSchemes); +ADD_ORIGINAL(28, WinHttpQueryDataAvailable); +ADD_ORIGINAL(29, WinHttpQueryHeaders); +ADD_ORIGINAL(30, WinHttpQueryOption); +ADD_ORIGINAL(31, WinHttpReadData); +ADD_ORIGINAL(32, WinHttpReceiveResponse); +ADD_ORIGINAL(33, WinHttpResetAutoProxy); +ADD_ORIGINAL(34, WinHttpSaveProxyCredentials); +ADD_ORIGINAL(35, WinHttpSendRequest); +ADD_ORIGINAL(36, WinHttpSetCredentials); +ADD_ORIGINAL(37, WinHttpSetDefaultProxyConfiguration); +ADD_ORIGINAL(38, WinHttpSetOption); +ADD_ORIGINAL(39, WinHttpSetStatusCallback); +ADD_ORIGINAL(40, WinHttpSetTimeouts); +ADD_ORIGINAL(41, WinHttpTimeFromSystemTime); +ADD_ORIGINAL(42, WinHttpTimeToSystemTime); +ADD_ORIGINAL(43, WinHttpWebSocketClose); +ADD_ORIGINAL(44, WinHttpWebSocketCompleteUpgrade); +ADD_ORIGINAL(45, WinHttpWebSocketQueryCloseStatus); +ADD_ORIGINAL(46, WinHttpWebSocketReceive); +ADD_ORIGINAL(47, WinHttpWebSocketSend); +ADD_ORIGINAL(48, WinHttpWebSocketShutdown); +ADD_ORIGINAL(49, WinHttpWriteData); + +} + +PROXY(0, WinHttpAddRequestHeaders); +PROXY(1, WinHttpAutoProxySvcMain); +PROXY(2, WinHttpCheckPlatform); +PROXY(3, WinHttpCloseHandle); +PROXY(4, WinHttpConnect); +PROXY(5, WinHttpConnectionDeleteProxyInfo); +PROXY(6, WinHttpConnectionFreeNameList); +PROXY(7, WinHttpConnectionFreeProxyInfo); +PROXY(8, WinHttpConnectionFreeProxyList); +PROXY(9, WinHttpConnectionGetNameList); +PROXY(10, WinHttpConnectionGetProxyInfo); +PROXY(11, WinHttpConnectionGetProxyList); +PROXY(12, WinHttpConnectionSetProxyInfo); +PROXY(13, WinHttpCrackUrl); +PROXY(14, WinHttpCreateProxyResolver); +PROXY(15, WinHttpCreateUrl); +PROXY(16, WinHttpDetectAutoProxyConfigUrl); +PROXY(17, WinHttpFreeProxyResult); +PROXY(18, WinHttpGetDefaultProxyConfiguration); +PROXY(19, WinHttpGetIEProxyConfigForCurrentUser); +PROXY(20, WinHttpGetProxyForUrl); +PROXY(21, WinHttpGetProxyForUrlEx); +PROXY(22, WinHttpGetProxyResult); +PROXY(23, WinHttpGetTunnelSocket); +PROXY(24, WinHttpOpen); +PROXY(25, WinHttpOpenRequest); +PROXY(26, WinHttpProbeConnectivity); +PROXY(27, WinHttpQueryAuthSchemes); +PROXY(28, WinHttpQueryDataAvailable); +PROXY(29, WinHttpQueryHeaders); +PROXY(30, WinHttpQueryOption); +PROXY(31, WinHttpReadData); +PROXY(32, WinHttpReceiveResponse); +PROXY(33, WinHttpResetAutoProxy); +PROXY(34, WinHttpSaveProxyCredentials); +PROXY(35, WinHttpSendRequest); +PROXY(36, WinHttpSetCredentials); +PROXY(37, WinHttpSetDefaultProxyConfiguration); +PROXY(38, WinHttpSetOption); +PROXY(39, WinHttpSetStatusCallback); +PROXY(40, WinHttpSetTimeouts); +PROXY(41, WinHttpTimeFromSystemTime); +PROXY(42, WinHttpTimeToSystemTime); +PROXY(43, WinHttpWebSocketClose); +PROXY(44, WinHttpWebSocketCompleteUpgrade); +PROXY(45, WinHttpWebSocketQueryCloseStatus); +PROXY(46, WinHttpWebSocketReceive); +PROXY(47, WinHttpWebSocketSend); +PROXY(48, WinHttpWebSocketShutdown); +PROXY(49, WinHttpWriteData); diff --git a/Doorstop/Proxy/proxy.def b/Doorstop/Proxy/proxy.def new file mode 100644 index 00000000..2b5dcf3a --- /dev/null +++ b/Doorstop/Proxy/proxy.def @@ -0,0 +1,54 @@ +LIBRARY proxy.dll +EXPORTS + WinHttpAddRequestHeaders @1 + WinHttpAutoProxySvcMain @2 + WinHttpCheckPlatform @3 + WinHttpCloseHandle @4 + WinHttpConnect @5 + WinHttpConnectionDeleteProxyInfo @6 + WinHttpConnectionFreeNameList @7 + WinHttpConnectionFreeProxyInfo @8 + WinHttpConnectionFreeProxyList @9 + WinHttpConnectionGetNameList @10 + WinHttpConnectionGetProxyInfo @11 + WinHttpConnectionGetProxyList @12 + WinHttpConnectionSetProxyInfo @13 + WinHttpCrackUrl @14 + WinHttpCreateProxyResolver @15 + WinHttpCreateUrl @16 + WinHttpDetectAutoProxyConfigUrl @17 + WinHttpFreeProxyResult @18 + WinHttpGetDefaultProxyConfiguration @19 + WinHttpGetIEProxyConfigForCurrentUser @20 + WinHttpGetProxyForUrl @21 + WinHttpGetProxyForUrlEx @22 + WinHttpGetProxyResult @23 + WinHttpGetTunnelSocket @24 + WinHttpOpen @25 + WinHttpOpenRequest @26 + WinHttpProbeConnectivity @27 + WinHttpQueryAuthSchemes @28 + WinHttpQueryDataAvailable @29 + WinHttpQueryHeaders @30 + WinHttpQueryOption @31 + WinHttpReadData @32 + WinHttpReceiveResponse @33 + WinHttpResetAutoProxy @34 + WinHttpSaveProxyCredentials @35 + WinHttpSendRequest @36 + WinHttpSetCredentials @37 + WinHttpSetDefaultProxyConfiguration @38 + WinHttpSetOption @39 + WinHttpSetStatusCallback @40 + WinHttpSetTimeouts @41 + WinHttpTimeFromSystemTime @42 + WinHttpTimeToSystemTime @43 + WinHttpWebSocketClose @44 + WinHttpWebSocketCompleteUpgrade @45 + WinHttpWebSocketQueryCloseStatus @46 + WinHttpWebSocketReceive @47 + WinHttpWebSocketSend @48 + WinHttpWebSocketShutdown @49 + WinHttpWriteData @50 + SetGetMessageHook @51 + SetPeekMessageHook @52 diff --git a/Doorstop/Proxy/proxy.h b/Doorstop/Proxy/proxy.h new file mode 100644 index 00000000..06542e7a --- /dev/null +++ b/Doorstop/Proxy/proxy.h @@ -0,0 +1,74 @@ +/* + * Proxy.h -- Definitions for proxy-related functionality + * + * The proxy works roughly as follows: + * - We define our exports in proxy.c (computer generated) + * - loadProxy initializes the proxy: + * 1. Look up the name of this DLL + * 2. Find the original DLL with the same name + * 3. Load the original DLL + * 4. Load all functions into originalFunctions array + * + * For more information, refer to proxy.c + */ + +#pragma once + +#pragma warning( disable : 4267 6387 6386 ) + +#include +#include +#include "assert_util.h" +#include + +#define ALT_POSTFIX L"_alt.dll" +#define DLL_POSTFIX L".dll" + +extern FARPROC originalFunctions[]; +extern void loadFunctions(HMODULE dll); + +// Load the proxy functions into memory +inline void loadProxy(wchar_t *moduleName) +{ + size_t module_name_len = wcslen(moduleName); + + size_t alt_name_len = module_name_len + STR_LEN(ALT_POSTFIX); + wchar_t *alt_name = memalloc(sizeof(wchar_t) * alt_name_len); + wmemcpy(alt_name, moduleName, module_name_len); + wmemcpy(alt_name + module_name_len, ALT_POSTFIX, STR_LEN(ALT_POSTFIX)); + + wchar_t *dll_path = NULL; // The final DLL path + + const int alt_full_path_len = GetFullPathNameW(alt_name, 0, NULL, NULL); + wchar_t *alt_full_path = memalloc(sizeof(wchar_t) * alt_full_path_len); + GetFullPathNameW(alt_name, alt_full_path_len, alt_full_path, NULL); + memfree(alt_name); + + LOG("Looking for original DLL from %S\n", alt_full_path); + + // Try to look for the alternative first in the same directory. + HMODULE handle = LoadLibrary(alt_full_path); + + if (handle == NULL) + { + size_t system_dir_len = GetSystemDirectoryW(NULL, 0); + dll_path = memalloc(sizeof(wchar_t) * (system_dir_len + module_name_len + STR_LEN(DLL_POSTFIX))); + _ASSERTE(dll_path != nullptr); + GetSystemDirectoryW(dll_path, system_dir_len); + dll_path[system_dir_len - 1] = L'\\'; + wmemcpy(dll_path + system_dir_len, moduleName, module_name_len); + wmemcpy(dll_path + system_dir_len + module_name_len, DLL_POSTFIX, STR_LEN(DLL_POSTFIX)); + + LOG("Looking for original DLL from %S\n", dll_path); + + handle = LoadLibraryW(dll_path); + } + + ASSERT_F(handle != NULL, L"Unable to load the original %s.dll (looked from system directory and from %s_alt.dll)!", + moduleName, moduleName); + + memfree(alt_full_path); + memfree(dll_path); + + loadFunctions(handle); +} diff --git a/Doorstop/Proxy/proxy.rc b/Doorstop/Proxy/proxy.rc new file mode 100644 index 00000000..a6245589 Binary files /dev/null and b/Doorstop/Proxy/proxy.rc differ diff --git a/Doorstop/Proxy/proxydefs.txt b/Doorstop/Proxy/proxydefs.txt new file mode 100644 index 00000000..c8423005 --- /dev/null +++ b/Doorstop/Proxy/proxydefs.txt @@ -0,0 +1,63 @@ +SvchostPushServiceGlobals +WinHttpAddRequestHeaders +WinHttpAutoProxySvcMain +WinHttpCheckPlatform +WinHttpCloseHandle +WinHttpConnect +WinHttpConnectionDeletePolicyEntries +WinHttpConnectionDeleteProxyInfo +WinHttpConnectionFreeNameList +WinHttpConnectionFreeProxyInfo +WinHttpConnectionFreeProxyList +WinHttpConnectionGetNameList +WinHttpConnectionGetProxyInfo +WinHttpConnectionGetProxyList +WinHttpConnectionSetPolicyEntries +WinHttpConnectionSetProxyInfo +WinHttpConnectionUpdateIfIndexTable +WinHttpCrackUrl +WinHttpCreateProxyResolver +WinHttpCreateUrl +WinHttpDetectAutoProxyConfigUrl +WinHttpFreeProxyResult +WinHttpFreeProxyResultEx +WinHttpFreeProxySettings +WinHttpGetDefaultProxyConfiguration +WinHttpGetIEProxyConfigForCurrentUser +WinHttpGetProxyForUrl +WinHttpGetProxyForUrlEx +WinHttpGetProxyForUrlEx2 +WinHttpGetProxyForUrlHvsi +WinHttpGetProxyResult +WinHttpGetProxyResultEx +WinHttpGetProxySettingsVersion +WinHttpGetTunnelSocket +WinHttpOpen +WinHttpOpenRequest +WinHttpProbeConnectivity +WinHttpQueryAuthSchemes +WinHttpQueryDataAvailable +WinHttpQueryHeaders +WinHttpQueryOption +WinHttpReadData +WinHttpReadProxySettings +WinHttpReadProxySettingsHvsi +WinHttpReceiveResponse +WinHttpResetAutoProxy +WinHttpSaveProxyCredentials +WinHttpSendRequest +WinHttpSetCredentials +WinHttpSetDefaultProxyConfiguration +WinHttpSetOption +WinHttpSetStatusCallback +WinHttpSetTimeouts +WinHttpTimeFromSystemTime +WinHttpTimeToSystemTime +WinHttpWebSocketClose +WinHttpWebSocketCompleteUpgrade +WinHttpWebSocketQueryCloseStatus +WinHttpWebSocketReceive +WinHttpWebSocketSend +WinHttpWebSocketShutdown +WinHttpWriteData +WinHttpWriteProxySettings \ No newline at end of file diff --git a/Doorstop/Proxy/winapi_util.h b/Doorstop/Proxy/winapi_util.h new file mode 100644 index 00000000..599342d5 --- /dev/null +++ b/Doorstop/Proxy/winapi_util.h @@ -0,0 +1,65 @@ +#pragma once + +#pragma warning( disable : 4267 6387 ) + +#include +#include "crt.h" + +inline size_t get_module_path(HMODULE module, wchar_t **result, size_t *size, size_t free_space) +{ + size_t i = 0; + size_t len, s; + *result = NULL; + do + { + if (*result != NULL) + memfree(*result); + i++; + s = i * MAX_PATH + 1; + *result = memalloc(sizeof(wchar_t) * s); + len = GetModuleFileNameW(module, *result, s); + } + while (GetLastError() == ERROR_INSUFFICIENT_BUFFER && s - len >= free_space); + + if (size != NULL) + *size = s; + return len; +} + +inline wchar_t *get_ini_entry(const wchar_t *config_file, const wchar_t *section, const wchar_t *key, + const wchar_t *default_val) +{ + size_t i = 0; + size_t size, read; + wchar_t *result = NULL; + do + { + if (result != NULL) + memfree(result); + i++; + size = i * MAX_PATH + 1; + result = memalloc(sizeof(wchar_t) * size); + read = GetPrivateProfileStringW(section, key, default_val, result, size, config_file); + } + while (read == size - 1); + return result; +} + +inline wchar_t *get_file_name_no_ext(wchar_t *str, size_t len) +{ + size_t ext_index = len; + size_t i; + for (i = len; i > 0; i--) + { + wchar_t c = *(str + i); + if (c == L'.' && ext_index == len) + ext_index = i; + else if (c == L'\\') + break; + } + + size_t result_len = ext_index - i; + wchar_t *result = memcalloc(sizeof(wchar_t) * result_len); + wmemcpy(result, str + i + 1, result_len - 1); + return result; +} diff --git a/Doorstop/README.md b/Doorstop/README.md new file mode 100644 index 00000000..f741936c --- /dev/null +++ b/Doorstop/README.md @@ -0,0 +1,47 @@ +

+ +

+ +![Github All Releases](https://img.shields.io/github/downloads/NeighTools/UnityDoorstop/total.svg) +![GitHub release](https://img.shields.io/github/release/NeighTools/UnityDoorstop.svg) +![license](https://img.shields.io/github/license/NeighTools/UnityDoorstop.svg) + +*Run managed code before Unity does!* + +# Unity Doorstop + +Doorstop is a tool to execute managed assemblies inside Unity as early as possible! + +This repository is indented mainly for developers! +Developers should package Doorstop into their applications for the end-users. + +## Features + +* **Runs first**: Doorstop runs its own code before Unity has an ability to do so. +* **Configurable**: A very basic configuration file allows to specify your own assembly to execute! +* **Public domain!** You are fully free to embed Doorstop into your application! + +## Guides for users and developers on [the wiki](https://github.com/NeighTools/UnityDoorstop/wiki) + +## Building + +To build, you will need: + +* Visual Studio 2017 (2015 might work as well with some modifications) +* Visual C++ Toolset v140 +* .NET Framework 3.5 or newer (only for the example, not used by the proxy) +* Python (only to generate proxy functions; not needed to build) + +Clone, open in Visual Studio, select the platform (x86/x64) and build. + +#### Custom proxy functions + +Doorstop's proxy is flexible and allows to be load as different DLLs. +You can modify which functions you want to proxy by adding/removing function names in `Proxy/proxydefs.txt` and running `proxygen/proxy_gen.py ../Proxy/proxydefs.txt` to generate an appropriate proxy functions. + +The current set up allows to use the proxy for the following DLLs: + +* `winhttp.dll` (All exports) +* `iphlpapi.dll` (Only `GetIpAddrTable`) + +(WIP: Currently all build results are placed in separate folders; will be changed later) diff --git a/Doorstop/UnityDoorstop.sln b/Doorstop/UnityDoorstop.sln new file mode 100644 index 00000000..0ba1ff64 --- /dev/null +++ b/Doorstop/UnityDoorstop.sln @@ -0,0 +1,77 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2043 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "proxy", "Proxy\Proxy.vcxproj", "{88609E16-731F-46C9-8139-6B1A7A83240D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoorstopTest", "DoorstopTest\DoorstopTest.csproj", "{62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Verbose_Release|Any CPU = Verbose_Release|Any CPU + Verbose_Release|x64 = Verbose_Release|x64 + Verbose_Release|x86 = Verbose_Release|x86 + Verbose|Any CPU = Verbose|Any CPU + Verbose|x64 = Verbose|x64 + Verbose|x86 = Verbose|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.ActiveCfg = Debug|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.Build.0 = Debug|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.ActiveCfg = Debug|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.Build.0 = Debug|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|Any CPU.ActiveCfg = Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.ActiveCfg = Release|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.Build.0 = Release|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.ActiveCfg = Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.Build.0 = Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.ActiveCfg = Verbose_Release|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.Build.0 = Verbose_Release|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.Build.0 = Verbose_Release|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|Any CPU.ActiveCfg = Verbose|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.ActiveCfg = Verbose|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.Build.0 = Verbose|x64 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.ActiveCfg = Verbose|Win32 + {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.Build.0 = Verbose|Win32 + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x64.ActiveCfg = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x64.Build.0 = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x86.ActiveCfg = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Debug|x86.Build.0 = Debug|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|Any CPU.Build.0 = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x64.ActiveCfg = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x64.Build.0 = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x86.ActiveCfg = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Release|x86.Build.0 = Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|Any CPU.Build.0 = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x64.ActiveCfg = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x64.Build.0 = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose_Release|x86.Build.0 = Verbose_Release|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|Any CPU.Build.0 = Verbose|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x64.ActiveCfg = Verbose|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x64.Build.0 = Verbose|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x86.ActiveCfg = Verbose|Any CPU + {62B61CCC-0775-4CF4-B37A-EB5E33CBD08C}.Verbose|x86.Build.0 = Verbose|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4EF47C82-18DC-4E9E-A878-8C003FFB303D} + EndGlobalSection +EndGlobal diff --git a/Doorstop/docs/logo.png b/Doorstop/docs/logo.png new file mode 100644 index 00000000..00b5565f Binary files /dev/null and b/Doorstop/docs/logo.png differ diff --git a/Doorstop/docs/logo_sm.png b/Doorstop/docs/logo_sm.png new file mode 100644 index 00000000..7774e6c1 Binary files /dev/null and b/Doorstop/docs/logo_sm.png differ diff --git a/Doorstop/proxygen/proxy_gen.py b/Doorstop/proxygen/proxy_gen.py new file mode 100644 index 00000000..92e4f218 --- /dev/null +++ b/Doorstop/proxygen/proxy_gen.py @@ -0,0 +1,52 @@ +import sys +import os +#import pefile +import io +import string + +def main(): + path = os.path.dirname(os.path.realpath(sys.argv[0])) + file = sys.argv[1] + + proxies = io.StringIO() + proxy_add = io.StringIO() + proxy_def = io.StringIO() + proxy_def_file = io.StringIO() + + count = 0 + + """ for dll in dlls: + pe = pefile.PE(dll, fast_load=True) + pe.parse_data_directories() + for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: + name = exp.name.decode() + + proxies.write("%s\n" % name) + proxy_add.write("ADD_ORIGINAL(%d, %s);\n" % (count, name)) + proxy_def.write("PROXY(%d, %s);\n" % (count, name)) + count = count + 1 """ + + with open(file, "r") as includes: + names = includes.readlines() + for name in names: + name = name.strip() + proxies.write(f"{name}\n") + proxy_add.write(f"ADD_ORIGINAL({count}, {name});\n") + proxy_def.write(f"PROXY({count}, {name});\n") + proxy_def_file.write(f" {name} @{count+1}\n") + count = count + 1 + + with open(f"{path}\\templates\\proxy_template.c", "r") as proxy_file: + new_proxy = string.Template(proxy_file.read()).safe_substitute(proxy_count=count, proxy_add=proxy_add.getvalue(), proxy_def=proxy_def.getvalue()) + + with open(f"{path}\\..\\Proxy\\proxy.c", "w") as proxy_c_file: + proxy_c_file.write(new_proxy) + + with open(f"{path}\\templates\\proxy_template.def", "r") as proxy_file: + new_proxy_def = string.Template(proxy_file.read()).safe_substitute(proxy_exports=proxy_def_file.getvalue()) + + with open(f"{path}\\..\\Proxy\\proxy.def", "w") as proxy_def_file: + proxy_def_file.write(new_proxy_def) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Doorstop/proxygen/templates/proxy_template.c b/Doorstop/proxygen/templates/proxy_template.c new file mode 100644 index 00000000..84788fed --- /dev/null +++ b/Doorstop/proxygen/templates/proxy_template.c @@ -0,0 +1,34 @@ +/* ================================== + * COMPUTER GENERATED -- DO NOT EDIT + * ================================== + * + * This file contains the definitions for all proxy functions this DLL supports. + * + * The proxies are very simple functions that should be optimizied into a + * single JMP instruction without editing the stack at all. + * + * NOTE: While this works, this is a somewhat hackish approach that is based on how + * the compiler optimizes the code. That said, the proxy will not work on Debug build currently + * (that can be fixed by changing the appropriate compile flag that I am yet to locate). + */ + +#pragma warning( disable : 4244 ) + +#include + +#define ADD_ORIGINAL(i, name) originalFunctions[i] = GetProcAddress(dll, #name) + +#define PROXY(i, name) \ + __declspec(dllexport) ULONG __stdcall name() \ + { \ + return originalFunctions[i](); \ + } + +FARPROC originalFunctions[${proxy_count}] = {0}; + +void loadFunctions(HMODULE dll) +{ +${proxy_add} +} + +${proxy_def} \ No newline at end of file diff --git a/Doorstop/proxygen/templates/proxy_template.def b/Doorstop/proxygen/templates/proxy_template.def new file mode 100644 index 00000000..564c35d5 --- /dev/null +++ b/Doorstop/proxygen/templates/proxy_template.def @@ -0,0 +1,3 @@ +LIBRARY proxy.dll +EXPORTS +${proxy_exports} \ No newline at end of file diff --git a/Doorstop/proxygen/winhttp.txt b/Doorstop/proxygen/winhttp.txt new file mode 100644 index 00000000..7ac96698 --- /dev/null +++ b/Doorstop/proxygen/winhttp.txt @@ -0,0 +1,50 @@ +WinHttpAddRequestHeaders +WinHttpAutoProxySvcMain +WinHttpCheckPlatform +WinHttpCloseHandle +WinHttpConnect +WinHttpConnectionDeleteProxyInfo +WinHttpConnectionFreeNameList +WinHttpConnectionFreeProxyInfo +WinHttpConnectionFreeProxyList +WinHttpConnectionGetNameList +WinHttpConnectionGetProxyInfo +WinHttpConnectionGetProxyList +WinHttpConnectionSetProxyInfo +WinHttpCrackUrl +WinHttpCreateProxyResolver +WinHttpCreateUrl +WinHttpDetectAutoProxyConfigUrl +WinHttpFreeProxyResult +WinHttpGetDefaultProxyConfiguration +WinHttpGetIEProxyConfigForCurrentUser +WinHttpGetProxyForUrl +WinHttpGetProxyForUrlEx +WinHttpGetProxyResult +WinHttpGetTunnelSocket +WinHttpOpen +WinHttpOpenRequest +WinHttpProbeConnectivity +WinHttpQueryAuthSchemes +WinHttpQueryDataAvailable +WinHttpQueryHeaders +WinHttpQueryOption +WinHttpReadData +WinHttpReceiveResponse +WinHttpResetAutoProxy +WinHttpSaveProxyCredentials +WinHttpSendRequest +WinHttpSetCredentials +WinHttpSetDefaultProxyConfiguration +WinHttpSetOption +WinHttpSetStatusCallback +WinHttpSetTimeouts +WinHttpTimeFromSystemTime +WinHttpTimeToSystemTime +WinHttpWebSocketClose +WinHttpWebSocketCompleteUpgrade +WinHttpWebSocketQueryCloseStatus +WinHttpWebSocketReceive +WinHttpWebSocketSend +WinHttpWebSocketShutdown +WinHttpWriteData \ No newline at end of file