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

64-bit support #5

Open
A-VORONKIN opened this issue Oct 29, 2015 · 35 comments
Open

64-bit support #5

A-VORONKIN opened this issue Oct 29, 2015 · 35 comments
Assignees
Milestone

Comments

@A-VORONKIN
Copy link

When it will support 64bit processes?

@A-VORONKIN A-VORONKIN changed the title Cheat Engine ASM 64 bit support Oct 29, 2015
@JamesMenetrey
Copy link
Owner

The 64-bit support is my next target for MemorySharp. No ETA yet :)

@JamesMenetrey JamesMenetrey added this to the v2.0 milestone Sep 5, 2016
@zcanann
Copy link

zcanann commented Sep 5, 2016

It shouldn't be too much work to do. at least for the memory side of things. Need a new struct for VirtualQueryEx.

As for Fasm, spin up a 32 bit process and use IPC to ask the process to assemble the bytes (easier said than done)

I'd raise a PR, but I only pulled in certain parts of the project and refactored them heavily -- so there is no easy way for me to add my contributions back to this project.

Here is what I did. Essentially either populate the MemoryBasicInformation32 or MemoryBasicInformation64 struct depending on the environment, then just always return the 64 bit struct. If its a 32 bit process, just copy the data into the 64 bit struct manually.

 [StructLayout(LayoutKind.Sequential)]
    public struct MemoryBasicInformation64
    {
        /// <summary>
        /// A pointer to the base address of the region of pages.
        /// </summary>
        public IntPtr BaseAddress;
        /// <summary>
        /// A pointer to the base address of a range of pages allocated by the VirtualAlloc function. The page pointed to by the BaseAddress member is contained within this allocation range.
        /// </summary>
        public IntPtr AllocationBase;
        /// <summary>
        /// The memory protection option when the region was initially allocated. This member can be one of the memory protection constants or 0 if the caller does not have access.
        /// </summary>
        public MemoryProtectionFlags AllocationProtect;
        /// <summary>
        /// Required in the 64 bit struct. Blame Windows.
        /// </summary>
        public UInt32 __alignment1;
        /// <summary>
        /// The size of the region beginning at the base address in which all pages have identical attributes, in bytes.
        /// </summary>
        public long RegionSize;
        /// <summary>
        /// The state of the pages in the region.
        /// </summary>
        public MemoryStateFlags State;
        /// <summary>
        /// The access protection of the pages in the region. This member is one of the values listed for the AllocationProtect member.
        /// </summary>
        public MemoryProtectionFlags Protect;
        /// <summary>
        /// The type of pages in the region.
        /// </summary>
        public MemoryTypeFlags Type;
        /// <summary>
        /// Required in the 64 bit struct. Blame Windows.
        /// </summary>
        public UInt32 __alignment2;
    };
public static MemoryBasicInformation64 Query(SafeMemoryHandle ProcessHandle, IntPtr BaseAddress)
        {
            MemoryBasicInformation64 MemoryInfo64 = new MemoryBasicInformation64();

            if (!Environment.Is64BitProcess)
            {
                // 32 Bit struct is not the same
                MemoryBasicInformation32 MemoryInfo32 = new MemoryBasicInformation32();

                // Query the memory region
                if (NativeMethods.VirtualQueryEx(ProcessHandle, BaseAddress, out MemoryInfo32, MarshalType<MemoryBasicInformation32>.Size) != 0)
                {
                    // Copy from the 32 bit struct to the 64 bit struct
                    MemoryInfo64.AllocationBase = MemoryInfo32.AllocationBase;
                    MemoryInfo64.AllocationProtect = MemoryInfo32.AllocationProtect;
                    MemoryInfo64.BaseAddress = MemoryInfo32.BaseAddress;
                    MemoryInfo64.Protect = MemoryInfo32.Protect;
                    MemoryInfo64.RegionSize = MemoryInfo32.RegionSize;
                    MemoryInfo64.State = MemoryInfo32.State;
                    MemoryInfo64.Type = MemoryInfo32.Type;

                    return MemoryInfo64;
                }
            }
            else
            {
                // Query the memory region
                if (NativeMethods.VirtualQueryEx(ProcessHandle, BaseAddress, out MemoryInfo64, MarshalType<MemoryBasicInformation64>.Size) != 0)
                {
                    return MemoryInfo64;
                }
            }

            return MemoryInfo64;
}

