JamesMenetrey / MemorySharp

A C# based memory editing library targeting Windows applications, offering various functions to extract and inject data and codes into remote processes to allow interoperability.
Other
634 stars 135 forks source link

Question about mixing Fasm.net and P/Invoke method #26

Closed smardine closed 1 year ago

smardine commented 1 year ago

Hi, i manage to use your library to get the CPU id with the following code :

 public static class Asm
    {
        [SuppressUnmanagedCodeSecurity] // disable security checks for better performance
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)] // cdecl - let caller (.NET) clean the stack
        private delegate IntPtr AssemblyReadRegistersFunction();
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate CpuIdResult CpuIDDelegate(int level);

        [StructLayout(LayoutKind.Sequential, Size = 16)]
        public struct CpuIdResult
        {
            public int Eax;
            public int Ebx;
            public int Ecx;
            public int Edx;
        }
        internal static string CPUIdAsm()
        {
            using (var currentProcess = new ProcessSharp(System.Diagnostics.Process.GetCurrentProcess(), MemoryType.Local))
            {
                FasmNet fasmNet = new FasmNet();
                fasmNet.AddLine("use32"); //Tell FASM.Net to use x86 (32bit) mode
                fasmNet.AddLine("PUSH EBX");
                fasmNet.AddLine("PUSH EDI");
                fasmNet.AddLine("MOV EDI, EAX");
                fasmNet.AddLine("MOV EAX, 1");
                fasmNet.AddLine("DW $A20F");
                fasmNet.AddLine("STOSD");
                fasmNet.AddLine("MOV EAX, EBX");
                fasmNet.AddLine("STOSD");
                fasmNet.AddLine("MOV EAX, ECX");
                fasmNet.AddLine("STOSD");
                fasmNet.AddLine("MOV EAX, EDX");
                fasmNet.AddLine("STOSD");
                fasmNet.AddLine("POP EDI");
                fasmNet.AddLine("POP EBX");
                fasmNet.AddLine("RET");  // in cdecl calling convention, return value is stored in EAX; so this will return both params added up
                byte[] assembledCode = fasmNet.Assemble();

                var allocatedCodeMemory = currentProcess.MemoryFactory.Allocate(
                    name: "Example3", // only used for debugging; not really needed
                    size: assembledCode.Length,
                    protection: MemoryProtectionFlags.ExecuteReadWrite /* It is important to mark the memory as executeable or we will get exceptions from DEP */
                );
                allocatedCodeMemory.Write(0, assembledCode);
                var myAssemblyFunction = Marshal.GetDelegateForFunctionPointer<CpuIDDelegate>(allocatedCodeMemory.BaseAddress);

                CpuIdResult result = myAssemblyFunction(1);

                var returnValue = result;
                // Warning: Potential memory leak!
                // Do not forget to dispose the allocated code memory after usage. 
                allocatedCodeMemory.Dispose();               
                int cpuid1 = returnValue.Eax;
                int cpuidpluscomplement = cpuid1 & 0x0FFF7FFF;
                string converted = cpuidpluscomplement.ToString("X8");                
                return converted;
            }

        }
    }

this work like a charm but only if i execute it "alone". If i first call

 string driveletter = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System));
            if (driveletter != "")
            {
                uint serialNumber = VolumeSerialNumber(driveletter);
                machineDesc.Append(serialNumber.ToString("X8"));
            }

where VolumeSerialNumber is

 private static uint VolumeSerialNumber(string driveLetter)
        {
            StringBuilder volname = new StringBuilder(261);
            StringBuilder fsname = new StringBuilder(261);
            uint sernum, maxlen;
            FileSystemFeature flags;
            GetVolumeInformation(driveLetter, volname, volname.Capacity, out sernum, out maxlen, out flags, fsname, fsname.Capacity);            
            return sernum;
        }

        #region DllImport
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rootPathName"></param>
        /// <param name="volumeNameBuffer"></param>
        /// <param name="volumeNameSize"></param>
        /// <param name="volumeSerialNumber"></param>
        /// <param name="maximumComponentLength"></param>
        /// <param name="fileSystemFlags"></param>
        /// <param name="fileSystemNameBuffer"></param>
        /// <param name="nFileSystemNameSize"></param>
        /// <returns></returns>
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal extern static bool GetVolumeInformation(string rootPathName, StringBuilder volumeNameBuffer, int volumeNameSize, out uint volumeSerialNumber, out uint maximumComponentLength, out FileSystemFeature fileSystemFlags, StringBuilder fileSystemNameBuffer, int nFileSystemNameSize);

        #endregion

the Asm.CPUIdAsm() method return 0 for all value in the struct

[StructLayout(LayoutKind.Sequential, Size = 16)]
        public struct CpuIdResult
        {
            public int Eax;
            public int Ebx;
            public int Ecx;
            public int Edx;
        }

i put a sample test in attachment.

I hope you can help me ConsoleApp1.zip

JamesMenetrey commented 1 year ago

Dear @smardine,

It seems you opened that issue in the wrong GitHub repository since creating an instance of MemorySharp involves playing with the class MemorySharp (not ProcessSharp). I will let you post on the appropriate project for further assistance.

That being said, it seems your assembly code uses some uninitialized registers (typically in the source of your MOV instructions). You have maybe forgotten to call the CPUID instruction beforehand.

Cheers

smardine commented 1 year ago

HI, sorry for having posting on the wrong repo. I modify my code to use MemorySharp instead. Here is the code:

internal static string CPUIIDMemorySharp()
        {
            var sharp = new MemorySharp(System.Diagnostics.Process.GetCurrentProcess());

            using (var memory = sharp.Memory.Allocate(24))
            {
                AssemblyTransaction fasmNet;
                using (fasmNet = sharp.Assembly.BeginTransaction(memory.BaseAddress))
                {
                    fasmNet.AddLine("use32"); //Tell FASM.Net to use x86 (32bit) mode
                    fasmNet.AddLine("PUSH EBX"); //{Save affected register}
                    fasmNet.AddLine("PUSH EDI");
                    fasmNet.AddLine("MOV EDI, EAX"); //{@Resukt}
                    fasmNet.AddLine("MOV EAX, 1");
                    fasmNet.AddLine("DW $A20F"); //CPUID Command}
                    fasmNet.AddLine("STOSD");  //{ CPUID[1]}
                    fasmNet.AddLine("MOV EAX, EBX");
                    fasmNet.AddLine("STOSD"); //{CPUID[2]}
                    fasmNet.AddLine("MOV EAX, ECX");
                    fasmNet.AddLine("STOSD"); //{CPUID[3]}
                    fasmNet.AddLine("MOV EAX, EDX");
                    fasmNet.AddLine("STOSD"); //{CPUID[4]}
                    fasmNet.AddLine("POP EDI"); //{Restore registers}
                    fasmNet.AddLine("POP EBX");
                    fasmNet.AddLine("RETN");  // in cdecl calling convention, return value is stored in EAX; so this will return both params added up
                }

                var myAssemblyFunction = Marshal.GetDelegateForFunctionPointer<CpuIDDelegate>(memory.BaseAddress);
                CpuIdResult result = myAssemblyFunction(1);              
                int cpuid1 = result.Eax;
                int cpuidpluscomplement = cpuid1 & 0x0FFF7FFF;
                string converted = cpuidpluscomplement.ToString("X8");
                return converted;
            }

I end with the same problem, if i execute this code first, i have a value in

CpuIdResult result = myAssemblyFunction(1);  
int cpuid1 = result.Eax;

If i made a call to a P/Invoke method first, the struct CpuIdResult has all this properties to 0. When i first call MemorySharp image

When i call a P/Invoke method before calling MemorySharp: image

Here is the modified example /repro project. ConsoleApp1.zip

I'm pretty sure i'm missing something but what... I hope you will have the time to take a look, and maybe a lead for me