Skip to content

Instantly share code, notes, and snippets.

@hadi77ir
Last active November 5, 2018 09:35
Show Gist options
  • Select an option

  • Save hadi77ir/e27f525fa24ba5077ba7c82ae0ce3234 to your computer and use it in GitHub Desktop.

Select an option

Save hadi77ir/e27f525fa24ba5077ba7c82ae0ce3234 to your computer and use it in GitHub Desktop.
Basic P/Invoke wrapper written in C# for Keystone Assembler Engine
using System;
using System.Runtime.InteropServices;
namespace NKeystone
{
/// <summary>
/// Represents a higher-level access to Keystone API.
/// </summary>
public class AssemblerEngine : IDisposable
{
/// <summary>
/// Dispose state
/// </summary>
protected bool _disposed;
/// <summary>
/// Underlying unmanaged Keystone instance.
/// </summary>
protected IntPtr _engine;
#region Version Info
/// <summary>
/// Version of the implemented API
/// </summary>
public static CombinedVersion ApiVersion { get; }
/// <summary>
/// Version of the underlying Shared Library / Keystone Runtime
/// </summary>
public static CombinedVersion LibraryVersion { get; }
#endregion
static AssemblerEngine()
{
ApiVersion = new CombinedVersion(NativeWrapper.KS_API_MAJOR, NativeWrapper.KS_API_MINOR);
uint major, minor;
NativeWrapper.ks_version(out major, out minor);
LibraryVersion = new CombinedVersion(major, minor);
}
/// <summary>
/// Initializes a new instance of Keystone.
/// </summary>
/// <param name="arch">Target architecture</param>
/// <param name="modes">Operating mode and flags</param>
/// <exception cref="KeystoneException">In case any errors occurred, the exception will be raised.</exception>
public AssemblerEngine(Architecture arch, Mode modes)
{
ErrorCode result;
if ((result = NativeWrapper.ks_open(arch, (int) modes, out _engine)) != ErrorCode.KS_ERR_OK)
{
throw new KeystoneException(result);
}
}
/// <summary>
/// Sets value for an option.
/// </summary>
/// <param name="key">Option key</param>
/// <param name="value">Option value</param>
/// <exception cref="KeystoneException">In case any errors occurred, the exception will be raised.</exception>
public void SetOption(OptionType key, OptionValue value)
{
ErrorCode result;
if ((result = NativeWrapper.ks_option(_engine, key, (int)value)) != ErrorCode.KS_ERR_OK)
{
throw new KeystoneException(result);
}
}
/// <summary>
/// Checks whether the specified architecture is supported by Keystone or not.
/// </summary>
/// <param name="arch">Architecture</param>
/// <returns>True if supported.</returns>
public static bool IsArchSupported(Architecture arch)
{
return NativeWrapper.ks_arch_supported(arch);
}
/// <summary>
/// Assembles the given assembly string.
/// </summary>
/// <param name="asm">Assembly input string</param>
/// <param name="address">Address of first assembly instruction. Set to 0 to ignore.</param>
/// <param name="statCount">Count of successfully processed statements.</param>
/// <returns>Assembled bytes</returns>
/// <exception cref="KeystoneException">In case any errors occurred, the exception will be raised.</exception>
public virtual byte[] Assemble(string asm, ulong address, out int statCount)
{
ErrorCode error;
// null-terminated string marshalling
IntPtr cStr = Marshal.StringToHGlobalAnsi(asm);
int result = NativeWrapper.ks_asm(_engine, cStr, address, out byte[] assembled, out int assembledSize,
out statCount);
// free unmanaged allocated memory
Marshal.FreeHGlobal(cStr);
if (result != 0)
{
error = NativeWrapper.ks_errno(_engine);
if (error != ErrorCode.KS_ERR_OK)
{
throw new KeystoneException(error);
}
}
byte[] output = new byte[assembledSize];
assembled.CopyTo(output, 0);
// free unmanaged pointer
NativeWrapper.ks_free(assembled);
return output;
}
public virtual byte[] Assemble(string asm, ulong address = 0)
{
return Assemble(asm, address, out _);
}
/// <summary>
/// Instance Finalizer
/// </summary>
~AssemblerEngine()
{
// only release unmanaged resources
Dispose(false);
}
/// <summary>
/// Releases resources and closes the underlying unmanaged engine.
/// </summary>
public void Dispose()
{
// release all resources and remove object from finalizing queue
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(_disposed)
return;
if (disposing)
{
// dispose managed resources if any.
}
// dispose unmanaged resources
ErrorCode result;
if ((result = NativeWrapper.ks_close(_engine)) != ErrorCode.KS_ERR_OK)
{
throw new KeystoneException(result);
}
_disposed = true;
}
}
public sealed class CombinedVersion : IComparable<CombinedVersion>
{
public uint Major { get; }
public uint Minor { get; }
public uint Combined { get; }
public CombinedVersion(uint combined)
{
Combined = combined;
Major = combined >> 8;
Minor = combined - (Major << 8);
}
public CombinedVersion(uint major, uint minor)
{
Major = major;
Minor = minor;
Combined = (major << 8) + minor;
}
public int CompareTo(CombinedVersion other)
{
if (ReferenceEquals(this, other)) return 0;
if (ReferenceEquals(null, other)) return 1;
return Combined.CompareTo(other.Combined);
}
}
}
// Keystone Assembler Engine (www.keystone-engine.org)
// By Nguyen Anh Quynh (aquynh@gmail.com), 2016
// Bindings by @hadi77ir
using System;
using System.Runtime.InteropServices;
namespace NKeystone
{
public static class NativeWrapper
{
///<summary>Keystone API version (major)</summary>
public const uint KS_API_MAJOR = 0;
///<summary>Keystone API version (minor)</summary>
public const uint KS_API_MINOR = 9;
/// <summary>
/// Constant that contains combined version which can be compared to result of ks_version() API.
/// </summary>
public const uint KS_VERSION = ((KS_API_MAJOR << 8) + KS_API_MINOR);
/// <summary>
/// All generic errors related to input assembly >= KS_ERR_ASM
/// </summary>
public const uint KS_ERR_ASM = 128;
/// <summary>
/// All architecture-specific errors related to input assembly >= KS_ERR_ASM_ARCH
/// </summary>
public const uint KS_ERR_ASM_ARCH = 512;
/// <summary>
/// Shared Library file name
/// </summary>
public const string LibraryName = "keystone";
/// <summary>
///Return combined API version & major and minor version numbers.
/// NOTE: This returned value can be compared with version number in <see cref="KS_VERSION"/> constant
/// NOTE: if you only care about returned value, but not major and minor values,
/// set both <paramref name="major"/> & <paramref name="minor"/> arguments to NULL.
///<param name="major">major number of API version</param>
///<param name="minor">minor number of API version</param>
///<returns>
/// hexical number, which contains both major & minor versions.
/// For example, for API version with 1 in @major, and 1 in @minor, the return value would be 0x0101
/// </returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern uint ks_version(out uint major, out uint minor);
/// <summary>
/// Determine if the given architecture is supported by this library.
/// </summary>
///<param name="arch">architecture type (KS_ARCH_*)>/param>
/// <returns> True if this library supports the given arch.</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern bool ks_arch_supported(Architecture arch);
/// <summary>
/// Create new instance of Keystone engine.
/// </summary>
/// <param name="arch">architecture type (KS_ARCH_*)</param>
/// <param name="mode">hardware mode. This is combined of KS_MODE_*</param>
/// <param name="ks">pointer to ks_engine, which will be updated at return time</param>
/// <returns>KS_ERR_OK on success, or other value on failure (see <see cref="ErrorCode"/>).</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern ErrorCode ks_open(Architecture arch, int mode, out IntPtr ks_engine_handle);
/// <summary>
///Close KS instance: MUST do to release the handle when it is not used anymore.
/// NOTE: this must be called only when there is no longer usage of Keystone.
/// The reason is the this API releases some cached memory, thus access to any
/// Keystone API after ks_close() might crash your application.
/// After this, @ks is invalid, and nolonger usable.
/// </summary>
/// <param name="ks"> @ks: pointer to a handle returned by ks_open()</param>
/// <returns>KS_ERR_OK on success, or other value on failure (<see cref="ErrorCode"/>).</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern ErrorCode ks_close(IntPtr ks);
/// <summary>
/// Report the last error number when some API function fail.
/// Like glibc's errno, ks_errno might not retain its old error once accessed.
/// </summary>
/// <param name="ks">handle returned by ks_open()</param>
///<returns>error code of ks_err enum type (see <see cref="ErrorCode"/>).</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern ErrorCode ks_errno(IntPtr ks);
/// <summary>
/// Return a string describing given error code.
/// </summary>
/// <param name="code">error code (see <see cref="ErrorCode"/>)</param>
/// <returns>returns a pointer to a string that describes the error code passed in the argument <paramref name="code"/></returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern string ks_strerror(ErrorCode code);
/// <summary>
/// Set option for Keystone engine at runtime
/// Refer to ks_err enum for detailed error.
/// </summary>
/// <param name="handle">returned by ks_open()</param>
/// <param name="type">type of option to be set</param>
/// <param name="value">option value corresponding with <paramref name="type"/></param>
/// <returns>KS_ERR_OK on success, or other value on failure.</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern ErrorCode ks_option(IntPtr ks, OptionType type, int value);
/// <summary>
/// Assemble a string given its the buffer, size, start address and number
/// of instructions to be decoded.
/// This API dynamically allocate memory to contain assembled instruction.
/// Resulted array of bytes containing the machine code is put into @*encoding
///
/// NOTE 1: this API will automatically determine memory needed to contain
/// output bytes in *encoding.
///
/// NOTE 2: caller must free the allocated memory itself to avoid memory leaking.
///
/// On failure, call ks_errno() for error code.
/// </summary>
/// <param name="ks">handle returned by ks_open()</param>
/// <param name="str">NULL-terminated assembly string. Use ; or \n to separate statements.</param>
/// <param name="address">address of the first assembly instruction, or 0 to ignore.</param>
/// <param name="encoding">
/// array of bytes containing encoding of input assembly string.
/// NOTE: *encoding will be allocated by this function, and should be freed
/// with ks_free() function.
/// </param>
/// <param name="encoding_size">size of *encoding</param>
/// <param name="stat_count">number of statements successfully processed</param>
/// <returns>0 on success, or -1 on failure.</returns>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern int ks_asm(IntPtr ks, IntPtr str, ulong address,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]
out byte[] encoding,
out int encoding_size, out int stat_count);
/// <summary>
/// Free memory allocated by ks_asm()
/// </summary>
/// <param name="p">memory allocated in @encoding argument of ks_asm()</param>
[DllImport(LibraryName, CharSet = CharSet.Ansi)]
public static extern void ks_free(byte[] p);
}
/// <summary>
/// Runtime option value (associated with ks_opt_type above)
/// </summary>
[Flags]
public enum OptionValue
{
#region KS_OPT_SYNTAX
KS_OPT_SYNTAX_INTEL = 1 << 0, // X86 Intel syntax - default on X86 (KS_OPT_SYNTAX).
KS_OPT_SYNTAX_ATT = 1 << 1, // X86 ATT asm syntax (KS_OPT_SYNTAX).
KS_OPT_SYNTAX_NASM = 1 << 2, // X86 Nasm syntax (KS_OPT_SYNTAX).
KS_OPT_SYNTAX_MASM = 1 << 3, // X86 Masm syntax (KS_OPT_SYNTAX) - unsupported yet.
KS_OPT_SYNTAX_GAS = 1 << 4, // X86 GNU GAS syntax (KS_OPT_SYNTAX).
#endregion
}
/// <summary>
/// Runtime option for the Keystone engine
/// </summary>
public enum OptionType
{
KS_OPT_SYNTAX = 1, // Choose syntax for input assembly
}
/// <summary>
/// Architecture type
/// </summary>
public enum Architecture
{
KS_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
KS_ARCH_ARM64, // ARM-64, also called AArch64
KS_ARCH_MIPS, // Mips architecture
KS_ARCH_X86, // X86 architecture (including x86 & x86-64)
KS_ARCH_PPC, // PowerPC architecture (currently unsupported)
KS_ARCH_SPARC, // Sparc architecture
KS_ARCH_SYSTEMZ, // SystemZ architecture (S390X)
KS_ARCH_HEXAGON, // Hexagon architecture
KS_ARCH_MAX,
}
/// <summary>
/// Mode type
/// </summary>
[Flags]
public enum Mode
{
#region Generic
KS_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode)
KS_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
#endregion
#region ARM / ARM64
KS_MODE_ARM = 1 << 0, // ARM mode
KS_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
KS_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM
#endregion
#region MIPS
KS_MODE_MICRO = 1 << 4, // MicroMips mode
KS_MODE_MIPS3 = 1 << 5, // Mips III ISA
KS_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA
KS_MODE_MIPS32 = 1 << 2, // Mips32 ISA
KS_MODE_MIPS64 = 1 << 3, // Mips64 ISA
#endregion
#region x86 / x64
KS_MODE_16 = 1 << 1, // 16-bit mode
KS_MODE_32 = 1 << 2, // 32-bit mode
KS_MODE_64 = 1 << 3, // 64-bit mode
#endregion
#region PowerPC
KS_MODE_PPC32 = 1 << 2, // 32-bit mode
KS_MODE_PPC64 = 1 << 3, // 64-bit mode
KS_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode
#endregion
#region SPARC
KS_MODE_SPARC32 = 1 << 2, // 32-bit mode
KS_MODE_SPARC64 = 1 << 3, // 64-bit mode
KS_MODE_V9 = 1 << 4, // SparcV9 mode
#endregion
}
/// <summary>
/// All type of errors encountered by Keystone API.
/// </summary>
public enum ErrorCode : uint
{
KS_ERR_OK = 0, // No error: everything was fine
KS_ERR_NOMEM, // Out-Of-Memory error: ks_open(), ks_emulate()
KS_ERR_ARCH, // Unsupported architecture: ks_open()
KS_ERR_HANDLE, // Invalid handle
KS_ERR_MODE, // Invalid/unsupported mode: ks_open()
KS_ERR_VERSION, // Unsupported version (bindings)
KS_ERR_OPT_INVALID, // Unsupported option
#region generic input assembly errors - parser specific
KS_ERR_ASM_EXPR_TOKEN = NativeWrapper.KS_ERR_ASM, // unknown token in expression
KS_ERR_ASM_DIRECTIVE_VALUE_RANGE, // literal value out of range for directive
KS_ERR_ASM_DIRECTIVE_ID, // expected identifier in directive
KS_ERR_ASM_DIRECTIVE_TOKEN, // unexpected token in directive
KS_ERR_ASM_DIRECTIVE_STR, // expected string in directive
KS_ERR_ASM_DIRECTIVE_COMMA, // expected comma in directive
KS_ERR_ASM_DIRECTIVE_RELOC_NAME, // expected relocation name in directive
KS_ERR_ASM_DIRECTIVE_RELOC_TOKEN, // unexpected token in .reloc directive
KS_ERR_ASM_DIRECTIVE_FPOINT, // invalid floating point in directive
KS_ERR_ASM_DIRECTIVE_UNKNOWN, // unknown directive
KS_ERR_ASM_DIRECTIVE_EQU, // invalid equal directive
KS_ERR_ASM_DIRECTIVE_INVALID, // (generic) invalid directive
KS_ERR_ASM_VARIANT_INVALID, // invalid variant
KS_ERR_ASM_EXPR_BRACKET, // brackets expression not supported on this target
KS_ERR_ASM_SYMBOL_MODIFIER, // unexpected symbol modifier following '@'
KS_ERR_ASM_SYMBOL_REDEFINED, // invalid symbol redefinition
KS_ERR_ASM_SYMBOL_MISSING, // cannot find a symbol
KS_ERR_ASM_RPAREN, // expected ')' in parentheses expression
KS_ERR_ASM_STAT_TOKEN, // unexpected token at start of statement
KS_ERR_ASM_UNSUPPORTED, // unsupported token yet
KS_ERR_ASM_MACRO_TOKEN, // unexpected token in macro instantiation
KS_ERR_ASM_MACRO_PAREN, // unbalanced parentheses in macro argument
KS_ERR_ASM_MACRO_EQU, // expected '=' after formal parameter identifier
KS_ERR_ASM_MACRO_ARGS, // too many positional arguments
KS_ERR_ASM_MACRO_LEVELS_EXCEED, // macros cannot be nested more than 20 levels deep
KS_ERR_ASM_MACRO_STR, // invalid macro string
KS_ERR_ASM_MACRO_INVALID, // invalid macro (generic error)
KS_ERR_ASM_ESC_BACKSLASH, // unexpected backslash at end of escaped string
KS_ERR_ASM_ESC_OCTAL, // invalid octal escape sequence (out of range)
KS_ERR_ASM_ESC_SEQUENCE, // invalid escape sequence (unrecognized character)
KS_ERR_ASM_ESC_STR, // broken escape string
KS_ERR_ASM_TOKEN_INVALID, // invalid token
KS_ERR_ASM_INSN_UNSUPPORTED, // this instruction is unsupported in this mode
KS_ERR_ASM_FIXUP_INVALID, // invalid fixup
KS_ERR_ASM_LABEL_INVALID, // invalid label
KS_ERR_ASM_FRAGMENT_INVALID, // invalid fragment
#endregion
#region generic input assembly errors - architecture specific
KS_ERR_ASM_INVALIDOPERAND = NativeWrapper.KS_ERR_ASM_ARCH,
KS_ERR_ASM_MISSINGFEATURE,
KS_ERR_ASM_MNEMONICFAIL,
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment