Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when injecting into CREATE_SUSPENDED process #13

Open
TinkerWorX opened this issue Sep 10, 2022 · 9 comments
Open

Error when injecting into CREATE_SUSPENDED process #13

TinkerWorX opened this issue Sep 10, 2022 · 9 comments

Comments

@TinkerWorX
Copy link

I get the following exception when I create an Injector for a suspended process:

System.ComponentModel.Win32Exception: 'Only part of a ReadProcessMemory or WriteProcessMemory request was completed'

This is the code I use to create the process and inject.

if (CreateProcess(location, new StringBuilder($@"""{location}"" {arguments}"), null, null, false, CREATE_PROCESS.CREATE_SUSPENDED, null, null, new STARTUPINFO(), out var processInformation))
{
    var process = Process.GetProcessById((int)processInformation.dwProcessId);
    var injector = new Injector(process);
}

If I create the process without the CREATE_SUSPENDED flag, it launches without any errors.

For context, I'm coming from EasyHook and was looking for a more updated alternative and found Reloaded.Injector/Reloaded.Hooks.
In EasyHook, injecting into a suspended thread works as expected, where I then resume the thread from inside when I'm ready.

@Sewer56
Copy link
Member

Sewer56 commented Sep 10, 2022

Hi,

This is unfortunately a limitation of the design of this library; it can't be used on a process that has started suspended. It is also the reason why I haven't really been updating this library ever since I released it; as I myself don't actively use it.

I originally wrote this library for use with Reloaded-II around 3 years ago but had to go back to the drawing board because I unfortunately ran across this exact same issue.

The exact specific reason comes down to EnumProcessModulesEx (and its friends). On Windows, you can't enumerate the modules of a process that was started suspended because they haven't been loaded in yet. This in turn means you can't get the address of kernel32.dll in an x86 process from a x64 process; and kernel32 is necessary for LoadLibraryW to in turn inject your DLLs.

What I wound up doing is creating a much, much more basic DLL Injector that spawns an x86 process to pass its address of Kernel32 to the main code using a memory mapped file process, injector, and reuse that in the DLL injection operation. Technically speaking, I could have done the same with this library; albeit the idea of using shellcode and PE parsing used here was pretty cool; and novel; so I kept it around. Unpacking and running a binary would also raise a lot of red flags from AV software.

@TinkerWorX
Copy link
Author

I appreciate the detailed description. I'll take a look at your other solution. I assume I can still use Reloaded.Hooks once injected?

@Sewer56
Copy link
Member

Sewer56 commented Sep 10, 2022

Yeah that'll work perfectly fine.

@Sewer56
Copy link
Member

Sewer56 commented Apr 17, 2023

I was thinking about this a bit when writing the spec for Reloaded3.
I actually found a workaround for this; so I'll probably release version 2.X.X sometime in the future and use it in Reloaded-II, thus resurrecting this library from limbo.

I just gotta get rid of some bloat (namely remove the need for PeNet, and parse the PE header in memory of target process directly. Most of the code for that already exists actually; I just need to move it out to a separate NuGet package.

@TinkerWorX
Copy link
Author

Let me know when you do, then I can try and test it with my project.

@Albeoris
Copy link

Albeoris commented Jun 7, 2024

@Sewer56, hello, how are you? :) Do you have the time and motivation to finish this task?

@Sewer56
Copy link
Member

Sewer56 commented Jun 7, 2024

I started working on 2.X, but decided to abandon it mid way through.
Instead I forked OpenByteDev/dll-syringe (Rust), which is available here.
My fork: Sewer56/dll-syringe.

On the add-c-exports branch (note: Ignore legacy readme), you can build the Rust library with C# bindings.
Those bindings aren't finalized, but they do work, and I made them work with suspended processes.

Reloaded-II had to switch over to using this fork/branch due to Defender suddenly disliking the old DLL Injection code, and me receiving around 200 user reports in the span of 2 days.

That API is here and a prebuilt package is here. But do note, they were made over a weekend in an emergency and are not final.

Main thing that's left to do with that code is some cleanup. I've been stripping dependencies etc. to decrease the DLL size, hope to get to around 1/5th of what the original Rust library was.

I wouldn't get to work on this for some months though. I need to finish the Reloaded3 spec first, and then Reloaded.Hooks-rs.

@drRobertDev
Copy link

drRobertDev commented Nov 26, 2024

I try to use your syringe method, maybe i did something wrong:
TestLegacy() work with reloaded.injector,
TestSuspend() instead failed to find function address

Is possible have minimal example to inject simple dll with hook in not c# process (i using easyhook_vs_reloadedhooks as template with .net 9, as you know not work with suspended process). Thanks a lot for your project.

`using System;
using System.Diagnostics;
using System.Text;

using dll_syringe.Net.Sys;

using Reloaded.Injector;

using static Vanara.PInvoke.Kernel32;

class Program
{
public static string LoaderName => "NetCoreLoaderNE.dll";
public static string InjectionLibName => "NetCoreLoader.dll";
public static string Loader64 => $@"{Environment.CurrentDirectory}\NetCoreLoader64{LoaderName}";

static void Main(string[] args)
{
    //Process notepadProc = null;
    try
    {
        TestSuspend();
        //TestLegacy();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }

    Console.ReadKey();

    //notepadProc.Kill();
}

static void TestLegacy()
{
    var targetExe = @$"Notepad3_x64.exe";

    Console.WriteLine($"Reloaded.Hooks...");
    Console.WriteLine("Started notepad process...");

    using var notepadProc = System.Diagnostics.Process.Start(targetExe);
    var counter_Exec = new PerformanceCounter("Process", "Working Set - Private", notepadProc.ProcessName);
    var memory = (double)counter_Exec.RawValue / (1024 * 1024);
    //notepadProc.Refresh();
    //var MemoryUsed = (notepadProc.WorkingSet64).ConvertBytesToMegabytes().ToString(CultureInfo.InvariantCulture);
    Console.WriteLine($"Notepad running ({memory} Mb)...");
    //Thread.Sleep(15000);
    using var inj = new Injector(notepadProc);
    memory = (double)counter_Exec.RawValue / (1024 * 1024);
    Console.WriteLine($"Initialized injector ({memory} Mb)...");
    //Thread.Sleep(15000);

    if (inj.Inject(Loader64) is 0)
        throw new Exception("Injection failed!");

    memory = (double)counter_Exec.RawValue / (1024 * 1024);
    Console.WriteLine($"Injected ({memory} Mb)...");
    //// I suppose we can directly call the function without checking for its address in order to detect success or failure
    if (inj.GetFunctionAddress(LoaderName, "StartHooking") is 0)
        throw new Exception("Failed to find function address!");

    // From what I saw, when res is negative then it's an internal error, otherwise the function returns code we expect
    int res = 0;
    if ((res = inj.CallFunction<int>(LoaderName, "StartHooking")) < 0)
        throw new Exception("Failed to call function address!");

    memory = (double)counter_Exec.RawValue / (1024 * 1024);
    Console.WriteLine($"Hooking started ({memory} Mb) Waiting 15 seconds before closing the hook...");
    //Console.WriteLine($"Hooking started Waiting 15 seconds before closing the hook...");
    System.Threading.Thread.Sleep(15000);
    //memory = (double)counter_Exec.RawValue / (1024*1024);
    //Console.WriteLine($"Waiting finished ({memory} Mb) ! Going to close...");
    if ((res = inj.CallFunction<int>(LoaderName, "StopHooking")) < 0)
        throw new Exception("Failed to call function address!");

    memory = (double)counter_Exec.RawValue / (1024 * 1024);
    Console.WriteLine($"Hook closed ({memory} Mb)!");
}

static void TestSuspend()
{
    System.Diagnostics.Process notepadProc = null;
    try
    {
        var targetExe = @$"Notepad3_x64.exe";

        //notepadProc = System.Diagnostics.Process.Start(targetExe);
        
        var siex = new STARTUPINFOEX();

        if (!CreateProcess(targetExe,
            null,
            null,
            null,
            false,
            // suspension disabled because seems unsupported from Reloaded injector
            CREATE_PROCESS.CREATE_SUSPENDED,
            //CREATE_PROCESS.NORMAL_PRIORITY_CLASS,
            null,
            null,
            siex,
            out var notepad))
            throw new Exception("Unable to create process!");
       
        notepadProc = System.Diagnostics.Process.GetProcessById((int)notepad.dwProcessId);             
        unsafe
        {
            var _syringe = NativeMethods.syringe_for_suspended_process((uint)notepadProc.Id);

            var bootstrapperPathBytes = Encoding.UTF8.GetBytes(Loader64);
            var bootstrapperPathWithNull = new byte[bootstrapperPathBytes.Length + 1];
            Array.Copy(bootstrapperPathBytes, bootstrapperPathWithNull, bootstrapperPathBytes.Length);
            bootstrapperPathWithNull[bootstrapperPathBytes.Length] = 0;

            bool success;
            fixed (byte* bootstrapperPathPtr = bootstrapperPathWithNull)
            {
                success = NativeMethods.syringe_inject(_syringe, bootstrapperPathPtr);
            }

            if (!success)
                throw new ArgumentException("Failed Inject");

            System.Threading.Thread.Sleep(2000);

            var functionPathBytes = Encoding.UTF8.GetBytes("StartHooking");
            var functionPathWithNull = new byte[functionPathBytes.Length + 1];
            Array.Copy(functionPathBytes, functionPathWithNull, functionPathBytes.Length);
            functionPathWithNull[functionPathBytes.Length] = 0;
            
            fixed (byte* functionPathPtr = functionPathWithNull)
            {
                var datar = NativeMethods.syringe_find_or_inject(_syringe, functionPathPtr);
                if (datar == null)
                    throw new ArgumentException("Failed to find function address!");
            }

            Console.WriteLine($"Hooking started Waiting 15 seconds before closing the hook...");
            //Console.WriteLine($"Hooking started Waiting 15 seconds before closing the hook...");
            System.Threading.Thread.Sleep(15000);

            //func = NativeMethods.syringe_find_or_inject(procsu, "StopHooking");
            //if (inj.CallFunction(LoaderName, "StopHooking", IntPtr.Zero) < 0)
            //throw new Exception("Failed to call function address! This is probably happened because in ReloadedHooksInjectionLib we're referencing some libraries. It doesn't matter if we directly reference the project or where we keep those libraries. The function call fails in any case as soon as we call a method which belong to referenced libraires. This does not happens in EasyHook prpject.");
            
            
            
            NativeMethods.syringe_free(_syringe);
        }

        notepadProc.Close();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

}`

@Sewer56
Copy link
Member

Sewer56 commented Nov 26, 2024

Can't comment at the current time, I still never fully finalized the work there.

That said, if you want some example usage, see the Reloaded-II repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants