diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index b5dbdb69..8cf5badd 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - targetplatform: [x64] + targetplatform: [x86] runs-on: windows-latest @@ -97,11 +97,6 @@ jobs: env: RuntimeIdentifier: win-${{ matrix.targetplatform }} - - name: Create publish injector files - run: dotnet publish .\Daybreak.Injector\Daybreak.Injector.csproj -c $env:Configuration -r $env:RuntimeIdentifier --property:SolutionDir=$env:GITHUB_WORKSPACE -p:PublishReadyToRun=true -p:PublishSingleFile=false --self-contained true -o .\Publish - env: - RuntimeIdentifier: win-x86 - - name: Pack publish files run: | Write-Host $env diff --git a/Daybreak.Injector/Daybreak.Injector.csproj b/Daybreak.Injector/Daybreak.Injector.csproj deleted file mode 100644 index 96c2a433..00000000 --- a/Daybreak.Injector/Daybreak.Injector.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - net8.0 - enable - AnyCPU;x86;x64 - enable - true - true - - - diff --git a/Daybreak.Injector/NativeMethods.cs b/Daybreak.Injector/NativeMethods.cs deleted file mode 100644 index 8faf6d46..00000000 --- a/Daybreak.Injector/NativeMethods.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Daybreak.Injector; - -internal static class NativeMethods -{ - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr GetModuleHandle(string lpModuleName); - [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] - public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr VirtualAllocEx( - IntPtr hProcess, - IntPtr lpAddress, - IntPtr dwSize, - uint dwAllocationType, - uint dwProtect); - - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualFreeEx( - IntPtr hProcess, - IntPtr lpAddress, - uint dwSize, - uint dwFreeType); - [DllImport("kernel32.dll")] - public static extern IntPtr CreateRemoteThread( - IntPtr hProcess, - IntPtr lpThreadAttributes, - uint dwStackSize, - IntPtr lpStartAddress, - IntPtr lpParameter, - uint dwCreationFlags, - out IntPtr lpThreadId); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern uint GetExitCodeThread(IntPtr hHandle, out IntPtr dwMilliseconds); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool WriteProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - IntPtr lpBuffer, - int nSize, - out IntPtr lpNumberOfBytesWritten); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - IntPtr lpBuffer, - int nSize, - out IntPtr lpNumberOfBytesRead); -} diff --git a/Daybreak.Injector/Program.cs b/Daybreak.Injector/Program.cs deleted file mode 100644 index f49b1922..00000000 --- a/Daybreak.Injector/Program.cs +++ /dev/null @@ -1,191 +0,0 @@ -// See https://aka.ms/new-console-template for more information -using Daybreak.Injector; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; - -static bool InjectWithWinApi(Process process, string pathToDll) -{ - string modulefullpath = Path.GetFullPath(pathToDll); - - if (!File.Exists(modulefullpath)) - { - Console.WriteLine("Dll to inject not found"); - return false; - } - - nint hKernel32 = NativeMethods.GetModuleHandle("kernel32.dll"); - if (hKernel32 == IntPtr.Zero) - { - Console.WriteLine("Unable to get a handle of kernel32.dll"); - return false; - } - - nint hLoadLib = NativeMethods.GetProcAddress(hKernel32, "LoadLibraryW"); - if (hLoadLib == IntPtr.Zero) - { - Console.WriteLine("Unable to get the address of LoadLibraryW"); - return false; - } - - nint hStringBuffer = NativeMethods.VirtualAllocEx(process.Handle, IntPtr.Zero, new IntPtr(2 * (modulefullpath.Length + 1)), - 0x3000 /* MEM_COMMIT | MEM_RESERVE */, 0x4 /* PAGE_READWRITE */); - if (hStringBuffer == IntPtr.Zero) - { - Console.WriteLine("Unable to allocate memory for module path"); - return false; - } - - WriteWString(process, hStringBuffer, modulefullpath); - if (ReadWString(process, hStringBuffer, 260) != modulefullpath) - { - Console.WriteLine("Module path string is not correct"); - return false; - } - - nint hThread = NativeMethods.CreateRemoteThread(process.Handle, IntPtr.Zero, 0, hLoadLib, hStringBuffer, 0, out _); - if (hThread == IntPtr.Zero) - { - Console.WriteLine("Unable to create remote thread"); - return false; - } - - uint threadResult = NativeMethods.WaitForSingleObject(hThread, 30000u); - if (threadResult is 0x102 or 0xFFFFFFFF /* WAIT_FAILED */) - { - Console.WriteLine($"Exception occurred while waiting for the remote thread. Result is {threadResult}"); - return false; - } - - uint dllResult = NativeMethods.GetExitCodeThread(hThread, out _); - if (dllResult == 0) - { - Console.WriteLine($"Injected dll returned non-success status code {dllResult}"); - return false; - } - - bool memoryFreeResult = NativeMethods.VirtualFreeEx(process.Handle, hStringBuffer, 0, 0x8000 /* MEM_RELEASE */); - if (!memoryFreeResult) - { - Console.WriteLine($"Failed to free dll memory"); - } - - return memoryFreeResult; -} - -static void WriteBytes(Process process, IntPtr address, byte[] data) -{ - int size = data.Length; - nint buffer = Marshal.AllocHGlobal(size); - Marshal.Copy(data, 0, buffer, size); - - _ = NativeMethods.WriteProcessMemory( - process.Handle, - address, - buffer, - size, - out _); - - Marshal.FreeHGlobal(buffer); -} - -static void WriteWString(Process process, IntPtr address, string data) -{ - WriteBytes(process, address, Encoding.Unicode.GetBytes(data)); -} - -static string ReadWString(Process process, IntPtr address, int maxsize, Encoding? encoding = null) -{ - encoding ??= Encoding.Unicode; - byte[] rawbytes = ReadBytes(process, address, maxsize); - if (rawbytes.Length == 0) - { - return ""; - } - - string ret = encoding.GetString(rawbytes); - if (ret.Contains('\0')) - { - ret = ret[..ret.IndexOf('\0')]; - } - - return ret; -} - -static byte[] ReadBytes(Process process, IntPtr address, int size) -{ - nint buffer = Marshal.AllocHGlobal(size); - - _ = NativeMethods.ReadProcessMemory(process.Handle, - address, - buffer, - size, - out _ - ); - - byte[] ret = new byte[size]; - Marshal.Copy(buffer, ret, 0, size); - Marshal.FreeHGlobal(buffer); - - return ret; -} - -Console.WriteLine("Hello, World!"); -int processId = -1; -string filePath = string.Empty; -for (int i = 0; i < args.Length; i++) -{ - if (args[i] is "-p" or "--processId") - { - i++; - if (!int.TryParse(args[i], out int id)) - { - Console.WriteLine($"Unable to parce process id {args[i]}"); - return 1; - } - - processId = id; - } - else if (args[i] is "-f" or "--filePath") - { - i++; - filePath = args[i]; - } - else - { - Console.WriteLine($"Unknown argument {args[i]} {args[(i + 1) % args.Length]}"); - return 1; - } -} - -if (processId is -1) -{ - Console.WriteLine("Must specify target process with -p [id]"); - return 1; -} - -if (string.IsNullOrWhiteSpace(filePath)) -{ - Console.WriteLine("Must specify dll to inject with -f [filePath]"); - return 1; -} - -Process process = default!; -try -{ - process = Process.GetProcessById(processId); -} -catch -{ - Console.WriteLine($"Failed to get process with id {processId}"); - return 1; -} - -if (!InjectWithWinApi(process, filePath)) -{ - Console.WriteLine("Injection failed"); - return 1; -} - -Console.WriteLine("Injection success"); -return 0; diff --git a/Daybreak.sln b/Daybreak.sln index 110b9e08..b07aaebc 100644 --- a/Daybreak.sln +++ b/Daybreak.sln @@ -34,8 +34,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.7ZipExtractor", "Daybreak.7ZipExtractor\Daybreak.7ZipExtractor.csproj", "{CA60F9AC-A496-4DB6-970E-ECE73B051C05}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Injector", "Daybreak.Injector\Daybreak.Injector.csproj", "{61CFB42E-947A-48AE-9F3C-4DF685882B1D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,8 +70,8 @@ Global {C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x86.Build.0 = Release|x86 {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x86 - {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x86 + {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x64 + {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x64 {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.ActiveCfg = Debug|x86 {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.Build.0 = Debug|x86 {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -84,28 +82,16 @@ Global {4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|x86.Build.0 = Release|x86 {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|x64 - {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|x64 + {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|Any CPU {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.ActiveCfg = Debug|x64 {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.Build.0 = Debug|x64 {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|Any CPU.ActiveCfg = Release|Any CPU {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|Any CPU.Build.0 = Release|Any CPU - {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x64.ActiveCfg = Release|x64 - {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x64.Build.0 = Release|x64 + {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x64.ActiveCfg = Release|Any CPU + {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x64.Build.0 = Release|Any CPU {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x86.ActiveCfg = Release|x64 {CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|x86.Build.0 = Release|x64 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|x64.ActiveCfg = Debug|x86 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|x64.Build.0 = Debug|x86 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|x86.ActiveCfg = Debug|x86 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Debug|x86.Build.0 = Debug|x86 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|Any CPU.Build.0 = Release|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|x64.ActiveCfg = Release|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|x64.Build.0 = Release|Any CPU - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|x86.ActiveCfg = Release|x86 - {61CFB42E-947A-48AE-9F3C-4DF685882B1D}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Daybreak/Daybreak.csproj b/Daybreak/Daybreak.csproj index 767f9788..86e9ab7b 100644 --- a/Daybreak/Daybreak.csproj +++ b/Daybreak/Daybreak.csproj @@ -12,7 +12,7 @@ preview Daybreak.ico true - 0.9.8.158 + 0.9.8.159 true cfb2a489-db80-448d-a969-80270f314c46 True diff --git a/Daybreak/Services/Injection/ProcessInjector.cs b/Daybreak/Services/Injection/ProcessInjector.cs index d269eac0..9bc6781a 100644 --- a/Daybreak/Services/Injection/ProcessInjector.cs +++ b/Daybreak/Services/Injection/ProcessInjector.cs @@ -1,8 +1,12 @@ -using Microsoft.Extensions.Logging; +using Daybreak.Utils; +using Microsoft.Extensions.Logging; +using System; using System.Core.Extensions; using System.Diagnostics; using System.Extensions; using System.IO; +using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,51 +21,138 @@ internal sealed class ProcessInjector : IProcessInjector this.logger = logger.ThrowIfNull(); } - public async Task Inject(Process process, string pathToDll, CancellationToken cancellationToken) + public Task Inject(Process process, string pathToDll, CancellationToken cancellationToken) { - return await this.InjectWithWinApi(process, pathToDll, cancellationToken); + return Task.Factory.StartNew(() => + { + return this.InjectWithWinApi(process, pathToDll); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } - private async Task InjectWithWinApi(Process process, string pathToDll, CancellationToken cancellationToken) + private bool InjectWithWinApi(Process process, string pathToDll) { var scopedLogger = this.logger.CreateScopedLogger(nameof(InjectWithWinApi), pathToDll); - if (!File.Exists(pathToDll)) + var modulefullpath = Path.GetFullPath(pathToDll); + + if (!File.Exists(modulefullpath)) { - scopedLogger.LogError("File does not exist"); + scopedLogger.LogError("Dll to inject not found"); return false; } - if (process is null) + var hKernel32 = NativeMethods.GetModuleHandle("kernel32.dll"); + if (hKernel32 == IntPtr.Zero) { - scopedLogger.LogError("Provided process is null"); + scopedLogger.LogError("Unable to get a handle of kernel32.dll"); return false; } - var injectionProcess = new Process() + var hLoadLib = NativeMethods.GetProcAddress(hKernel32, "LoadLibraryW"); + if (hLoadLib == IntPtr.Zero) { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardInput = true, - RedirectStandardOutput = true, - FileName = Path.GetFullPath("Daybreak.Injector.exe"), - UseShellExecute = false, - Arguments = $"-p {process.Id} -f {pathToDll}" - } - }; - - injectionProcess.Start(); - await injectionProcess.WaitForExitAsync(cancellationToken); - var output = await injectionProcess.StandardOutput.ReadToEndAsync(cancellationToken); - var error = await injectionProcess.StandardError.ReadToEndAsync(cancellationToken); - if (injectionProcess.ExitCode != 0) - { - scopedLogger.LogError($"Failed to inject\nOutput: {output}\nError: {error}"); + scopedLogger.LogError("Unable to get the address of LoadLibraryW"); return false; } - scopedLogger.LogInformation("Injected"); - return true; + var hStringBuffer = NativeMethods.VirtualAllocEx(process.Handle, IntPtr.Zero, new IntPtr(2 * (modulefullpath.Length + 1)), + 0x3000 /* MEM_COMMIT | MEM_RESERVE */, 0x4 /* PAGE_READWRITE */); + if (hStringBuffer == IntPtr.Zero) + { + scopedLogger.LogError("Unable to allocate memory for module path"); + return false; + } + + WriteWString(process, hStringBuffer, modulefullpath); + if (ReadWString(process, hStringBuffer, 260) != modulefullpath) + { + scopedLogger.LogError("Module path string is not correct"); + return false; + } + + var hThread = NativeMethods.CreateRemoteThread(process.Handle, IntPtr.Zero, 0, hLoadLib, hStringBuffer, 0, out _); + if (hThread == IntPtr.Zero) + { + scopedLogger.LogError("Unable to create remote thread"); + return false; + } + + var threadResult = NativeMethods.WaitForSingleObject(hThread, 30000u); + if (threadResult is 0x102 or 0xFFFFFFFF /* WAIT_FAILED */) + { + scopedLogger.LogError($"Exception occurred while waiting for the remote thread. Result is {threadResult}"); + return false; + } + + var dllResult = NativeMethods.GetExitCodeThread(hThread, out _); + if (dllResult == 0) + { + scopedLogger.LogError($"Injected dll returned non-success status code {dllResult}"); + return false; + } + + var memoryFreeResult = NativeMethods.VirtualFreeEx(process.Handle, hStringBuffer, 0, 0x8000 /* MEM_RELEASE */); + if (!memoryFreeResult) + { + scopedLogger.LogError($"Failed to free dll memory"); + } + + return memoryFreeResult; + } + + private static void WriteBytes(Process process, IntPtr address, byte[] data) + { + var size = data.Length; + var buffer = Marshal.AllocHGlobal(size); + Marshal.Copy(data, 0, buffer, size); + + NativeMethods.WriteProcessMemory( + process.Handle, + address, + buffer, + size, + out _); + + Marshal.FreeHGlobal(buffer); + } + + private static void WriteWString(Process process, IntPtr address, string data) + { + WriteBytes(process, address, Encoding.Unicode.GetBytes(data)); + } + + private static string ReadWString(Process process, IntPtr address, int maxsize, Encoding? encoding = null) + { + encoding ??= Encoding.Unicode; + var rawbytes = ReadBytes(process, address, maxsize); + if (rawbytes.Length == 0) + { + return ""; + } + + var ret = encoding.GetString(rawbytes); + if (ret.Contains('\0')) + { + ret = ret[..ret.IndexOf('\0')]; + } + + return ret; + } + + private static byte[] ReadBytes(Process process, IntPtr address, int size) + { + var buffer = Marshal.AllocHGlobal(size); + + NativeMethods.ReadProcessMemory(process.Handle, + address, + buffer, + size, + out _ + ); + + var ret = new byte[size]; + Marshal.Copy(buffer, ret, 0, size); + Marshal.FreeHGlobal(buffer); + + return ret; } }