[DllImport("kernel32.dll", SetLastError = true)]
        public static extern int VirtualQueryEx(SafeMemoryHandle hProcess, IntPtr lpAddress, out MemoryBasicInformation32 lpBuffer, int dwLength);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern int VirtualQueryEx(SafeMemoryHandle hProcess, IntPtr lpAddress, out MemoryBasicInformation64 lpBuffer, int dwLength);

@JamesMenetrey
Copy link
Owner

Hey @zcanann,

Interesting! I've searched a way to use fasm in x86-64 for long time and basically come to the conclusion that either we request to the developers of Fasm to provide a compatible code (even not worth trying) or we isolate the assembler in a separated process, as you suggested.

I don't really like the idea of having an executable next to the library but we don't have a lot of choice in this situation. As you seem to use this way, did you notice some perf issues around IPC ?

I think I'll go for a lightweight usage of WCF, as Microsoft was nice enough to provide this high-level API. :)

Cheers
Zen

@zcanann
Copy link

zcanann commented Sep 8, 2016

@ZenLulz

I didn't notice any performance issues, but I only used it lightly (assembling < 50 bytes of data at a time very rarely)

I tried desperately to get 64 bit to work, but some form of IPC looks like it was the only solution. I was also considering exploring Nasm and Masm to see if there was similar capabilities, but I gave up because I was going insane. I ended up going with WCF as well (or at least I think so -- Microsoft has so many damn IPC options). I'll post the relevant code from my project, just in case there are any pieces you want to use.

Ended up with 3 projects. A main project, a shared library to define shared interfaces, and a 32 bit project.
Main project -> AnyCPU
FasmProxy -> AnyCPU
FasmProxy32 -> x86

Pardon the PascaleCase in advance as well as the use of full structure names for primitives (ie Int32), I know these aren't the same convention as your project

In the project FasmProxy I had 2 classes and 1 interface:

public class FasmProxy
{
    private const Int32 ParentCheckDelayMs = 500;

    public FasmProxy(Int32 ParentProcessId, String PipeName, String WaitEventName)
    {
        // Create an event to have the client wait until we are finished starting the service
        EventWaitHandle ProcessStartingEvent = new EventWaitHandle(false, EventResetMode.ManualReset, WaitEventName);

        InitializeAutoExit(ParentProcessId);

        ServiceHost ServiceHost = new ServiceHost(typeof(ProxyService));
        ServiceHost.Description.Behaviors.Remove(typeof(ServiceDebugBehavior));
        ServiceHost.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
        NetNamedPipeBinding Binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        ServiceHost.AddServiceEndpoint(typeof(IProxyService), Binding, PipeName);
        ServiceHost.Open();

        ProcessStartingEvent.Set();

        Console.WriteLine("Fasm proxy library loaded");
        Console.ReadLine();
    }

    public static Boolean IsRunning(Int32 ParentProcessId)
    {
        try
        {
            Process.GetProcessById(ParentProcessId);
        }
        catch (ArgumentException)
        {
            return false;
        }

        return true;
    }

    private void InitializeAutoExit(Int32 ParentProcessId)
    {
        Task.Run(() =>
        {
            while (true)
            {
                if (!IsRunning(ParentProcessId))
                    break;

                Thread.Sleep(ParentCheckDelayMs);
            }

            Environment.Exit(0);
        });
    }

} // End class
[ServiceContract()]
public interface IProxyService
{
    [OperationContract]
    Byte[] Assemble(Boolean IsProcess32Bit, String Assembly, UInt64 BaseAddress);
}  // End interface
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class ProxyService : IProxyService
{
    private const Int32 AttachTimeout = 5000;

    public ProxyService() { }

    public Byte[] Assemble(Boolean IsProcess32Bit, String Assembly, UInt64 BaseAddress)
    {
        if (Assembly == null)
            return null;

        // Add header information about process
        if (IsProcess32Bit)
            Assembly = String.Format("use32\n" + "org 0x{0:X8}\n", BaseAddress) + Assembly;
        else
            Assembly = String.Format("use64\n" + "org 0x{0:X16}\n", BaseAddress) + Assembly;

        // Print fully assembly to console
        Console.WriteLine("\n" + Assembly + "\n");

        Byte[] Result;
        try
        {
            // Call C++ FASM wrapper which will call the 32-bit FASM library which can assemble all x86/x64 instructions
            Result = FasmNet.Assemble(Assembly);

            // Print bytes to console
            Array.ForEach(Result, (X => Console.Write(X.ToString() + " ")));
        }
        catch
        {
            Result = null;
        }
        return Result;
    }
}  // End class

In the project FasmProxy32:

class Program
{
    private static FasmProxy.FasmProxy FasmProxy;

    static void Main(String[] Args)
    {
        if (Args.Length < 3)
            return;

        Console.WriteLine("Initialized FasmProxy 32-bit helper process");
        FasmProxy = new FasmProxy.FasmProxy(Int32.Parse(Args[0]), Args[1], Args[2]);
    }

} // End class

Then finally a static singleton class in the main project

class ProxyCommunicator
{
    // Singleton instance of proxy communication class
    private static Lazy<ProxyCommunicator> ProxyCommunicatorInstance = new Lazy<ProxyCommunicator>(() => { return new ProxyCommunicator(); }, LazyThreadSafetyMode.PublicationOnly);

    private const String FasmProxy32Executable = "FasmProxy32.exe";
    private const String WaitEventName = @"Global\Fasm";
    private const String UriPrefix = "net.pipe://localhost/";

    private IProxyService FasmProxy32;

    private ProxyCommunicator() { }

    public static ProxyCommunicator GetInstance()
    {
        return ProxyCommunicatorInstance.Value;
    }

    public void InitializeServices()
    {
        // Initialize channel names
        String FasmProxy32ServerName = UriPrefix + Guid.NewGuid().ToString();

        // Start 32 bit proxy service
        FasmProxy32 = StartProxyService(FasmProxy32Executable, FasmProxy32ServerName);
    }

    private IProxyService StartProxyService(String ExecutableName, String ChannelServerName)
    {
        // Start the proxy service
        EventWaitHandle ProcessStartEvent = new EventWaitHandle(false, EventResetMode.ManualReset, WaitEventName);
        ProcessStartInfo ProcessInfo = new ProcessStartInfo(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), ExecutableName));
        ProcessInfo.Arguments = Process.GetCurrentProcess().Id.ToString() + " " + ChannelServerName + " " + WaitEventName;
        ProcessInfo.UseShellExecute = false;
        ProcessInfo.CreateNoWindow = true;
        Process.Start(ProcessInfo);
        ProcessStartEvent.WaitOne();

        // Create connection
        NetNamedPipeBinding Binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        Binding.MaxReceivedMessageSize = Int32.MaxValue;
        Binding.MaxBufferSize = Int32.MaxValue;

        EndpointAddress Endpoint = new EndpointAddress(ChannelServerName);
        IProxyService ProxyService = ChannelFactory<IProxyService>.CreateChannel(Binding, Endpoint);

        return ProxyService;
    }

    public IProxyService GetProxyService()
    {
            return FasmProxy32;
    }

} // End class

@JamesMenetrey
Copy link
Owner

Excellent @zcanann, many thanks for your code. No worries about the naming convention, you code is easy to read and well commented. I think this is a nice approach. In all the case, if it suffers from performance issues, I will add some caching in MemorySharp in order to keep high performance. From what I could read, some software need to heavily rely on assembly injection, which I would like to promote, as a out-of-process memory editing library.

A side note about your code, I'm a bit concerned by the attribute ServiceBehavior, more specifically the parameter InstanceContextMode = InstanceContextMode.PerSession (to not confuse with ConcurrencyMode). With this parameter, we configure the server to have a thread per client. The consequence is when you have two clients, the server can handle their requests simultaneously. I've made a test with this configuration based on [this article](http://www.codeproject.com/Articles/89858/WCF-Concurrency-Single-Multiple-and-Reentrant-and#Instance mode = per session and Concurrency = single). After a bit of pimping in order to display when the connections start and end, I got the following result:

persession

As you can see, the server indeed manage multiple requests at the same time (multiple consecutive [start] flags). The issue here is the Fasm assembler is not thread-safe, meaning your hosted Fasm service can lead undefined results. I've tried with the parameter InstanceContextMode = InstanceContextMode.Single afterwards. As illustrated below, the server is handling request synchronously for all the clients this time:

single

Thank you again @zcanann. I'll add a contributor file to reference everybody who help/helped me to design the library. :)

Cheers,
Zen

@zcanann
Copy link

zcanann commented Sep 9, 2016

@ZenLulz

Nice catch, thanks for letting me know. And no problem, it's the least I can do to give back.

@JamesMenetrey
Copy link
Owner

64-bit support.

@lolp1
Copy link

lolp1 commented Sep 10, 2016

@ZenLulz
Some thoughts on x64/random stuff

From what I could read, some software need to heavily rely on assembly injection
What reasonable argument is there for some software to rely heavily on assembly injection?
which I would like to promote, as a out-of-process memory editing library.
I personally think the project is much more interesting when it is able to do both in a managed language, especially if the in-process implementation has high-quality and simplicity that the external parts of MemorySharp brings.

Some things can make "in-process" C# much more interesting to the casual programmer, mostly the same things that attract external users, increased simplicity.

Here are some of the main issues that turn people off about in-process c# code, and my solutions so far.

-Injecting a C# app into a process.

The answer to this is rather simple. If you review this project I updated https://github.com/lolp1/DomainWrapper you can see the process is simplified to injecting the DLL and calling the HostDomain export with the path to the C# program to host.

Everything from WinForms, Console apps, and even class library's can be hosted easily. In the case of C# projects with out an "entry point", the injector can use an attribute [such as STAThread] to search for in reflection for the "entry" method to call when the c# domain is hosted. All you really need is to write a user-friendly injector.

-Using pointers (both for delegates/functions and data)
Again, this can be answered by stealing some tricks from other libs.
https://github.com/lolp1/Process.NET/blob/master/src/Process.NET/Extensions/UnsafeMemoryExtensions.cs

-Making use of in-process code (such as calling functions in the process via unmanaged function pointer delegates) in a thread-safe way.
Thanks to Jadd, this is also pretty easy to make user friendly. Use this WndProc override https://github.com/lolp1/Process.NET/blob/master/src/Process.NET/Windows/WndProcHook.cs to forward messages to have the main UI thread run code youw ant to execute.

A little example of some of my syntax sugar..

    internal class Function<T> where T : class
    {
        internal Function(IntPtr address)
        {
            Instance = address.ToDelegate<T>();
        }

        internal T Instance { get; }

        internal void Execute(params object[] objects)
        {
           Process.Instance.Window.Invoke(Instance, objects);
        }
        internal TT Execute<TT>(params object[] objects)
        {
            return (TT) Process.Instance.Window.Invoke(Instance, objects);
        }
    }

This lets you do stuff like this to invoke code inside the main thread in a safe way using the WndProc messages to communicate via a wrapper that makes use of a queue and the WndProc override linked.

        private Function<IsAutoTrackingDelegate> _isSomethingTrueFunction;
    // _isSomethingTrueFunction = new Function<IsSomethingTrue>(IntPtr.Zero);
        // bool isItTrue = _isSomethingTrueFunction.Execute<bool>((IntPtr)0x500);       
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        private delegate bool IsSomethingTrue(IntPtr thisSomething);

-Making use of hooks
You can read my recent blog http://blog.stylesoftware.net/2016/09/09/tl-dr-hooks/ to see how simple hooks can be used when the right code is in place.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace TestApp
{
    internal class Program
    {
        internal static readonly List TimeStamps = new List();
        internal static readonly Process Process = Process.GetCurrentProcess();

        private static D3DHook _d3DHook;

        private static void Main(string[] args)
        {
            _d3DHook = new D3D9(new DetourManager(Process.Handle));

            _d3DHook.Initialize();
            _d3DHook.Frame += D3DHookOnFrame;
        }

        private static void D3DHookOnFrame(object sender, EventArgs eventArgs)
        {
            TimeStamps.Add(DateTime.Now);
        }
    }
}

There is more, but if you ask me, using a well written user-friendly C# injector and a well-designed C# library that supports in-process can make in-process stuff a lot more attractive and simple. Being entirely done in C# while doing that is a nice bonus.

Aside from that.. I supported x64 in my memory sharp clone by turning everything into interfaces that will be used forever, and implementing anything that broke as I went on the fly. I had the same issue with bringing the ASM code execution/injection support MemorySharp brought in x32 to x64.

In the end I just went full C# in-process, but in my efforts, I had the same solutions thought of here. I used a third party to handle assembling things. Microsoft has great built in IPC stuff as well as amazing interop with c# in general.

I'd love to see an effort to bring MemorySharp to the in-process world along side its out-of-process world that is already mostly built.

@JamesMenetrey
Copy link
Owner

Hey @lolp1,

The main reason of being out-of-process is to reduce the footprint of memory editing in the targeted process, keeping it as low as possible and have the full control of what is altered in the target process. This is why I oriented MemorySharp to be out-of-process. As soon as you end up injecting managed code in a process, you require to start the CLR runtime in that process. That is not a bad practice by essense of course, nonetheless, this can restrain developers to use it in several scenarios, where the target process is wise enough to detect such modifications.

The code you posted is very interesting. I'm going to have a look to the abstraction you used in your clone of the library. :)

Most of the stuff (detour, hooking, etc.) can also be achieved while being out-of-process, without having the users of the library to write assembly code. That's obviously more work in the library and much more interesting to do. :D

MemorySharp was initially written to be efficient as an out-of-process library and the implementation of memory management/type conversion was thought in that way. A lot of stuff in that library should be rewritten differently to make it worth using MemorySharp in a context of in-process. Obviously, those changes would be only applicable if the library is in-process. This is why I'm not so keen to inject MemorySharp in the target process.

I'll add you in Skype this week-end. :)

Cheers
Zen

@lolp1
Copy link

lolp1 commented Sep 17, 2016

@ZenLulz

Sounds good :) I have more thoughts but of course this place is not the best for such communication. [email protected] is my skype :) add that one when you have a chance.

@sesey
Copy link

sesey commented Nov 6, 2016

When will 64-bit support come?

@JamesMenetrey
Copy link
Owner

Hey !

@sesey Still no ETA currently ^^

@lolp1 Hey man ! Sorry I forgot you ! Just added you now. :)

@JamesMenetrey
Copy link
Owner

I created the branch x86-64 to work on this feature. This enables you to follow the progress as well.

Cheers

@A-VORONKIN A-VORONKIN changed the title 64 bit support 64-bit support Nov 7, 2016
@JamesMenetrey
Copy link
Owner

Side note for me.

PEB 32 and 64-bit support

PEB structures vary depending on the OS and architecture. A research around all the PEB from XP is available here: http://blog.rewolf.pl/blog/?p=573.

This leads to the Terminus Project, that enables us to compare Windows structures here: http://blog.rewolf.pl/blog/?p=1438. The PEB structures on Terminus Project can be found here: http://terminus.rewolf.pl/terminus/structures/ntdll/_PEB_combined.html. MemorySharp will certainly only maintain the independent OS fields.

TEB 32 and 64-bit support

Similar to the PEB, the TEB's are available here: http://terminus.rewolf.pl/terminus/structures/ntdll/_TEB_combined.html

@sesey
Copy link

sesey commented Dec 12, 2016

What about the workings for 64 bit support?

@JamesMenetrey
Copy link
Owner

This is in progress ! :)

@sesey
Copy link

sesey commented Dec 12, 2016

I want to help but I know little of c# language.

@Kruithne
Copy link

Any update/ETA on this milestone for the project?

@lolp1
Copy link

lolp1 commented Jan 16, 2017

Just so people know @ZenLulz is a professional dev who does this for a living and AFAIK this is a bit of a hobby project that he takes great pride in it being quality work so it takes some time for releases.

In the meantime, feel free to suggest anything you want in MemorySharp and I'll do my best to get some of that done in ways that meets the standards ZenLulz desires and pull request it. You may also check out my spin-off (FULL CREDITS to @ZenLulz for my project too!) in the mean-time which does support x64.

https://github.com/lolp1/Process.NET

@KairuByte
Copy link

Was this ever finalized? I tried using the files built from the x86-64 and no matter what I do I keep getting 'Couldn't get the information from the thread, error code '-1073741820'.'

@lolp1
Copy link

lolp1 commented Aug 5, 2017

@KairuByte That error is due to an issue in his structures with x32-64, will look into fixing it later and pull requesting.

@ghost
Copy link

ghost commented Sep 3, 2017

@lolp1 I still have this error, hasn't this been fixed yet?

@roboserg
Copy link

roboserg commented Nov 6, 2017

I too have the error code '-1073741820'.' one year later, this is sad :(

@zcanann
Copy link

zcanann commented Nov 6, 2017

For those interested, I have 64-bit support working for some features (just read/write and the x86/x64 assembler)

https://github.com/Squalr/Squalr

This project was never really meant to be used as a library though -- but it may be possible to repurpose it for that.

EDIT: My backend has been made available via NuGet

@roboserg
Copy link

roboserg commented Nov 6, 2017

Thanks, but I need to inject my c# dll to another unmanaged process. For read / write there already many libs. RIP

@MohamedAlaaJameel
Copy link

@zcanann I want to execute calls on x64 remote process , is that possible&how ?

@JamesMenetrey
Copy link
Owner

Hey @MohamedAlaaJameel,

The branch deepening-project implements some support for x64 calling convention, notably here: https://github.com/JamesMenetrey/MemorySharp/blob/deepening-project/src/MemorySharp/Assembly/CallingConvention/MicrosoftX64CallingConvention.cs.

Feel free to go to that branch, compile the library and check to see if that fits your needs :)

@JamesMenetrey
Copy link
Owner

A sidenote for everyone here, I'm still planning in revamping this library with the latest C# features (e.g., span, etc.). :)

@MohamedAlaaJameel
Copy link

@JamesMenetrey

I have compiled the library , but I get these errors /

on 32-Compile : can't open 64 bit proc from 32 .
on 64-Compile :System.ApplicationException: 'Couldn't get the information from the process, error code '-1073741820'.'
I have recorded a video please watch :
https://youtu.be/CMO2uwc0tLs

@JamesMenetrey
Copy link
Owner

@MohamedAlaaJameel Try to force your .NET app to be compiled as 64-bit. This is usually done in Visual Studio by changing AnyCPU into 64bit.

@MohamedAlaaJameel
Copy link

MohamedAlaaJameel commented Jul 26, 2022

@JamesMenetrey
the problem is solved it seems I have cloned the old repo , thank you for the lib and your efforts .

@JamesMenetrey
Copy link
Owner

JamesMenetrey commented Jul 26, 2022

Great! To answer your other question: I have dropped Fasm for Keystone for 64-bit assembler support :)

@lolp1
Copy link

lolp1 commented Jul 26, 2022

Great! To answer your other question: I have dropped Fasm for Keystone for 64-bit assembler support :)

@JamesMenetrey

Off-topic, but I forgot your discord name could you throw me a private message over on there :D?

@MohamedAlaaJameel
Copy link

MohamedAlaaJameel commented Jul 26, 2022

MR @JamesMenetrey
image
I am using 4.7.2 .net framework
extension methods of intptr are not resolved at run time .
I have add IsEqual manually and fixed .
image
is there any good solution ? instead of implementing all extension methods of IntPtr manually?

edit :
fix :
open ThreadFactory.cs
add using Binarysharp.MemoryManagement.Helpers;
change
var ret = ThreadCore.NtQueryInformationThread(
to
ThreadBasicInformation ret = ThreadCore.NtQueryInformationThread(
I have found it here :
https://stackoverflow.com/questions/7562205/why-is-an-expandoobject-breaking-code-that-otherwise-works-just-fine

I need your discord if that's possible

@BinToss
Copy link

BinToss commented Jul 12, 2023

@MohamedAlaaJameel Convert/cast the IntPtr values to Long or ULong.


About a year ago, I wrote my own 32-bit and 64-bit definitions of these structs. Instead of IntPtr which depends on the .NET Runtime for bit length, I wrote facades IntPtr32, IntPtr64, UIntPtr32, UIntPtr64. I also implemented generic variants (e.g. UIntPtr64<T>) to indicate the Type of the object at the pointer.

For structs with definitions dependent on bit-length, I wrote a managed definition (e.g. MemoryBasicInformation) which wrapped either 32-bit or 64-bit unmanaged structs. The Managed definition stores the length-specific struct or pointer as a nullable member of a tuple e.g.

public class ProcessBasicInformation
{
   public ProcessBasicInformation(PROCESS_BASIC_INFORMATION pbi)
   {
       ExitStatus = pbi.ExitStatus;
       unsafe { PebBaseAddress = Environment.Is64BitProcess ? (null, (ulong)pbi.PebBaseAddress) : ((uint)pbi.PebBaseAddress, null); }
       AffinityMask = Environment.Is64BitProcess ? (null, (ulong)pbi.AffinityMask) : ((uint)pbi.AffinityMask, null);
       BasePriority = pbi.BasePriority;
       ProcessId = pbi.ProcessId;
       ParentProcessId = pbi.ParentProcessId;
   }

   public ProcessBasicInformation(PROCESS_BASIC_INFORMATION32 pbi)
   {
       ExitStatus = pbi.ExitStatus;
       PebBaseAddress = (pbi.PebBaseAddress, null);
       AffinityMask = (pbi.AffinityMask, null);
       BasePriority = pbi.BasePriority;
       ProcessId = pbi.UniqueProcessId;
       ParentProcessId = pbi.InheritedFromUniqueProcessId;
   }

   public ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi)
   {
       ExitStatus = pbi.ExitStatus;
       PebBaseAddress = (null, pbi.PebBaseAddress);
       AffinityMask = (null, pbi.AffinityMask);
       BasePriority = pbi.BasePriority;
       ProcessId = (uint)pbi.UniqueProcessId;
       ParentProcessId = (uint)pbi.InheritedFromUniqueProcessId;
   }

   public (UIntPtr32<PEB32>? w32, UIntPtr64<PEB64>? w64) PebBaseAddress { get; }
   public ProcessEnvironmentBlock? ProcessEnvironmentBlock { get; private set; }

   public NTSTATUS ExitStatus { get; }
   public (uint? w32, ulong? w64) AffinityMask { get; }
   public KPRIORITY BasePriority { get; }
   public uint ProcessId { get; }
   public uint ParentProcessId { get; }

   /// <summary>Read the process's private memory to recursively copy the PEB.</summary>
   /// <param name="hProcess">A handle opened with <see cref="PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ"/>. Requires Debug and/or admin privileges.</param>
   /// <exception cref="AccessViolationException">Read operation failed; The memory region is protected and Read access to the memory region was denied.</exception>
   /// <exception cref="NullReferenceException">Unable to copy PEB; The 32-bit and 64-bit pointers are both null.</exception>
   /// <exception cref="NTStatusException">NtWow64ReadVirtualMemory failed to copy 64-bit PEB from target process; (native error message)</exception>
   /// <exception cref="Exception">ReadProcessMemory failed; (native error message)</exception>
   public unsafe ProcessEnvironmentBlock GetPEB(SafeProcessHandle hProcess)
   {
       if (PebBaseAddress is (null, null))
           throw new NullReferenceException("Unable to copy PEB; The 32-bit and 64-bit pointers are both null.");

       using SafeBuffer<PEB64> buffer = new(numElements: 2); // We only use the type for allocation length. It's large enough for either PEB64 or PEB32.

       if (!Environment.Is64BitProcess && PebBaseAddress.w64 is not null)
       {
           ulong bytesRead64 = 0;
           NTSTATUS status;

           if ((status = PInvoke.NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)PebBaseAddress.w64, (void*)buffer.DangerousGetHandle(), buffer.ByteLength, &bytesRead64)).Code is Code.STATUS_PARTIAL_COPY)
               throw new AccessViolationException("NtWow64ReadVirtualMemory64 failed; The memory region is protected and Read access to the memory region was denied.", new NTStatusException(status));
           else if (status.Code is not Code.STATUS_SUCCESS)
               throw new NTStatusException(status, "NtWow64ReadVirtualMemory failed to copy 64-bit PEB from target process; " + status.Message);
           else
               return ProcessEnvironmentBlock = new ProcessEnvironmentBlock(buffer.Read<PEB64>(0));
       }
       else
       {
           nuint bytesRead = 0;
           if (PebBaseAddress.w32 is not null && PInvoke.ReadProcessMemory(hProcess, (void*)PebBaseAddress.w32, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead))
           {
               return ProcessEnvironmentBlock = new(buffer.Read<PEB32>(0));
           }
           else if (PebBaseAddress.w64 is not null && PInvoke.ReadProcessMemory(hProcess, (void*)PebBaseAddress.w64, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead))
           {
               return ProcessEnvironmentBlock = new(buffer.Read<PEB64>(0));
           }
           else
           {
               Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError();
               if (err is Win32ErrorCode.ERROR_PARTIAL_COPY)
                   throw new AccessViolationException("ReadProcessMemory failed; The memory region is protected and Read access to the memory region was denied.", new Win32Exception(err));
               else
                   throw new Exception("ReadProcessMemory failed; " + err.GetMessage(), new Win32Exception(err));
           }
       }
   }

Some relevant definitions and .dib gists:

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

No branches or pull requests

10 participants