using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; namespace Keystone { /// /// Represents a Keystone engine. /// public sealed class Engine : IDisposable { private IntPtr engine = IntPtr.Zero; private bool addedResolveSymbol; private readonly ResolverInternal internalImpl; private readonly List resolvers = new List(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool ResolverInternal(IntPtr symbol, ref ulong value); /// /// Gets or sets a value that represents whether a /// should be thrown on error. /// public bool ThrowOnError { get; set; } /// /// Delegate for defining symbol resolvers. /// /// Symbol to resolve. /// Address of taid symbol, if found. /// Whether the symbol was recognized. public delegate bool Resolver(string symbol, ref ulong value); /// /// Event raised when keystone is resolving a symbol. /// /// This event is only available on Keystone 0.9.2 or higher. public event Resolver ResolveSymbol { add { if (!addedResolveSymbol) { KeystoneError err = NativeInterop.SetOption(engine, (int)OptionType.SYM_RESOLVER, Marshal.GetFunctionPointerForDelegate(internalImpl)); if (err == KeystoneError.KS_ERR_OK) addedResolveSymbol = true; else throw new KeystoneException("Could not add symbol resolver", err); } resolvers.Add(value); } remove { if (addedResolveSymbol && resolvers.Count == 0) { KeystoneError err = NativeInterop.SetOption(engine, (int)OptionType.SYM_RESOLVER, IntPtr.Zero); if (err == KeystoneError.KS_ERR_OK) addedResolveSymbol = false; else throw new KeystoneException("Could not remove symbol resolver", err); } resolvers.Remove(value); } } /// /// Method used for symbol resolving. /// /// Pointer to the name of the symbol. /// Address of the symbol, if found. /// Whether the symbol could be recognized. private bool ResolveSymbolInternal(IntPtr symbolPtr, ref ulong value) { string symbol = Marshal.PtrToStringAnsi(symbolPtr); foreach (Resolver item in resolvers) { bool result = item(symbol, ref value); if (result) return true; } return false; } /// /// Constructs the engine with a given architecture and a given mode. /// /// The target architecture. /// The mode, i.e. endianness, word size etc. /// /// Some architectures are not supported. /// Check with if the engine /// supports the target architecture. /// public Engine(Architecture architecture, Mode mode) { internalImpl = ResolveSymbolInternal; var result = NativeInterop.Open(architecture, (int)mode, ref engine); if (result != KeystoneError.KS_ERR_OK) throw new KeystoneException("Error while initializing keystone", result); } /// /// Sets an option in the engine. /// /// Type of the option. /// Value it the option. /// Whether the option was correctly set. /// An error encountered when setting the option. public bool SetOption(OptionType type, uint value) { var result = NativeInterop.SetOption(engine, (int)type, (IntPtr)value); if (result != KeystoneError.KS_ERR_OK) { if (ThrowOnError) throw new KeystoneException("Error while setting option", result); return false; } return true; } /// /// Encodes the given statement(s). /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Size of the buffer produced by the operation. /// Number of statements found and encoded. /// Result of the operation, or null if it failed and is false. /// A null argument was given. /// An error encountered when encoding the instructions. public byte[] Assemble(string toEncode, ulong address, out int size, out int statementCount) { if (toEncode == null) throw new ArgumentNullException(nameof(toEncode)); int result = NativeInterop.Assemble(engine, toEncode, address, out IntPtr encoding, out uint size_, out uint statementCount_); if (result != 0) { if (ThrowOnError) throw new KeystoneException("Error while assembling instructions", GetLastKeystoneError()); size = statementCount = 0; return null; } size = (int)size_; statementCount = (int)statementCount_; byte[] buffer = new byte[size]; Marshal.Copy(encoding, buffer, 0, size); NativeInterop.Free(encoding); return buffer; } /// /// Encodes the given statement(s). /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Result of the operation, or null if it failed and is false. /// A null argument was given. /// An error encountered when encoding the instructions. public EncodedData Assemble(string toEncode, ulong address) { byte[] buffer = Assemble(toEncode, address, out int size, out int statementCount); if (buffer == null) return null; return new EncodedData(buffer, statementCount, address); } /// /// Encodes the given statement(s) into the given buffer. /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Buffer into which the data shall be written. /// Index into the buffer after which the data shall be written. /// Number of statements found and encoded. /// Size of the data writen by the operation., or 0 if it failed and is false. /// A null argument was given. /// The provided index is invalid. /// An error encountered when encoding the instructions. public int Assemble(string toEncode, ulong address, byte[] buffer, int index, out int statementCount) { if (toEncode == null) throw new ArgumentNullException(nameof(toEncode)); if (buffer == null) throw new ArgumentNullException(nameof(buffer)); if (index < 0 || index >= buffer.Length) throw new ArgumentOutOfRangeException(nameof(buffer)); int result = NativeInterop.Assemble(engine, toEncode, address, out IntPtr encoding, out uint size_, out uint statementCount_); int size = (int)size_; statementCount = (int)statementCount_; if (result != 0) { if (ThrowOnError) throw new KeystoneException("Error while assembling instructions", GetLastKeystoneError()); return 0; } Marshal.Copy(encoding, buffer, index, size); NativeInterop.Free(encoding); return size; } /// /// Encodes the given statement(s) into the given buffer. /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Buffer into which the data shall be written. /// Index into the buffer after which the data shall be written. /// Size of the data writen by the operation., or 0 if it failed and is false. /// A null argument was given. /// The provided index is invalid. /// An error encountered when encoding the instructions. public int Assemble(string toEncode, ulong address, byte[] buffer, int index) { return Assemble(toEncode, address, buffer, index, out _); } /// /// Encodes the given statement(s) into the given stream. /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Buffer into which the data shall be written. /// Size of the buffer produced by the operation. /// Number of statements found and encoded. /// true on success, or false if it failed and is false. /// A null argument was given. /// An error encountered when encoding the instructions. public bool Assemble(string toEncode, ulong address, Stream stream, out int size, out int statementCount) { if (stream == null) throw new ArgumentNullException(nameof(stream)); byte[] enc = Assemble(toEncode, address, out size, out statementCount); if (enc == null) return false; stream.Write(enc, 0, size); return true; } /// /// Encodes the given statement(s) into the given stream. /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Buffer into which the data shall be written. /// Size of the buffer produced by the operation. /// true on success, or false if it failed and is false. /// A null argument was given. /// An error encountered when encoding the instructions. public bool Assemble(string toEncode, ulong address, Stream stream, out int size) { return Assemble(toEncode, address, stream, out size, out _); } /// /// Encodes the given statement(s) into the given stream. /// /// String that contains the statements to encode. /// Address of the first instruction to encode. /// Buffer into which the data shall be written. /// true on success, or false if it failed and is false. /// A null argument was given. /// An error encountered when encoding the instructions. public bool Assemble(string toEncode, ulong address, Stream stream) { return Assemble(toEncode, address, stream, out _, out _); } /// /// Gets the last error for this instance. /// /// The last error code. /// /// It might not retain its old error once accessed. /// public KeystoneError GetLastKeystoneError() { return NativeInterop.GetLastKeystoneError(engine); } /// /// Returns the string associated with a given error code. /// public static string ErrorToString(KeystoneError code) { IntPtr error = NativeInterop.ErrorToString(code); if (error != IntPtr.Zero) return Marshal.PtrToStringAnsi(error); return string.Empty; } /// /// Checks if the given architecture is supported. /// public static bool IsArchitectureSupported(Architecture architecture) { return NativeInterop.IsArchitectureSupported(architecture); } /// /// Gets the version of the engine. /// /// Major version number. /// Minor version number. /// Unique identifier for this version. public static uint GetKeystoneVersion(ref uint major, ref uint minor) { return NativeInterop.Version(ref major, ref minor); } /// /// Releases the engine. /// public void Dispose() { IntPtr currentEngine = Interlocked.Exchange(ref engine, IntPtr.Zero); if (currentEngine != IntPtr.Zero) NativeInterop.Close(currentEngine); } } }