diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..239c869 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "WARNING01": "*********************************************************************************", + "WARNING02": "The C# extension was unable to automatically decode projects in the current", + "WARNING03": "workspace to create a runnable launch.json file. A template launch.json file has", + "WARNING04": "been created as a placeholder.", + "WARNING05": "", + "WARNING06": "If OmniSharp is currently unable to load your project, you can attempt to resolve", + "WARNING07": "this by restoring any missing project dependencies (example: run 'dotnet restore')", + "WARNING08": "and by fixing any reported errors from building the projects in your workspace.", + "WARNING09": "If this allows OmniSharp to now load your project then --", + "WARNING10": " * Delete this file", + "WARNING11": " * Open the Visual Studio Code command palette (View->Command Palette)", + "WARNING12": " * run the command: '.NET: Generate Assets for Build and Debug'.", + "WARNING13": "", + "WARNING14": "If your project requires a more complex launch configuration, you may wish to delete", + "WARNING15": "this configuration and pick a different template using the 'Add Configuration...'", + "WARNING16": "button at the bottom of this file.", + "WARNING17": "*********************************************************************************", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//.dll", + "args": [], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/DemoApp.Abstracts.Linux/DemoApp.Abstracts.Linux.csproj b/DemoApp.Abstracts.Linux/DemoApp.Abstracts.Linux.csproj new file mode 100644 index 0000000..3f121b5 --- /dev/null +++ b/DemoApp.Abstracts.Linux/DemoApp.Abstracts.Linux.csproj @@ -0,0 +1,38 @@ + + + + Exe + netcoreapp3.1 + AnyCPU;x64 + + + + true + + + + true + + + + true + + + + true + + + + + + + + + + + + + + + + diff --git a/DemoApp.Abstracts.Linux/Program.cs b/DemoApp.Abstracts.Linux/Program.cs new file mode 100644 index 0000000..b06b82f --- /dev/null +++ b/DemoApp.Abstracts.Linux/Program.cs @@ -0,0 +1,53 @@ +using MemoryModule.Abstractions; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; + +namespace DemoApp.Abstracts.Linux +{ + unsafe class Program + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int GetScretProc(); + + static void Main(string[] args) + { + var asm = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DemoApp.Abstracts.Linux.Tls{(Environment.Is64BitProcess ? "64" : "")}.so") as UnmanagedMemoryStream; + + var module = Loader.Load((IntPtr)asm.PositionPointer); + + Loader.AllocateSections(module, NativeFunctions.Default); + Loader.PerformRebase(module); + Loader.PerformBinding(module); + Loader.PerformPageProtection(module); + Loader.PerformInitialization(module); + + var getSecretPtr = module.Exports.FirstOrDefault(sym => sym.Name == "GetThreadLocalInt")?.Address ?? IntPtr.Zero; + var getSecret = Marshal.GetDelegateForFunctionPointer(getSecretPtr); + + Console.WriteLine("Done."); + for (int i = 0; i < 10; ++i) + { + Console.WriteLine(getSecret()); + } + + var thread = new Thread(() => + { + for (int i = 0; i < 10; ++i) + { + Console.WriteLine(getSecret()); + } + }); + + thread.Start(); + thread.Join(); + + Loader.PerformFinalization(module); + Loader.UnloadReferences(module); + Loader.DeallocateSections(module); + } + } +} diff --git a/DemoApp.Abstracts.Linux/Properties/launchSettings.json b/DemoApp.Abstracts.Linux/Properties/launchSettings.json new file mode 100644 index 0000000..e992400 --- /dev/null +++ b/DemoApp.Abstracts.Linux/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "DemoApp.Abstracts.Linux": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/DemoApp.Abstracts.Linux/Secret64.so b/DemoApp.Abstracts.Linux/Secret64.so new file mode 100644 index 0000000..3700d32 Binary files /dev/null and b/DemoApp.Abstracts.Linux/Secret64.so differ diff --git a/DemoApp.Abstracts.Linux/Tls.cpp b/DemoApp.Abstracts.Linux/Tls.cpp new file mode 100644 index 0000000..407c213 --- /dev/null +++ b/DemoApp.Abstracts.Linux/Tls.cpp @@ -0,0 +1,12 @@ +#include + +thread_local int secret = 69; + +extern "C" +{ + int GetThreadLocalInt() + { + std::cout << "Address of secret: " << &secret << std::endl; + return secret++; + } +} diff --git a/DemoApp.Abstracts.Linux/Tls64.so b/DemoApp.Abstracts.Linux/Tls64.so new file mode 100644 index 0000000..013a000 Binary files /dev/null and b/DemoApp.Abstracts.Linux/Tls64.so differ diff --git a/DemoApp.Abstracts.MacOS/DemoApp.Abstracts.MacOS.csproj b/DemoApp.Abstracts.MacOS/DemoApp.Abstracts.MacOS.csproj new file mode 100644 index 0000000..a8035c4 --- /dev/null +++ b/DemoApp.Abstracts.MacOS/DemoApp.Abstracts.MacOS.csproj @@ -0,0 +1,37 @@ + + + + Exe + netcoreapp3.1 + AnyCPU;x64 + + + + true + + + + true + + + + true + + + + true + + + + + + + + + + + + + + + diff --git a/DemoApp.Abstracts.MacOS/Program.cs b/DemoApp.Abstracts.MacOS/Program.cs new file mode 100644 index 0000000..e76e394 --- /dev/null +++ b/DemoApp.Abstracts.MacOS/Program.cs @@ -0,0 +1,37 @@ +using MemoryModule.Abstractions; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace DemoApp.Abstracts.MacOS +{ + unsafe class Program + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int GetScretProc(); + + static void Main(string[] args) + { + var asm = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DemoApp.Abstracts.MacOS.Secret{(Environment.Is64BitProcess ? "64" : "")}.dylib") as UnmanagedMemoryStream; + + var module = Loader.Load((IntPtr)asm.PositionPointer); + + Loader.AllocateSections(module, NativeFunctions.Default); + Loader.PerformRebase(module); + Loader.PerformBinding(module); + Loader.PerformPageProtection(module); + Loader.PerformInitialization(module); + + var getSecretPtr = module.Exports.FirstOrDefault(sym => sym.Name == "GetSecret")?.Address ?? IntPtr.Zero; + var getSecret = Marshal.GetDelegateForFunctionPointer(getSecretPtr); + + Console.WriteLine(getSecret()); + + Loader.PerformFinalization(module); + Loader.UnloadReferences(module); + Loader.DeallocateSections(module); + } + } +} diff --git a/DemoApp.Abstracts.MacOS/Properties/launchSettings.json b/DemoApp.Abstracts.MacOS/Properties/launchSettings.json new file mode 100644 index 0000000..96c1041 --- /dev/null +++ b/DemoApp.Abstracts.MacOS/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "DemoApp.Abstracts.MacOS": { + "commandName": "Project", + "remoteDebugEnabled": false, + "remoteDebugMachine": "192.168.1.6", + "authenticationMode": "None" + } + } +} \ No newline at end of file diff --git a/DemoApp.Abstracts.MacOS/Secret64.dylib b/DemoApp.Abstracts.MacOS/Secret64.dylib new file mode 100644 index 0000000..b243aef Binary files /dev/null and b/DemoApp.Abstracts.MacOS/Secret64.dylib differ diff --git a/DemoApp.Abstracts.Windows/DemoApp.Abstracts.Windows.csproj b/DemoApp.Abstracts.Windows/DemoApp.Abstracts.Windows.csproj new file mode 100644 index 0000000..82fcde6 --- /dev/null +++ b/DemoApp.Abstracts.Windows/DemoApp.Abstracts.Windows.csproj @@ -0,0 +1,53 @@ + + + + Exe + netcoreapp3.1 + AnyCPU;x86;x64 + + + + true + + + + true + + + + true + + + + true + + + + true + + + + true + + + + + + + + + + + + Never + + + + + + + + + + + diff --git a/DemoApp.Abstracts.Windows/Program.cs b/DemoApp.Abstracts.Windows/Program.cs new file mode 100644 index 0000000..68fb05c --- /dev/null +++ b/DemoApp.Abstracts.Windows/Program.cs @@ -0,0 +1,53 @@ +using MemoryModule.Abstractions; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; + +namespace DemoApp.Abstracts.Windows +{ + unsafe class Program + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int GetScretProc(); + + static void Main(string[] args) + { + var asm = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DemoApp.Abstracts.Windows.Tls{(Environment.Is64BitProcess ? "64" : "")}.dll") as UnmanagedMemoryStream; + + var module = Loader.Load((IntPtr)asm.PositionPointer); + + Loader.AllocateSections(module, NativeFunctions.Default); + Loader.PerformRebase(module); + Loader.PerformBinding(module); + Loader.PerformPageProtection(module); + Loader.PerformInitialization(module); + + var getSecretPtr = module.Exports.FirstOrDefault(sym => sym.Name == "GetThreadLocalInt")?.Address ?? IntPtr.Zero; + var getSecret = Marshal.GetDelegateForFunctionPointer(getSecretPtr); + + Console.WriteLine("Done."); + for (int i = 0; i < 10; ++i) + { + Console.WriteLine(getSecret()); + } + + var thread = new Thread(() => + { + for (int i = 0; i < 10; ++i) + { + Console.WriteLine(getSecret()); + } + }); + + thread.Start(); + thread.Join(); + + Loader.PerformFinalization(module); + Loader.UnloadReferences(module); + Loader.DeallocateSections(module); + } + } +} diff --git a/DemoApp.Abstracts.Windows/Properties/launchSettings.json b/DemoApp.Abstracts.Windows/Properties/launchSettings.json new file mode 100644 index 0000000..91d9e2c --- /dev/null +++ b/DemoApp.Abstracts.Windows/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "DemoApp.Abstracts.Windows": { + "commandName": "Project", + "nativeDebugging": true + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/DemoApp.Abstracts.Windows/Secret.dll b/DemoApp.Abstracts.Windows/Secret.dll new file mode 100644 index 0000000..03aed98 Binary files /dev/null and b/DemoApp.Abstracts.Windows/Secret.dll differ diff --git a/DemoApp.Abstracts.Windows/Secret64.dll b/DemoApp.Abstracts.Windows/Secret64.dll new file mode 100644 index 0000000..27888ba Binary files /dev/null and b/DemoApp.Abstracts.Windows/Secret64.dll differ diff --git a/DemoApp.Abstracts.Windows/Tls.cpp b/DemoApp.Abstracts.Windows/Tls.cpp new file mode 100644 index 0000000..ca9d60d --- /dev/null +++ b/DemoApp.Abstracts.Windows/Tls.cpp @@ -0,0 +1,12 @@ +#include + +thread_local int secret = 69; + +extern "C" +{ + __declspec(dllexport) int __cdecl GetThreadLocalInt() + { + std::cout << "Address of secret: " << &secret << std::endl; + return secret++; + } +} diff --git a/DemoApp.Abstracts.Windows/Tls.dll b/DemoApp.Abstracts.Windows/Tls.dll new file mode 100644 index 0000000..297eb23 Binary files /dev/null and b/DemoApp.Abstracts.Windows/Tls.dll differ diff --git a/DemoApp.Abstracts.Windows/Tls.exp b/DemoApp.Abstracts.Windows/Tls.exp new file mode 100644 index 0000000..cbe0388 Binary files /dev/null and b/DemoApp.Abstracts.Windows/Tls.exp differ diff --git a/DemoApp.Abstracts.Windows/Tls.lib b/DemoApp.Abstracts.Windows/Tls.lib new file mode 100644 index 0000000..33c243f Binary files /dev/null and b/DemoApp.Abstracts.Windows/Tls.lib differ diff --git a/DemoApp.Abstracts.Windows/Tls64.dll b/DemoApp.Abstracts.Windows/Tls64.dll new file mode 100644 index 0000000..67b9fe3 Binary files /dev/null and b/DemoApp.Abstracts.Windows/Tls64.dll differ diff --git a/DemoApp.MacOS/SampleDll.dylib b/DemoApp.MacOS/SampleDll.dylib new file mode 100644 index 0000000..ed93600 Binary files /dev/null and b/DemoApp.MacOS/SampleDll.dylib differ diff --git a/DemoApp.MacOS/Secret.dylib b/DemoApp.MacOS/Secret.dylib new file mode 100644 index 0000000..b243aef Binary files /dev/null and b/DemoApp.MacOS/Secret.dylib differ diff --git a/MemoryModule.Demo.sln b/MemoryModule.Demo.sln index b26a31d..dbe399f 100644 --- a/MemoryModule.Demo.sln +++ b/MemoryModule.Demo.sln @@ -7,7 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemoryModule", "MemoryModul EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "DemoApp\DemoApp.csproj", "{95A0C909-90EE-40A7-A6B6-F5901373E5C7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp.Linux", "DemoApp.Linux\DemoApp.Linux.csproj", "{B7B3F358-B30B-42B8-B72D-C65DDA16ACA7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp.Linux", "DemoApp.Linux\DemoApp.Linux.csproj", "{B7B3F358-B30B-42B8-B72D-C65DDA16ACA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp.Abstracts.Linux", "DemoApp.Abstracts.Linux\DemoApp.Abstracts.Linux.csproj", "{3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp.Abstracts.Windows", "DemoApp.Abstracts.Windows\DemoApp.Abstracts.Windows.csproj", "{584F22B7-4999-4229-AE6D-E2296652E3A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp.Abstracts.MacOS", "DemoApp.Abstracts.MacOS\DemoApp.Abstracts.MacOS.csproj", "{67FD47C3-5E5C-4956-A09B-1534FF9145EC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -55,6 +61,42 @@ Global {B7B3F358-B30B-42B8-B72D-C65DDA16ACA7}.Release|x64.Build.0 = Release|Any CPU {B7B3F358-B30B-42B8-B72D-C65DDA16ACA7}.Release|x86.ActiveCfg = Release|Any CPU {B7B3F358-B30B-42B8-B72D-C65DDA16ACA7}.Release|x86.Build.0 = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|x64.ActiveCfg = Debug|x64 + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|x64.Build.0 = Debug|x64 + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Debug|x86.Build.0 = Debug|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|Any CPU.Build.0 = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|x64.ActiveCfg = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|x64.Build.0 = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|x86.ActiveCfg = Release|Any CPU + {3F2A8EF3-DA2A-4A78-B665-903CDC0E7A4B}.Release|x86.Build.0 = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|x64.ActiveCfg = Debug|x64 + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|x64.Build.0 = Debug|x64 + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|x86.ActiveCfg = Debug|x86 + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Debug|x86.Build.0 = Debug|x86 + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|Any CPU.Build.0 = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|x64.ActiveCfg = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|x64.Build.0 = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|x86.ActiveCfg = Release|Any CPU + {584F22B7-4999-4229-AE6D-E2296652E3A6}.Release|x86.Build.0 = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|x64.ActiveCfg = Debug|x64 + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|x64.Build.0 = Debug|x64 + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Debug|x86.Build.0 = Debug|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|Any CPU.Build.0 = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|x64.ActiveCfg = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|x64.Build.0 = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|x86.ActiveCfg = Release|Any CPU + {67FD47C3-5E5C-4956-A09B-1534FF9145EC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MemoryModule/Abstractions/IBind.cs b/MemoryModule/Abstractions/IBind.cs new file mode 100644 index 0000000..1bc744e --- /dev/null +++ b/MemoryModule/Abstractions/IBind.cs @@ -0,0 +1,26 @@ +namespace MemoryModule.Abstractions +{ + public interface IBind + { + /// + /// Offset to affected address from the start of the module's memory. + /// + ulong AffectedAddress { get; } + /// + /// Module to look for the symbol. Empty string means the current module, null means to search everywhere. + /// + string ModuleName { get; } + /// + /// Symbol to look for. + /// + string SymbolName { get; } + /// + /// Symbol index in the target library. Optional. + /// + ulong? SymbolIndex { get; } + /// + /// An optional addend. + /// + ulong Addend { get; } + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/IFinalizer.cs b/MemoryModule/Abstractions/IFinalizer.cs new file mode 100644 index 0000000..6e05744 --- /dev/null +++ b/MemoryModule/Abstractions/IFinalizer.cs @@ -0,0 +1,10 @@ +namespace MemoryModule.Abstractions +{ + public interface IFinalizer + { + /// + /// Runs the finalizer. + /// + void Run(); + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/IInitializer.cs b/MemoryModule/Abstractions/IInitializer.cs new file mode 100644 index 0000000..989ae1b --- /dev/null +++ b/MemoryModule/Abstractions/IInitializer.cs @@ -0,0 +1,25 @@ +using System; + +namespace MemoryModule.Abstractions +{ + public interface IInitializer + { + /// + /// Runs the initializer. + /// + /// A boolean that indicates whether the initializer succeeded. + bool Run(); + /// + /// Runs the initializer with the specified arguments, which may or may not be loaded. + /// + /// Platform specific arguments + /// A boolean that indicates whether the initializer succeeded. + bool Run(params object[] args); + /// + /// Module-specific arguments required. + /// This is often an and an enum for PE binaries, and + /// int argc, char** argv for ELF modules. + /// + Type[] Arguments { get; } + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/INativeFunctions.cs b/MemoryModule/Abstractions/INativeFunctions.cs new file mode 100644 index 0000000..f8e5b0c --- /dev/null +++ b/MemoryModule/Abstractions/INativeFunctions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Abstractions +{ + /// + /// Provides low-level functions, such as memory mapping and module loading. + /// + public interface INativeFunctions + { + /// + /// Loads a native library from a file. + /// + /// The library's path. + /// A handle to the library. + IntPtr LoadLibrary(string name); + /// + /// Loads an exported symbol from a library. + /// + /// The library's handle, as provided by + /// The symbol's name. + /// The address of the symbol. + IntPtr GetSymbolFromLibrary(IntPtr handle, string name); + /// + /// Loads an exported symbol from a library. + /// + /// The library's handle, as provided by + /// Either a pointer to the symbol's string, or the index of the symbol in + /// the module's export table. + /// The address of the symbol. + IntPtr GetSymbolFromLibrary(IntPtr handle, IntPtr nameValue); + /// + /// Frees a native library. + /// + /// The library's handle + /// A boolean, indicating whether the operation succeeded. + bool FreeLibrary(IntPtr handle); + /// + /// Allocates a block of memory from the system pages. The memory should be zero-inited. + /// + /// A hinted address. The system should allocate here if possible. + /// The size of the allocated memory. + /// The protection of the newly allocated pages. + /// The newly allocated memory. + IntPtr VirtualAllocate(IntPtr hint, ulong size, MemoryProtection protection); + /// + /// Changes the protection of a range of pages. and must be a multiple + /// of the system page size. + /// + /// The starting address. + /// The size of the protected memory, in bytes. + /// The new protection. + /// A boolean, indicating whether the operation succeeded. + bool VirtualProtect(IntPtr addr, ulong size, MemoryProtection protection); + /// + /// Frees a region of memory allocated by . + /// + /// The address of the allocated region. + /// The size of the allocated region. Must be the original size requested + /// in , else the behavior is undefined. + /// A boolean, indicating whether the operation succeded. + bool VirtualFree(IntPtr addr, ulong size); + /// + /// Fills a region of bytes, starting from , with the byte specified in . + /// + /// The destination address + /// Number of bytes to fill + /// The bytes filled + /// The destination address + IntPtr FillMemory(IntPtr dest, byte ch, ulong size); + /// + /// Copies a region of bytes, from to + /// + /// The destination memory + /// The source memory + /// The size of the block of memory to copy + /// + IntPtr CopyMemory(IntPtr dest, IntPtr src, ulong size); + } +} diff --git a/MemoryModule/Abstractions/IRebase.cs b/MemoryModule/Abstractions/IRebase.cs new file mode 100644 index 0000000..4c806c1 --- /dev/null +++ b/MemoryModule/Abstractions/IRebase.cs @@ -0,0 +1,18 @@ +namespace MemoryModule.Abstractions +{ + public interface IRebase + { + /// + /// Offset to affected address from the start of the module's memory. + /// + ulong AffectedAddress { get; } + /// + /// An optional addend. + /// + ulong Addend { get; } + /// + /// Ignores the existing value in the current memory slot. + /// + bool IgnoreExistingValue { get; } + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/ISection.cs b/MemoryModule/Abstractions/ISection.cs new file mode 100644 index 0000000..c6ce4cf --- /dev/null +++ b/MemoryModule/Abstractions/ISection.cs @@ -0,0 +1,36 @@ +using System; + +namespace MemoryModule.Abstractions +{ + public interface ISection + { + /// + /// Relative offset of this section from the start of the module's memory. + /// + ulong MemoryOffset { get; } + /// + /// Size of the section when loaded in memory. + /// + ulong MemorySize { get; } + /// + /// Offset of this section from the start of the file. + /// + ulong FileOffset { get; } + /// + /// Size of section, on the original file. May be smaller than . + /// + ulong FileSize { get; } + /// + /// Protection of this section (Read, Write, Execute). + /// + MemoryProtection MemoryProtection { get; } + /// + /// Name of this section (if available). + /// + string Name { get; } + /// + /// Type of this section. + /// + SectionType Type { get; } + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/ISymbol.cs b/MemoryModule/Abstractions/ISymbol.cs new file mode 100644 index 0000000..1da1ddb --- /dev/null +++ b/MemoryModule/Abstractions/ISymbol.cs @@ -0,0 +1,23 @@ +using System; + +namespace MemoryModule.Abstractions +{ + /// + /// A symbol, as defined in the library. It may point to a variable, a function, or nothing at all. + /// + public interface ISymbol + { + /// + /// Name of the symbol + /// + string Name { get; } + /// + /// "Value" of the symbol, as stated in the library. + /// + ulong Value { get; } + /// + /// Real address of the symbol, in memory. if not applicable (such as ELF TLS symbols). + /// + IntPtr Address { get; } + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/Loader.cs b/MemoryModule/Abstractions/Loader.cs new file mode 100644 index 0000000..c6ca002 --- /dev/null +++ b/MemoryModule/Abstractions/Loader.cs @@ -0,0 +1,373 @@ +using MemoryModule.Formats.Elf; +using MemoryModule.Formats.Macho; +using MemoryModule.Formats.PE; +using MemoryModule.Tls; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Abstractions +{ + public unsafe static class Loader + { + /// + /// Maps a module's sections into memory. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void AllocateSections(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + // TO-DO: Better section-to page strategy. For now, it is assumed that all sections + // are mapped contiguously. + + var minAddress = ulong.MaxValue; + var maxAddress = ulong.MinValue; + + foreach (var section in module.Sections) + { + minAddress = Math.Min(section.MemoryOffset, minAddress); + maxAddress = Math.Max(section.MemoryOffset + section.MemorySize, maxAddress); + } + + // Assuming that we're on a sane machine whose page size is a power of 2. + var size = AlignValueUp(maxAddress - minAddress, (ulong)Environment.SystemPageSize); + + // If we want to subtract later, we must add first. + var truePreferredAddress = (module.PreferredAddress == IntPtr.Zero) ? 0 : (ulong)module.PreferredAddress + minAddress; + + // Map pages with write access first. + // We subtract from minAddress, as the first page's offset might not be zero! + var baseAddress = (byte*)infrastructure.VirtualAllocate((IntPtr)truePreferredAddress, size, MemoryProtection.Read | MemoryProtection.Write) - minAddress; + module.MemoryAddress = (IntPtr)baseAddress; + module.MemorySize = size; + + foreach (var section in module.Sections) + { + // Memory is copied. Mind the type casts, it's strict in C#. + infrastructure.CopyMemory((IntPtr)(baseAddress + section.MemoryOffset), (IntPtr)((ulong)module.FileAddress + section.FileOffset), section.FileSize); + // No need to zero the extra bytes. For most platforms, VirtualAllocate's native counterpart has + // already done that for us. + } + + module.AfterAllocation(); + } + + /// + /// Relocates the module symbols. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void PerformRebase(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + // C#'s integral types are well defined... I guess?? + unchecked + { + var delta = (ulong)module.MemoryAddress - (ulong)module.PreferredAddress; + foreach (var rebase in module.Rebases) + { + var addr = (byte**)((byte*)module.MemoryAddress + rebase.AffectedAddress); + if (rebase.IgnoreExistingValue) + { + *addr = (byte *)(delta + rebase.Addend); + } + else + { + *addr += delta + rebase.Addend; + } + } + } + module.AfterRebase(); + } + + /// + /// Binds the module's symbols to external targets. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void PerformBinding(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + var libraries = new Dictionary(); + module.ReferencedLibraryHandles = libraries; + + foreach (var reference in module.ReferencedLibraries) + { + // Key clash should never occur. + libraries.Add(reference, infrastructure.LoadLibrary(reference)); + } + + // To-Do: Lazy bindings, or not? + foreach (var binding in module.Bindings.Concat(module.LazyBindings)) + { + var addr = (byte**)((byte*)module.MemoryAddress + binding.AffectedAddress); + + if (binding.SymbolIndex != null) + { + var index = (ulong)binding.SymbolIndex; + // Module Name should be present. It's stupid to bind from an index out of nowhere, no linker + // would do that. + if (binding.ModuleName == null) + { + *addr = (byte*)module.Exports[(int)index].Address + binding.Addend; + } + else + { + *addr = (byte*)infrastructure.GetSymbolFromLibrary(libraries[binding.ModuleName], (IntPtr)index) + binding.Addend; + } + } + else + { + // null means searching from self, all deps, and then global scope. + if (binding.ModuleName == null) + { + // Look from self exports. + byte* symbol = (byte*)module.PrivateSymbols.FirstOrDefault(sym => sym.Name == binding.SymbolName)?.Address; + + // Look from deps + if (symbol == null) + { + foreach (var kvp in libraries) + { + symbol = (byte*)infrastructure.GetSymbolFromLibrary(kvp.Value, binding.SymbolName); + if (symbol == null) + { + break; + } + } + } + + // Last desperate call for RTLD_GLOBAL + if (symbol == null) + { + symbol = (byte*)infrastructure.GetSymbolFromLibrary(IntPtr.Zero, binding.SymbolName); + } + + if (symbol == null) + { + // Should throw something here. + } + + *addr = symbol + binding.Addend; + } + else if (binding.ModuleName != string.Empty) + { + *addr = (byte*)infrastructure.GetSymbolFromLibrary(libraries[binding.ModuleName], binding.SymbolName) + binding.Addend; + } + // Empty: Seaching from current module. This should not happen. + else + { + *addr = (byte*)module.Exports.FirstOrDefault(sym => sym.Name == binding.SymbolName).Address + binding.Addend; + } + } + } + + foreach (var binding in module.WeakBindings) + { + var addr = (byte**)((byte*)module.MemoryAddress + binding.AffectedAddress); + byte* symbol = null; + + if (binding.SymbolIndex != null) + { + var index = (ulong)binding.SymbolIndex; + if (binding.ModuleName == null) + { + *addr = (byte*)module.Exports[(int)index].Address + binding.Addend; + } + else + { + *addr = (byte*)infrastructure.GetSymbolFromLibrary(libraries[binding.ModuleName], (IntPtr)index) + binding.Addend; + } + } + else + { + // Look in our dependencies. + foreach (var kvp in libraries) + { + symbol = (byte*)infrastructure.GetSymbolFromLibrary(kvp.Value, binding.SymbolName) + binding.Addend; + if (symbol == null) + { + break; + } + } + + // Look from global symbols + if (symbol == null) + { + symbol = (byte*)infrastructure.GetSymbolFromLibrary(IntPtr.Zero, binding.SymbolName) + binding.Addend; + } + + // Finally, look at home, if someone else hasn't defined this symbol yet. + if (symbol == null) + { + symbol = (byte*)module.PrivateSymbols?.FirstOrDefault(sym => sym.Name == binding.SymbolName)?.Address; + } + + // Don't crash the app, just ignore. That's the whole point of weak binding. + if (symbol != null) + { + *addr = symbol + binding.Addend; + } + } + } + + // The module's ID is bound in this phase: + if (module.HasTls) + { + var realAddress = module.TlsImageAddress; + if (realAddress == IntPtr.Zero) + { + realAddress = (IntPtr)((ulong)module.MemoryAddress + module.TlsImageOffset); + } + module.TlsModuleId = TlsHandler.AssignModId(realAddress, module.TlsFileSize, module.TlsMemorySize); + + foreach (var modIdBindAddr in module.TlsModuleIdBindings) + { + var addr = (byte**)((ulong)module.MemoryAddress + modIdBindAddr); + *(IntPtr*)addr = (IntPtr)module.TlsModuleId; + } + + foreach (var tlsGetAddrBindAddr in module.TlsGetAddrBindings) + { + var addr = (byte**)((ulong)module.MemoryAddress + tlsGetAddrBindAddr); + *(IntPtr*)addr = Marshal.GetFunctionPointerForDelegate(TlsHandler.TlsGetAddr); + } + } + + module.AfterBinding(); + } + + /// + /// Protects the allocated pages. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void PerformPageProtection(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + // Do this only after rebasing, binding, and evil TLS redirection. + foreach (var section in module.Sections) + { + // Although our sections can theoretically be anywhere, sane binaries don't contain such pages. + infrastructure.VirtualProtect((IntPtr)((ulong)module.MemoryAddress + section.MemoryOffset), section.MemorySize, section.MemoryProtection); + } + } + + /// + /// Runs the module constructors, if any are available. + /// + /// The target module. + public static void PerformInitialization(Module module) + { + foreach (var init in module.Initializers) + { + init.Run(); + } + } + + /// + /// Runs the module destructors. + /// + /// The target module. + public static void PerformFinalization(Module module) + { + foreach (var fini in module.Finalizers) + { + fini.Run(); + } + } + + /// + /// Frees the module's referenced libraries. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void UnloadReferences(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + foreach (var kvp in module.ReferencedLibraryHandles) + { + infrastructure.FreeLibrary(kvp.Value); + } + } + + /// + /// Deallocate the library's sections. + /// + /// The target module. + /// The native functions (infrastructure) provided by the operating system. + public static void DeallocateSections(Module module, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + infrastructure.VirtualFree(module.MemoryAddress, module.MemorySize); + } + + /// + /// Detects the file type using its magic number and loads the + /// module. + /// + /// A pointer to the module's memory + /// A module object + public static Module Load(IntPtr ptr) + { + var magic = new List(_maxMagicLength); + + for (int i = 1; i <= _maxMagicLength; ++i) + { + magic.Add(((byte*)ptr)[i - 1]); + if (_moduleType.TryGetValue(magic.ToArray(), out Type type)) + { + return (Module)Activator.CreateInstance(type, ptr); + } + } + + throw new NotSupportedException("Image type not supported."); + } + + /// + /// Registers a new loader type. + /// + /// Type of the module format. + /// Magic number of the number, represented as a byte array. + public static void Register(byte[] magicNumber) where T : Module + { + _moduleType.Add(magicNumber, typeof(T)); + _maxMagicLength = Math.Max(_maxMagicLength, magicNumber.Length); + } + + // Some known formats here. + private static readonly Dictionary _moduleType = new Dictionary(new ByteArrayEqualityComparer()) + { + // 0x7F, 'E', 'L', 'F' + { new byte[]{ 0x7F, 0x45, 0x4C, 0x46 }, typeof(ElfModule) }, + // DOS header: 'M', 'Z' + { new byte[]{ 0x4D, 0x5A }, typeof(PeModule) }, + // 0xFEEDFACE 32 Bit Macho + { new byte[]{ 0xCE, 0xFA, 0xED, 0xFE }, typeof(MachoModule) }, + // 0xFEEDFACF 64 Bit Macho + { new byte[]{ 0xCF, 0xFA, 0xED, 0xFE }, typeof(MachoModule) }, + //MachoFat = 0xcafebabe, + //MachoFat64 = 0xcafebabf, + //Fat Macho not supported. + }; + + private static int _maxMagicLength = 4; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong AlignValueUp(ulong value, ulong alignment) + { + return (value + alignment - 1) & ~(alignment - 1); + } + } +} diff --git a/MemoryModule/Abstractions/MemoryProtection.cs b/MemoryModule/Abstractions/MemoryProtection.cs new file mode 100644 index 0000000..3361d76 --- /dev/null +++ b/MemoryModule/Abstractions/MemoryProtection.cs @@ -0,0 +1,16 @@ +using System; + +namespace MemoryModule.Abstractions +{ + /// + /// Platform independent memory protection flags. + /// This should be mapped to mprotect or VirtualProtect flags by implementations. + /// + [Flags] + public enum MemoryProtection + { + Read = 0x1, + Write = 0x2, + Execute = 0x4 + } +} \ No newline at end of file diff --git a/MemoryModule/Abstractions/Module.cs b/MemoryModule/Abstractions/Module.cs new file mode 100644 index 0000000..f7858ad --- /dev/null +++ b/MemoryModule/Abstractions/Module.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace MemoryModule.Abstractions +{ + public abstract class Module + { + /// + /// The module's file address in memory. + /// + public abstract IntPtr FileAddress { get; protected set; } + /// + /// The module's address when loaded in memory. + /// + public abstract IntPtr MemoryAddress { get; internal set; } + /// + /// The module's size when loaded in memory. + /// + public abstract ulong MemorySize { get; internal set; } + /// + /// The preferred address of a module. If successfully loaded here, + /// rebases can be ignored. + /// + public abstract IntPtr PreferredAddress { get; protected set; } + /// + /// The module's architecture. + /// + public abstract Architecture Architecture { get; protected set; } + /// + /// Parts of the module that must be mapped and copied into memory. + /// + public abstract IReadOnlyList Sections { get; protected set; } + /// + /// Locations that must be added to the base address of the module when loaded in memory. + /// + public abstract IReadOnlyList Rebases { get; protected set; } + /// + /// Symbols that must be resolved in the module before initialization. + /// + public abstract IReadOnlyList Bindings { get; protected set; } + /// + /// Symbols that can be lazily bound, if the implementation supports. + /// + public abstract IReadOnlyList LazyBindings { get; protected set; } + /// + /// Symbols that may or may not be bound. Useful for C++ template statics. + /// + public abstract IReadOnlyList WeakBindings { get; protected set; } + /// + /// Addresses that must be set to the current module ID. + /// + public abstract IReadOnlyList TlsModuleIdBindings { get; protected set; } + /// + /// Addresses that must be set to the managed TlsGetAddr implementation. + /// + public abstract IReadOnlyList TlsGetAddrBindings { get; protected set; } + /// + /// Size of the module's Tls region. + /// + public abstract ulong TlsMemorySize { get; protected set; } + /// + /// Size of the module's Tls image. + /// + public abstract ulong TlsFileSize { get; protected set; } + /// + /// The module ID assigned by the Loader. + /// + public abstract int TlsModuleId { get; internal set; } + /// + /// The module's TLS image address. + /// + public abstract IntPtr TlsImageAddress { get; protected set; } + /// + /// The module's TLS image region, relative to its memory address. + /// + public abstract ulong TlsImageOffset { get; protected set; } + /// + /// Checks whether this module has TLS. + /// + public virtual bool HasTls => TlsMemorySize != 0; + /// + /// Module initializers. Run when module finishes loading. + /// + public abstract IReadOnlyList Initializers { get; protected set; } + /// + /// Module finalizers. Run before module is unloaded. + /// + public abstract IReadOnlyList Finalizers { get; protected set; } + /// + /// List of exported symbols. This symbols are often accessible through dlsym. + /// + public abstract IReadOnlyList Exports { get; protected set; } + /// null on other platforms. + /// + public abstract IReadOnlyList PrivateSymbols { get; protected set; } + /// + /// List of referenced libraries. + /// + public abstract IReadOnlyList ReferencedLibraries { get; protected set; } + /// + /// Table of opened library handles. + /// + public abstract IReadOnlyDictionary ReferencedLibraryHandles { get; internal set; } + /// + /// Updates values after allocation. + /// + internal virtual void AfterAllocation() + { + + } + /// + /// Updates values after rebasing. + /// + internal virtual void AfterRebase() + { + + } + /// + /// Updates values after binding. + /// + internal virtual void AfterBinding() + { + + } + } +} diff --git a/MemoryModule/Abstractions/NativeFunctions.cs b/MemoryModule/Abstractions/NativeFunctions.cs new file mode 100644 index 0000000..7cff8e8 --- /dev/null +++ b/MemoryModule/Abstractions/NativeFunctions.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Abstractions +{ + /// + /// A base class for platform-specific native functions. + /// + public abstract class NativeFunctions : INativeFunctions + { + public abstract bool FreeLibrary(IntPtr handle); + public abstract IntPtr GetSymbolFromLibrary(IntPtr handle, string name); + public abstract IntPtr GetSymbolFromLibrary(IntPtr handle, IntPtr nameValue); + public abstract IntPtr LoadLibrary(string name); + public abstract IntPtr VirtualAllocate(IntPtr hint, ulong size, MemoryProtection protection); + public abstract bool VirtualFree(IntPtr addr, ulong size); + public abstract bool VirtualProtect(IntPtr addr, ulong size, MemoryProtection protection); + +#if STANDALONE + public abstract IntPtr CopyMemory(IntPtr dest, IntPtr src, ulong size); + public abstract IntPtr FillMemory(IntPtr dest, byte ch, ulong size); +#else + public IntPtr CopyMemory(IntPtr dest, IntPtr src, ulong size) + { + unsafe + { + Unsafe.CopyBlockUnaligned((void*)dest, (void*)src, (uint)size); + } + return dest; + } + + public IntPtr FillMemory(IntPtr dest, byte ch, ulong size) + { + unsafe + { + Unsafe.InitBlockUnaligned((void*)dest, ch, (uint)size); + } + return dest; + } +#endif + + /// + /// Gets the default set of native functions for the current platform. + /// + public static NativeFunctions Default { get; private set; } + + static NativeFunctions() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Default = new Windows.WindowsNativeFunctions(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Default = new Linux.LinuxNativeFunctions(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Default = new MacOS.MacNativeFunctions(); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} diff --git a/MemoryModule/Abstractions/SectionType.cs b/MemoryModule/Abstractions/SectionType.cs new file mode 100644 index 0000000..44472e1 --- /dev/null +++ b/MemoryModule/Abstractions/SectionType.cs @@ -0,0 +1,26 @@ +namespace MemoryModule.Abstractions +{ + public enum SectionType + { + /// + /// An unknown section. + /// + Unknown = 0, + /// + /// A section that usually contains executable code (Mapped as read, execute). + /// + Text = 1, + /// + /// A section that usually contains variables (Mapped as read, write). + /// + Data = 2, + /// + /// A section that usually contains const variables, such as C string literals (Mapped as read). + /// + DataConst = 3, + /// + /// Thread local variable sections. + /// + Tls = 4 + } +} \ No newline at end of file diff --git a/MemoryModule/AssemblyHandler/NativeMemoryCodeReader.cs b/MemoryModule/AssemblyHandler/NativeMemoryCodeReader.cs new file mode 100644 index 0000000..0f34d1b --- /dev/null +++ b/MemoryModule/AssemblyHandler/NativeMemoryCodeReader.cs @@ -0,0 +1,34 @@ +using Iced.Intel; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.AssemblyHandler +{ + class NativeMemoryCodeReader : CodeReader + { + private IntPtr _mem; + private ulong count; + + public NativeMemoryCodeReader(IntPtr position, ulong size) + { + _mem = position; + count = size; + } + + public override int ReadByte() + { + if (count == 0) + { + return -1; + } + + var b = Marshal.ReadByte(_mem); + _mem += 1; + --count; + + return b; + } + } +} diff --git a/MemoryModule/AssemblyHandler/UnsafeNativeMemoryCodeReader.cs b/MemoryModule/AssemblyHandler/UnsafeNativeMemoryCodeReader.cs new file mode 100644 index 0000000..f31a230 --- /dev/null +++ b/MemoryModule/AssemblyHandler/UnsafeNativeMemoryCodeReader.cs @@ -0,0 +1,32 @@ +using Iced.Intel; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.AssemblyHandler +{ + unsafe class UnsafeNativeMemoryCodeReader : CodeReader + { + private byte* _begin; + private readonly byte* _end; + + public UnsafeNativeMemoryCodeReader(byte* position, ulong size) + { + _begin = position; + _end = _begin + size; + } + + public override int ReadByte() + { + if (_begin == _end) + { + return -1; + } + + var temp = *_begin; + ++_begin; + + return temp; + } + } +} diff --git a/MemoryModule/ByteArrayEqualityComparer.cs b/MemoryModule/ByteArrayEqualityComparer.cs new file mode 100644 index 0000000..c1b968d --- /dev/null +++ b/MemoryModule/ByteArrayEqualityComparer.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MemoryModule +{ + class ByteArrayEqualityComparer : EqualityComparer + { + public override bool Equals(byte[] x, byte[] y) + { + return x.SequenceEqual(y); + } + + public override int GetHashCode(byte[] obj) + { + int result = 0; + unchecked + { + foreach (byte b in obj) + { + result <<= 5; + result += b; + } + } + return result; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfAbi.cs b/MemoryModule/Formats/Elf/ElfAbi.cs new file mode 100644 index 0000000..d01bd61 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfAbi.cs @@ -0,0 +1,11 @@ +namespace MemoryModule.Formats.Elf +{ + internal enum ElfAbi : byte + { + /// + /// It is often set to 0 regardless of the target platform. + /// + SystemV = 0x00, + Linux = 0x03 + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfArchitecture.cs b/MemoryModule/Formats/Elf/ElfArchitecture.cs new file mode 100644 index 0000000..780aa4b --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfArchitecture.cs @@ -0,0 +1,12 @@ +namespace MemoryModule.Formats.Elf +{ + internal enum ElfArchitecture : ushort + { + None = 0x00, + x86 = 0x03, + ARM = 0x28, + IA_64 = 0x32, + x86_64 = 0x3E, + ARM_64 = 0xB7 + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfDynamicSectionArray.cs b/MemoryModule/Formats/Elf/ElfDynamicSectionArray.cs new file mode 100644 index 0000000..7b33ccc --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfDynamicSectionArray.cs @@ -0,0 +1,30 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfDynamicSectionArray : ElfManagedArray + { + // A null ElfDynamicSectionItem marks the end of the array. + public ElfDynamicSectionArray(byte* data, ulong offset) : base(data, offset, 0) + { + ulong _count = 0; + while (((ElfDynamicSectionItemNative *)_first)[_count].Tag != (UIntPtr)ElfDynamicSectionItemType.Null) + { + ++_count; + } + + // Allows accessing the last element. + ++_count; + + Count = _count; + } + + protected override ElfDynamicSectionItem Construct(void* ptr) + { + return new ElfDynamicSectionItem(_memory, (ElfDynamicSectionItemNative*)ptr); + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfDynamicSectionItem.cs b/MemoryModule/Formats/Elf/ElfDynamicSectionItem.cs new file mode 100644 index 0000000..2e3fb19 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfDynamicSectionItem.cs @@ -0,0 +1,32 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfDynamicSectionItem + { + private byte* _memory; + private ElfDynamicSectionItemNative* _obj; + + public ElfDynamicSectionItemType Tag => (ElfDynamicSectionItemType)_obj->Tag; + public UIntPtr Pointer => _obj->ValueOrPtr.Ptr; + public ulong Value => (ulong)_obj->ValueOrPtr.Value; + + public ElfDynamicSectionItem(byte* memory, ElfDynamicSectionItemNative* ptr) + { + _memory = memory; + _obj = ptr; + } + + public override string ToString() + { + return +$@"ELF Dynamic Section: +- Tag: {Tag} +- Value/Pointer: 0x{Value:x} +"; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfDynamicSectionItemNative.cs b/MemoryModule/Formats/Elf/ElfDynamicSectionItemNative.cs new file mode 100644 index 0000000..1d77064 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfDynamicSectionItemNative.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Sequential)] + internal struct ElfDynamicSectionItemNative + { + [StructLayout(LayoutKind.Explicit)] + public struct DummyUnion + { + [FieldOffset(0)] + public UIntPtr Value; + [FieldOffset(0)] + public UIntPtr Ptr; + } + + public UIntPtr Tag; + public DummyUnion ValueOrPtr; + } +} diff --git a/MemoryModule/Formats/Elf/ElfDynamicSectionItemType.cs b/MemoryModule/Formats/Elf/ElfDynamicSectionItemType.cs new file mode 100644 index 0000000..a9ecdc6 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfDynamicSectionItemType.cs @@ -0,0 +1,39 @@ +namespace MemoryModule.Formats.Elf +{ + internal enum ElfDynamicSectionItemType : uint + { + Null = 0, + Needed = 1, + PltRelSz = 2, + PltGot = 3, + Hash = 4, + StrTab = 5, + SymTab = 6, + Rela = 7, + RelaSz = 8, + RelaEnt = 9, + StrSz = 10, + SymEnt = 11, + Init = 12, + Fini = 13, + SoName = 14, + RPath = 15, + Symbolic = 16, + Rel = 17, + RelSz = 18, + RelEnt = 19, + PltRel = 20, + Debug = 21, + TextRel = 22, + JmpRel = 23, + BindNow = 24, + InitArray = 25, + FiniArray = 26, + InitArraySz = 27, + FiniArraySz = 28, + LoOs = 0x60000000, + HiOs = 0x6fffffff, + LoProc = 0x70000000, + HiProc = 0x7fffffff, + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfEndianess.cs b/MemoryModule/Formats/Elf/ElfEndianess.cs new file mode 100644 index 0000000..cc386a1 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfEndianess.cs @@ -0,0 +1,8 @@ +namespace MemoryModule.Formats.Elf +{ + internal enum ElfEndianess : byte + { + Little = 1, + Big = 2 + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfFinalizer.cs b/MemoryModule/Formats/Elf/ElfFinalizer.cs new file mode 100644 index 0000000..35680b0 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfFinalizer.cs @@ -0,0 +1,31 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + class ElfFinalizer : IFinalizer + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void FiniDelegate(); + + private FiniDelegate _del; + + public IntPtr Address { get; internal set; } + + public void Run() + { + _del = Marshal.GetDelegateForFunctionPointer(Address); + _del(); + } + + internal ElfFinalizer(IntPtr del) + { + Address = del; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfHeader.cs b/MemoryModule/Formats/Elf/ElfHeader.cs new file mode 100644 index 0000000..5980e67 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfHeader.cs @@ -0,0 +1,80 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfHeader + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void InitDelegate(int argc, byte** argv, byte** envp); + + private static readonly byte[] MagicSymbol = { 0x7F, 0x45, 0x4c, 0x46 }; + + private byte* _memory; + private ElfHeaderNative* obj; + + public readonly ElfProgramHeaderArray ProgramHeaders; + public readonly ElfSectionHeaderArray SectionHeaders; + + public readonly ElfSectionHeader DynamicSectionHeader; + + private readonly Dictionary _dependencyHandles = new Dictionary(); + + public ElfArchitecture Architecture => obj->Machine; + + public ElfHeader(byte* ptr) + { + _memory = ptr; + obj = (ElfHeaderNative*)ptr; + + if ((obj->Magic[0] != MagicSymbol[0]) + || (obj->Magic[1] != MagicSymbol[1]) + || (obj->Magic[2] != MagicSymbol[2]) + || (obj->Magic[3] != MagicSymbol[3])) + { + throw new InvalidOperationException("Not a valid ELF header."); + } + + ProgramHeaders = new ElfProgramHeaderArray(ptr, (ulong)obj->ProgramHeaderOffset, obj->ProgramHeaderCount); + SectionHeaders = new ElfSectionHeaderArray(ptr, (ulong)obj->SectionHeaderOffset, obj->SectionHeaderCount, obj->SectionHeaderStringIndex); + + DynamicSectionHeader = SectionHeaders.FirstOrDefault(header => header.Type == ElfSectionHeaderType.Dynamic); + } + + public byte* GetAddress(ulong Offset = 0) + { + return _memory + Offset; + } + + public override string ToString() + { + return +$@"ELF Header at: 0x{(ulong)_memory:x} +- Pointer class: {obj->Class} +- Endian: {obj->Endianess} +- ELF Version: {obj->Version} +- ABI: {obj->OsAbi} +- ABI Version: {obj->AbiVersion} +- Object type: {obj->Type} +- Instruction set: {obj->Machine} +- ELF Version: {obj->ElfVersion} +- Entry Point Address: 0x{(ulong)obj->Entry:x} +- Program Header Table Location: 0x{(ulong)obj->ProgramHeaderOffset:x} +- Section Header Table Location: 0x{(ulong)obj->SectionHeaderOffset:x} +- Flags: {obj->ArchitectureDependentFlags} +- Header size: {obj->ElfHeaderSize} +- Program Header Table Entry Size: 0x{(ulong)obj->ProgramHeaderSize:x} +- Program Header Table Entry Count: {obj->ProgramHeaderCount} +- Section Header Table Entry Size: 0x{(ulong)obj->SectionHeaderSize:x} +- Section Header Table Entry Count: {obj->SectionHeaderCount} +- Section Header Table Name Index: {obj->SectionHeaderStringIndex} +"; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfHeaderNative.cs b/MemoryModule/Formats/Elf/ElfHeaderNative.cs new file mode 100644 index 0000000..d6ff34b --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfHeaderNative.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct ElfHeaderNative + { + public fixed byte Magic[4]; + public ElfPointerClass Class; + public ElfEndianess Endianess; + public byte Version; + public ElfAbi OsAbi; + public byte AbiVersion; + public fixed byte Padding[7]; + public ElfObjectType Type; + public ElfArchitecture Machine; + public int ElfVersion; + public IntPtr Entry; + public IntPtr ProgramHeaderOffset; + public IntPtr SectionHeaderOffset; + public int ArchitectureDependentFlags; + public ushort ElfHeaderSize; + public ushort ProgramHeaderSize; + public ushort ProgramHeaderCount; + public ushort SectionHeaderSize; + public ushort SectionHeaderCount; + public ushort SectionHeaderStringIndex; + } +} diff --git a/MemoryModule/Formats/Elf/ElfInitializer.cs b/MemoryModule/Formats/Elf/ElfInitializer.cs new file mode 100644 index 0000000..0a369ec --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfInitializer.cs @@ -0,0 +1,83 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + class ElfInitializer : IInitializer + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void InitDelegate(int argc, byte** argv, byte** envp); + + private InitDelegate _del; + + public Type[] Arguments { get; } = new Type[] { typeof(int) /*argc*/, typeof(string[]) /*argv*/, typeof(string[]) /*envp*/ }; + + public IntPtr Address { get; internal set; } + + private static readonly string[] _argvString; + private static readonly string[] _envpString; + + static ElfInitializer() + { + _argvString = Environment.GetCommandLineArgs(); + _envpString = Environment.GetEnvironmentVariables() + .Cast() + .Select(x => $"{x.Key}={x.Value}") + .ToArray(); + } + + public bool Run() + { + return RunInternal(_argvString.Length, _argvString, _envpString); + } + + public bool Run(params object[] args) + { + if (args.Length != Arguments.Length) + { + return false; + } + + for (int i = 0; i < args.Length; ++i) + { + if (!Arguments[i].IsAssignableFrom(args[i].GetType())) + { + return false; + } + } + + return RunInternal((int)args[0], (string[])args[1], (string[])args[2]); + } + + private unsafe bool RunInternal(int argc, string[] argv, string[] envp) + { + _del = Marshal.GetDelegateForFunctionPointer(Address); + + var argvArr = argv.Select(str => Marshal.StringToHGlobalAnsi(str)).ToArray(); + var envpArr = argv.Select(str => Marshal.StringToHGlobalAnsi(str)).Concat(new[] { IntPtr.Zero }).ToArray(); + + fixed (IntPtr* argvPtr = &argvArr[0]) + fixed (IntPtr* envpPtr = &envpArr[0]) + { + _del(argc, (byte**)argvPtr, (byte**)envpPtr); + } + + foreach (var ptr in argvArr.Concat(envpArr.Reverse().Skip(1))) + { + Marshal.FreeHGlobal(ptr); + } + + return true; + } + + internal ElfInitializer(IntPtr del) + { + Address = del; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfManagedArray.cs b/MemoryModule/Formats/Elf/ElfManagedArray.cs new file mode 100644 index 0000000..b65e159 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfManagedArray.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal abstract unsafe class ElfManagedArray : IEnumerable, IEnumerable + where TNative: struct + where TManaged : class + { + private static readonly ulong _nativeSize = (ulong)Marshal.SizeOf(); + + private ulong _count; + protected byte* _memory; + protected byte* _first; + + protected TManaged[] _managed; + + public ElfManagedArray(byte* data, ulong offset, ulong count) + { + _memory = data; + _first = data + offset; + _count = count; + _managed = new TManaged[count]; + } + + public TManaged this[ulong index] + { + get + { + if (index > _count) + { + throw new IndexOutOfRangeException($"{index} is greater than array range {_count}"); + } + + return _managed[index] = _managed[index] ?? Construct(_first + index * _nativeSize); + } + } + + public ulong Count + { + get => _count; + protected set + { + _count = value; + _managed = new TManaged[_count]; + } + } + + public IEnumerator GetEnumerator() + { + for (ulong i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (ulong i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + protected abstract TManaged Construct(void* ptr); + } +} diff --git a/MemoryModule/Formats/Elf/ElfModule.cs b/MemoryModule/Formats/Elf/ElfModule.cs new file mode 100644 index 0000000..c54aae8 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfModule.cs @@ -0,0 +1,303 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + unsafe class ElfModule : Module + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate IntPtr TlsGetAddrProc(IntPtr TlvIndex); + + public override IntPtr FileAddress { get; protected set; } + public override IntPtr MemoryAddress { get; internal set; } + public override ulong MemorySize { get; internal set; } + public override IntPtr PreferredAddress { get; protected set; } + public override Architecture Architecture { get; protected set; } + public override IReadOnlyList Sections { get; protected set; } + public override IReadOnlyList Rebases { get; protected set; } + public override IReadOnlyList Bindings { get; protected set; } + public override IReadOnlyList LazyBindings { get; protected set; } + public override IReadOnlyList WeakBindings { get; protected set; } + public override IReadOnlyList Initializers { get; protected set; } + public override IReadOnlyList Finalizers { get; protected set; } + public override IReadOnlyList Exports { get; protected set; } + public override IReadOnlyList PrivateSymbols { get; protected set; } + public override IReadOnlyList ReferencedLibraries { get; protected set; } + public override IReadOnlyDictionary ReferencedLibraryHandles { get; internal set; } + public override ulong TlsMemorySize { get; protected set; } + public override ulong TlsFileSize { get; protected set; } + public override int TlsModuleId { get; internal set; } + public override IReadOnlyList TlsModuleIdBindings { get; protected set; } + public override IReadOnlyList TlsGetAddrBindings { get; protected set; } + public override IntPtr TlsImageAddress { get; protected set; } + public override ulong TlsImageOffset { get; protected set; } + + private ElfHeader _header; + + public ElfModule(IntPtr handle) : this((byte*)handle) + { + + } + + public ElfModule(byte* data) + { + FileAddress = (IntPtr)data; + _header = new ElfHeader(data); + + Architecture = ConvertArchitecture(_header.Architecture); + + // LOAD type program headers are equivalent to sections. + Sections = _header.ProgramHeaders.Where(header => header.Type == ElfProgramHeaderType.Load).Cast().ToList(); + + // Referenced libraries: + var dynSection = _header.DynamicSectionHeader; + var items = dynSection.DynamicSectionItems.ToList(); + + var strTable = items.First(item => item.Tag == ElfDynamicSectionItemType.StrTab); + + ReferencedLibraries = items + .Where(item => item.Tag == ElfDynamicSectionItemType.Needed) + .Select(item => Marshal.PtrToStringAnsi((IntPtr)_header.GetAddress((ulong)strTable.Pointer + item.Value))) + .ToList(); + + // Exports. + var symbolTable = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.SymTab); + var symbolTableSection = _header.SectionHeaders.First(header => header.Offset == symbolTable.Value); + + var symbolArray = new ElfSymbolArray(_header.GetAddress(symbolTableSection.Offset), 0, symbolTableSection.Size / symbolTableSection.EntrySize); + var stringTable = new ElfStringTable(_header.GetAddress(), strTable.Value); + + foreach (var symbol in symbolArray) + { + symbol.ResolveName(stringTable); + } + + Exports = symbolArray.Where(sym => sym.Binding == ElfSymbolBinding.Global).ToList(); + + PrivateSymbols = symbolArray.ToList(); + + // Relocations, binds, and weak binds. + var relaTable = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.Rela); + var relaSize = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.RelaSz); + var relaEntrySize = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.RelaEnt); + + var relaArr = relaTable == null ? Enumerable.Empty() : + new ElfRelaArray(_header.GetAddress(), relaTable.Value, relaSize.Value / relaEntrySize.Value); + + var jumpRel = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.JmpRel); + var jumpRelaArr = Enumerable.Empty(); + + if (jumpRel != null) + { + var jumpRelSection = _header.SectionHeaders.FirstOrDefault(header => header.Offset == jumpRel.Value); + jumpRelaArr = new ElfRelaArray(_header.GetAddress(), jumpRelSection.Offset, jumpRelSection.Size / jumpRelSection.EntrySize); + } + + var _rebases = new List(); + var _bindings = new List(); + var _lazyBindings = new List(); + var _weakBindings = new List(); + + var tlsAddresses = new List(); + var tlsGetAddrAddress = new List(); + + foreach (var rela in relaArr.Concat(jumpRelaArr)) + { + if (rela.Type.IsNone()) + { + continue; + } + else if (rela.Type.IsRelative()) + { + _rebases.Add(new GenericRebase() + { + AffectedAddress = rela.Offset, + Addend = rela.Addend, + IgnoreExistingValue = true + }); + } + else if (rela.DependsOnSymbol()) + { + var sym = symbolArray[rela.Symbol]; + + // Magic symbol provided by glibc for TLS support. + if (sym.Name == "__tls_get_addr") + { + tlsGetAddrAddress.Add(rela.Offset); + continue; + } + + // To-Do: JumpSlots are actually lazy binds, right? + var bind = new GenericBind() + { + AffectedAddress = rela.Offset, + // ELF sadly doesn't give us this info. + ModuleName = null, + SymbolName = sym.Name, + Addend = rela.Addend + }; + + if (sym.Binding != ElfSymbolBinding.Weak) + { + _bindings.Add(bind); + } + else + { + _weakBindings.Add(bind); + } + } + // These rebases serves TLS. + // ModID will be handled by the Loader. + // TLS offset will be baked into the assembly right in this phase. + // Furthermore, a special symbol, __tls_get_addr, must be trapped by the runtime + // in this phase, rather than allowing it to resolve the glibc's default implementation. + else if (rela.Type.IsModule()) + { + //*affected = (void*)module.map.TlsModuleId; + tlsAddresses.Add(rela.Offset); + } + else if (rela.Type.IsOffset()) + { + // var currentSymbol = module.symbolArray[rela.Symbol]; + // *affected = (void*)currentSymbol.Value; + var bind = new GenericBind() + { + AffectedAddress = rela.Offset, + ModuleName = string.Empty, + SymbolName = symbolArray[rela.Symbol].Name, + Addend = rela.Addend + }; + + _bindings.Add(bind); + } + else + { + throw new NotImplementedException($"Unimplemented relocation type: {rela.Type}"); + } + } + + Rebases = _rebases; + Bindings = _bindings; + LazyBindings = _lazyBindings; + WeakBindings = _weakBindings; + + TlsModuleIdBindings = tlsAddresses; + TlsGetAddrBindings = tlsGetAddrAddress; + + // TLS applies to this module: + if (TlsModuleIdBindings.Count != 0) + { + var tlsSection = _header.ProgramHeaders.First(header => header.Type == ElfProgramHeaderType.ThreadLocalStorage); + TlsImageOffset = tlsSection.MemoryOffset; + TlsFileSize = tlsSection.FileSize; + TlsMemorySize = tlsSection.MemorySize; + } + + // That's all we know during construction. + } + + private static IntPtr TlsGetAddr(IntPtr TlvIndex) + { + Console.WriteLine("Boo!"); + return (IntPtr)6969; + } + + // Here, rebase should be complete, and MemoryAddress should be valid. + internal override void AfterRebase() + { + // Now symbols have their correct addresses. + foreach (ElfSymbol sym in PrivateSymbols) + { + if (sym.Type != ElfSymbolType.TLS && sym.Value != 0) + { + sym.Address = (IntPtr)(sym.Value + (ulong)MemoryAddress); + } + else + { + sym.Address = (IntPtr)sym.Value; + } + } + + var dynSection = _header.DynamicSectionHeader; + var items = dynSection.DynamicSectionItems.ToList(); + + // Initializers. + var initItem = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.Init); + var initArrItem = items.First(item => item.Tag == ElfDynamicSectionItemType.InitArray); + var initArrSizeItem = items.First(item => item.Tag == ElfDynamicSectionItemType.InitArraySz); + var initArrCount = initArrSizeItem.Value / (ulong)sizeof(IntPtr); + + var init = new List(); + init.Add(new ElfInitializer((IntPtr)((ulong)MemoryAddress + initItem.Value))); + + var initPtrs = new IntPtr[initArrCount]; + + // Must be the real codeBase, not the file. + Marshal.Copy((IntPtr)((ulong)MemoryAddress + initArrItem.Value), initPtrs, 0, (int)initArrCount); + + foreach (var initPtr in initPtrs) + { + // These functions are called AFTER relocation, therefore, + // they must not be manually "relocated" by adding to the module.codeBase + init.Add(new ElfInitializer(initPtr)); + } + + Initializers = init; + + // Finalizers + var finiItem = items.FirstOrDefault(item => item.Tag == ElfDynamicSectionItemType.Fini); + var finiArrItem = items.First(item => item.Tag == ElfDynamicSectionItemType.FiniArray); + var finiArrSizeItem = items.First(item => item.Tag == ElfDynamicSectionItemType.FiniArraySz); + var finiArrCount = finiArrSizeItem.Value / (ulong)sizeof(IntPtr); + + var fini = new List(); + fini.Add(new ElfFinalizer((IntPtr)((ulong)MemoryAddress + finiItem.Value))); + + var finiPtrs = new IntPtr[finiArrCount]; + + // Must be the real codeBase, not the file. + Marshal.Copy((IntPtr)((ulong)MemoryAddress + finiArrItem.Value), finiPtrs, 0, (int)finiArrCount); + + foreach (var finiPtr in finiPtrs) + { + // These functions are called AFTER relocation, therefore, + // they must not be manually "relocated" by adding to the module.codeBase + fini.Add(new ElfFinalizer(finiPtr)); + } + + Finalizers = fini; + + //// Resolve __tls_get_addr as the normal bindings won't. + //var ptrTlsGetAddr = Marshal.GetFunctionPointerForDelegate(TlsGetAddr); + //foreach (var offset in TlsGetAddrBindings) + //{ + // var addr = (byte**)((ulong)MemoryAddress + offset); + // *addr = (byte*)ptrTlsGetAddr; + // Console.WriteLine($"Address at 0x{(ulong)addr:x} bound to TlsGetAddr"); + //} + + TlsImageAddress = (IntPtr)((ulong)MemoryAddress + TlsImageOffset); + } + + private static Architecture ConvertArchitecture(ElfArchitecture architecture) + { + switch (architecture) + { + case ElfArchitecture.ARM: + return Architecture.Arm; + case ElfArchitecture.ARM_64: + return Architecture.Arm64; + case ElfArchitecture.x86_64: + return Architecture.X64; + case ElfArchitecture.x86: + return Architecture.X86; + default: + throw new NotSupportedException("Unsupported architecture."); + } + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfObjectType.cs b/MemoryModule/Formats/Elf/ElfObjectType.cs new file mode 100644 index 0000000..d5fa26f --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfObjectType.cs @@ -0,0 +1,15 @@ +namespace MemoryModule.Formats.Elf +{ + internal enum ElfObjectType : ushort + { + None = 0x00, + Rel = 0x01, + Exec = 0x02, + Dyn = 0x03, + Core = 0x04, + LoOS = 0xFE00, + HiOS = 0xFEFF, + LoProc = 0xFF00, + HiProc = 0xFFFF + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfPointerClass.cs b/MemoryModule/Formats/Elf/ElfPointerClass.cs new file mode 100644 index 0000000..9fe8b0a --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfPointerClass.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + public enum ElfPointerClass : byte + { + Pointer32 = 1, + Pointer64 = 2 + } +} diff --git a/MemoryModule/Formats/Elf/ElfProgramHeader.cs b/MemoryModule/Formats/Elf/ElfProgramHeader.cs new file mode 100644 index 0000000..22c0603 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfProgramHeader.cs @@ -0,0 +1,95 @@ +using MemoryModule.Abstractions; +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + unsafe class ElfProgramHeader : ISection + { + private byte* memory; + private ElfProgramHeaderNative* obj; + + public ElfProgramHeaderType Type + { + get => (Environment.Is64BitProcess) ? obj->Data64.Type : obj->Data32.Type; + } + + [Obsolete("Use FileOffset instead")] + public ulong Offset => FileOffset; + + [Obsolete("Use MemoryOffset instead")] + public ulong VirtualAddress => MemoryOffset; + + public ulong Align + { + get => Environment.Is64BitProcess ? (ulong)obj->Data64.Align : obj->Data32.Align; + } + + public ElfProgramHeaderLoadFlags Flags => (ElfProgramHeaderLoadFlags)(Environment.Is64BitProcess ? obj->Data64.Flags : obj->Data32.Flags); + + #region ISection + public ulong MemoryOffset => Environment.Is64BitProcess ? (ulong)obj->Data64.VirtualAddress : obj->Data32.VirtualAddress; + + public ulong MemorySize => Environment.Is64BitProcess ? (ulong)obj->Data64.MemorySize : obj->Data32.MemorySize; + + public ulong FileOffset => Environment.Is64BitProcess ? (ulong)obj->Data64.Offset : obj->Data32.Offset; + + public ulong FileSize => Environment.Is64BitProcess ? (ulong)obj->Data64.FileSize : obj->Data32.FileSize; + + public MemoryProtection MemoryProtection => ElfLoadFlagsToMemoryProtection(Flags); + + public string Name => null; + + SectionType ISection.Type => SectionType.Unknown; + #endregion + + public ElfProgramHeader(byte* memory, ElfProgramHeaderNative* obj) + { + this.memory = memory; + this.obj = obj; + } + + private MemoryProtection ElfLoadFlagsToMemoryProtection(ElfProgramHeaderLoadFlags flags) + { + var result = (MemoryProtection)0; + + if (flags.HasFlag(ElfProgramHeaderLoadFlags.Execute)) + { + result |= MemoryProtection.Execute; + } + + if (flags.HasFlag(ElfProgramHeaderLoadFlags.Read)) + { + result |= MemoryProtection.Read; + } + + if (flags.HasFlag(ElfProgramHeaderLoadFlags.Write)) + { + result |= MemoryProtection.Write; + } + + return result; + } + + public override string ToString() + { + return Environment.Is64BitProcess ? +$@"ELF Program Header at: 0x{(ulong)obj:x}, owned by 0x{(ulong)memory:x} +- Type: {obj->Data64.Type}, +- Size on file: 0x{(ulong)obj->Data64.Offset:x}..0x{(obj->Data64.Offset.ToUInt64() + obj->Data64.FileSize.ToUInt64()):x}, {obj->Data64.FileSize} bytes. +- Virtual address: 0x{(ulong)obj->Data64.VirtualAddress:x}..0x{(obj->Data64.VirtualAddress.ToUInt64() + obj->Data64.MemorySize.ToUInt64()):x} +- Flags: {obj->Data64.Flags} +- Alignment: {obj->Data64.Align} +" : +$@"ELF Program Header at: 0x{(ulong)obj:x}, owned by 0x{(ulong)memory:x} +- Type: {obj->Data32.Type}, +- Size on file: 0x{(ulong)obj->Data32.Offset:x}..0x{(obj->Data32.Offset + obj->Data32.FileSize):x}, {obj->Data64.FileSize} bytes. +- Virtual address: 0x{(ulong)obj->Data32.VirtualAddress:x}..0x{(obj->Data32.VirtualAddress + obj->Data32.MemorySize):x} +- Flags: {obj->Data32.Flags} +- Alignment: {obj->Data32.Align} +"; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfProgramHeaderArray.cs b/MemoryModule/Formats/Elf/ElfProgramHeaderArray.cs new file mode 100644 index 0000000..b22732d --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfProgramHeaderArray.cs @@ -0,0 +1,52 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace MemoryModule.Formats.Elf +{ + unsafe class ElfProgramHeaderArray : IReadOnlyList + { + private byte* _memory; + private ElfProgramHeaderNative* _first; + private int _count; + + public ElfProgramHeaderArray(byte* data, ulong offset, int count) + { + _memory = data; + _first = (ElfProgramHeaderNative*)(data + offset); + _count = count; + } + + public ElfProgramHeader this[int index] + { + get + { + if (index >= _count) + { + throw new IndexOutOfRangeException($"Index {index} out of range {_count}"); + } + + return new ElfProgramHeader(_memory, _first + index); + } + } + + public int Count => _count; + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfProgramHeaderLoadFlags.cs b/MemoryModule/Formats/Elf/ElfProgramHeaderLoadFlags.cs new file mode 100644 index 0000000..c258795 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfProgramHeaderLoadFlags.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [Flags] + public enum ElfProgramHeaderLoadFlags + { + Execute = 0x1, + Write = 0x2, + Read = 0x4, + } +} diff --git a/MemoryModule/Formats/Elf/ElfProgramHeaderNative.cs b/MemoryModule/Formats/Elf/ElfProgramHeaderNative.cs new file mode 100644 index 0000000..31e579e --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfProgramHeaderNative.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Explicit)] + public struct ElfProgramHeaderNative + { + // As the different between 8 bytes and + // 4 bytes has been addressed using UIntPtr's, + // the program's header should be correct, even on 32-bit mode. + + // The alignment is retarded. + // For the 32-bit data layout, we must stil use + // uint's, as the 64-bit's alignment might make + // pointers grow too big. + // For the 64 bit, we MUST use UIntPtr to make + // sure they shrink when running on x86. + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct DataLayout32 + { + public ElfProgramHeaderType Type; + public uint Offset; + public uint VirtualAddress; + public uint PhysicalAddress; + public uint FileSize; + public uint MemorySize; + public uint Flags; + public uint Align; + } + + [StructLayout(LayoutKind.Sequential)] + public struct DataLayout64 + { + public ElfProgramHeaderType Type; + public uint Flags; + public UIntPtr Offset; + public UIntPtr VirtualAddress; + public UIntPtr PhysicalAddress; + public UIntPtr FileSize; + public UIntPtr MemorySize; + public UIntPtr Align; + } + + [FieldOffset(0)] + public DataLayout32 Data32; + + [FieldOffset(0)] + public DataLayout64 Data64; + } +} diff --git a/MemoryModule/Formats/Elf/ElfProgramHeaderType.cs b/MemoryModule/Formats/Elf/ElfProgramHeaderType.cs new file mode 100644 index 0000000..1202158 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfProgramHeaderType.cs @@ -0,0 +1,18 @@ +namespace MemoryModule.Formats.Elf +{ + public enum ElfProgramHeaderType : uint + { + Null = 0x0, + Load = 0x1, + Dynamic = 0x2, + Interpreter = 0x3, + Note = 0x4, + ShLib = 0x5, + ProgramHeaderTable = 0x6, + ThreadLocalStorage = 0x7, + LoOS = 0x60000000, + HiOS = 0x6FFFFFFF, + LoProc = 0x70000000, + HiProc = 0x7FFFFFFF, + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfRela.cs b/MemoryModule/Formats/Elf/ElfRela.cs new file mode 100644 index 0000000..a970232 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfRela.cs @@ -0,0 +1,69 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + public unsafe class ElfRela + { + private byte* _memory; + private ElfRelaNative* _obj; + + public ulong Offset => (ulong)_obj->Offset; + public ElfRelocationType Type + { + get + { + if (Environment.Is64BitProcess) + { + return new ElfRelocationType((ulong)_obj->Info & 0xffffffff); + } + else + { + return new ElfRelocationType((ulong)_obj->Info & 0xff); + } + } + } + + public ulong Symbol + { + get + { + if (Environment.Is64BitProcess) + { + return (ulong)_obj->Info >> 32; + } + else + { + return (ulong)_obj->Info >> 8; + } + + } + } + + public ulong Addend => (ulong)_obj->Addend; + + public bool DependsOnSymbol() + { + return Type.Is16() || Type.Is64() || Type.IsGlobDat() || Type.IsJmpSlot(); + } + + public ElfRela(byte* memory, ElfRelaNative* obj) + { + _memory = memory; + _obj = obj; + } + + public override string ToString() + { + return +$@"ELF Relocation Entry at: 0x{(ulong)_obj:x} +- Offset: 0x{Offset:x} +- Type: {Type} +- Symbol: {Symbol} +- Addend: {Addend} +"; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfRelaArray.cs b/MemoryModule/Formats/Elf/ElfRelaArray.cs new file mode 100644 index 0000000..4567f69 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfRelaArray.cs @@ -0,0 +1,19 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfRelaArray : ElfManagedArray + { + public ElfRelaArray(byte* data, ulong offset, ulong count) : base(data, offset, count) + { + } + + protected override unsafe ElfRela Construct(void* ptr) + { + return new ElfRela(_memory, (ElfRelaNative*)ptr); + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfRelaNative.cs b/MemoryModule/Formats/Elf/ElfRelaNative.cs new file mode 100644 index 0000000..09b3029 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfRelaNative.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Sequential)] + public struct ElfRelaNative + { + public UIntPtr Offset; + public UIntPtr Info; + public IntPtr Addend; + } +} diff --git a/MemoryModule/Formats/Elf/ElfRelocationType.cs b/MemoryModule/Formats/Elf/ElfRelocationType.cs new file mode 100644 index 0000000..b5b3667 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfRelocationType.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Sequential)] + public struct ElfRelocationType + { + private uint _value; + + public ElfRelocationType(ulong value) + { + _value = (uint)value; + } + + public override string ToString() + { + return Environment.Is64BitProcess ? + $"x86_64_{(x86_64)_value}" : $"x86_{(x86)_value}"; + } + + public bool IsNone() + { + return _value == None; + } + + public bool IsRelative() + { + return _value == Relative; + } + + public bool IsGlobDat() + { + return _value == GlobDat; + } + + public bool IsJmpSlot() + { + return _value == JmpSlot; + } + + public bool Is64() + { + if (!Environment.Is64BitProcess) + { + return false; + } + return _value == _64; + } + + public bool Is16() + { + return _value == _16; + } + + public bool IsModule() + { + return _value == Module; + } + + public bool IsOffset() + { + return _value == Offset; + } + + public static explicit operator ElfRelocationType(ulong value) + { + return new ElfRelocationType(value); + } + + public static explicit operator uint(ElfRelocationType relocationType) + { + return relocationType._value; + } + + public static explicit operator ulong(ElfRelocationType relocationType) + { + return relocationType._value; + } + + public static readonly uint None = + Environment.Is64BitProcess ? (uint)x86_64.None : (uint)x86.None; + + public static readonly uint Relative = + Environment.Is64BitProcess ? (uint)x86_64.Relative : (uint)x86.Relative; + + public static readonly uint GlobDat = + Environment.Is64BitProcess ? (uint)x86_64.GlobDat : (uint)x86.GlobDat; + + public static readonly uint JmpSlot = + Environment.Is64BitProcess ? (uint)x86_64.JmpSlot : (uint)x86.JmpSlot; + + public static readonly uint _64 = (uint)x86_64._64; + + public static readonly uint _16 = + Environment.Is64BitProcess ? (uint)x86_64._16 : (uint)x86._16; + + public static readonly uint Module = + Environment.Is64BitProcess ? (uint)x86_64.DTPMOD64 : (uint)x86.DTPMOD32; + + public static readonly uint Offset = + Environment.Is64BitProcess ? (uint)x86_64.DTPOFF64 : (uint)x86.DTPOFF32; + + private enum x86 : uint + { + // x86 + None = 0, //None + _32 = 1, //word32 S + A + PC32 = 2, //word32 S + A - P + GOT32 = 3, //word32 G + A + PLT32 = 4, //word32 L + A - P + Copy = 5, //None + GlobDat = 6, //S + JmpSlot = 7, //word32 S + Relative = 8, //B + A + GOTOff = 9, //S + A - GOT + GOTPC = 10, // GOT + A - P + _32PLT = 11, //L + A + _16 = 20, //word16 S + A + PC16 = 21, // word16 S + A - P + _8 = 22, //word8 S + A + PC8 = 23, //word8 S + A - P + DTPMOD32 = 35, + DTPOFF32 = 36, + Size32 = 38, //word32 Z + A + } + + private enum x86_64 : uint + { + //64 + None = 0, //None + _64 = 1, //word64 S + A + PC32 = 2, //word32 S + A - P + GOT32 = 3, //word32 G + A + PLT32 = 4, //word32 L + A - P + Copy = 5, //None + GlobDat = 6, //word64 S + JmpSlot = 7, //word64 S + Relative = 8, //word64 B + A + GOTPCREL = 9, //word32 G + GOT + A - P + _32 = 10, // word32 S + A + _32S = 11, // word32 S + A + _16 = 12, // word16 S + A + PC16 = 13, // word16 S + A - P + _8 = 14, // word8 S + A + PC8 = 15, // word8 S + A - P + DTPMOD64 = 16, // word64 + DTPOFF64 = 17, // word64 + PC64 = 24, // word64 S + A - P + GOTOff64 = 25, //word64 S + A - GOT + GOTPC32 = 26, //word32 GOT + A + P + Size32 = 32, // word32 Z + A + Size64 = 33, // word64 Z + A + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfSectionHeader.cs b/MemoryModule/Formats/Elf/ElfSectionHeader.cs new file mode 100644 index 0000000..a41b6be --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSectionHeader.cs @@ -0,0 +1,64 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfSectionHeader + { + private byte* memory; + private ElfSectionHeaderNative* obj; + private string name; + + public ElfSectionHeaderType Type => obj->Type; + + public readonly ElfDynamicSectionArray DynamicSectionItems; + + public ulong Offset => (ulong)obj->Offset; + public UIntPtr Pointer => (UIntPtr)obj; + public ulong Size => (ulong)obj->Size; + public ulong EntrySize => (ulong)obj->EntrySize; + + public ElfSectionHeader(byte* memory, ElfSectionHeaderNative* obj, byte* namePtr = null) + { + this.memory = memory; + this.obj = obj; + if (namePtr != null) + { + name = Marshal.PtrToStringAnsi((IntPtr)namePtr); + } + + switch (obj->Type) + { + case ElfSectionHeaderType.Dynamic: + DynamicSectionItems = new ElfDynamicSectionArray(memory, (ulong)obj->Offset); + break; + } + } + + public byte* GetData() + { + return memory + (ulong)obj->Offset; + } + + public override string ToString() + { +// Dump in a random order, as I don't know what the fuck's happening. + return +$@"ELF Section Header at: 0x{(ulong)obj:x}, owned by 0x{(ulong)memory:x} +- Type: {obj->Type} +- Name: {name} +- Info: {obj->Info} +- Offset: 0x{(ulong)obj->Offset:x} +- EntrySize: {obj->EntrySize} +- Flags: {obj->Flags} +- Alignment: {obj->Alignment} +- Virtual Address: 0x{(ulong)obj->VirtualAddress:x} +- Memory Size: {obj->Size} +- Link: {obj->Link} +"; + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfSectionHeaderArray.cs b/MemoryModule/Formats/Elf/ElfSectionHeaderArray.cs new file mode 100644 index 0000000..15bff4f --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSectionHeaderArray.cs @@ -0,0 +1,58 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfSectionHeaderArray : IReadOnlyList + { + private byte* memory; + private ElfSectionHeaderNative* first; + private int count; + private byte* nameData; + + public ElfSectionHeaderArray(byte* data, ulong offset, int count, int nameIndex = -1) + { + memory = data; + first = (ElfSectionHeaderNative*)(data + offset); + this.count = count; + if (nameIndex != 0) + { + nameData = new ElfSectionHeader(memory, first + nameIndex).GetData(); + } + } + + public ElfSectionHeader this[int index] + { + get + { + if (index >= count) + { + throw new IndexOutOfRangeException($"Index {index} out of range {count}"); + } + + return new ElfSectionHeader(memory, first + index, nameData + first[index].Name); + } + } + + public int Count => count; + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < count; ++i) + { + yield return this[i]; + } + } + } + +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfSectionHeaderFlags.cs b/MemoryModule/Formats/Elf/ElfSectionHeaderFlags.cs new file mode 100644 index 0000000..35f41d8 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSectionHeaderFlags.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [Flags] + public enum ElfSectionHeaderFlags : ulong + { +//0x1 SHF_WRITE Writable +//0x2 SHF_ALLOC Occupies memory during execution +//0x4 SHF_EXECINSTR Executable +//0x10 SHF_MERGE Might be merged +//0x20 SHF_STRINGS Contains null-terminated strings +//0x40 SHF_INFO_LINK 'sh_info' contains SHT index +//0x80 SHF_LINK_ORDER Preserve order after combining +//0x100 SHF_OS_NONCONFORMING Non-standard OS specific handling required +//0x200 SHF_GROUP Section is member of a group +//0x400 SHF_TLS Section hold thread-local data +//0x0ff00000 SHF_MASKOS OS-specific +//0xf0000000 SHF_MASKPROC Processor-specific +//0x4000000 SHF_ORDERED Special ordering requirement (Solaris) +//0x8000000 SHF_EXCLUDE Section is excluded unless referenced or allocated (Solaris) + + Writeable = 0x1, + Alloc = 0x2, + Executable = 0x4, + Merge = 0x10, + Strings = 0x20, + InfoLink = 0x40, + LinkOrder = 0x80, + OsNonconforming = 0x100, + Group = 0x200, + ThreadLocalStorage = 0x400, + MaskOs = 0x0ff00000, + MaskProc = 0xf0000000 + } +} diff --git a/MemoryModule/Formats/Elf/ElfSectionHeaderNative.cs b/MemoryModule/Formats/Elf/ElfSectionHeaderNative.cs new file mode 100644 index 0000000..7b79fe2 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSectionHeaderNative.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Sequential)] + public struct ElfSectionHeaderNative + { + /// + /// An offset to a string in the .shstrtab section that represents the name of this section. + /// + public uint Name; + public ElfSectionHeaderType Type; + public UIntPtr Flags; + public UIntPtr VirtualAddress; + public UIntPtr Offset; + public UIntPtr Size; + public uint Link; + public uint Info; + public UIntPtr Alignment; + public UIntPtr EntrySize; + } +} diff --git a/MemoryModule/Formats/Elf/ElfSectionHeaderType.cs b/MemoryModule/Formats/Elf/ElfSectionHeaderType.cs new file mode 100644 index 0000000..376c248 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSectionHeaderType.cs @@ -0,0 +1,46 @@ +namespace MemoryModule.Formats.Elf +{ + public enum ElfSectionHeaderType : uint + { +//0x0 SHT_NULL Section header table entry unused +//0x1 SHT_PROGBITS Program data +//0x2 SHT_SYMTAB Symbol table +//0x3 SHT_STRTAB String table +//0x4 SHT_RELA Relocation entries with addends +//0x5 SHT_HASH Symbol hash table +//0x6 SHT_DYNAMIC Dynamic linking information +//0x7 SHT_NOTE Notes +//0x8 SHT_NOBITS Program space with no data (bss) +//0x9 SHT_REL Relocation entries, no addends +//0x0A SHT_SHLIB Reserved +//0x0B SHT_DYNSYM Dynamic linker symbol table +//0x0E SHT_INIT_ARRAY Array of constructors +//0x0F SHT_FINI_ARRAY Array of destructors +//0x10 SHT_PREINIT_ARRAY Array of pre-constructors +//0x11 SHT_GROUP Section group +//0x12 SHT_SYMTAB_SHNDX Extended section indices +//0x13 SHT_NUM Number of defined types. +//0x60000000 SHT_LOOS Start OS-specific. + + Null = 0x0, + ProgramBits = 0x1, + SymbolTable = 0x2, + StringTable = 0x3, + RelocationAddends = 0x4, + Hash = 0x5, + Dynamic = 0x6, + Note = 0x7, + NoBits = 0x8, + Relocation = 0x9, + ShLib = 0x0A, + DynamicSymbol = 0x0B, + InitArray = 0x0E, + FinalizeArray = 0x0F, + PreInitArray = 0x10, + Group = 0x11, + SymbolTableExtendedIndices = 0x12, + NumberOfDefindedTypes = 0x13, + + LoOS = 0x60000000, + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfStringTable.cs b/MemoryModule/Formats/Elf/ElfStringTable.cs new file mode 100644 index 0000000..b46d6ab --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfStringTable.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Elf +{ + public unsafe class ElfStringTable + { + private byte* _memory; + private byte* _start; + + public ElfStringTable(byte* memory, ulong offset) + { + _memory = memory; + _start = _memory + offset; + } + + public string GetString(ulong offset) + { + return Marshal.PtrToStringAnsi((IntPtr)(_start + offset)); + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Elf/ElfSymbol.cs b/MemoryModule/Formats/Elf/ElfSymbol.cs new file mode 100644 index 0000000..d0ec1d5 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSymbol.cs @@ -0,0 +1,95 @@ +using MemoryModule.Abstractions; +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfSymbol : ISymbol + { + private byte* _memory; + private ElfSymbolNative* _obj; + private string _name; + + public ElfSymbol(byte* memory, ElfSymbolNative* obj) + { + _memory = memory; + _obj = obj; + } + + public ElfSymbolType Type + { + get + { + var info = Environment.Is64BitProcess ? _obj->Data64.Info : _obj->Data32.Info; + return (ElfSymbolType)(info & 0xf); + } + } + + public ElfSymbolBinding Binding + { + get + { + var info = Environment.Is64BitProcess ? _obj->Data64.Info : _obj->Data32.Info; + return (ElfSymbolBinding)(info >> 4); + } + } + + public string Name => _name; + + public ushort SectionHeaderTableIndex + { + get => Environment.Is64BitProcess ? _obj->Data64.SectionHeaderTableIndex : _obj->Data32.SectionHeaderTableIndex; + } + + public ushort Other => Environment.Is64BitProcess ? _obj->Data64.Other : _obj->Data32.Other; + + public ulong Value + { + get => Environment.Is64BitProcess ? (ulong)_obj->Data64.Value : _obj->Data32.Value; + set + { + if (Environment.Is64BitProcess) + { + _obj->Data64.Value = (UIntPtr)value; + } + else + { + _obj->Data32.Value = (uint)value; + } + + } + } + + public ulong Size => Environment.Is64BitProcess ? (ulong)_obj->Data64.Size : _obj->Data32.Size; + + public IntPtr Address { get; internal set; } + + public override string ToString() + { + return +$@"ELF Symbol: +- Name: {Name} +- Type: {Type} +- Binding: {Binding} +- Other: {Other} +- Value: 0x{Value:x} +- Size: {Size} +- Index: {SectionHeaderTableIndex} +"; + } + + /// + /// Resolves the symbols name, using values from the specified + /// symbol table. Subsequent calls to Name will return this value. + /// + /// + /// + public string ResolveName(ElfStringTable stringTable) + { + return _name = stringTable.GetString(Environment.Is64BitProcess ? _obj->Data64.Name : _obj->Data32.Name); + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfSymbolArray.cs b/MemoryModule/Formats/Elf/ElfSymbolArray.cs new file mode 100644 index 0000000..80b9f2a --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSymbolArray.cs @@ -0,0 +1,19 @@ +using MemoryModule.Formats.Elf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + internal unsafe class ElfSymbolArray : ElfManagedArray + { + public ElfSymbolArray(byte* data, ulong offset, ulong count) : base(data, offset, count) + { + } + + protected override unsafe ElfSymbol Construct(void* ptr) + { + return new ElfSymbol(_memory, (ElfSymbolNative*)ptr); + } + } +} diff --git a/MemoryModule/Formats/Elf/ElfSymbolBinding.cs b/MemoryModule/Formats/Elf/ElfSymbolBinding.cs new file mode 100644 index 0000000..5d84c59 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSymbolBinding.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + public enum ElfSymbolBinding : byte + { + Local = 0, + Global = 1, + Weak = 2, + LoProc = 13, + HiProc = 15 + } +} diff --git a/MemoryModule/Formats/Elf/ElfSymbolNative.cs b/MemoryModule/Formats/Elf/ElfSymbolNative.cs new file mode 100644 index 0000000..e6e9305 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSymbolNative.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + [StructLayout(LayoutKind.Explicit)] + public struct ElfSymbolNative + { + public struct DataLayout64 + { + public uint Name; + public byte Info; + public byte Other; + public ushort SectionHeaderTableIndex; + public UIntPtr Value; + public UIntPtr Size; + } + + public struct DataLayout32 + { + public uint Name; + public uint Value; + public uint Size; + public byte Info; + public byte Other; + public ushort SectionHeaderTableIndex; + } + + [FieldOffset(0)] + public DataLayout32 Data32; + + [FieldOffset(0)] + public DataLayout64 Data64; + } +} diff --git a/MemoryModule/Formats/Elf/ElfSymbolType.cs b/MemoryModule/Formats/Elf/ElfSymbolType.cs new file mode 100644 index 0000000..a72cc29 --- /dev/null +++ b/MemoryModule/Formats/Elf/ElfSymbolType.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Elf +{ + public enum ElfSymbolType : byte + { + None = 0, + Object = 1, + Function = 2, + Section = 3, + File = 4, + Common = 5, + TLS = 6, + LoOS = 10, + HiOS = 12, + LoProc = 13, + HiProc = 15, + } +} diff --git a/MemoryModule/Formats/GenericBind.cs b/MemoryModule/Formats/GenericBind.cs new file mode 100644 index 0000000..17c18f4 --- /dev/null +++ b/MemoryModule/Formats/GenericBind.cs @@ -0,0 +1,23 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + /// + /// A generic class for formats that don't define its own binding structs. + /// + class GenericBind : IBind + { + public ulong AffectedAddress { get; set; } + + public string ModuleName { get; set; } + + public string SymbolName { get; set; } + + public ulong? SymbolIndex { get; set; } + + public ulong Addend { get; set; } + } +} diff --git a/MemoryModule/Formats/GenericRebase.cs b/MemoryModule/Formats/GenericRebase.cs new file mode 100644 index 0000000..e59e7ca --- /dev/null +++ b/MemoryModule/Formats/GenericRebase.cs @@ -0,0 +1,14 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + class GenericRebase : IRebase + { + public ulong AffectedAddress { get; set; } + public ulong Addend { get; set; } + public bool IgnoreExistingValue { get; set; } + } +} diff --git a/MemoryModule/Formats/GenericSection.cs b/MemoryModule/Formats/GenericSection.cs new file mode 100644 index 0000000..71efb86 --- /dev/null +++ b/MemoryModule/Formats/GenericSection.cs @@ -0,0 +1,24 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + class GenericSection : ISection + { + public ulong MemoryOffset { get; set; } + + public ulong MemorySize { get; set; } + + public ulong FileOffset { get; set; } + + public ulong FileSize { get; set; } + + public MemoryProtection MemoryProtection { get; set; } + + public string Name { get; set; } + + public SectionType Type { get; set; } + } +} diff --git a/MemoryModule/Formats/GenericSymbol.cs b/MemoryModule/Formats/GenericSymbol.cs new file mode 100644 index 0000000..39414a0 --- /dev/null +++ b/MemoryModule/Formats/GenericSymbol.cs @@ -0,0 +1,16 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + class GenericSymbol : ISymbol + { + public string Name { get; set; } + + public ulong Value { get; set; } + + public IntPtr Address { get; set; } + } +} diff --git a/MemoryModule/Formats/Macho/MachoBind.cs b/MemoryModule/Formats/Macho/MachoBind.cs new file mode 100644 index 0000000..89758e7 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoBind.cs @@ -0,0 +1,38 @@ +using System; + +namespace MemoryModule.Formats.Macho +{ + class MachoBind : ICloneable + { + public ulong SegmentIndex { get; internal set; } + public ulong SegmentOffset { get; internal set; } + public MachoBindType Type { get; internal set; } + public ulong LibraryOrdinal { get; internal set; } + public string Name { get; internal set; } + public ulong Addend { get; internal set; } + + public object Clone() + { + return new MachoBind() + { + SegmentIndex = SegmentIndex, + SegmentOffset = SegmentOffset, + Type = Type, + LibraryOrdinal = LibraryOrdinal, + Name = Name, + Addend = Addend + }; + } + + public override string ToString() + { + return +$@"Segment Index: {SegmentIndex}, +Segment Offset: 0x{SegmentOffset:x}, +Type: {Type}, +Library Ordinal: {LibraryOrdinal}, +Name: {Name}, +Addend: 0x{Addend:x}"; + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Macho/MachoBindCollection.cs b/MemoryModule/Formats/Macho/MachoBindCollection.cs new file mode 100644 index 0000000..d58f2ea --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoBindCollection.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoBindCollection : MachoCompressedCollection + { + const byte OpMask = (byte)MachoBindOpcode.Mask; + const byte ImMask = (byte)MachoBindOpcode.ImmediateMask; + + private static readonly ulong PtrSize = (ulong)sizeof(IntPtr); + + public MachoBindCollection(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + + protected override List Decompress() + { + var ptr = _data; + var end = _data + _size; + + var result = new List(); + + var currentBind = new MachoBind(); + + while (ptr < end) + { + var op = (MachoBindOpcode)((*ptr) & OpMask); + var im = (byte)((*ptr) & ImMask); + ++ptr; + + switch (op) + { + case MachoBindOpcode.Done: + currentBind = new MachoBind(); + break; + case MachoBindOpcode.AddAddrUleb: + var addr = ReadUleb128(ref ptr); + currentBind.SegmentOffset += addr; + break; + case MachoBindOpcode.SetAddendSleb: + var addend = ReadUleb128(ref ptr); + currentBind.Addend = addend; + break; + case MachoBindOpcode.SetDylibOrdinalImm: + currentBind.LibraryOrdinal = im; + break; + case MachoBindOpcode.SetDylibOrdinalUleb: + currentBind.LibraryOrdinal = ReadUleb128(ref ptr); + break; + case MachoBindOpcode.SetDylibSpecialImm: + currentBind.LibraryOrdinal = (im == 0) ? 0ul : (byte)(OpMask | im); + break; + case MachoBindOpcode.SetSegmentAndOffsetUleb: + currentBind.SegmentIndex = im; + currentBind.SegmentOffset = ReadUleb128(ref ptr); + break; + case MachoBindOpcode.SetSymbolTrailingFlagsImm: + currentBind.Name = ReadUtf8(ref ptr); + break; + case MachoBindOpcode.SetTypeImm: + currentBind.Type = (MachoBindType)im; + break; + case MachoBindOpcode.DoBind: + result.Add((MachoBind)currentBind.Clone()); + currentBind.SegmentOffset += PtrSize; + break; + case MachoBindOpcode.DoBindAddAddrImmScaled: + result.Add((MachoBind)currentBind.Clone()); + currentBind.SegmentOffset += im * PtrSize; + break; + case MachoBindOpcode.DoBindAddAddrUleb: + result.Add((MachoBind)currentBind.Clone()); + currentBind.SegmentOffset += ReadUleb128(ref ptr); + break; + case MachoBindOpcode.DoBindUlebTimesSkippingUleb: + var count = ReadUleb128(ref ptr); + var skip = ReadUleb128(ref ptr); + + for (ulong i = 0; i < count; ++i) + { + result.Add((MachoBind)currentBind.Clone()); + currentBind.SegmentOffset += skip; + } + + break; + default: + System.Diagnostics.Debug.WriteLine($"Unknown opcode: 0x{(ulong)op:x}"); + break; + } + } + + return result; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoBindOpcode.cs b/MemoryModule/Formats/Macho/MachoBindOpcode.cs new file mode 100644 index 0000000..9b2a333 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoBindOpcode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoBindOpcode : byte + { + Mask = 0xF0, + ImmediateMask = 0x0F, + Done = 0x00, + SetDylibOrdinalImm = 0x10, + SetDylibOrdinalUleb = 0x20, + SetDylibSpecialImm = 0x30, + SetSymbolTrailingFlagsImm = 0x40, + SetTypeImm = 0x50, + SetAddendSleb = 0x60, + SetSegmentAndOffsetUleb = 0x70, + AddAddrUleb = 0x80, + DoBind = 0x90, + DoBindAddAddrUleb = 0xA0, + DoBindAddAddrImmScaled = 0xB0, + DoBindUlebTimesSkippingUleb = 0xC0, + } +} diff --git a/MemoryModule/Formats/Macho/MachoBindType.cs b/MemoryModule/Formats/Macho/MachoBindType.cs new file mode 100644 index 0000000..74adac3 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoBindType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoBindType : byte + { + Pointer = 1, + TextAbsolute32 = 2, + TextPcrel32 = 3, + } +} diff --git a/MemoryModule/Formats/Macho/MachoCompressedCollection.cs b/MemoryModule/Formats/Macho/MachoCompressedCollection.cs new file mode 100644 index 0000000..a3925d3 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoCompressedCollection.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + /// + /// A collection of a data type that are compressed in Macho images. + /// + /// The represented data type + unsafe abstract class MachoCompressedCollection : IReadOnlyCollection + { + private readonly List _binds; + + protected byte* _memory; + protected byte* _data; + protected ulong _size; + + public MachoCompressedCollection(byte* memory, ulong offset, ulong size) + { + _memory = memory; + _data = _memory + offset; + _size = size; + _binds = Decompress(); + } + + public int Count => ((IReadOnlyCollection)_binds).Count; + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_binds).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_binds).GetEnumerator(); + } + + protected abstract List Decompress(); + + protected const byte Low7Bits = (1 << 7) - 1; + + protected static ulong ReadUleb128(ref byte* ptr) + { + ulong result = 0; + int shift = 0; + while (true) + { + var current = *ptr; + result |= ((ulong)(current & Low7Bits)) << shift; + if (current < Low7Bits) + break; + shift += 7; + ++ptr; + } + ++ptr; + return result; + } + + protected static long ReadSleb128(ref byte* ptr) + { + ulong result = 0; + int shift = 0; + while (true) + { + var current = *ptr; + result |= ((ulong)(current & Low7Bits)) << shift; + if (current < Low7Bits) + { + if (((current >> 6) & 1) == 1) + { + shift += 7; + if (shift < 64) + { + result |= ~0ul << shift; + } + } + break; + } + shift += 7; + ++ptr; + } + ++ptr; + return unchecked((long)result); + } + + //protected static string ReadAscii(ref byte* ptr) + //{ + // var sb = new StringBuilder(); + // while (*ptr != 0) + // { + // sb.Append((char)*ptr); + // ++ptr; + // } + // ++ptr; + // return sb.ToString(); + //} + + protected static string ReadUtf8(ref byte* ptr) + { + byte* startPtr = ptr; + while (*ptr != 0) + { + ++ptr; + } + ++ptr; + return new string((sbyte*)startPtr, 0, (int)(ptr - startPtr) - 1, Encoding.UTF8); + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoCpuSubtype.cs b/MemoryModule/Formats/Macho/MachoCpuSubtype.cs new file mode 100644 index 0000000..ac62a31 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoCpuSubtype.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoCpuSubtype + { + uint value; + + const uint CPU_SUBTYPE_MASK = 0xff000000; + const uint CPU_SUBTYPE_LIB64 = 0x80000000; + + enum x86Subtypes + { + CPU_SUBTYPE_I386 = 3, + CPU_SUBTYPE_486 = 4, + CPU_SUBTYPE_486SX = 132, + CPU_SUBTYPE_586 = 5, + CPU_SUBTYPE_PENT = CPU_SUBTYPE_586, + CPU_SUBTYPE_PENTPRO = 22, + CPU_SUBTYPE_PENTII_M3 = 54, + CPU_SUBTYPE_PENTII_M5 = 86, + CPU_SUBTYPE_PENTIUM_4 = 10, + } + + enum MC680X0Subtypes + { + CPU_SUBTYPE_MC680X0_ALL = 1, + CPU_SUBTYPE_MC68030 = CPU_SUBTYPE_MC680X0_ALL, + CPU_SUBTYPE_MC68040 = 2, + CPU_SUBTYPE_MC68030_ONLY = 3, + } + + enum x86_64Subtypes + { + CPU_SUBTYPE_X86_64_ALL = x86Subtypes.CPU_SUBTYPE_I386, + CPU_SUBTYPE_X86_64_H = 8, + } + + enum ARMSubtypes + { + CPU_SUBTYPE_ARM_ALL = 0, + CPU_SUBTYPE_ARM_V4T = 5, + CPU_SUBTYPE_ARM_V6 = 6, + CPU_SUBTYPE_ARM_V5TEJ = 7, + CPU_SUBTYPE_ARM_XSCALE = 8, + CPU_SUBTYPE_ARM_V7 = 9, + CPU_SUBTYPE_ARM_V7F = 10, + CPU_SUBTYPE_ARM_V7S = 11, + CPU_SUBTYPE_ARM_V7K = 12, + CPU_SUBTYPE_ARM_V6M = 14, + CPU_SUBTYPE_ARM_V7M = 15, + CPU_SUBTYPE_ARM_V7EM = 16, + CPU_SUBTYPE_ARM_V8 = 13, + } + + enum ARM64Subtypes + { + CPU_SUBTYPE_ARM64_ALL = 0, + CPU_SUBTYPE_ARM64_V8 = 1, + CPU_SUBTYPE_ARM64E = 2, + } + + enum ARM64_32Subtypes + { + CPU_SUBTYPE_ARM64_32_V8 = 1, + } + + enum MC88000Subtypes + { + CPU_SUBTYPE_MC88000_ALL = 0, + CPU_SUBTYPE_MMAX_JPC = CPU_SUBTYPE_MC88000_ALL, + CPU_SUBTYPE_MC88100 = 1, + CPU_SUBTYPE_MC88110 = 2, + } + + enum PowerPCSubtypes + { + CPU_SUBTYPE_POWERPC_ALL = 0, + CPU_SUBTYPE_POWERPC_601 = 1, + CPU_SUBTYPE_POWERPC_602 = 2, + CPU_SUBTYPE_POWERPC_603 = 3, + CPU_SUBTYPE_POWERPC_603E = 4, + CPU_SUBTYPE_POWERPC_603EV = 5, + CPU_SUBTYPE_POWERPC_604 = 6, + CPU_SUBTYPE_POWERPC_604E = 7, + CPU_SUBTYPE_POWERPC_620 = 8, + CPU_SUBTYPE_POWERPC_750 = 9, + CPU_SUBTYPE_POWERPC_7400 = 10, + CPU_SUBTYPE_POWERPC_7450 = 11, + CPU_SUBTYPE_POWERPC_970 = 100, + } + + enum PowerPC64Subtypes + { + CPU_SUBTYPE_POWERPC64_ALL = PowerPCSubtypes.CPU_SUBTYPE_POWERPC_ALL, + } + + public string ToString(MachoCpuType type) + { + switch (type) + { + case MachoCpuType.MC680X0: + return ((MC680X0Subtypes)value).ToString(); + case MachoCpuType.MC88000: + return ((MC88000Subtypes)value).ToString(); + case MachoCpuType.I386: + return ((x86Subtypes)value).ToString(); + case MachoCpuType.X86_64: + return ((x86_64Subtypes)value).ToString(); + case MachoCpuType.ARM: + return ((ARMSubtypes)value).ToString(); + case MachoCpuType.ARM64: + return ((ARM64Subtypes)value).ToString(); + case MachoCpuType.ARM64_32: + return ((ARM64_32Subtypes)value).ToString(); + case MachoCpuType.PowerPC: + return ((PowerPCSubtypes)value).ToString(); + case MachoCpuType.PowerPC64: + return ((PowerPC64Subtypes)value).ToString(); + default: + throw new NotSupportedException($"Unsupported CPU type: {type}"); + } + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoCpuType.cs b/MemoryModule/Formats/Macho/MachoCpuType.cs new file mode 100644 index 0000000..625a768 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoCpuType.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoCpuType : uint + { + ABI64 = 0x01000000, + ABI32 = 0x02000000, + Any = unchecked((uint)-1), + MC680X0 = 0x06, + I386 = 0x07, + X86_64 = I386 | ABI64, + ARM = 0x0c, + MC88000 = 0xd, + ARM64 = ARM | ABI64, + ARM64_32 = ARM | ABI32, + PowerPC = 0x12, + PowerPC64 = PowerPC | ABI64 + } +} diff --git a/MemoryModule/Formats/Macho/MachoDyldInfoOnlyLoadCommand.cs b/MemoryModule/Formats/Macho/MachoDyldInfoOnlyLoadCommand.cs new file mode 100644 index 0000000..1844caf --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoDyldInfoOnlyLoadCommand.cs @@ -0,0 +1,44 @@ +using MemoryModule.Formats.Macho.Natives; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoDyldInfoOnlyLoadCommand : MachoLoadCommand + { + private MachoDyldInfoLoadCommandNative* Command => (MachoDyldInfoLoadCommandNative*)_data; + + public MachoDyldInfoOnlyLoadCommand(byte* memory, ulong fileOffset) : base(memory, fileOffset) + { + } + + public ulong RebaseOffset => Command->rebase_off; + public ulong RebaseSize => Command->rebase_size; + + public ulong BindOffset => Command->bind_off; + public ulong BindSize => Command->bind_size; + + public ulong WeakBindOffset => Command->weak_bind_off; + public ulong WeakBindSize => Command->weak_bind_size; + + public ulong LazyBindOffset => Command->lazy_bind_off; + public ulong LazyBindSize => Command->lazy_bind_size; + + public ulong ExportOffset => Command->export_off; + public ulong ExportSize => Command->export_size; + + public override string ToString() + { + return +$@"Rebase offset: {Command->rebase_off}, +Rebase size: {Command->rebase_size}, +Bind offset: {Command->bind_off}, +Bind size: {Command->bind_size}, +Weak bind offset: {Command->weak_bind_off}, +Weak bind size: {Command->weak_bind_size}, +Lazy bind offset: {Command->lazy_bind_off}, +Lazy bind size: {Command->lazy_bind_size}, +Export offset: {Command->export_off}, +Export size: {Command->export_size}"; + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Macho/MachoDylibLoadCommand.cs b/MemoryModule/Formats/Macho/MachoDylibLoadCommand.cs new file mode 100644 index 0000000..850754f --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoDylibLoadCommand.cs @@ -0,0 +1,29 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoDylibLoadCommand : MachoLoadCommand + { + private MachoDylibLoadCommandNative* Command => (MachoDylibLoadCommandNative*)_data; + private string _name; + + public MachoDylibLoadCommand(byte* memory, ulong offset) : base(memory, offset) + { + _name = Marshal.PtrToStringAnsi((IntPtr)(_data + Command->name)); + } + + public string Name => _name; + public byte* NamePtr => _data + Command->name; + + public override string ToString() + { + return +$@"{base.ToString()}, +{((CommandType == MachoLoadCommandType.LC_ID_DYLIB) ? +$"Library name: {Name}" : +$"Requires: {Name}")}"; + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Macho/MachoExport.cs b/MemoryModule/Formats/Macho/MachoExport.cs new file mode 100644 index 0000000..5fc2301 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoExport.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + class MachoExport + { + public MachoExportSymbolFlags Flags { get; internal set; } + public string Name { get; internal set; } + public ulong Location { get; internal set; } + public ulong LibraryOrdinal { get; internal set; } + public string ReexportName { get; internal set; } + + public override string ToString() + { + return +$@"Type: {Flags}, +Name: {Name}, +Offset: 0x{Location:x}, +LibraryOrdinal: {LibraryOrdinal}, +ReexportName: {ReexportName}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoExportCollection.cs b/MemoryModule/Formats/Macho/MachoExportCollection.cs new file mode 100644 index 0000000..80a2695 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoExportCollection.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoExportCollection : MachoCompressedCollection + { + public MachoExportCollection(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + + protected override List Decompress() + { + var result = new List(); + var builder = new StringBuilder(); + + Decompress(_data, result, builder); + + System.Diagnostics.Debug.Assert(builder.Length == 0); + + return result; + } + + private void Decompress(byte* ptr, List result, StringBuilder prefix) + { + // Nodes for a symbol start with a uleb128 that is the length of + // the exported symbol information for the string so far. + var length = ReadUleb128(ref ptr); + + // If there is no exported symbol, the node starts with a zero byte. + if (length != 0) + { + var currentExport = new MachoExport(); + + currentExport.Name = prefix.ToString(); + + // If there is exported info, it follows the length. First is + // a uleb128 containing flags. + currentExport.Flags = (MachoExportSymbolFlags)ReadUleb128(ref ptr); + + + // If the flags + // is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is + // a uleb128 encoded library ordinal, then a zero terminated + // UTF8 string. + if (currentExport.Flags.HasFlag(MachoExportSymbolFlags.Reexport)) + { + currentExport.LibraryOrdinal = ReadUleb128(ref ptr); + currentExport.ReexportName = ReadUtf8(ref ptr); + // If the string is zero length, then the symbol + // is re-export from the specified dylib with the same name. + if (string.IsNullOrEmpty(currentExport.ReexportName)) + { + currentExport.ReexportName = currentExport.Name; + } + } + // Normally, it is followed by a + // uleb128 encoded offset which is location of the content named + // by the symbol from the mach_header for the image. + else + { + currentExport.Location = ReadUleb128(ref ptr); + } + + result.Add(currentExport); + } + + // After the optional exported symbol information is a byte of + // how many edges(0 - 255) that this node has leaving it, + var edges = *ptr; + ++ptr; + // followed by each edge. + for (int i = 0; i < edges; ++i) + { + // Each edge is a zero terminated UTF8 of the addition chars + // in the symbol, + var additionalString = ReadUtf8(ref ptr); + prefix.Append(additionalString); + // followed by a uleb128 offset for the node that + // edge points to. + var nodeOffset = _data + ReadUleb128(ref ptr); + + Decompress(nodeOffset, result, prefix); + + prefix.Remove(prefix.Length - additionalString.Length, additionalString.Length); + } + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoExportSymbolFlags.cs b/MemoryModule/Formats/Macho/MachoExportSymbolFlags.cs new file mode 100644 index 0000000..102ea1c --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoExportSymbolFlags.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + [Flags] + enum MachoExportSymbolFlags : uint + { + KindMask = 0x03, + KindRegular = 0x00, + KindThreadLocal = 0x01, + WeakDefinition = 0x04, + Reexport = 0x08, + StubAndResolver = 0x10, + } +} diff --git a/MemoryModule/Formats/Macho/MachoFileFlags.cs b/MemoryModule/Formats/Macho/MachoFileFlags.cs new file mode 100644 index 0000000..5212ce0 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoFileFlags.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + [Flags] + enum MachoFileFlags : uint + { + /// + /// the object file has no undefined references + /// + Noundefs = 0x1, + /// + /// the object file is the output of an incremental link against a base file and can't be link edited again + /// + Incrlink = 0x2, + /// + /// the object file is input for the dynamic linker and can't be staticly link edited again + /// + Dyldlink = 0x4, + /// + /// the object file's undefined references are bound by the dynamic linker when loaded. + /// + Bindatload = 0x8, + /// + /// the file has its dynamic undefined references prebound. + /// + Prebound = 0x10, + /// + /// the file has its read-only and read-write segments split + /// + SplitSegs = 0x20, + /// + /// the shared library init routine is to be run lazily via catching memory faults to its writeable segments (obsolete) + /// + LazyInit = 0x40, + /// + /// the image is using two-level name space bindings + /// + Twolevel = 0x80, + /// + /// the executable is forcing all images to use flat name space bindings + /// + ForceFlat = 0x100, + /// + /// this umbrella guarantees no multiple defintions of symbols in its sub-images so the two-level namespace hints can always be used. + /// + Nomultidefs = 0x200, + /// + /// do not have dyld notify the prebinding agent about this executable + /// + Nofixprebinding = 0x400, + /// + /// the binary is not prebound but can have its prebinding redone. only used when MH_PREBOUND is not set. + /// + Prebindable = 0x800, + /// + /// indicates that this binary binds to all two-level namespace modules of its dependent libraries. only used when MH_PREBINDABLE and MH_TWOLEVEL are both set. + /// + Allmodsbound = 0x1000, + /// + /// safe to divide up the sections into sub-sections via symbols for dead code stripping + /// + SubsectionsViaSymbols = 0x2000, + /// + /// the binary has been canonicalized via the unprebind operation + /// + Canonical = 0x4000, + /// + /// the final linked image contains external weak symbols + /// + WeakDefines = 0x8000, + /// + /// the final linked image uses weak symbols + /// + BindsToWeak = 0x10000, + /// + /// When this bit is set, all stacks in the task will be given stack execution privilege. Only used in MH_EXECUTE filetypes. + /// + AllowStackExecution = 0x20000, + /// + /// When this bit is set, the binary declares it is safe for use in processes with uid zero + /// + RootSafe = 0x40000, + /// + /// When this bit is set, the binary declares it is safe for use in processes when issetugid() is true + /// + SetuidSafe = 0x80000, + /// + /// When this bit is set on a dylib, the static linker does not need to examine dependent dylibs to see if any are re-exported + /// + NoReexportedDylibs = 0x100000, + /// + /// When this bit is set, the OS will load the main executable at a random address. Only used in MH_EXECUTE filetypes. + /// + Pie = 0x200000, + /// + /// Only for use on dylibs. When linking against a dylib that has this bit set, the static linker will automatically not create a LC_LOAD_DYLIB load command to the dylib if no symbols are being referenced from the dylib. + /// + DeadStrippableDylib = 0x400000, + /// + /// Contains a section of type S_THREAD_LOCAL_VARIABLES + /// + HasTlvDescriptors = 0x800000, + /// + /// When this bit is set, the OS will run the main executable with a non-executable heap even on platforms (e.g. i386) that don't require it. Only used in MH_EXECUTE filetypes. + /// + NoHeapExecution = 0x1000000, + /// + /// The code was linked for use in an application extension. + /// + AppExtensionSafe = 0x02000000, + } +} diff --git a/MemoryModule/Formats/Macho/MachoFileType.cs b/MemoryModule/Formats/Macho/MachoFileType.cs new file mode 100644 index 0000000..a18439b --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoFileType.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoFileType : uint + { + Object = 0x1, + Execute = 0x2, + Fvmlib = 0x3, + Core = 0x4, + Preload = 0x5, + Dylib = 0x6, + Dylinker = 0x7, + Bundle = 0x8, + DylibStub = 0x9, + Dsym = 0xa, + KextBundle = 0xb, + } +} diff --git a/MemoryModule/Formats/Macho/MachoFinalizer.cs b/MemoryModule/Formats/Macho/MachoFinalizer.cs new file mode 100644 index 0000000..555335b --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoFinalizer.cs @@ -0,0 +1,31 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + class MachoFinalizer : IFinalizer + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void FiniDelegate(); + + private FiniDelegate _del; + + public IntPtr Address { get; internal set; } + + public void Run() + { + _del = Marshal.GetDelegateForFunctionPointer(Address); + _del(); + } + + internal MachoFinalizer(IntPtr del) + { + Address = del; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoHeader.cs b/MemoryModule/Formats/Macho/MachoHeader.cs new file mode 100644 index 0000000..70407d4 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoHeader.cs @@ -0,0 +1,158 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + unsafe delegate void InitFunc(int argc, byte** argv, byte** envp, byte** apple); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + delegate void FiniFunc(); + + unsafe class MachoHeader + { + private static readonly void* RtldDefault = (void*)-2; + private readonly Dictionary _globalExport = new Dictionary(); + private readonly Dictionary _globalExportWeak = new Dictionary(); + + private readonly byte* _memory; + private byte* _baseAddress; + private readonly MachoHeaderNative* _header; + private readonly MachoLoadCommandCollection _collection; + private readonly MachoDyldInfoOnlyLoadCommand _infoOnly; + private readonly MachoSymbolTableLoadCommand _symTab; + + private readonly MachoRebaseCollection _rebase; + private readonly MachoBindCollection _bind; + private readonly MachoBindCollection _lazyBind; + private readonly MachoBindCollection _weakBind; + private readonly MachoExportCollection _export; + + private readonly MachoSegmentLoadCommand _linkEdit; + + private readonly List _segments; + private readonly List _dylibs; + + private readonly IntPtr[] _dylibHandles; + + private readonly Dictionary _exportMap = new Dictionary(); + + public MachoLoadCommandCollection Commands => _collection; + + public MachoRebaseCollection Rebase => _rebase; + public MachoBindCollection Bindings => _bind; + public MachoBindCollection LazyBindings => _lazyBind; + public MachoBindCollection WeakBindings => _weakBind; + public MachoExportCollection Exports => _export; + + public List Segments => _segments; + public List Dylibs => _dylibs; + + public MachoCpuType CpuType => _header->cputype; + + private MachoHeader(byte* data, bool runtime) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _memory = data; + _header = (MachoHeaderNative*)data; + _collection = new MachoLoadCommandCollection(_memory, (ulong)sizeof(MachoHeaderNative), _header->ncmds); + _infoOnly = _collection.OfType().FirstOrDefault(); + _symTab = _collection.OfType().FirstOrDefault(); + + if (_infoOnly != null && !runtime) + { + _rebase = new MachoRebaseCollection(_memory, _infoOnly.RebaseOffset, _infoOnly.RebaseSize); + _bind = new MachoBindCollection(_memory, _infoOnly.BindOffset, _infoOnly.BindSize); + _lazyBind = new MachoBindCollection(_memory, _infoOnly.LazyBindOffset, _infoOnly.LazyBindSize); + _weakBind = new MachoBindCollection(_memory, _infoOnly.WeakBindOffset, _infoOnly.WeakBindSize); + _export = new MachoExportCollection(_memory, _infoOnly.ExportOffset, _infoOnly.ExportSize); + } + + _segments = _collection.OfType().ToList(); + _dylibs = _collection.OfType().ToList(); + + _linkEdit = _segments.FirstOrDefault(seg => seg.Name == "__LINKEDIT"); + + _dylibHandles = new IntPtr[_dylibs.Count]; + } + + public MachoHeader(byte* data) : this(data, false) + { + + } + + /// + /// Inspect an already loaded Macho binary. + /// + /// The file's data + /// The runtime address of the library + internal MachoHeader(byte* data, byte* runtimeAddress) : this(data, true) + { + _baseAddress = runtimeAddress; + } + + public Dictionary BuildRuntimeSymbolTable() + { + // Some dylibs load at specific addresses. + ulong firstSegmentVirtual = _segments.FirstOrDefault()?.VirtualAddress ?? 0; + + var result = new Dictionary(); + + var symbolOffsetFromLinkedit = _symTab.SymbolTableOffset - _linkEdit.Offset; + var stringOffsetFromLinkedit = _symTab.StringTableOffset - _linkEdit.Offset; + var stringTablePtr = _baseAddress + _linkEdit.VirtualAddress - firstSegmentVirtual + stringOffsetFromLinkedit; + + var symbolTable = new MachoSymbolTableArray(_baseAddress, _linkEdit.VirtualAddress - firstSegmentVirtual + symbolOffsetFromLinkedit, _symTab.SymbolCount); + + foreach (var sym in symbolTable) + { + try + { + var name = Marshal.PtrToStringAnsi((IntPtr)(stringTablePtr + sym.StringTableIndex)); + + // Probably some lazy bound imported symbol. + if (sym.Value == 0) + { + continue; + } + + var value = _baseAddress + sym.Value - firstSegmentVirtual; + + if (!result.ContainsKey(name)) + { + result.Add(name, (IntPtr)value); + } + } + catch (Exception e) + { + Console.WriteLine($"Fail: {e}"); + } + } + + return result; + } + + public override string ToString() + { + return +$@"Magic: {_header->magic} +CPU type: {_header->cputype} +CPU subtype: {_header->cpusubtype.ToString(_header->cputype)} +File type: {_header->filetype} +Command count: {_header->ncmds} +Command size: {_header->sizeofcmds} +Flags: {_header->flags}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoInitializer.cs b/MemoryModule/Formats/Macho/MachoInitializer.cs new file mode 100644 index 0000000..d27a229 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoInitializer.cs @@ -0,0 +1,87 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + class MachoInitializer : IInitializer + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void InitDelegate(int argc, byte** argv, byte** envp, byte** apple); + + private IntPtr _addr; + private InitDelegate _del; + + public Type[] Arguments { get; } = new Type[] { typeof(int), typeof(IntPtr), typeof(IntPtr), typeof(IntPtr) }; + + private static readonly string[] _argvString; + private static readonly string[] _envpString; + private static readonly string[] _appleString; + + static MachoInitializer() + { + _argvString = Environment.GetCommandLineArgs(); + _envpString = Environment.GetEnvironmentVariables() + .Cast() + .Select(x => $"{x.Key}={x.Value}") + .ToArray(); + _appleString = new string[] { Assembly.GetEntryAssembly().Location }; + } + + public bool Run() + { + return RunInternal(_argvString.Length, _argvString, _envpString, _appleString); + } + + public bool Run(params object[] args) + { + if (args.Length != Arguments.Length) + { + return false; + } + + for (int i = 0; i < args.Length; ++i) + { + if (!Arguments[i].IsAssignableFrom(args[i].GetType())) + { + return false; + } + } + + return RunInternal((int)args[0], (string[])args[1], (string[])args[2], (string[])args[3]); + } + + private unsafe bool RunInternal(int argc, string[] argv, string[] envp, string[] apple) + { + _del = Marshal.GetDelegateForFunctionPointer(_addr); + + var argvArr = argv.Select(str => Marshal.StringToHGlobalAnsi(str)).ToArray(); + var envpArr = envp.Select(str => Marshal.StringToHGlobalAnsi(str)).Concat(new[] { IntPtr.Zero }).ToArray(); + var appleArr = apple.Select(str => Marshal.StringToHGlobalAnsi(str)).Concat(new[] { IntPtr.Zero }).ToArray(); + + fixed (IntPtr* argvPtr = &argvArr[0]) + fixed (IntPtr* envpPtr = &envpArr[0]) + fixed (IntPtr* applePtr = &appleArr[0]) + { + _del(argc, (byte**)argvPtr, (byte**)envpPtr, (byte**)applePtr); + } + + foreach (var ptr in argvArr.Concat(envpArr.Reverse().Skip(1)).Concat(appleArr.Reverse().Skip(1))) + { + Marshal.FreeHGlobal(ptr); + } + + return true; + } + + internal MachoInitializer(IntPtr del) + { + _addr = del; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoLoadCommand.cs b/MemoryModule/Formats/Macho/MachoLoadCommand.cs new file mode 100644 index 0000000..88cea3e --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoLoadCommand.cs @@ -0,0 +1,52 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoLoadCommand + { + private MachoLoadCommandNative* Command => (MachoLoadCommandNative *)_data; + + protected byte* _memory; + protected byte* _data; + + protected MachoLoadCommand(byte* memory, ulong offset) + { + _memory = memory; + _data = _memory + offset; + } + + public MachoLoadCommandType CommandType => Command->cmd; + public uint CommandSize => Command->cmdsize; + + public static MachoLoadCommand Construct(byte* data, ulong offset) + { + var _command = (MachoLoadCommandNative*)(data + offset); + + switch (_command->cmd) + { + case MachoLoadCommandType.LC_SEGMENT: + case MachoLoadCommandType.LC_SEGMENT_64: + return new MachoSegmentLoadCommand(data, offset); + case MachoLoadCommandType.LC_DYLD_INFO_ONLY: + return new MachoDyldInfoOnlyLoadCommand(data, offset); + case MachoLoadCommandType.LC_ID_DYLIB: + case MachoLoadCommandType.LC_LOAD_DYLIB: + return new MachoDylibLoadCommand(data, offset); + case MachoLoadCommandType.LC_SYMTAB: + return new MachoSymbolTableLoadCommand(data, offset); + default: + return new MachoLoadCommand(data, offset); + } + } + + public override string ToString() + { + return +$@"Command Type: {Command->cmd}, +Command Size: {Command->cmdsize}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoLoadCommandCollection.cs b/MemoryModule/Formats/Macho/MachoLoadCommandCollection.cs new file mode 100644 index 0000000..5ebfd7d --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoLoadCommandCollection.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + unsafe class MachoLoadCommandCollection : IReadOnlyCollection + { + private int _count; + private ulong _arrOffset; + private byte* _memory; + private byte* _data; + + public MachoLoadCommandCollection(byte* data, ulong offset, uint count) + { + _count = (int)count; + _memory = data; + _data = data + offset; + _arrOffset = offset; + } + + public int Count => _count; + + public IEnumerator GetEnumerator() + { + uint offset = 0; + + for (int i = 0; i < _count; ++i) + { + yield return Construct(ref offset); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private MachoLoadCommand Construct(ref uint offset) + { + var command = MachoLoadCommand.Construct(_memory, _arrOffset + offset); + offset += command.CommandSize; + return command; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoLoadCommandType.cs b/MemoryModule/Formats/Macho/MachoLoadCommandType.cs new file mode 100644 index 0000000..54d6a6b --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoLoadCommandType.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoLoadCommandType : uint + { + LC_SEGMENT = 0x1, + LC_SYMTAB = 0x2, + LC_SYMSEG = 0x3, + LC_THREAD = 0x4, + LC_UNIXTHREAD = 0x5, + LC_LOADFVMLIB = 0x6, + LC_IDFVMLIB = 0x7, + LC_IDENT = 0x8, + LC_FVMFILE = 0x9, + LC_PREPAGE = 0xa, + LC_DYSYMTAB = 0xb, + LC_LOAD_DYLIB = 0xc, + LC_ID_DYLIB = 0xd, + LC_LOAD_DYLINKER = 0xe, + LC_ID_DYLINKER = 0xf, + LC_PREBOUND_DYLIB = 0x10, + LC_ROUTINES = 0x11, + LC_SUB_FRAMEWORK = 0x12, + LC_SUB_UMBRELLA = 0x13, + LC_SUB_CLIENT = 0x14, + LC_SUB_LIBRARY = 0x15, + LC_TWOLEVEL_HINTS = 0x16, + LC_PREBIND_CKSUM = 0x17, + LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD), + LC_SEGMENT_64 = 0x19, + LC_ROUTINES_64 = 0x1a, + LC_UUID = 0x1b, + LC_RPATH = (0x1c | LC_REQ_DYLD), + LC_CODE_SIGNATURE = 0x1d, + LC_SEGMENT_SPLIT_INFO = 0x1e, + LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD), + LC_LAZY_LOAD_DYLIB = 0x20, + LC_ENCRYPTION_INFO = 0x21, + LC_DYLD_INFO = 0x22, + LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD), + LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD), + LC_VERSION_MIN_MACOSX = 0x24, + LC_VERSION_MIN_IPHONEOS = 0x25, + LC_FUNCTION_STARTS = 0x26, + LC_DYLD_ENVIRONMENT = 0x27, + LC_MAIN = (0x28 | LC_REQ_DYLD), + LC_DATA_IN_CODE = 0x29, + LC_SOURCE_VERSION = 0x2a, + LC_DYLIB_CODE_SIGN_DRS = 0x2b, + LC_ENCRYPTION_INFO_64 = 0x2c, + LC_LINKER_OPTION = 0x2d, + LC_LINKER_OPTIMIZATION_HINT = 0x2e, + LC_VERSION_MIN_TVOS = 0x2f, + LC_VERSION_MIN_WATCHOS = 0x30, + LC_NOTE = 0x31, + LC_BUILD_VERSION = 0x32, + LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD), + LD_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD), + LC_REQ_DYLD = 0x80000000 + } +} diff --git a/MemoryModule/Formats/Macho/MachoMagic.cs b/MemoryModule/Formats/Macho/MachoMagic.cs new file mode 100644 index 0000000..647b901 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoMagic.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoMagic : uint + { + Macho32Bit = 0xfeedface, + Macho64Bit = 0xfeedfacf, + MachoFat = 0xcafebabe, + MachoFat64 = 0xcafebabf, + } +} diff --git a/MemoryModule/Formats/Macho/MachoManagedArray.cs b/MemoryModule/Formats/Macho/MachoManagedArray.cs new file mode 100644 index 0000000..941fdcd --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoManagedArray.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho +{ + internal abstract unsafe class MachoManagedArray : IEnumerable, IEnumerable + where TNative : struct + where TManaged : class + { + private static readonly ulong _nativeSize = (ulong)Marshal.SizeOf(); + + private ulong _count; + protected byte* _memory; + protected byte* _first; + + protected TManaged[] _managed; + + public MachoManagedArray(byte* data, ulong offset, ulong count) + { + _memory = data; + _first = data + offset; + _count = count; + _managed = new TManaged[count]; + } + + public TManaged this[ulong index] + { + get + { + if (index > _count) + { + throw new IndexOutOfRangeException($"{index} is greater than array range {_count}"); + } + + return _managed[index] = _managed[index] ?? Construct(_first + index * _nativeSize); + } + } + + public ulong Count + { + get => _count; + protected set + { + _count = value; + _managed = new TManaged[_count]; + } + } + + public IEnumerator GetEnumerator() + { + for (ulong i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (ulong i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + protected abstract TManaged Construct(void* ptr); + } +} diff --git a/MemoryModule/Formats/Macho/MachoModule.cs b/MemoryModule/Formats/Macho/MachoModule.cs new file mode 100644 index 0000000..9c6d019 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoModule.cs @@ -0,0 +1,250 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoModule : Module + { + public override IntPtr FileAddress { get; protected set; } + public override IntPtr MemoryAddress { get; internal set; } + public override ulong MemorySize { get; internal set; } + public override IntPtr PreferredAddress { get; protected set; } + public override Architecture Architecture { get; protected set; } + public override IReadOnlyList Sections { get; protected set; } + public override IReadOnlyList Rebases { get; protected set; } + public override IReadOnlyList Bindings { get; protected set; } + public override IReadOnlyList LazyBindings { get; protected set; } + public override IReadOnlyList WeakBindings { get; protected set; } + public override IReadOnlyList Initializers { get; protected set; } + public override IReadOnlyList Finalizers { get; protected set; } + public override IReadOnlyList Exports { get; protected set; } + public override IReadOnlyList PrivateSymbols { get; protected set; } + public override IReadOnlyList ReferencedLibraries { get; protected set; } + public override IReadOnlyDictionary ReferencedLibraryHandles { get; internal set; } + public override IReadOnlyList TlsModuleIdBindings { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override IReadOnlyList TlsGetAddrBindings { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsMemorySize { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsFileSize { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override int TlsModuleId { get => throw new NotImplementedException(); internal set => throw new NotImplementedException(); } + public override IntPtr TlsImageAddress { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsImageOffset { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + + private readonly MachoHeader _header; + + public MachoModule(IntPtr data) : this((byte*)data) + { + + } + + public MachoModule(byte* data) + { + _header = new MachoHeader(data); + + FileAddress = (IntPtr)data; + Architecture = ConvertArchitecture(_header.CpuType); + + // It's fair to say that the whole managed Module class is inspired by Macho. + Sections = _header.Segments; + + // MachoRebases are in terms of segment index and segment offset. + // We need to convert the format to affected virtual address that the loader uses. + var rebases = new List(); + foreach (var rebase in _header.Rebase) + { + rebases.Add(new GenericRebase() + { + Addend = 0, + AffectedAddress = _header.Segments[(int)rebase.SegmentIndex].MemoryOffset + rebase.SegmentOffset, + IgnoreExistingValue = false + }); + } + Rebases = rebases; + + List ConvertBind(MachoBindCollection collection) + { + var binds = new List(); + foreach (var bind in collection) + { + /// To do: This function assumes that all binds are of type + /// + /// Some more work may need to be done if some type of + /// or + /// suddenly crops up. + binds.Add(new GenericBind() + { + AffectedAddress = _header.Segments[(int)bind.SegmentIndex].MemoryOffset + bind.SegmentOffset, + Addend = 0, + ModuleName = _header.Dylibs[(int)bind.LibraryOrdinal].Name, + SymbolName = StripUnderscorePrefix(bind.Name), + }); + } + return binds; + } + + Bindings = ConvertBind(_header.Bindings); + LazyBindings = ConvertBind(_header.LazyBindings); + WeakBindings = ConvertBind(_header.WeakBindings); + + ReferencedLibraries = _header.Dylibs.Skip(1).Select(dylib => dylib.Name).ToList(); + } + + internal override void AfterRebase() + { + // Intializers: + var initList = new List(); + foreach (var seg in _header.Segments) + { + foreach (var sect in seg.Sections) + { + if (sect.Type == MachoSectionType.ModInitFuncPointers) + { + byte* addr = (byte*)MemoryAddress + sect.MemoryAddress; + ulong size = sect.MemorySize / (ulong)sizeof(IntPtr); + IntPtr* init = (IntPtr*)addr; + for (ulong i = 0; i < size; ++i) + { + initList.Add(new MachoInitializer((IntPtr)((ulong)MemoryAddress + (ulong)init[i]))); + } + } + } + } + + Initializers = initList; + + // Finalizers: + var finiList = new List(); + foreach (var seg in _header.Segments) + { + foreach (var sect in seg.Sections) + { + if (sect.Type == MachoSectionType.ModTermFuncPointers) + { + byte* addr = (byte*)MemoryAddress + sect.MemoryAddress; + ulong size = sect.MemorySize / (ulong)sizeof(IntPtr); + IntPtr* fini = (IntPtr*)addr; + for (ulong i = 0; i < size; ++i) + { + finiList.Add(new MachoFinalizer((IntPtr)((ulong)MemoryAddress + (ulong)fini[i]))); + } + } + } + } + + Finalizers = finiList; + + // Some functions expect an empty list. + Exports = new List(); + + // PrivateSymbols (an incomplete list, some functions that need local binding will use this list). + PrivateSymbols = BuildRuntimeSymbolTable(); + } + + internal override void AfterBinding() + { + // Exports: + var exports = new List(); + foreach (var export in _header.Exports) + { + if (export.ReexportName != null) + { + var handle = ReferencedLibraryHandles[_header.Dylibs[(int)export.LibraryOrdinal].Name]; + exports.Add(new GenericSymbol() + { + Name = StripUnderscorePrefix(export.Name), + Value = export.LibraryOrdinal, + Address = NativeFunctions.Default.GetSymbolFromLibrary(handle, export.ReexportName) + }); + } + else + { + exports.Add(new GenericSymbol() + { + Name = StripUnderscorePrefix(export.Name), + Value = export.Location, + Address = (IntPtr)((byte*)MemoryAddress + export.Location) + }); + } + } + + Exports = exports; + + // PrivateSymbols: Some (re-exports) might be invalid before binding. + PrivateSymbols = BuildRuntimeSymbolTable(); + } + + private List BuildRuntimeSymbolTable() + { + // Some dylibs load at specific addresses. + ulong firstSegmentVirtual = _header.Segments.FirstOrDefault()?.MemoryOffset ?? 0; + + var result = new List(); + + var symTab = _header.Commands.OfType().FirstOrDefault(); + var linkEdit = _header.Segments.FirstOrDefault(seg => seg.Name == "__LINKEDIT"); + var baseAddress = (byte*)MemoryAddress; + + + var symbolOffsetFromLinkedit = symTab.SymbolTableOffset - linkEdit.FileOffset; + var stringOffsetFromLinkedit = symTab.StringTableOffset - linkEdit.FileOffset; + var stringTablePtr = baseAddress + linkEdit.MemoryOffset - firstSegmentVirtual + stringOffsetFromLinkedit; + + var symbolTable = new MachoSymbolTableArray(baseAddress, linkEdit.MemoryOffset - firstSegmentVirtual + symbolOffsetFromLinkedit, symTab.SymbolCount); + + foreach (var sym in symbolTable) + { + try + { + var name = Marshal.PtrToStringAnsi((IntPtr)(stringTablePtr + sym.StringTableIndex)); + + // Probably some lazy bound imported symbol. + if (sym.Value == 0) + { + continue; + } + + var value = baseAddress + sym.Value - firstSegmentVirtual; + + result.Add(new GenericSymbol() + { + Name = StripUnderscorePrefix(name), + Value = sym.Value, + Address = (IntPtr)value + }); + } + catch (Exception e) + { + Console.WriteLine($"Fail: {e}"); + } + } + + return result; + } + + private static string StripUnderscorePrefix(string name) + { + return (name?.StartsWith("_") ?? false) ? name.Substring(1) : name; + } + + private static Architecture ConvertArchitecture(MachoCpuType cpuType) + { + switch (cpuType) + { + case MachoCpuType.I386: + return Architecture.X86; + case MachoCpuType.X86_64: + return Architecture.X64; + case MachoCpuType.ARM: + case MachoCpuType.ARM64_32: + return Architecture.Arm; + case MachoCpuType.ARM64: + return Architecture.Arm64; + default: + throw new NotSupportedException($"Unsupported architecture: {cpuType}"); + } + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoRebase.cs b/MemoryModule/Formats/Macho/MachoRebase.cs new file mode 100644 index 0000000..c292b65 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoRebase.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + class MachoRebase + { + //seg-index, seg-offset, type + public ulong SegmentIndex { get; internal set; } + public ulong SegmentOffset { get; internal set; } + public MachoRebaseType Type { get; internal set; } + + public object Clone() + { + return new MachoRebase() + { + SegmentIndex = SegmentIndex, + SegmentOffset = SegmentOffset, + Type = Type, + }; + } + + public override string ToString() + { + return +$@"Segment Index: {SegmentIndex}, +Segment Offset: 0x{SegmentOffset:x}, +Type: {Type}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoRebaseCollection.cs b/MemoryModule/Formats/Macho/MachoRebaseCollection.cs new file mode 100644 index 0000000..2d7270e --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoRebaseCollection.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoRebaseCollection : MachoCompressedCollection + { + const byte OpMask = (byte)MachoRebaseOpcode.Mask; + const byte ImMask = (byte)MachoRebaseOpcode.ImmediateMask; + + private static readonly ulong PtrSize = (ulong)sizeof(IntPtr); + + public MachoRebaseCollection(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + + protected override List Decompress() + { + var ptr = _data; + var end = _data + _size; + + var result = new List(); + + var currentRebase = new MachoRebase(); + + while (ptr < end) + { + var op = (MachoRebaseOpcode)((*ptr) & OpMask); + var im = (byte)((*ptr) & ImMask); + ++ptr; + + switch (op) + { + case MachoRebaseOpcode.Done: + currentRebase = new MachoRebase(); + break; + case MachoRebaseOpcode.AddAddrImmScaled: + currentRebase.SegmentOffset += PtrSize * im; + break; + case MachoRebaseOpcode.AddAddrUleb: + currentRebase.SegmentOffset += ReadUleb128(ref ptr); + break; + case MachoRebaseOpcode.SetSegmentAndOffsetUleb: + currentRebase.SegmentIndex = im; + currentRebase.SegmentOffset = ReadUleb128(ref ptr); + break; + case MachoRebaseOpcode.SetTypeImm: + currentRebase.Type = (MachoRebaseType)im; + break; + case MachoRebaseOpcode.DoRebaseAddAddrUleb: + result.Add((MachoRebase)currentRebase.Clone()); + currentRebase.SegmentOffset += ReadUleb128(ref ptr); + break; + case MachoRebaseOpcode.DoRebaseImmTimes: + // Bruh, this is faster than a Linq + for (int i = 0; i < im; ++i) + { + result.Add((MachoRebase)currentRebase.Clone()); + // The pointer should go forward, that makes more sense. + currentRebase.SegmentOffset += PtrSize; + } + break; + case MachoRebaseOpcode.DoRebaseUlebTimes: + { + ulong count = ReadUleb128(ref ptr); + for (ulong i = 0; i < count; ++i) + { + result.Add((MachoRebase)currentRebase.Clone()); + currentRebase.SegmentOffset += PtrSize; + } + } + break; + case MachoRebaseOpcode.DoRebaseUlebTimesSkippingUleb: + { + ulong count = ReadUleb128(ref ptr); + ulong skip = ReadUleb128(ref ptr); + for (ulong i = 0; i < count; ++i) + { + result.Add((MachoRebase)currentRebase.Clone()); + currentRebase.SegmentOffset += skip; + } + } + break; + default: + System.Diagnostics.Debug.WriteLine($"Unknown opcode: 0x{(ulong)op:x}"); + break; + } + } + + return result; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoRebaseOpcode.cs b/MemoryModule/Formats/Macho/MachoRebaseOpcode.cs new file mode 100644 index 0000000..f05556e --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoRebaseOpcode.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoRebaseOpcode + { + Mask = 0xF0, + ImmediateMask = 0x0F, + Done = 0x00, + SetTypeImm = 0x10, + SetSegmentAndOffsetUleb = 0x20, + AddAddrUleb = 0x30, + AddAddrImmScaled = 0x40, + DoRebaseImmTimes = 0x50, + DoRebaseUlebTimes = 0x60, + DoRebaseAddAddrUleb = 0x70, + DoRebaseUlebTimesSkippingUleb = 0x80, + } +} diff --git a/MemoryModule/Formats/Macho/MachoRebaseType.cs b/MemoryModule/Formats/Macho/MachoRebaseType.cs new file mode 100644 index 0000000..9dff0d4 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoRebaseType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoRebaseType + { + Pointer = 1, + TextAbsolute32 = 2, + TextPcrel32 = 3, + } +} diff --git a/MemoryModule/Formats/Macho/MachoSection.cs b/MemoryModule/Formats/Macho/MachoSection.cs new file mode 100644 index 0000000..e1b1d49 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSection.cs @@ -0,0 +1,56 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSection + { + const uint SectionTypeMask = 0x000000ff; /* 256 section types */ + + private static readonly ulong _sectnameOffset = (ulong)Marshal.OffsetOf(nameof(MachoSectionNative.sectname)); + private static readonly ulong _segnameOffset = (ulong)Marshal.OffsetOf(nameof(MachoSectionNative.segname)); + + private byte* _memory; + private byte* _data; + private string _sectname; + private string _segname; + + private MachoSectionNative* Section => (MachoSectionNative*)_data; + + public MachoSection(byte* memory, ulong offset) + { + _memory = memory; + _data = _memory + offset; + + _sectname = Marshal.PtrToStringAnsi((IntPtr)(_data + _sectnameOffset)); + _segname = Marshal.PtrToStringAnsi((IntPtr)(_data + _segnameOffset)); + } + + public string SectionName => _sectname; + public string SegmentName => _segname; + public ulong MemoryAddress => (ulong)Section->addr; + public ulong MemorySize => (ulong)Section->size; + public uint FileOffset => Section->offset; + public uint Alignment => Section->align; + public uint RelocOffset => Section->reloff; + public uint RelocCount => Section->nreloc; + public MachoSectionType Type => (MachoSectionType)(Section->flags & SectionTypeMask); + + public override string ToString() + { + return +$@"Section Name: {SectionName}, +Segment Name: {SegmentName}, +Type: {Type}, +Memory Address: 0x{(ulong)MemoryAddress:x}, +Memory Size: 0x{MemorySize:x}, +File Offset: 0x{FileOffset:x}, +Alignment: 0x{Alignment:x}, +Reloc Offset: 0x{RelocOffset}, +Reloc Count: {RelocCount}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoSectionArray.cs b/MemoryModule/Formats/Macho/MachoSectionArray.cs new file mode 100644 index 0000000..df6cc5c --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSectionArray.cs @@ -0,0 +1,19 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSectionArray : MachoManagedArray + { + public MachoSectionArray(byte* data, ulong offset, ulong count) : base(data, offset, count) + { + } + + protected override unsafe MachoSection Construct(void* ptr) + { + return new MachoSection(_memory, (ulong)((byte *)ptr - _memory)); + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoSectionType.cs b/MemoryModule/Formats/Macho/MachoSectionType.cs new file mode 100644 index 0000000..19855cc --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSectionType.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + enum MachoSectionType : uint + { + Regular = 0x0, + Zerofill = 0x1, + CstringLiterals = 0x2, + _4ByteLiterals = 0x3, + _8ByteLiterals = 0x4, + LiteralPointers = 0x5, + NonLazySymbolPointers = 0x6, + LazySymbolPointers = 0x7, + SymbolStubs = 0x8, + ModInitFuncPointers = 0x9, + ModTermFuncPointers = 0xa, + Coalesced = 0xb, + GBZerofile = 0xc, + Interposing = 0xd, + _16ByteLiterals = 0xe, + DtraceDOF = 0xf, + LazyDylibSymbolPointers = 0x10, + ThreadLocalRegular = 0x11, + ThreadLocalZeroFill = 0x12, + ThreadLocalVariables = 0x13, + ThreadLocalVariablePointers = 0x14, + ThreadLocalInitFunctionPointers = 0x15 + } +} diff --git a/MemoryModule/Formats/Macho/MachoSegmentLoadCommand.cs b/MemoryModule/Formats/Macho/MachoSegmentLoadCommand.cs new file mode 100644 index 0000000..f0da0bc --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSegmentLoadCommand.cs @@ -0,0 +1,87 @@ +using MemoryModule.Abstractions; +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSegmentLoadCommand : MachoLoadCommand, ISection + { + private MachoSegmentLoadCommandNative* Command => (MachoSegmentLoadCommandNative*)_data; + private string _name; + + private MachoSectionArray _sections; + + public MachoSegmentLoadCommand(byte* memory, ulong fileOffset) : base(memory, fileOffset) + { + Debug.Assert(CommandType == (Environment.Is64BitProcess ? MachoLoadCommandType.LC_SEGMENT_64 : MachoLoadCommandType.LC_SEGMENT)); + + var offset = Marshal.OffsetOf(nameof(MachoSegmentLoadCommandNative.segname)); + _name = Marshal.PtrToStringAnsi((IntPtr)(_data + (ulong)offset)); + + _sections = new MachoSectionArray(_memory, fileOffset + (ulong)sizeof(MachoSegmentLoadCommandNative), Command->nsects); + } + + public string Name => _name; + + public MachoSectionArray Sections => _sections; + + [Obsolete("Use MemoryOffset instead.")] + public ulong VirtualAddress => MemoryOffset; + [Obsolete("Use FileOffset instead.")] + public ulong Offset => FileOffset; + + public MachoVmProtection InitialProtection => Command->initprot; + public MachoVmProtection MaxProtection => Command->maxprot; + + public ulong MemoryOffset => (ulong)Command->vmaddr; + public ulong MemorySize => (ulong)Command->vmsize; + + public ulong FileOffset => (ulong)Command->fileoff; + public ulong FileSize => (ulong)Command->filesize; + + public MemoryProtection MemoryProtection => ConvertProtection(Command->initprot); + + public SectionType Type => SectionType.Unknown; + + private static MemoryProtection ConvertProtection(MachoVmProtection protection) + { + var result = (MemoryProtection)0; + + if (protection.HasFlag(MachoVmProtection.Read)) + { + result |= MemoryProtection.Read; + } + + if (protection.HasFlag(MachoVmProtection.Write)) + { + result |= MemoryProtection.Write; + } + + if (protection.HasFlag(MachoVmProtection.Execute)) + { + result |= MemoryProtection.Execute; + } + + return result; + } + + public override string ToString() + { + return +$@"{base.ToString()}, +Name: {_name}, +Memory offset: 0x{(ulong)Command->vmaddr:x}, +Memory size: 0x{(ulong)Command->vmsize:x}, +File offset: 0x{(ulong)Command->fileoff:x}, +File size: 0x{(ulong)Command->filesize:x}, +Max protection: 0x{(ulong)Command->maxprot:x}, +Init protection: 0x{(ulong)Command->initprot:x}, +Section count: {Command->nsects}, +Sections: +{string.Join("\n", Sections.Select(sec => " " + sec.ToString().Replace("\n", "\n ")))}"; + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Macho/MachoSymbolTableArray.cs b/MemoryModule/Formats/Macho/MachoSymbolTableArray.cs new file mode 100644 index 0000000..963e4b2 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSymbolTableArray.cs @@ -0,0 +1,19 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSymbolTableArray : MachoManagedArray + { + public MachoSymbolTableArray(byte* data, ulong offset, ulong count) : base(data, offset, count) + { + } + + protected override unsafe MachoSymbolTableEntry Construct(void* ptr) + { + return new MachoSymbolTableEntry(_memory, (ulong)((byte*)ptr - _memory)); + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoSymbolTableEntry.cs b/MemoryModule/Formats/Macho/MachoSymbolTableEntry.cs new file mode 100644 index 0000000..a2b98f5 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSymbolTableEntry.cs @@ -0,0 +1,24 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSymbolTableEntry + { + private byte* _memory; + private byte* _data; + + private MachoSymbolTableEntryNative* Entry => (MachoSymbolTableEntryNative*)_data; + + public MachoSymbolTableEntry(byte* memory, ulong offset) + { + _memory = memory; + _data = _memory + offset; + } + + public ulong Value => (ulong)Entry->n_value; + public uint StringTableIndex => Entry->n_un.n_strx; + } +} diff --git a/MemoryModule/Formats/Macho/MachoSymbolTableLoadCommand.cs b/MemoryModule/Formats/Macho/MachoSymbolTableLoadCommand.cs new file mode 100644 index 0000000..4a82ca1 --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoSymbolTableLoadCommand.cs @@ -0,0 +1,32 @@ +using MemoryModule.Formats.Macho.Natives; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + unsafe class MachoSymbolTableLoadCommand : MachoLoadCommand + { + private MachoSymbolTableLoadCommandNative* Command => (MachoSymbolTableLoadCommandNative*)_data; + + public MachoSymbolTableLoadCommand(byte* memory, ulong offset) : base(memory, offset) + { + } + + public uint SymbolTableOffset => Command->symoff; + public uint SymbolCount => Command->nsyms; + + public uint StringTableOffset => Command->stroff; + public uint StringTableSize => Command->strsize; + + public override string ToString() + { + return +$@"{base.ToString()} +Symbol table offset: 0x{SymbolTableOffset:x} +Symbol count: {SymbolCount} +String table offset: 0x{StringTableOffset:x} +String table size: {StringTableSize}"; + } + } +} diff --git a/MemoryModule/Formats/Macho/MachoVmProtection.cs b/MemoryModule/Formats/Macho/MachoVmProtection.cs new file mode 100644 index 0000000..45b7d1c --- /dev/null +++ b/MemoryModule/Formats/Macho/MachoVmProtection.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.Macho +{ + [Flags] + enum MachoVmProtection : uint + { + Read = 0x1, + Write = 0x2, + Execute = 0x4, + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoDyldInfoLoadCommandNative.cs b/MemoryModule/Formats/Macho/Natives/MachoDyldInfoLoadCommandNative.cs new file mode 100644 index 0000000..861560a --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoDyldInfoLoadCommandNative.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoDyldInfoLoadCommandNative + { + public uint cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */ + public uint cmdsize; /* sizeof(struct dyld_info_command) */ + + /* + * Dyld rebases an image whenever dyld loads it at an address different + * from its preferred address. The rebase information is a stream + * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_. + * Conceptually the rebase information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like "every n'th offset for m times" can be encoded in a few + * bytes. + */ + public uint rebase_off; /* file offset to rebase info */ + public uint rebase_size; /* size of rebase info */ + + /* + * Dyld binds an image during the loading process, if the image + * requires any pointers to be initialized to symbols in other images. + * The bind information is a stream of byte sized + * opcodes whose symbolic names start with BIND_OPCODE_. + * Conceptually the bind information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like for runs of pointers initialzed to the same value can be + * encoded in a few bytes. + */ + public uint bind_off; /* file offset to binding info */ + public uint bind_size; /* size of binding info */ + + /* + * Some C++ programs require dyld to unique symbols so that all + * images in the process use the same copy of some code/data. + * This step is done after binding. The content of the weak_bind + * info is an opcode stream like the bind_info. But it is sorted + * alphabetically by symbol name. This enable dyld to walk + * all images with weak binding information in order and look + * for collisions. If there are no collisions, dyld does + * no updating. That means that some fixups are also encoded + * in the bind_info. For instance, all calls to "operator new" + * are first bound to libstdc++.dylib using the information + * in bind_info. Then if some image overrides operator new + * that is detected when the weak_bind information is processed + * and the call to operator new is then rebound. + */ + public uint weak_bind_off; /* file offset to weak binding info */ + public uint weak_bind_size; /* size of weak binding info */ + + /* + * Some uses of external symbols do not need to be bound immediately. + * Instead they can be lazily bound on first use. The lazy_bind + * are contains a stream of BIND opcodes to bind all lazy symbols. + * Normal use is that dyld ignores the lazy_bind section when + * loading an image. Instead the static linker arranged for the + * lazy pointer to initially point to a helper function which + * pushes the offset into the lazy_bind area for the symbol + * needing to be bound, then jumps to dyld which simply adds + * the offset to lazy_bind_off to get the information on what + * to bind. + */ + public uint lazy_bind_off; /* file offset to lazy binding info */ + public uint lazy_bind_size; /* size of lazy binding infs */ + + /* + * The symbols exported by a dylib are encoded in a trie. This + * is a compact representation that factors out common prefixes. + * It also reduces LINKEDIT pages in RAM because it encodes all + * information (name, address, flags) in one small, contiguous range. + * The export area is a stream of nodes. The first node sequentially + * is the start node for the trie. + * + * Nodes for a symbol start with a uleb128 that is the length of + * the exported symbol information for the string so far. + * If there is no exported symbol, the node starts with a zero byte. + * If there is exported info, it follows the length. First is + * a uleb128 containing flags. Normally, it is followed by a + * uleb128 encoded offset which is location of the content named + * by the symbol from the mach_header for the image. If the flags + * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is + * a uleb128 encoded library ordinal, then a zero terminated + * UTF8 string. If the string is zero length, then the symbol + * is re-export from the specified dylib with the same name. + * + * After the optional exported symbol information is a byte of + * how many edges (0-255) that this node has leaving it, + * followed by each edge. + * Each edge is a zero terminated UTF8 of the addition chars + * in the symbol, followed by a uleb128 offset for the node that + * edge points to. + * + */ + public uint export_off; /* file offset to lazy binding info */ + public uint export_size; /* size of lazy binding infs */ + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoDylibLoadCommandNative.cs b/MemoryModule/Formats/Macho/Natives/MachoDylibLoadCommandNative.cs new file mode 100644 index 0000000..279c375 --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoDylibLoadCommandNative.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoDylibLoadCommandNative + { + public MachoLoadCommandType cmd; /* type of load command */ + public uint cmdsize; /* total size of command in bytes */ + + public uint name; // Offset to the name + public uint timestamp; + public uint current_version; + public uint compatibility_version; + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoHeaderNative.cs b/MemoryModule/Formats/Macho/Natives/MachoHeaderNative.cs new file mode 100644 index 0000000..646965b --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoHeaderNative.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Explicit)] + struct MachoHeaderNative + { + [FieldOffset(0)] + public MachoMagic magic; /* mach magic number identifier */ + [FieldOffset(4)] + public MachoCpuType cputype; /* cpu specifier */ + [FieldOffset(8)] + public MachoCpuSubtype cpusubtype; /* machine specifier */ + [FieldOffset(12)] + public MachoFileType filetype; /* type of file */ + [FieldOffset(16)] + public uint ncmds; /* number of load commands */ + [FieldOffset(20)] + public uint sizeofcmds; /* the size of all the load commands */ + [FieldOffset(24)] + public UIntPtr flags_and_reserved; /* A bundle that is 64-bit on 64-bit platforms. */ + [FieldOffset(24)] + public MachoFileFlags flags; /* flags */ + + public uint reserved + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (uint)((ulong)flags_and_reserved & uint.MaxValue); + } + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoLoadCommandNative.cs b/MemoryModule/Formats/Macho/Natives/MachoLoadCommandNative.cs new file mode 100644 index 0000000..aafff3a --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoLoadCommandNative.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoLoadCommandNative + { + public MachoLoadCommandType cmd; /* type of load command */ + public uint cmdsize; /* total size of command in bytes */ + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/Macho/Natives/MachoSectionNative.cs b/MemoryModule/Formats/Macho/Natives/MachoSectionNative.cs new file mode 100644 index 0000000..0501038 --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoSectionNative.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct MachoSectionNative + { + public fixed byte sectname[16]; /* name of this section */ + public fixed byte segname[16]; /* segment this section goes in */ + public UIntPtr addr; /* memory address of this section */ + public UIntPtr size; /* size in bytes of this section */ + public uint offset; /* file offset of this section */ + public uint align; /* section alignment (power of 2) */ + public uint reloff; /* file offset of relocation entries */ + public uint nreloc; /* number of relocation entries */ + public uint flags; /* flags (section type and attributes)*/ + public uint reserved1; /* reserved (for offset or index) */ + public UIntPtr reserved2_3; /* reserved (for count or sizeof) */ + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoSegmentLoadCommandNative.cs b/MemoryModule/Formats/Macho/Natives/MachoSegmentLoadCommandNative.cs new file mode 100644 index 0000000..126e53d --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoSegmentLoadCommandNative.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoSegmentLoadCommandNative + { + public uint cmd; /* LC_SEGMENT_64 */ + public uint cmdsize; /* includes sizeof section_64 structs */ + public unsafe fixed byte segname[16]; /* segment name */ + public UIntPtr vmaddr; /* memory address of this segment */ + public UIntPtr vmsize; /* memory size of this segment */ + public UIntPtr fileoff; /* file offset of this segment */ + public UIntPtr filesize; /* amount to map from the file */ + public MachoVmProtection maxprot; /* maximum VM protection */ + public MachoVmProtection initprot; /* initial VM protection */ + public uint nsects; /* number of sections in segment */ + public uint flags; /* flags */ + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoSymbolTableEntryNative.cs b/MemoryModule/Formats/Macho/Natives/MachoSymbolTableEntryNative.cs new file mode 100644 index 0000000..2714e39 --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoSymbolTableEntryNative.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoSymbolTableEntryNative + { + [StructLayout(LayoutKind.Explicit)] + public unsafe struct DummyUnion + { + [FieldOffset(0)] + private uint n_name_field; + [FieldOffset(0)] + public uint n_strx; + public byte* n_name => (byte*)n_name_field; + } + public DummyUnion n_un; + public byte n_type; /* type flag, see below */ + public byte n_sect; /* section number or NO_SECT */ + public short n_desc; /* see */ + public UIntPtr n_value; /* value of this symbol (or stab offset) */ + } +} diff --git a/MemoryModule/Formats/Macho/Natives/MachoSymbolTableLoadCommandNative.cs b/MemoryModule/Formats/Macho/Natives/MachoSymbolTableLoadCommandNative.cs new file mode 100644 index 0000000..be6f353 --- /dev/null +++ b/MemoryModule/Formats/Macho/Natives/MachoSymbolTableLoadCommandNative.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.Macho.Natives +{ + [StructLayout(LayoutKind.Sequential)] + struct MachoSymbolTableLoadCommandNative + { + public uint cmd; /* LC_SYMTAB */ + public uint cmdsize; /* sizeof(struct symtab_command) */ + public uint symoff; /* symbol table offset */ + public uint nsyms; /* number of symbol table entries */ + public uint stroff; /* string table offset */ + public uint strsize; /* string table size in bytes */ + } +} diff --git a/MemoryModule/Formats/MemoryObject.cs b/MemoryModule/Formats/MemoryObject.cs new file mode 100644 index 0000000..127ce75 --- /dev/null +++ b/MemoryModule/Formats/MemoryObject.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + /// + /// An object that's represented by an offset to a base address. + /// + unsafe abstract class MemoryObject + { + protected byte* _memory; + protected byte* _data; + + public MemoryObject(byte* memory, ulong offset) + { + _memory = memory; + _data = _memory + offset; + } + + /// + /// Gets an address bytes from the base of this object. + /// + /// The desired offset + /// The address + /// This function is useful for purposes such as resolving string tables in some executable formats. + public byte* GetAddress(ulong offset = 0) + { + return _memory + offset; + } + } +} diff --git a/MemoryModule/Formats/MemoryValueObject.cs b/MemoryModule/Formats/MemoryValueObject.cs new file mode 100644 index 0000000..4c5d2b4 --- /dev/null +++ b/MemoryModule/Formats/MemoryValueObject.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + /// + /// A MemoryObject that wraps around an umanaged struct . + /// + /// The corresponding native object type. + unsafe class MemoryValueObject : MemoryObject where T: unmanaged + { + protected T* _native => (T*)_data; + + public MemoryValueObject(byte* memory, ulong offset) : base(memory, offset) + { + } + } +} diff --git a/MemoryModule/Formats/MemoryValueObjectArray.cs b/MemoryModule/Formats/MemoryValueObjectArray.cs new file mode 100644 index 0000000..bba5063 --- /dev/null +++ b/MemoryModule/Formats/MemoryValueObjectArray.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats +{ + /// + /// A contiguous array of unmanaged objects, wrapped by a + /// + /// The native type. + /// The corresponding managed type + unsafe abstract class MemoryValueObjectArray : MemoryObject, IReadOnlyList + where T : unmanaged + where TManaged : MemoryValueObject + { + protected ulong _count; + protected TManaged[] _arr; + + /// + /// Creates an array, with a base of , starting at , and can hold + /// native elements. + /// + /// The base memory + /// The offset + /// The size, in ELEMENTS, not bytes. + protected MemoryValueObjectArray(byte* memory, ulong offset, ulong size) : base(memory, offset) + { + _count = size; + _arr = (TManaged[])Array.CreateInstance(typeof(TManaged), (long)_count); + } + + protected abstract TManaged Construct(byte* memory, ulong offset); + + public TManaged this[int index] + { + get + { + if (index < 0) + { + throw new IndexOutOfRangeException(); + } + return this[(ulong)index]; + } + } + + public TManaged this[ulong index] + { + get + { + // This checked bounds for us. + if (_arr[index] == null) + { + _arr[index] = Construct(_memory, (ulong)(_data - _memory) + (ulong)sizeof(T) * index); + } + return _arr[index]; + } + } + + public int Count => ((IReadOnlyCollection)_arr).Count; + + public IEnumerator GetEnumerator() + { + // Elements might be null on first enumeration. + for (ulong i = 0; i < _count; ++i) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _arr.GetEnumerator(); + } + } + + unsafe abstract class MemoryValueObjectArray : MemoryValueObjectArray> + where T: unmanaged + { + protected MemoryValueObjectArray(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + } + +} diff --git a/MemoryModule/Formats/PE/Dll.cs b/MemoryModule/Formats/PE/Dll.cs new file mode 100644 index 0000000..d73651b --- /dev/null +++ b/MemoryModule/Formats/PE/Dll.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + internal enum DllOperation : uint + { + ProcessAttach = 1, + ThreadAttach = 2, + ThreadDetach = 3, + ProcessDetach = 0, + ProcessVerifier = 4 + } +} diff --git a/MemoryModule/Formats/PE/DosHeader.cs b/MemoryModule/Formats/PE/DosHeader.cs new file mode 100644 index 0000000..708d21e --- /dev/null +++ b/MemoryModule/Formats/PE/DosHeader.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class DosHeader : MemoryValueObject + { + public DosHeader(byte* memory, ulong offset = 0) : base(memory, offset) + { + } + + /// + /// The offset to the new PE header. + /// + public ulong NewHeaderOffset => (ulong)_native->e_lfanew; + } +} diff --git a/MemoryModule/Formats/PE/DosHeaderNative.cs b/MemoryModule/Formats/PE/DosHeaderNative.cs new file mode 100644 index 0000000..04b05c1 --- /dev/null +++ b/MemoryModule/Formats/PE/DosHeaderNative.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct DosHeaderNative + { + public ushort e_magic; + public ushort e_cblp; + public ushort e_cp; + public ushort e_crlc; + public ushort e_cparhdr; + public ushort e_minalloc; + public ushort e_maxalloc; + public ushort e_ss; + public ushort e_sp; + public ushort e_csum; + public ushort e_ip; + public ushort e_cs; + public ushort e_lfarlc; + public ushort e_ovno; + public fixed ushort e_res[4]; + public ushort e_oemid; + public ushort e_oeminfo; + public fixed ushort e_res2[10]; + public int e_lfanew; + } +} diff --git a/MemoryModule/Formats/PE/PeBaseRelocation.cs b/MemoryModule/Formats/PE/PeBaseRelocation.cs new file mode 100644 index 0000000..915bf1a --- /dev/null +++ b/MemoryModule/Formats/PE/PeBaseRelocation.cs @@ -0,0 +1,36 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeBaseRelocation : MemoryValueObject, IRebase + { + public PeBaseRelocationType Type { get; private set; } + + public PeBaseRelocation(byte* memory, ulong offset, ulong parentRva) : base(memory, offset) + { + var value = *(ushort*)(memory + offset); + AffectedAddress = parentRva + (value & 0xfffu); + Type = (PeBaseRelocationType)(value >> 12); + + switch (Type) + { + case PeBaseRelocationType.Absolute: + case PeBaseRelocationType.Highlow: + case PeBaseRelocationType.Highadj: + case PeBaseRelocationType.Dir64: + break; + default: + throw new NotImplementedException($"Unsupported relocation type: {Type}"); + } + } + + public ulong AffectedAddress { get; private set; } + + public ulong Addend => 0; + + public bool IgnoreExistingValue => false; + } +} diff --git a/MemoryModule/Formats/PE/PeBaseRelocationBlock.cs b/MemoryModule/Formats/PE/PeBaseRelocationBlock.cs new file mode 100644 index 0000000..23dfd11 --- /dev/null +++ b/MemoryModule/Formats/PE/PeBaseRelocationBlock.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeBaseRelocationBlock : MemoryValueObject, IReadOnlyList + { + private class PeBaseRelocationBlockInternal : MemoryValueObjectArray + { + private ulong _parentRva; + + public PeBaseRelocationBlockInternal(byte* memory, ulong offset, ulong size, ulong parentRva) : base(memory, offset, size) + { + _parentRva = parentRva; + } + + protected override unsafe PeBaseRelocation Construct(byte* memory, ulong offset) + { + return new PeBaseRelocation(memory, offset, _parentRva); + } + } + + private readonly PeBaseRelocationBlockInternal _list; + + public PeBaseRelocationBlock(byte* memory, ulong offset) : base(memory, offset) + { + _list = new PeBaseRelocationBlockInternal(memory, offset + (uint)sizeof(PeBaseRelocationBlockNative), (_native->SizeOfBlock - (uint)sizeof(PeBaseRelocationBlockNative)) / sizeof(ushort), _native->VirtualAddress); + } + + public int Count => ((IReadOnlyCollection)_list).Count; + + public PeBaseRelocation this[int index] => ((IReadOnlyList)_list)[index]; + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + } +} diff --git a/MemoryModule/Formats/PE/PeBaseRelocationBlockNative.cs b/MemoryModule/Formats/PE/PeBaseRelocationBlockNative.cs new file mode 100644 index 0000000..0e74901 --- /dev/null +++ b/MemoryModule/Formats/PE/PeBaseRelocationBlockNative.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + struct PeBaseRelocationBlockNative + { + public uint VirtualAddress; + public uint SizeOfBlock; + } +} diff --git a/MemoryModule/Formats/PE/PeBaseRelocationType.cs b/MemoryModule/Formats/PE/PeBaseRelocationType.cs new file mode 100644 index 0000000..75ff830 --- /dev/null +++ b/MemoryModule/Formats/PE/PeBaseRelocationType.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + enum PeBaseRelocationType : uint + { + /// + /// The base relocation is skipped. This type can be used to pad a block. + /// + Absolute = 0, + /// + /// The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the high value of a 32-bit word. + /// + High = 1, + /// + /// The base relocation adds the low 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the low half of a 32-bit word. + /// + Low = 2, + /// + /// The base relocation applies all 32 bits of the difference to the 32-bit field at offset. + /// + Highlow = 3, + /// + /// The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the high value of a 32-bit word. The low 16 bits of the 32-bit value are stored in the 16-bit word that follows this base relocation. This means that this base relocation occupies two slots. + /// + Highadj = 4, + /// + /// The relocation interpretation is dependent on the machine type. When the machine type is MIPS, the base relocation applies to a MIPS jump instruction. + /// + MipsJmpaddr = 5, + /// + /// This relocation is meaningful only when the machine type is ARM or Thumb. The base relocation applies the 32-bit address of a symbol across a consecutive MOVW/MOVT instruction pair. + /// + ArmMov32 = 5, + /// + /// This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the high 20 bits of a 32-bit absolute address. + /// + RiscvHigh20 = 5, + /// + /// This relocation is meaningful only when the machine type is Thumb. The base relocation applies the 32-bit address of a symbol to a consecutive MOVW/MOVT instruction pair. + /// + ThumbMov32 = 7, + /// + /// This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the low 12 bits of a 32-bit absolute address formed in RISC-V I-type instruction format. + /// + RiscvLow12i = 7, + /// + /// This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the low 12 bits of a 32-bit absolute address formed in RISC-V S-type instruction format. + /// + RiscvLow12s = 8, + /// + /// The relocation is only meaningful when the machine type is MIPS. The base relocation applies to a MIPS16 jump instruction. + /// + MipsJmpaddr16 = 9, + /// + /// The base relocation applies the difference to the 64-bit field at offset. + /// + Dir64 = 10, + } +} diff --git a/MemoryModule/Formats/PE/PeCoffHeader.cs b/MemoryModule/Formats/PE/PeCoffHeader.cs new file mode 100644 index 0000000..c1cee7b --- /dev/null +++ b/MemoryModule/Formats/PE/PeCoffHeader.cs @@ -0,0 +1,13 @@ +namespace MemoryModule.Formats.PE +{ + unsafe class PeCoffHeader : MemoryValueObject + { + public PeCoffHeader(byte* memory, ulong offset) : base(memory, offset) + { + } + + public PeMachineType MachineType => _native->Machine; + public ushort SizeOfOptionalHeader => _native->SizeOfOptionalHeader; + public ushort NumberOfSections => _native->NumberOfSections; + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeCoffHeaderNative.cs b/MemoryModule/Formats/PE/PeCoffHeaderNative.cs new file mode 100644 index 0000000..fbab937 --- /dev/null +++ b/MemoryModule/Formats/PE/PeCoffHeaderNative.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + struct PeCoffHeaderNative + { + public PeMachineType Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } +} diff --git a/MemoryModule/Formats/PE/PeDataDirectory.cs b/MemoryModule/Formats/PE/PeDataDirectory.cs new file mode 100644 index 0000000..70ed6ba --- /dev/null +++ b/MemoryModule/Formats/PE/PeDataDirectory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeDataDirectory : MemoryValueObject + { + public PeDataDirectory(byte* memory, ulong offset) : base(memory, offset) + { + } + + public uint VirtualAddress => _native->VirtualAddress; + public uint Size => _native->Size; + } +} diff --git a/MemoryModule/Formats/PE/PeDataDirectoryArray.cs b/MemoryModule/Formats/PE/PeDataDirectoryArray.cs new file mode 100644 index 0000000..15b5ffe --- /dev/null +++ b/MemoryModule/Formats/PE/PeDataDirectoryArray.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeDataDirectoryArray : MemoryValueObjectArray + { + public PeDataDirectoryArray(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + + public PeDataDirectory this[PeDirectoryEntryType type] => this[(ulong)type]; + + protected override unsafe PeDataDirectory Construct(byte* memory, ulong offset) + { + return new PeDataDirectory(memory, offset); + } + } +} diff --git a/MemoryModule/Formats/PE/PeDataDirectoryNative.cs b/MemoryModule/Formats/PE/PeDataDirectoryNative.cs new file mode 100644 index 0000000..ea9bfb3 --- /dev/null +++ b/MemoryModule/Formats/PE/PeDataDirectoryNative.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + struct PeDataDirectoryNative + { + public uint VirtualAddress; + public uint Size; + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeDirectoryEntryType.cs b/MemoryModule/Formats/PE/PeDirectoryEntryType.cs new file mode 100644 index 0000000..54f607e --- /dev/null +++ b/MemoryModule/Formats/PE/PeDirectoryEntryType.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + enum PeDirectoryEntryType : uint + { + Export = 0, + Import = 1, + Resource = 2, + Exception = 3, + Security = 4, + BaseRelocation = 5, + Debug = 6, + Copyright = 7, + Architecture = 7, + GlobalPtr = 8, + Tls = 9, + LoadConfig = 10, + BoundImport = 11, + Iat = 12, + DelayImport = 13, + ComDescriptor = 14 + } +} diff --git a/MemoryModule/Formats/PE/PeExportDirectory.cs b/MemoryModule/Formats/PE/PeExportDirectory.cs new file mode 100644 index 0000000..b58202c --- /dev/null +++ b/MemoryModule/Formats/PE/PeExportDirectory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeExportDirectory : MemoryValueObject + { + public PeExportDirectory(byte* memory, ulong offset) : base(memory, offset) + { + Name = Marshal.PtrToStringAnsi((IntPtr)GetAddress(_native->Name)); + } + + public string Name { get; private set; } + public uint OrdinalBase => _native->Base; + public uint EntryCount => _native->NumberOfFunctions; + public uint NameCount => _native->NumberOfNames; + public uint NameArrayOffset => _native->AddressOfNames; + public uint AddressArrayOffset => _native->AddressOfFunctions; + public uint OrdinalArrayOffset => _native->AddressOfNameOrdinals; + } +} diff --git a/MemoryModule/Formats/PE/PeExportDirectoryNative.cs b/MemoryModule/Formats/PE/PeExportDirectoryNative.cs new file mode 100644 index 0000000..d0c6e5e --- /dev/null +++ b/MemoryModule/Formats/PE/PeExportDirectoryNative.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + struct PeExportDirectoryNative + { + public uint Characteristics; + public uint TimeDateStamp; + public ushort MajorVersion; + public ushort MinorVersion; + public uint Name; + public uint Base; + public uint NumberOfFunctions; + public uint NumberOfNames; + public uint AddressOfFunctions; + public uint AddressOfNames; + public uint AddressOfNameOrdinals; + } +} diff --git a/MemoryModule/Formats/PE/PeExportSymbol.cs b/MemoryModule/Formats/PE/PeExportSymbol.cs new file mode 100644 index 0000000..bb341eb --- /dev/null +++ b/MemoryModule/Formats/PE/PeExportSymbol.cs @@ -0,0 +1,19 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + class PeExportSymbol : ISymbol + { + // Name. + public string Name { get; set; } + + // Will contain the ordinal. + public ulong Value { get; set; } + + // This is the address. + public IntPtr Address { get; set; } + } +} diff --git a/MemoryModule/Formats/PE/PeFinalizer.cs b/MemoryModule/Formats/PE/PeFinalizer.cs new file mode 100644 index 0000000..2a3bc08 --- /dev/null +++ b/MemoryModule/Formats/PE/PeFinalizer.cs @@ -0,0 +1,27 @@ +using MemoryModule.Abstractions; +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.PE +{ + class PeFinalizer : IFinalizer + { + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate bool DllEntryProc(IntPtr hinstDLL, DllOperation fdwReason, IntPtr lpReserved); + + private IntPtr _handle; + private IntPtr _del; + + public PeFinalizer(IntPtr handle, IntPtr del) + { + _handle = handle; + _del = del; + } + + public void Run() + { + var _run = Marshal.GetDelegateForFunctionPointer(_del); + _run(_handle, DllOperation.ProcessDetach, IntPtr.Zero); + } + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeHeader.cs b/MemoryModule/Formats/PE/PeHeader.cs new file mode 100644 index 0000000..2d0e2a1 --- /dev/null +++ b/MemoryModule/Formats/PE/PeHeader.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeHeader : MemoryValueObject + { + public uint Magic => _native->Signature; + public PeCoffHeader FileHeader => _coff; + public PeOptionalHeader PeOptionalHeader => _pe; + + private readonly PeCoffHeader _coff; + private readonly PeOptionalHeader _pe; + + public PeHeader(byte* memory, ulong offset) : base(memory, offset) + { + _coff = new PeCoffHeader(memory, offset + (ulong)Marshal.OffsetOf(nameof(PeHeaderNative.FileHeader))); + _pe = new PeOptionalHeader(memory, offset + (ulong)Marshal.OffsetOf(nameof(PeHeaderNative.OptionalHeader))); + } + } +} diff --git a/MemoryModule/Formats/PE/PeHeaderNative.cs b/MemoryModule/Formats/PE/PeHeaderNative.cs new file mode 100644 index 0000000..9077441 --- /dev/null +++ b/MemoryModule/Formats/PE/PeHeaderNative.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct PeHeaderNative + { + public uint Signature; + public PeCoffHeaderNative FileHeader; + public PeOptionalHeaderNative OptionalHeader; + } +} diff --git a/MemoryModule/Formats/PE/PeHintNameTableEntry.cs b/MemoryModule/Formats/PE/PeHintNameTableEntry.cs new file mode 100644 index 0000000..783d81c --- /dev/null +++ b/MemoryModule/Formats/PE/PeHintNameTableEntry.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeHintNameTableEntry : MemoryValueObject + { + public PeHintNameTableEntry(byte* memory, ulong offset) : base(memory, offset) + { + Name = Marshal.PtrToStringAnsi((IntPtr)_native->Name); + } + + public string Name { get; private set; } + } +} diff --git a/MemoryModule/Formats/PE/PeHintNameTableEntryNative.cs b/MemoryModule/Formats/PE/PeHintNameTableEntryNative.cs new file mode 100644 index 0000000..99e1424 --- /dev/null +++ b/MemoryModule/Formats/PE/PeHintNameTableEntryNative.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct PeHintNameTableEntryNative + { + public ushort Hint; + // It's not 1, it's actually a variable-length, null-terminated char array. + public fixed byte Name[1]; + } +} diff --git a/MemoryModule/Formats/PE/PeImportDescriptor.cs b/MemoryModule/Formats/PE/PeImportDescriptor.cs new file mode 100644 index 0000000..3842a83 --- /dev/null +++ b/MemoryModule/Formats/PE/PeImportDescriptor.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeImportDescriptor : MemoryValueObject + { + public PeImportDescriptor(byte* memory, ulong offset) : base(memory, offset) + { + if (_native->Name != 0) + { + Name = Marshal.PtrToStringAnsi((IntPtr)GetAddress(_native->Name)); + } + } + + public string Name { get; private set; } + public ulong OriginalFirstThunk => _native->OriginalFirstThunk; + public ulong FirstThunk => _native->FirstThunk; + } +} diff --git a/MemoryModule/Formats/PE/PeImportDescriptorNative.cs b/MemoryModule/Formats/PE/PeImportDescriptorNative.cs new file mode 100644 index 0000000..b344d9a --- /dev/null +++ b/MemoryModule/Formats/PE/PeImportDescriptorNative.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Explicit)] + struct PeImportDescriptorNative + { + [FieldOffset(0)] + public uint Characteristics; + [FieldOffset(0)] + public uint OriginalFirstThunk; + [FieldOffset(sizeof(uint))] + public uint TimeDateStamp; + [FieldOffset(sizeof(uint) * 2)] + public uint ForwarderChain; + [FieldOffset(sizeof(uint) * 3)] + public uint Name; + [FieldOffset(sizeof(uint) * 4)] + public uint FirstThunk; + } +} diff --git a/MemoryModule/Formats/PE/PeInitializer.cs b/MemoryModule/Formats/PE/PeInitializer.cs new file mode 100644 index 0000000..40266e1 --- /dev/null +++ b/MemoryModule/Formats/PE/PeInitializer.cs @@ -0,0 +1,50 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeInitializer : IInitializer + { + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate bool DllEntryProc(IntPtr hinstDLL, DllOperation fdwReason, IntPtr lpReserved); + + public Type[] Arguments { get; } = new[] { typeof(IntPtr), typeof(DllOperation), typeof(IntPtr) }; + + private IntPtr _handle; + private IntPtr _del; + + public PeInitializer(IntPtr handle, IntPtr del) + { + _handle = handle; + _del = del; + } + + public bool Run() + { + var _run = Marshal.GetDelegateForFunctionPointer(_del); + return _run(_handle, DllOperation.ProcessAttach, IntPtr.Zero); + } + + public bool Run(params object[] args) + { + if (args.Length != Arguments.Length) + { + return false; + } + + for (int i = 0; i < args.Length; ++i) + { + if (!Arguments[i].IsAssignableFrom(args[i].GetType())) + { + return false; + } + } + + var _run = Marshal.GetDelegateForFunctionPointer(_del); + return _run((IntPtr)args[0], (DllOperation)args[1], (IntPtr)args[2]); + } + } +} diff --git a/MemoryModule/Formats/PE/PeMachineType.cs b/MemoryModule/Formats/PE/PeMachineType.cs new file mode 100644 index 0000000..1254614 --- /dev/null +++ b/MemoryModule/Formats/PE/PeMachineType.cs @@ -0,0 +1,106 @@ +namespace MemoryModule.Formats.PE +{ + public enum PeMachineType : ushort + { + /// + /// The content of this field is assumed to be applicable to any machine type + /// + Unknown = 0x0, + /// + /// Matsushita AM33 + /// + Am33 = 0x1d3, + /// + /// x64 + /// + Amd64 = 0x8664, + /// + /// ARM little endian + /// + Arm = 0x1c0, + /// + /// ARM64 little endian + /// + Arm64 = 0xaa64, + /// + /// ARM Thumb-2 little endian + /// + Armnt = 0x1c4, + /// + /// EFI byte code + /// + Ebc = 0xebc, + /// + /// Intel 386 or later processors and compatible processors + /// + I386 = 0x14c, + /// + /// Intel Itanium processor family + /// + Ia64 = 0x200, + /// + /// Mitsubishi M32R little endian + /// + M32r = 0x9041, + /// + /// MIPS16 + /// + Mips16 = 0x266, + /// + /// MIPS with FPU + /// + Mipsfpu = 0x366, + /// + /// MIPS16 with FPU + /// + Mipsfpu16 = 0x466, + /// + /// Power PC little endian + /// + Powerpc = 0x1f0, + /// + /// Power PC with floating point support + /// + Powerpcfp = 0x1f1, + /// + /// MIPS little endian + /// + R4000 = 0x166, + /// + /// RISC-V 32-bit address space + /// + Riscv32 = 0x5032, + /// + /// RISC-V 64-bit address space + /// + Riscv64 = 0x5064, + /// + /// RISC-V 128-bit address space + /// + Riscv128 = 0x5128, + /// + /// Hitachi SH3 + /// + Sh3 = 0x1a2, + /// + /// Hitachi SH3 DSP + /// + Sh3dsp = 0x1a3, + /// + /// Hitachi SH4 + /// + Sh4 = 0x1a6, + /// + /// Hitachi SH5 + /// + Sh5 = 0x1a8, + /// + /// Thumb + /// + Thumb = 0x1c2, + /// + /// MIPS little-endian WCE v2 + /// + Wcemipsv2 = 0x169, + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeModule.cs b/MemoryModule/Formats/PE/PeModule.cs new file mode 100644 index 0000000..29e5fa7 --- /dev/null +++ b/MemoryModule/Formats/PE/PeModule.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Iced.Intel; +using MemoryModule.Abstractions; +using MemoryModule.AssemblyHandler; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeModule : Module + { + public override IntPtr FileAddress { get; protected set; } + public override IntPtr MemoryAddress { get; internal set; } + public override ulong MemorySize { get; internal set; } + public override IntPtr PreferredAddress { get; protected set; } + public override Architecture Architecture { get; protected set; } + public override IReadOnlyList Sections { get; protected set; } + public override IReadOnlyList Rebases { get; protected set; } + public override IReadOnlyList Bindings { get; protected set; } + public override IReadOnlyList LazyBindings { get; protected set; } + public override IReadOnlyList WeakBindings { get; protected set; } + public override IReadOnlyList Initializers { get; protected set; } + public override IReadOnlyList Finalizers { get; protected set; } + public override IReadOnlyList Exports { get; protected set; } + public override IReadOnlyList PrivateSymbols { get; protected set; } + public override IReadOnlyList ReferencedLibraries { get; protected set; } + public override IReadOnlyDictionary ReferencedLibraryHandles { get; internal set; } + public override IReadOnlyList TlsModuleIdBindings { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override IReadOnlyList TlsGetAddrBindings { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsMemorySize { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsFileSize { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override int TlsModuleId { get => throw new NotImplementedException(); internal set => throw new NotImplementedException(); } + public override IntPtr TlsImageAddress { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override ulong TlsImageOffset { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + + private readonly DosHeader _dosHeader; + private readonly PeHeader _ntHeader; + + private readonly PeSectionHeaderArray _sections; + + public PeModule(IntPtr memory) : this((byte*)memory) + { + + } + + public PeModule(byte* memory) + { + FileAddress = (IntPtr)memory; + + _dosHeader = new DosHeader(memory); + + ulong ntHeaderOffset = _dosHeader.NewHeaderOffset; + _ntHeader = new PeHeader(memory, ntHeaderOffset); + + PreferredAddress = (IntPtr)(ulong)_ntHeader.PeOptionalHeader.ImageBase; + Architecture = ConvertArchitecture(_ntHeader.FileHeader.MachineType); + + // Sections are right after the optional headers. + ulong sectionOffset = ntHeaderOffset + (ulong)Marshal.OffsetOf(nameof(PeHeaderNative.OptionalHeader)) + _ntHeader.FileHeader.SizeOfOptionalHeader; + _sections = new PeSectionHeaderArray(memory, sectionOffset, _ntHeader.FileHeader.NumberOfSections); + // Dummy section to trick the loader. + // Apparently sometimes relocation just _don't_ work. + var dummySectionArray = + new ISection[] + { + new GenericSection() + { + FileOffset = 0, + FileSize = 0, + MemoryOffset = 0, + MemorySize = 0, + } + }; + Sections = dummySectionArray.Concat(_sections).ToList(); + + + var executableSections = _sections.Where(sect => sect.MemoryProtection.HasFlag(MemoryProtection.Execute)); + + switch (Architecture) + { + case Architecture.X64: + PatchX86_64(executableSections, _sections.Last().MemoryOffset + _sections.Last().MemorySize); + break; + case Architecture.X86: + case Architecture.Arm: + case Architecture.Arm64: + throw new NotSupportedException("TLS patching not supported for this architecture."); + } + + } + + internal override void AfterAllocation() + { + // Rebases (Relocations in PE terminology) + var baseRelocationDirectory = _ntHeader.PeOptionalHeader.DataDirectory[PeDirectoryEntryType.BaseRelocation]; + Rebases = new PeBaseRelocationBlock((byte *)MemoryAddress, baseRelocationDirectory.VirtualAddress); + + // Bindings (no weak or lazy binds I guess) + // Because of the nature of PE, we get our ref libraries here too! + var _refs = new List(); + var _binds = new List(); + + var importDirectory = _ntHeader.PeOptionalHeader.DataDirectory[PeDirectoryEntryType.Import]; + + ulong importDirectoryEnd = importDirectory.VirtualAddress + importDirectory.Size; + // We don't know how many elements so it's best not to + for (ulong i = 0; i + (ulong)sizeof(PeImportDescriptorNative) <= importDirectoryEnd; i += (ulong)sizeof(PeImportDescriptorNative)) + { + var table = new PeImportDescriptor((byte*)MemoryAddress, importDirectory.VirtualAddress + i); + + if (table.Name == null) + { + break; + } + + _refs.Add(table.Name); + + void** thunkRef = (void**)((ulong)MemoryAddress + (table.OriginalFirstThunk == 0 ? table.FirstThunk : table.OriginalFirstThunk)); + void** funcRef = (void**)((ulong)MemoryAddress + table.FirstThunk); + + while (*thunkRef != null) + { + ulong value = (ulong)*thunkRef; + + var bind = new GenericBind() + { + Addend = 0, + AffectedAddress = (ulong)funcRef - (ulong)MemoryAddress, + ModuleName = table.Name, + }; + + if (SnapToOrdinal(value, out var newValue)) + { + bind.SymbolIndex = newValue; + } + else + { + var hintNameEntry = new PeHintNameTableEntry((byte*)MemoryAddress, value); + bind.SymbolName = hintNameEntry.Name; + } + + _binds.Add(bind); + + ++thunkRef; + ++funcRef; + } + } + + ReferencedLibraries = _refs; + Bindings = _binds; + LazyBindings = new List(); + WeakBindings = new List(); + } + + internal override void AfterRebase() + { + // Now to the exports. + var exportDirectory = _ntHeader.PeOptionalHeader.DataDirectory[PeDirectoryEntryType.Export]; + var exportTable = new PeExportDirectory((byte*)MemoryAddress, exportDirectory.VirtualAddress); + + var list = new List((int)exportTable.NameCount); + + // This array always store 32-bit offsets. + uint* nameList = (uint*)((ulong)MemoryAddress + exportTable.NameArrayOffset); + // Same for the funcList, they are not functions addresses, just 32-bit RVAs. + uint* funcList = (uint*)((ulong)MemoryAddress + exportTable.AddressArrayOffset); + ushort* ordinalTable = (ushort*)((ulong)MemoryAddress + exportTable.OrdinalArrayOffset); + + for (ulong i = 0; i < exportTable.NameCount; ++i) + { + list.Add(new PeExportSymbol() + { + Value = ordinalTable[i], + Name = Marshal.PtrToStringAnsi(IntPtr.Add(MemoryAddress, (int)nameList[i])), + Address = (IntPtr)((ulong)MemoryAddress + funcList[ordinalTable[i]]) + }); + } + + Exports = list; + + // Finally, init and fini. + if (_ntHeader.PeOptionalHeader.EntryPointOffset != 0) + { + Initializers = new List() + { + new PeInitializer(MemoryAddress, IntPtr.Add(MemoryAddress, (int)_ntHeader.PeOptionalHeader.EntryPointOffset)) + }; + Finalizers = new List() + { + new PeFinalizer(MemoryAddress, IntPtr.Add(MemoryAddress, (int)_ntHeader.PeOptionalHeader.EntryPointOffset)) + }; + } + } + + private static bool SnapToOrdinal(ulong value, out ulong newValue) + { + const ulong OrdinalFlag64 = 0x8000000000000000; + const ulong OrdinalFlag32 = 0x80000000; + + var flag = Environment.Is64BitProcess ? OrdinalFlag64 : OrdinalFlag32; + + if ((value & flag) != 0) + { + newValue = value & ~flag; + return true; + } + newValue = value; + return false; + } + + private static Architecture ConvertArchitecture(PeMachineType machineType) + { + switch (machineType) + { + case PeMachineType.I386: + return Architecture.X86; + case PeMachineType.Amd64: + return Architecture.X64; + case PeMachineType.Arm: + return Architecture.Arm; + case PeMachineType.Arm64: + return Architecture.Arm64; + default: + throw new NotSupportedException("Unsupported architecture."); + } + } + + private void PatchX86_64(IEnumerable sections, ulong imageTlsMemoryOffset) + { + if (imageTlsMemoryOffset > int.MaxValue) + { + throw new NotSupportedException("TLS hooks for image larger than 2GB is not supported."); + } + + foreach (var sect in sections) + { + var sectAddr = (byte*)FileAddress + sect.FileOffset; + var decoder = Iced.Intel.Decoder.Create(64, new UnsafeNativeMemoryCodeReader(sectAddr, sect.FileSize)); + decoder.IP = (ulong)sectAddr; + var endRip = (ulong)sectAddr + sect.MemorySize; + + while (decoder.IP < endRip) + { + var instr = decoder.Decode(); + + if (instr.SegmentPrefix != Register.GS) + { + continue; + } + + if (!instr.HasOpKind(OpKind.Memory)) + { + continue; + } + + if (instr.MemoryDisplacement64 != 11ul * (ulong)sizeof(IntPtr)) + { + continue; + } + + if (instr.OpCount != 2) + { + continue; + } + + if (instr.Op0Kind != OpKind.Register) + { + continue; + } + + Console.WriteLine(instr.ToString()); + Console.WriteLine($"0x{instr.IP:x}"); + Console.WriteLine(instr.Op0Register); + Console.WriteLine(instr.MemoryDisplacement64); + Console.WriteLine(instr.SegmentPrefix); + + var sectOffset = instr.IP - (ulong)sectAddr; + var memoryAddress = sect.MemoryOffset + sectOffset + (ulong)instr.Length; + + var rel32 = imageTlsMemoryOffset - memoryAddress; + + var asm = new Assembler(64); + + //var code = Tls.x86_64.HookGenerator.GenerateHookFunction(instr.Op0Register, instr.Op0Register, ) + } + } + } + } +} diff --git a/MemoryModule/Formats/PE/PeOptionalHeader.cs b/MemoryModule/Formats/PE/PeOptionalHeader.cs new file mode 100644 index 0000000..860886a --- /dev/null +++ b/MemoryModule/Formats/PE/PeOptionalHeader.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeOptionalHeader : MemoryValueObject + { + private readonly PeDataDirectoryArray _dataDirectory; + + public PeOptionalHeader(byte* memory, ulong offset) : base(memory, offset) + { + _dataDirectory = new PeDataDirectoryArray(memory, + offset + (ulong)Marshal.OffsetOf(nameof(PeOptionalHeaderNative.DataDirectoryBuffer)), + _native->NumberOfRvaAndSizes); + } + + public UIntPtr ImageBase => _native->ImageBase; + public PeDataDirectoryArray DataDirectory => _dataDirectory; + public uint EntryPointOffset => _native->AddressOfEntryPoint; + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeOptionalHeaderNative.cs b/MemoryModule/Formats/PE/PeOptionalHeaderNative.cs new file mode 100644 index 0000000..8070d7a --- /dev/null +++ b/MemoryModule/Formats/PE/PeOptionalHeaderNative.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct PeOptionalHeaderNative + { + public const int NumberOfDirectoryEntries = 16; + + public ushort Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + + // In C#, we don't need macros. + // We have more evil stuff. + [StructLayout(LayoutKind.Explicit, Size = 4, Pack = 1)] + private struct DUMMY8BYTEVALUE + { + // 32 bit fields + [FieldOffset(0)] + public uint BaseOfData; + [FieldOffset(4)] + public uint ImageBase32; + // 64 bit fields + [FieldOffset(0)] + public ulong ImageBase64; + } + private DUMMY8BYTEVALUE _architectureSpecificValue1; + + public uint BaseOfData + { + get => _architectureSpecificValue1.BaseOfData; + set => _architectureSpecificValue1.BaseOfData = value; + } + public UIntPtr ImageBase + { + get => Environment.Is64BitProcess ? + (UIntPtr)_architectureSpecificValue1.ImageBase64 : + (UIntPtr)_architectureSpecificValue1.ImageBase32; + set + { + if (Environment.Is64BitProcess) + { + _architectureSpecificValue1.ImageBase64 = (ulong)value; + } + else + { + _architectureSpecificValue1.ImageBase32 = (uint)value; + } + } + } + + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public ushort Subsystem; + public ushort DllCharacteristics; + public UIntPtr SizeOfStackReserve; + public UIntPtr SizeOfStackCommit; + public UIntPtr SizeOfHeapReserve; + public UIntPtr SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + + public fixed ulong DataDirectoryBuffer[NumberOfDirectoryEntries]; + } +} \ No newline at end of file diff --git a/MemoryModule/Formats/PE/PeSectionFlags.cs b/MemoryModule/Formats/PE/PeSectionFlags.cs new file mode 100644 index 0000000..2027086 --- /dev/null +++ b/MemoryModule/Formats/PE/PeSectionFlags.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [Flags] + enum PeSectionFlags : uint + { + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPad = 0x00000008, + /// + /// The section contains executable code. + /// + CntCode = 0x00000020, + /// + /// The section contains initialized data. + /// + CntInitializedData = 0x00000040, + /// + /// The section contains uninitialized data. + /// + CntUninitializedData = 0x00000080, + /// + /// Reserved for future use. + /// + LnkOther = 0x00000100, + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LnkInfo = 0x00000200, + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LnkRemove = 0x00000800, + /// + /// The section contains COMDAT data. For more information, see COMDAT Sections (Object Only). This is valid only for object files. + /// + LnkComdat = 0x00001000, + /// + /// The section contains data referenced through the global pointer (GP). + /// + Gprel = 0x00008000, + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + /// + /// Reserved for future use. + /// + Mem16Bit = 0x00020000, + /// + /// Reserved for future use. + /// + MemLocked = 0x00040000, + /// + /// Reserved for future use. + /// + MemPreload = 0x00080000, + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + /// + /// Align data on a 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + /// + /// Align data on a 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + /// + /// Mask to get Align value. + /// + AlignMask = 0x00F00000, + /// + /// The section contains extended relocations. + /// + LnkNrelocOvfl = 0x01000000, + /// + /// The section can be discarded as needed. + /// + MemDiscardable = 0x02000000, + /// + /// The section cannot be cached. + /// + MemNotCached = 0x04000000, + /// + /// The section is not pageable. + /// + MemNotPaged = 0x08000000, + /// + /// The section can be shared in memory. + /// + MemShared = 0x10000000, + /// + /// The section can be executed as code. + /// + MemExecute = 0x20000000, + /// + /// The section can be read. + /// + MemRead = 0x40000000, + /// + /// The section can be written to. + /// + MemWrite = 0x80000000, + } +} diff --git a/MemoryModule/Formats/PE/PeSectionHeader.cs b/MemoryModule/Formats/PE/PeSectionHeader.cs new file mode 100644 index 0000000..92c2e30 --- /dev/null +++ b/MemoryModule/Formats/PE/PeSectionHeader.cs @@ -0,0 +1,64 @@ +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeSectionHeader : MemoryValueObject, ISection + { + private string _name; + private MemoryProtection _protection; + private SectionType _type; + + public PeSectionHeader(byte* memory, ulong offset) : base(memory, offset) + { + if (_native->Characteristics.HasFlag(PeSectionFlags.CntCode)) + { + _type = SectionType.Text; + } + + _protection = 0; + if (_native->Characteristics.HasFlag(PeSectionFlags.MemExecute)) + { + _protection |= MemoryProtection.Execute; + } + if (_native->Characteristics.HasFlag(PeSectionFlags.MemWrite)) + { + _protection |= MemoryProtection.Write; + } + if (_native->Characteristics.HasFlag(PeSectionFlags.MemRead)) + { + _protection |= MemoryProtection.Read; + } + + //An 8-byte, null-padded UTF-8 encoded string. If the string is exactly 8 characters long, there is no terminating null. + //For longer names, this field contains a slash (/) that is followed by an ASCII representation of a decimal number that + //is an offset into the string table. + int zeroIndex; + for (zeroIndex = 0; zeroIndex < 8; ++zeroIndex) + { + if (_native->Name[zeroIndex] == 0) + { + break; + } + } + _name = Marshal.PtrToStringAnsi((IntPtr)_native->Name, zeroIndex); + } + + public ulong MemoryOffset => _native->VirtualAddress; + + public ulong MemorySize => _native->Misc.VirtualSize; + + public ulong FileOffset => _native->PointerToRawData; + + public ulong FileSize => _native->SizeOfRawData; + + public MemoryProtection MemoryProtection => _protection; + + public string Name => _name; + + public SectionType Type => _type; + } +} diff --git a/MemoryModule/Formats/PE/PeSectionHeaderArray.cs b/MemoryModule/Formats/PE/PeSectionHeaderArray.cs new file mode 100644 index 0000000..e4bfb8e --- /dev/null +++ b/MemoryModule/Formats/PE/PeSectionHeaderArray.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + unsafe class PeSectionHeaderArray : MemoryValueObjectArray + { + public PeSectionHeaderArray(byte* memory, ulong offset, ulong size) : base(memory, offset, size) + { + } + + protected override unsafe PeSectionHeader Construct(byte* memory, ulong offset) + { + return new PeSectionHeader(memory, offset); + } + } +} diff --git a/MemoryModule/Formats/PE/PeSectionHeaderNative.cs b/MemoryModule/Formats/PE/PeSectionHeaderNative.cs new file mode 100644 index 0000000..9217e80 --- /dev/null +++ b/MemoryModule/Formats/PE/PeSectionHeaderNative.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Formats.PE +{ + [StructLayout(LayoutKind.Sequential)] + unsafe struct PeSectionHeaderNative + { + [StructLayout(LayoutKind.Explicit)] + public struct MiscUnion + { + [FieldOffset(0)] + public uint PhysicalAddress; + [FieldOffset(0)] + public uint VirtualSize; + } + + public fixed byte Name[8]; + public MiscUnion Misc; + public uint VirtualAddress; + public uint SizeOfRawData; + public uint PointerToRawData; + public uint PointerToRelocations; + public uint PointerToLinenumbers; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public PeSectionFlags Characteristics; + } +} diff --git a/MemoryModule/Linux/GlibcInterop/DtvSlotInfoArray.cs b/MemoryModule/Linux/GlibcInterop/DtvSlotInfoArray.cs index 16794fd..394d89f 100644 --- a/MemoryModule/Linux/GlibcInterop/DtvSlotInfoArray.cs +++ b/MemoryModule/Linux/GlibcInterop/DtvSlotInfoArray.cs @@ -8,9 +8,7 @@ public unsafe class DtvSlotInfoArray : ManagedArray +// /// Loads a native Macho assembly to the process. +// /// This implementation is different from the one from Windows and Linux, where the assembly is loaded in managed C# code. +// /// Here, the assembly is loaded directly using dyld's internal API, and the Macho parser library is included solely to inspect +// /// dyld. +// /// We might have loaded the library ourselves, but support for TLS would be much harder (dyld does not expose anything). +// /// +// internal static unsafe class NativeAssemblyImpl +// { +// /// +// /// Load EXE/DLL from memory location with the given size. +// /// All dependencies are resolved using default LoadLibrary/GetProcAddress +// /// calls through the Windows API, or through passed delegates. +// /// +// /// The assembly's code +// /// The assembly's code's length +// /// A handle to the loaded assembly +// public static IntPtr LoadLibrary( +// byte* dataPtr, +// long length, +// CustomAllocFunc allocMemory = null, +// CustomFreeFunc freeMemory = null, +// CustomLoadLibraryFunc loadLibrary = null, +// CustomGetProcAddressFunc getProcAddress = null, +// CustomFreeLibraryFunc freeLibrary = null +// ) +// { +// allocMemory = allocMemory ?? MemoryDefaultAllocDelegate; +// freeMemory = freeMemory ?? MemoryDefaultFreeDelegate; +// loadLibrary = loadLibrary ?? MemoryDefaultLoadLibraryDelegate; +// getProcAddress = getProcAddress ?? MemoryDefaultGetProcAddressDelegate; +// freeLibrary = freeLibrary ?? MemoryDefaultFreeLibraryDelegate; + +// return (IntPtr)MemoryLoadLibraryEx( +// dataPtr, +// (ulong)length, +// allocMemory, +// freeMemory, +// loadLibrary, +// getProcAddress, +// freeLibrary, +// null +// ); +// } + +// /// +// /// Free previously loaded EXE/DLL. +// /// +// /// +// public static bool FreeLibrary(IntPtr handle) +// { +// return MemoryFreeLibrary(handle); +// } + +// public static IntPtr GetSymbol(IntPtr module, string name) +// { +// char* namePtr = null; +// try +// { +// namePtr = (char*)Marshal.StringToHGlobalAnsi(name); +// return (IntPtr)MemoryGetProcAddress(module, namePtr); +// } +// finally +// { +// Marshal.FreeHGlobal((IntPtr)namePtr); +// } +// } + +// public static void* GetSymbolUnsafe(void* module, char* name) +// { +// return MemoryGetProcAddress((IntPtr)module, name); +// } + +// // To ensure that the GC doesn't fuck up our delegates. +// public static CustomAllocFunc MemoryDefaultAllocDelegate = MemoryDefaultAlloc; +// public static CustomFreeFunc MemoryDefaultFreeDelegate = MemoryDefaultFree; +// public static CustomLoadLibraryFunc MemoryDefaultLoadLibraryDelegate = MemoryDefaultLoadLibrary; +// public static CustomGetProcAddressFunc MemoryDefaultGetProcAddressDelegate = MemoryDefaultGetProcAddress; +// public static CustomFreeLibraryFunc MemoryDefaultFreeLibraryDelegate = MemoryDefaultFreeLibrary; + +// #region Default dependency resolvers +// internal static void* MemoryDefaultAlloc(void* address, UIntPtr size, MemoryAllocation allocationType, PageProtection protect, void* userdata) +// { +// return mmap(address, size, Helpers.WindowsToUnixProtection(protect), MmapMappingFlags.Anonymous | MmapMappingFlags.Private); +// } + +// internal static bool MemoryDefaultFree(void* lpAddress, UIntPtr dwSize, MemoryAllocation dwFreeType, void* userdata) +// { +// return munmap(lpAddress, dwSize) == 0; +// } + +// internal static void* MemoryDefaultLoadLibrary(char* filename, void* userdata) +// { +// void* result; +// result = dlopen(filename); +// return result; +// } + +// internal static void* MemoryDefaultGetProcAddress(void* module, char* name, void* userdata) +// { +// return dlsym(module, name); +// } + +// internal static bool MemoryDefaultFreeLibrary(void* module, void* userdata) +// { +// return dlclose(module) == 0; +// } +// #endregion + +// #region Core C functions +// private static void* MemoryLoadLibrary(void* data, ulong size) +// { +// return MemoryLoadLibraryEx( +// data, +// size, +// MemoryDefaultAllocDelegate, +// MemoryDefaultFreeDelegate, +// MemoryDefaultLoadLibraryDelegate, +// MemoryDefaultGetProcAddressDelegate, +// MemoryDefaultFreeLibraryDelegate, +// null); +// } + +// private static void* MemoryLoadLibraryEx(void* data, ulong size, +// CustomAllocFunc allocMemory, +// CustomFreeFunc freeMemory, +// CustomLoadLibraryFunc loadLibrary, +// CustomGetProcAddressFunc getProcAddress, +// CustomFreeLibraryFunc freeLibrary, +// void* userdata) +// { +// var result = new MemoryModule(); +// var gcHandle = GCHandle.Alloc(result); +// var resultHandle = (IntPtr)gcHandle; + +// try +// { +// CheckSize(size, (ulong)sizeof(MachoHeaderNative)); + +// var header = (MachoHeaderNative*)data; +// if (header->magic != MachoMagic.Macho32Bit && header->magic != MachoMagic.Macho64Bit) +// { +// throw new NativeAssemblyLoadException("Bad image format: Wrong magic number."); +// } + +// if (CpuTypeToArchitecture(header->cputype) != RuntimeInformation.OSArchitecture) +// { +// throw new NativeAssemblyLoadException("Bad image format: Wrong architecture."); +// } + +// if (header->filetype != MachoFileType.Bundle && header->filetype != MachoFileType.Dylib) +// { +// throw new NativeAssemblyLoadException("Only Macho dynamic objects and bundles are supported."); +// } + +// var managedHeader = new MachoHeader((byte*)data); + +// result.alloc = allocMemory; +// result.free = freeMemory; +// result.getProcAddress = getProcAddress; +// result.loadLibrary = loadLibrary; +// result.freeLibrary = freeLibrary; +// result.userdata = userdata; +// result.pageSize = (ulong)sysconf(_SC_PAGESIZE); + +// ResolveDependencies(result, managedHeader); + +// result.nativeHandle = Dyld.Load((byte *)data, size, managedHeader.Dylibs[0].Name); + +// return (void *)resultHandle; +// } +// catch (NativeAssemblyLoadException) +// { +// MemoryFreeLibrary(resultHandle); +// throw; +// } +// catch (Exception e) +// { +// MemoryFreeLibrary(resultHandle); +// throw new NativeAssemblyLoadException("Failed to load assembly", e); +// } +// } +// private static bool MemoryFreeLibrary(IntPtr handle) +// { +// if (handle == IntPtr.Zero) +// { +// return true; +// } + +// var gcHandle = GCHandle.FromIntPtr(handle); +// var module = (MemoryModule)gcHandle.Target; + +// if (module == null) +// { +// return true; +// } + +// if (module.dependencies != null) +// { +// // free previously opened libraries +// foreach (var dep in module.dependencies) +// { +// if (dep != null) +// { +// module.freeLibrary((void *)dep, module.userdata); +// } +// } +// } + +// if (module.codeBase != null) +// { +// // release memory of library +// module.free(module.codeBase, (UIntPtr)0, MemoryAllocation.Release, module.userdata); +// } + +// if (module.nativeHandle != IntPtr.Zero) +// { +// Dyld.Unload(handle); +// } + +// gcHandle.Free(); + +// return true; +// } + +// private static void* MemoryGetProcAddress(IntPtr handle, char* name) +// { +// var gcHandle = GCHandle.FromIntPtr(handle); +// var module = gcHandle.Target as MemoryModule; + +// if (module == null) +// { +// return null; +// } + +// return (void*)Dyld.Sym(module.nativeHandle, name); +// } +// #endregion + +// #region Helpers +// private static void CheckSize(ulong size, ulong expected) +// { +// if (size < expected) +// { +// throw new NativeAssemblyLoadException("Bad image format."); +// } +// } + +// private static Architecture? CpuTypeToArchitecture(MachoCpuType type) +// { +// switch (type) +// { +// case MachoCpuType.ARM: +// case MachoCpuType.ARM64_32: +// return Architecture.Arm; +// case MachoCpuType.ARM64: +// return Architecture.Arm64; +// case MachoCpuType.I386: +// return Architecture.X86; +// case MachoCpuType.X86_64: +// return Architecture.X64; +// default: +// return null; +// } +// } +// #endregion + +// private static void ResolveDependencies(MemoryModule module, MachoHeader managedHeader) +// { +// var dylibs = managedHeader.Dylibs; + +// var deps = new List(); + +// // First dylib is itself, so skip it. +// foreach (var item in dylibs.Skip(1)) +// { +// var name = item.NamePtr; +// var addr = module.loadLibrary((char *)name, module.userdata); +// if (addr == null) +// { +// module.dependencies = deps.ToArray(); +// throw new NativeAssemblyLoadException($"Cannot load dependency: {Marshal.PtrToStringAnsi((IntPtr)name)}"); +// } +// deps.Add((IntPtr)addr); +// } + +// module.dependencies = deps.ToArray(); +// } + +// #region Nightmare +// class MemoryModule +// { +// public void* codeBase; +// public ulong codeSize; +// public unsafe IntPtr[] dependencies; +// public IntPtr nativeHandle; +// public CustomAllocFunc alloc; +// public CustomFreeFunc free; +// public CustomLoadLibraryFunc loadLibrary; +// public CustomGetProcAddressFunc getProcAddress; +// public CustomFreeLibraryFunc freeLibrary; +// public unsafe void* userdata; +// public ulong pageSize; +// } + +// [StructLayout(LayoutKind.Sequential, Size = 8, Pack = 4)] +// public struct POINTER_LIST +// { +// public unsafe POINTER_LIST* next; +// public unsafe void* address; +// } +// #endregion + +// #region Delegates + +// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +// private unsafe delegate void InitDelegate(int argc, byte** argv, byte** envp); + +// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +// private unsafe delegate void FiniDelegate(); + +// #endregion + +// #region P/Invoke +// private const int _SC_PAGESIZE = 29; + +// [DllImport("libc")] +// private static extern void* mmap( +// void* addr, +// UIntPtr length, +// MmapProtectionFlags protectionFlags, +// MmapMappingFlags mappingFlags, +// int fileDescriptor = -1, int offset = 0); + +// [DllImport("libc")] +// private static extern IntPtr mprotect(void* addr, UIntPtr length, MmapProtectionFlags protectionFlags); + +// [DllImport("libc")] +// private static extern int munmap(void* addr, UIntPtr length); + +// // For page size. +// [DllImport("libc")] +// private static extern IntPtr sysconf(int name); + +// [DllImport("/usr/lib/libSystem.dylib")] +// private static extern void* dlopen(char* name, int mode = 0x01 | 0x00100 /*RTLD_LAZY | RTLD_GLOBAL*/); + +// [DllImport("/usr/lib/libSystem.dylib")] +// private static extern void* dlsym(void* handle, char* name); + +// [DllImport("/usr/lib/libSystem.dylib")] +// private static extern int dlclose(void* handle); + +// #endregion +// } +//} \ No newline at end of file diff --git a/MemoryModule/MemoryModule.csproj b/MemoryModule/MemoryModule.csproj index 084401f..5ab351c 100644 --- a/MemoryModule/MemoryModule.csproj +++ b/MemoryModule/MemoryModule.csproj @@ -24,8 +24,16 @@ Works on Windows and Linux only, both on .NET Framework and .NET Core (and of co README.md --> + + + + + + + + diff --git a/MemoryModule/Tls/TlsGlobalDescriptor.cs b/MemoryModule/Tls/TlsGlobalDescriptor.cs new file mode 100644 index 0000000..911033c --- /dev/null +++ b/MemoryModule/Tls/TlsGlobalDescriptor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Tls +{ + class TlsGlobalDescriptor + { + public ulong Generation; + public ulong InitSize; + public ulong Size; + public IntPtr Address; + } +} diff --git a/MemoryModule/Tls/TlsHandler.cs b/MemoryModule/Tls/TlsHandler.cs new file mode 100644 index 0000000..37ce6ce --- /dev/null +++ b/MemoryModule/Tls/TlsHandler.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MemoryModule.Tls +{ + static class TlsHandler + { + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate IntPtr TlsGetAddrHandler(IntPtr descriptor); + public static readonly TlsGetAddrHandler TlsGetAddr = TlsGetAddrInternal; + + private static ulong _globalGeneration = 0; + private static readonly List _globalDescriptor = new List(); + + [ThreadStatic] + private static List _descriptors; + + public static int AssignModId(IntPtr addr, ulong initSize, ulong size) + { + lock (_globalDescriptor) + { + ++_globalGeneration; + var modId = _globalDescriptor.Count; + _globalDescriptor.Add(new TlsGlobalDescriptor() + { + Generation = _globalGeneration, + Size = size, + InitSize = initSize, + Address = addr + }); + return modId; + } + } + + public static void FreeModId(int modId) + { + lock (_globalDescriptor) + { + ++_globalGeneration; + _globalDescriptor[modId].Generation = _globalGeneration; + _globalDescriptor[modId].Size = 0; + + while (_globalDescriptor.Count > 0 && _globalDescriptor[_globalDescriptor.Count - 1].Size == 0) + { + _globalDescriptor.RemoveAt(_globalDescriptor.Count - 1); + } + } + } + + private static IntPtr TlsGetAddrInternal(IntPtr variableDescriptor) + { + ulong modId = (ulong)Marshal.ReadIntPtr(variableDescriptor); + ulong offset = (ulong)Marshal.ReadIntPtr(IntPtr.Add(variableDescriptor, Marshal.SizeOf())); + + _descriptors = _descriptors ?? new List(); + + lock (_descriptors) + lock (_globalDescriptor) + { + if (_descriptors.Count < _globalDescriptor.Count) + { + _descriptors.AddRange(Enumerable.Range(0, _globalDescriptor.Count - _descriptors.Count).Select(x => new TlsMemoryDescriptor())); + } + var currentDesc = _descriptors[(int)modId]; + var globalDesc = _globalDescriptor[(int)modId]; + + if (currentDesc.Generation < globalDesc.Generation) + { + Marshal.FreeHGlobal(currentDesc.Value); + currentDesc.Value = Marshal.AllocHGlobal((int)globalDesc.Size); + currentDesc.Generation = globalDesc.Generation; + unsafe + { + Unsafe.CopyBlockUnaligned((byte*)currentDesc.Value, (byte*)globalDesc.Address, (uint)globalDesc.InitSize); + } + } + + return (IntPtr)((ulong)currentDesc.Value + offset); + } + } + } +} diff --git a/MemoryModule/Tls/TlsHookFunctionDelegate.cs b/MemoryModule/Tls/TlsHookFunctionDelegate.cs new file mode 100644 index 0000000..2e9e179 --- /dev/null +++ b/MemoryModule/Tls/TlsHookFunctionDelegate.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MemoryModule.Tls +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr TlsHookFunctionDelegate(IntPtr input); +} diff --git a/MemoryModule/Tls/TlsMemoryDescriptor.cs b/MemoryModule/Tls/TlsMemoryDescriptor.cs new file mode 100644 index 0000000..83d22ee --- /dev/null +++ b/MemoryModule/Tls/TlsMemoryDescriptor.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MemoryModule.Tls +{ + class TlsMemoryDescriptor + { + public ulong Generation; + public IntPtr Value; + } +} \ No newline at end of file diff --git a/MemoryModule/Tls/x86_64/HookGenerator.cs b/MemoryModule/Tls/x86_64/HookGenerator.cs new file mode 100644 index 0000000..d86b7b3 --- /dev/null +++ b/MemoryModule/Tls/x86_64/HookGenerator.cs @@ -0,0 +1,251 @@ +using Iced.Intel; +using MemoryModule.Abstractions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using static Iced.Intel.AssemblerRegisters; + +namespace MemoryModule.Tls.x86_64 +{ + unsafe static class HookGenerator + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void GetCpuInfoDelegate(); + + [StructLayout(LayoutKind.Sequential)] + private struct CpuInfo + { + public bool HasXSave; + public uint XSaveBufferSize; + public uint XSaveLowFlags; + public uint XSaveHighFlags; + } + + private static readonly CpuInfo _cpuInfo = GetCpuInfo(); + private static readonly bool _isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + private static readonly bool _isUnix = Environment.OSVersion.Platform == PlatformID.Unix; + + public static byte[] GenerateHookFunction(AssemblerRegister64 hookSource, AssemblerRegister64 hookDest, TlsHookFunctionDelegate del) + { + AssemblerRegister64 managedSource, managedDest; + if (_isWindows) + { + managedSource = rcx; + managedDest = rax; + } + else if (_isUnix) + { + managedSource = rdi; + managedDest = rax; + } + else + { + throw new PlatformNotSupportedException(); + } + + var asm = new Assembler(64); + asm.push(rbp); + asm.mov(rbp, rsp); + asm.sub(rsp, 0x80); + + asm.mov(__qword_ptr[rbp - 0x8], rdi); + asm.mov(__qword_ptr[rbp - 0x10], rsi); + asm.mov(__qword_ptr[rbp - 0x18], rax); + asm.mov(__qword_ptr[rbp - 0x20], rbx); + asm.mov(__qword_ptr[rbp - 0x28], rcx); + asm.mov(__qword_ptr[rbp - 0x30], rdx); + asm.mov(__qword_ptr[rbp - 0x38], r8); + asm.mov(__qword_ptr[rbp - 0x40], r9); + asm.mov(__qword_ptr[rbp - 0x48], r10); + asm.mov(__qword_ptr[rbp - 0x50], r11); + asm.mov(__qword_ptr[rbp - 0x58], r12); + asm.mov(__qword_ptr[rbp - 0x60], r13); + asm.mov(__qword_ptr[rbp - 0x68], r14); + asm.mov(__qword_ptr[rbp - 0x70], r15); + asm.mov(__qword_ptr[rbp - 0x78], hookSource); + + // Here, we take advantage of JIT compilation: + // We can generate different assembly depending on + // whether the CPU supports XSAVE or not, without + // having to branch every time. + if (_cpuInfo.HasXSave) + { + asm.mov(rdi, rsp); + asm.sub(rdi, (int)_cpuInfo.XSaveBufferSize); + // Align stack to 64 bytes. + asm.and(rdi, -64); + + asm.mov(rsp, rdi); + + asm.xor(rbx, rbx); + asm.mov(r9, rsp); + asm.mov(r10, rsp); + asm.add(r10, (int)_cpuInfo.XSaveBufferSize); + + // Memory needs to be zeroed out. + var temp = asm.CreateLabel("fillMem"); + asm.Label(ref temp); + asm.mov(__byte_ptr[r9], rbx); + asm.add(r9, 0x8); + asm.cmp(r9, r10); + asm.jne(temp); + + asm.mov(eax, (int)_cpuInfo.XSaveLowFlags); + asm.mov(edx, (int)_cpuInfo.XSaveHighFlags); + asm.xsave(__[rsp]); + } + else + { + asm.sub(rsp, 0x80); + asm.movdqa(__xmmword_ptr[rsp + 0x0], xmm0); + asm.movdqa(__xmmword_ptr[rsp + 0x10], xmm1); + asm.movdqa(__xmmword_ptr[rsp + 0x20], xmm2); + asm.movdqa(__xmmword_ptr[rsp + 0x30], xmm3); + asm.movdqa(__xmmword_ptr[rsp + 0x40], xmm4); + asm.movdqa(__xmmword_ptr[rsp + 0x50], xmm5); + asm.movdqa(__xmmword_ptr[rsp + 0x60], xmm6); + asm.movdqa(__xmmword_ptr[rsp + 0x70], xmm7); + } + + var ptr = Marshal.GetFunctionPointerForDelegate(del); + var funcRegister = managedSource == rax ? rbx : rax; + + asm.mov(funcRegister, (ulong)ptr); + asm.mov(managedSource, __qword_ptr[rbp - 0x78]); + + if (_isWindows) + { + // So-called shadow space. + asm.sub(rsp, 0x20); + } + + asm.call(funcRegister); + asm.mov(__qword_ptr[rbp - 0x78], managedDest); + + if (_isWindows) + { + asm.add(rsp, 0x20); + } + + if (_cpuInfo.HasXSave) + { + asm.mov(eax, (int)_cpuInfo.XSaveLowFlags); + asm.mov(edx, (int)_cpuInfo.XSaveHighFlags); + asm.xrstor(__[rsp]); + } + else + { + // Manually restore xxm: + asm.movdqa(xmm0, __xmmword_ptr[rsp + 0x0]); + asm.movdqa(xmm1, __xmmword_ptr[rsp + 0x10]); + asm.movdqa(xmm2, __xmmword_ptr[rsp + 0x20]); + asm.movdqa(xmm3, __xmmword_ptr[rsp + 0x30]); + asm.movdqa(xmm4, __xmmword_ptr[rsp + 0x40]); + asm.movdqa(xmm5, __xmmword_ptr[rsp + 0x50]); + asm.movdqa(xmm6, __xmmword_ptr[rsp + 0x60]); + asm.movdqa(xmm7, __xmmword_ptr[rsp + 0x70]); + } + + // Now for the normal registers: + asm.mov(rdi, __qword_ptr[rbp - 0x8]); + asm.mov(rsi, __qword_ptr[rbp - 0x10]); + asm.mov(rax, __qword_ptr[rbp - 0x18]); + asm.mov(rbx, __qword_ptr[rbp - 0x20]); + asm.mov(rcx, __qword_ptr[rbp - 0x28]); + asm.mov(rdx, __qword_ptr[rbp - 0x30]); + asm.mov(r8, __qword_ptr[rbp - 0x38]); + asm.mov(r9, __qword_ptr[rbp - 0x40]); + asm.mov(r10, __qword_ptr[rbp - 0x48]); + asm.mov(r11, __qword_ptr[rbp - 0x50]); + asm.mov(r12, __qword_ptr[rbp - 0x58]); + asm.mov(r13, __qword_ptr[rbp - 0x60]); + asm.mov(r14, __qword_ptr[rbp - 0x68]); + asm.mov(r15, __qword_ptr[rbp - 0x70]); + + // Finally, the result: + asm.mov(hookDest, __qword_ptr[rbp - 0x78]); + + asm.mov(rsp, rbp); + asm.pop(rbp); + asm.ret(); + + var ms = new MemoryStream(); + asm.Assemble(new StreamCodeWriter(ms), 0); + + var arr = ms.ToArray(); + + ms.Dispose(); + + return arr; + } + + private static CpuInfo GetCpuInfo() + { + var result = default(CpuInfo); + + var asm = new Assembler(64); + + asm.mov(r8, (ulong)&result); + asm.mov(eax, 0x1); + asm.cpuid(); + // Check for XSAVE flag. + asm.and(ecx, 0x08000000); + asm.mov(__dword_ptr[r8], ecx); + asm.cmp(ecx, 0x0); + + var skip = asm.CreateLabel("skip"); + asm.je(skip); + + asm.mov(eax, 0x0d); + asm.mov(ecx, 0x00); + asm.cpuid(); + + // Buffer size, Lo32, Hi32 + asm.mov(__dword_ptr[r8 + 0x4], ecx); + asm.mov(__dword_ptr[r8 + 0x8], eax); + asm.mov(__dword_ptr[r8 + 0xc], edx); + + asm.Label(ref skip); + asm.ret(); + + var infra = NativeFunctions.Default; + var (mem, size) = GenerateCode(asm, infra); + var getCpuInfo = Marshal.GetDelegateForFunctionPointer(mem); + getCpuInfo(); + + infra.VirtualFree(mem, size); + + return result; + } + + private static (IntPtr, ulong) GenerateCode(Assembler asm, INativeFunctions infrastructure = null) + { + infrastructure = infrastructure ?? NativeFunctions.Default; + + var ms = new MemoryStream(); + // Because we don't use any global variables or functions here + // except a few hard coded addresses to managed functions and objects, + // we don't have to set the correct base address. + asm.Assemble(new StreamCodeWriter(ms), 0); + + var size = AlignValueUp((ulong)ms.Length, (ulong)Environment.SystemPageSize); + var mem = infrastructure.VirtualAllocate(IntPtr.Zero, size, MemoryProtection.Write); + + var stream = new UnmanagedMemoryStream((byte*)mem, (int)size, (int)size, FileAccess.Write); + ms.CopyTo(stream); + + infrastructure.VirtualProtect(mem, size, MemoryProtection.Read | MemoryProtection.Execute); + + return (mem, size); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong AlignValueUp(ulong value, ulong alignment) + { + return (value + alignment - 1) & ~(alignment - 1); + } + } +} diff --git a/MemoryModule/Windows/WindowsNativeFunctions.cs b/MemoryModule/Windows/WindowsNativeFunctions.cs new file mode 100644 index 0000000..82b75d8 --- /dev/null +++ b/MemoryModule/Windows/WindowsNativeFunctions.cs @@ -0,0 +1,108 @@ +using MemoryModule.Abstractions; +using System; +using System.Runtime.InteropServices; + +namespace MemoryModule.Windows +{ + class WindowsNativeFunctions : NativeFunctions + { + public override bool FreeLibrary(IntPtr handle) + { + return FreeLibrary_(handle); + } + + public override IntPtr GetSymbolFromLibrary(IntPtr handle, string name) + { + return GetProcAddress(handle, name); + } + + public override IntPtr GetSymbolFromLibrary(IntPtr handle, IntPtr nameValue) + { + return GetProcAddress(handle, nameValue); + } + + public override IntPtr LoadLibrary(string name) + { + return LoadLibraryA(name); + } + + public override IntPtr VirtualAllocate(IntPtr hint, ulong size, MemoryProtection protection) + { + var flag = MemoryProtectionToNativePageProtection(protection); + + var result = VirtualAlloc(hint, (UIntPtr)size, MemoryAllocation.Commit | MemoryAllocation.Reserve, flag); + + // The memory at hint is already allocated. + if (result == IntPtr.Zero) + { + result = VirtualAlloc(IntPtr.Zero, (UIntPtr)size, MemoryAllocation.Commit | MemoryAllocation.Reserve, flag); + } + + return result; + } + + public override bool VirtualFree(IntPtr addr, ulong size) + { + return VirtualFree(addr, UIntPtr.Zero, MemoryAllocation.Decommit | MemoryAllocation.Release); + } + + public override bool VirtualProtect(IntPtr addr, ulong size, MemoryProtection protection) + { + var flag = MemoryProtectionToNativePageProtection(protection); + unsafe + { + return VirtualProtect(addr, (UIntPtr)size, flag, (IntPtr)(&flag)) != 0; + } + } + + private static PageProtection MemoryProtectionToNativePageProtection(MemoryProtection protection) + { + return ProtectionFlags + [protection.HasFlag(MemoryProtection.Execute) == false ? 0 : 1] + [protection.HasFlag(MemoryProtection.Read) == false ? 0 : 1] + [protection.HasFlag(MemoryProtection.Write) == false ? 0 : 1]; + } + + // Protection flags for memory pages (Executable, Readable, Writeable) + private static readonly PageProtection[][][] ProtectionFlags = new PageProtection[][][] + { + new PageProtection[][] + { + // not executable + new [] {PageProtection.NoAccess, PageProtection.ReadWrite}, + new [] {PageProtection.ReadOnly, PageProtection.ReadWrite}, + }, + new PageProtection[][] + { + // executable + new [] { PageProtection.Execute, PageProtection.ExecuteReadWrite}, + new [] { PageProtection.ExecuteRead, PageProtection.ExecuteReadWrite}, + }, + }; + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, MemoryAllocation flAllocationType, PageProtection flProtect); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, MemoryAllocation dwFreeType); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern int VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, PageProtection flNewProtect, IntPtr lpflOldProtect); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)] + // The library name seems to always be ANSI, so we must use LoadLibraryA in this case. + private static extern IntPtr LoadLibraryA([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, IntPtr lpProcName); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true, EntryPoint = "FreeLibrary")] + private static extern bool FreeLibrary_(IntPtr hLibModule); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)] + private static extern void GetNativeSystemInfo(IntPtr lpSystemInfo); + } +}