Gee.External.Capstone

Capstone.NET is a .NET Core and a .NET Framework binding for the Capstone disassembly framework. It is written in C#, supports Capstone 4, and has a friendly and simple type safe API that is ridiculously easy to learn and quick to pick up.


Keywords
arm, capstone, disassembler, reverse-engineering, security, x86, arm64, armv8, c-sharp, csharp, dotnet, dotnet-core, dotnet-framework, dotnet-standard, dotnetcore, dotnetframework, netcore, x64, x86-64
License
MIT
Install
Install-Package Gee.External.Capstone -Version 2.3.0

Documentation

Capstone.NET

Capstone.NET is an opinionated .NET Core and a .NET Framework binding for the Capstone disassembly framework. It is written in C#, supports Capstone 4, and has a friendly and simple type safe API that is ridiculously easy to learn and quick to pick up.

Nuget

Features

  • Supports Capstone 4. Only the ARM, ARM64, M68K, MIPS, PowerPC, X86, and XCore architectures are supported right now

  • Supports .NET 5 and greater, and .NET Framework 4.6.1 and greater

  • A friendly and simple to use type safe API that is ridiculously easy to learn and pick up

Installation

Either open the NuGet Package Manager in Visual Studio and search for "Capstone.NET" or head over to the NuGet website and download the NuGet package yourself. The NuGet package bundles Capstone. You can also clone this repository and build Capstone.NET yourself.

Building

You can build Capstone.NET by opening the Visual Studio solution in Visual Studio 2017 or greater, and building from there. The Visual Studio solution has "Debug" and "Release" configurations. Make sure you select the configuration you're targeting before you build.

Acknowledgments

Greetings to my sensi Mohamed Saher (@halsten) who suggested I take on this project and was a major help in debugging some stucture alignment issues, the Capstone team (@capstone_engine) for all the great work they are doing, and Matt Graeber (@mattifestation) who wrote the original binding for Capstone 2 and whose work was a starting point for this project.

Short Example

using Gee.External.Capstone;
using Gee.External.Capstone.Arm64;

namespace Example {
    /// <summary>
    ///     Example Program.
    /// </summary>
    internal static class Program {
        /// <summary>
        ///     Run Program.
        /// </summary>
        /// <param name="args">
        ///     An array of arguments passed from the command line.
        /// </param>
        internal static void Main(string[] args) {
            // ...
            //
            // Creating the disassembler in a "using" statement ensures that resources get cleaned up automatically
            // when it is no longer needed.
            const Arm64DisassembleMode disassembleMode = Arm64DisassembleMode.Arm;
            using (CapstoneArm64Disassembler disassembler = CapstoneDisassembler.CreateArm64Disassembler(disassembleMode)) {
                // ....
                //
                // Enables disassemble details, which are disabled by default, to provide more detailed information on
                // disassembled binary code.
                disassembler.EnableInstructionDetails = true;
                disassembler.DisassembleSyntax = DisassembleSyntax.Intel;

                var binaryCode = new byte[] {
                    0x09, 0x00, 0x38, 0xd5, 0xbf, 0x40, 0x00, 0xd5, 0x0c, 0x05, 0x13, 0xd5, 0x20, 0x50, 0x02, 0x0e,
                    0x20, 0xe4, 0x3d, 0x0f, 0x00, 0x18, 0xa0, 0x5f, 0xa2, 0x00, 0xae, 0x9e, 0x9f, 0x37, 0x03, 0xd5,
                    0xbf, 0x33, 0x03, 0xd5, 0xdf, 0x3f, 0x03, 0xd5, 0x21, 0x7c, 0x02, 0x9b, 0x21, 0x7c, 0x00, 0x53,
                    0x00, 0x40, 0x21, 0x4b, 0xe1, 0x0b, 0x40, 0xb9, 0x20, 0x04, 0x81, 0xda, 0x20, 0x08, 0x02, 0x8b,
                    0x10, 0x5b, 0xe8, 0x3c
                };

                Arm64Instruction[] instructions = disassembler.Disassemble(binaryCode);
                foreach (Arm64Instruction instruction in instructions) {
                    // ...
                    //
                    // Do your magic!
                }
            }
        }
    }
}

Complete Example

using Gee.External.Capstone;
using Gee.External.Capstone.Arm64;
using System;

namespace Example {
    /// <summary>
    ///     Example Program.
    /// </summary>
    internal static class Program {
        /// <summary>
        ///     Run Program.
        /// </summary>
        /// <param name="args">
        ///     An array of arguments passed from the command line.
        /// </param>
        internal static void Main(string[] args) {
            // ...
            //
            // Creating the disassembler in a "using" statement ensures that resources get cleaned up automatically
            // when it is no longer needed.
            const Arm64DisassembleMode disassembleMode = Arm64DisassembleMode.Arm;
            using (CapstoneArm64Disassembler disassembler = CapstoneDisassembler.CreateArm64Disassembler(disassembleMode)) {
                // ....
                //
                // Enables disassemble details, which are disabled by default, to provide more detailed information on
                // disassembled binary code.
                disassembler.EnableInstructionDetails = true;
                disassembler.DisassembleSyntax = DisassembleSyntax.Intel;

                var binaryCode = new byte[] {
                    0x09, 0x00, 0x38, 0xd5, 0xbf, 0x40, 0x00, 0xd5, 0x0c, 0x05, 0x13, 0xd5, 0x20, 0x50, 0x02, 0x0e,
                    0x20, 0xe4, 0x3d, 0x0f, 0x00, 0x18, 0xa0, 0x5f, 0xa2, 0x00, 0xae, 0x9e, 0x9f, 0x37, 0x03, 0xd5,
                    0xbf, 0x33, 0x03, 0xd5, 0xdf, 0x3f, 0x03, 0xd5, 0x21, 0x7c, 0x02, 0x9b, 0x21, 0x7c, 0x00, 0x53,
                    0x00, 0x40, 0x21, 0x4b, 0xe1, 0x0b, 0x40, 0xb9, 0x20, 0x04, 0x81, 0xda, 0x20, 0x08, 0x02, 0x8b,
                    0x10, 0x5b, 0xe8, 0x3c
                };

                var hexCode = BitConverter.ToString(binaryCode).Replace("-", " ");
                Console.WriteLine(hexCode);
                Console.WriteLine();

                Arm64Instruction[] instructions = disassembler.Disassemble(binaryCode);
                foreach (Arm64Instruction instruction in instructions) {
                    var address = instruction.Address;
                    Arm64InstructionId id = instruction.Id;
                    if (!instruction.IsDietModeEnabled) {
                        // ...
                        //
                        // An instruction's mnemonic and operand text are only available when Diet Mode is disabled.
                        // An exception is thrown otherwise!
                        var mnemonic = instruction.Mnemonic;
                        var operand = instruction.Operand;
                        Console.WriteLine("{0:X}: \t {1} \t {2}", address, mnemonic, operand);
                        Console.WriteLine("\t Instruction Id = {0}", id);
                    }
                    else {
                        Console.WriteLine("{0:X}:", address);
                        Console.WriteLine("\t Id = {0}", id);
                    }

                    hexCode = BitConverter.ToString(instruction.Bytes).Replace("-", " ");
                    Console.WriteLine("\t Machine Bytes = {0}", hexCode);

                    if (instruction.HasDetails) {
                        Console.WriteLine("\t Condition Code = {0}", instruction.Details.ConditionCode);
                        Console.WriteLine("\t Update Flags? = {0}", instruction.Details.UpdateFlags);
                        Console.WriteLine("\t Write Back? = {0}", instruction.Details.WriteBack);

                        if (!instruction.IsDietModeEnabled) {
                            // ...
                            //
                            // Instruction groups are only available when Diet Mode is disabled. An exception is
                            // thrown otherwise!
                            Arm64InstructionGroup[] instructionGroups = instruction.Details.Groups;
                            Console.WriteLine("\t # of Instruction Groups: {0}", instructionGroups.Length);
                            for (var i = 0; i < instructionGroups.Length; i++) {
                                // ...
                                //
                                // A instruction group's name is only available when Diet Mode is disabled. An
                                // exception is thrown otherwise! But since we already checked that it is disabled, we
                                // don't need to perform another check.
                                Arm64InstructionGroup instructionGroup = instructionGroups[i];
                                Console.Write("\t\t {0}) ", i + 1);
                                Console.WriteLine("Id = {0}, Name = {1}", instructionGroup.Id, instructionGroup.Name);
                            }

                            // ...
                            //
                            // Explicitly read registers are only available when Diet Mode is disabled. An exception
                            // is thrown otherwise!
                            Arm64Register[] registers = instruction.Details.ExplicitlyReadRegisters;
                            Console.WriteLine("\t # of Explicitly Read Registers: {0}", registers.Length);
                            for (var i = 0; i < registers.Length; i++) {
                                // ...
                                //
                                // A register's name is only available when Diet Mode is disabled. An exception is
                                // thrown otherwise! But since we already checked that it is disabled, we don't need
                                // to perform another check.
                                Arm64Register register = registers[i];
                                Console.Write("\t\t {0}) ", i + 1);
                                Console.WriteLine("Id = {0}, Name = {1}", register.Id, register.Name);
                            }

                            // ...
                            //
                            // Explicitly modified registers are only available when Diet Mode is disabled. An
                            // exception is thrown otherwise!
                            registers = instruction.Details.ExplicitlyWrittenRegisters;
                            Console.WriteLine("\t # of Explicitly Modified Registers: {0}", registers.Length);
                            for (var i = 0; i < registers.Length; i++) {
                                Arm64Register register = registers[i];
                                Console.Write("\t\t {0}) ", i + 1);
                                Console.WriteLine("Id = {0}, Name = {1}", register.Id, register.Name);
                            }

                            // ...
                            //
                            // Implicitly read registers are only available when Diet Mode is disabled. An exception
                            // is thrown otherwise!
                            registers = instruction.Details.ImplicitlyReadRegisters;
                            Console.WriteLine("\t # of Implicitly Read Registers: {0}", registers.Length);
                            for (var i = 0; i < registers.Length; i++) {
                                Arm64Register register = registers[i];
                                Console.Write("\t\t {0}) ", i + 1);
                                Console.WriteLine("Id = {0}, Name = {1}", register.Id, register.Name);
                            }

                            // ...
                            //
                            // Implicitly modified registers are only available when Diet Mode is disabled. An
                            // exception is thrown otherwise!
                            registers = instruction.Details.ImplicitlyWrittenRegisters;
                            Console.WriteLine("\t # of Implicitly Modified Registers: {0}", registers.Length);
                            for (var i = 0; i < registers.Length; i++) {
                                Arm64Register register = registers[i];
                                Console.Write("\t\t {0}) ", i + 1);
                                Console.WriteLine("Id = {0}, Name = {1}", register.Id, register.Name);
                            }
                        }

                        // ...
                        //
                        // An Instruction's operands are always available.
                        Arm64Operand[] operands = instruction.Details.Operands;
                        Console.WriteLine("\t # of Operands: {0}", operands.Length);
                        for (var i = 0; i < operands.Length; i++) {
                            // ...
                            //
                            // Always check the operand's type before retrieving the associated property. An exception
                            // is thrown otherwise!
                            Arm64Operand operand = operands[i];
                            Arm64OperandType operandType = operand.Type;
                            Console.WriteLine("\t\t {0}) Operand Type: {1}", i + 1, operandType);
                            if (operand.Type == Arm64OperandType.AtOperation) {
                                Arm64AtOperation atOperation = operand.AtOperation;
                                Console.WriteLine("\t\t\t Address Translation (AT) Operation = {0}", atOperation);
                            }
                            else if (operand.Type == Arm64OperandType.BarrierOperation) {
                                Arm64BarrierOperation barrierOperation = operand.BarrierOperation;
                                Console.WriteLine("\t\t\t Barrier Operation = {0}", barrierOperation);
                            }
                            else if (operand.Type == Arm64OperandType.DcOperation) {
                                Arm64DcOperation dcOperation = operand.DcOperation;
                                Console.WriteLine("\t\t\t Data Cache (DC) Operation = {0}", dcOperation);
                            }
                            else if (operand.Type == Arm64OperandType.FloatingPoint) {
                                var floatingPoint = operand.FloatingPoint;
                                Console.WriteLine("\t\t\t Floating Point Value = {0}", floatingPoint);
                            }
                            else if (operand.Type == Arm64OperandType.IcOperation) {
                                Arm64IcOperation icOperation = operand.IcOperation;
                                Console.WriteLine("\t\t\t Instruction Cache (IC) Operation = {0}", icOperation);
                            }
                            else if (operand.Type == Arm64OperandType.Immediate) {
                                var immediate = operand.Immediate;
                                Console.WriteLine("\t\t\t Immediate Value = {0:X}", immediate);
                            }
                            else if (operand.Type == Arm64OperandType.Memory) {
                                Arm64MemoryOperandValue memory = operand.Memory;
                                Console.WriteLine("\t\t\t Memory Value:");

                                // ...
                                //
                                // For a memory operand, an irrelevant base register will be a null reference!
                                Arm64Register @base = memory.Base;
                                if (@base != null) {
                                    if (!@base.IsDietModeEnabled) {
                                        // ...
                                        //
                                        // A register's name is only available when Diet Mode is disabled. An
                                        // exception is thrown otherwise!
                                        Arm64RegisterId baseId = @base.Id;
                                        var baseName = @base.Name;
                                        Console.WriteLine("\t\t\t\t Base: Id = {0}, Name = {1}", baseId, baseName);
                                    }
                                    else {
                                        // ...
                                        //
                                        // A register's unique identifier is always available.
                                        Console.WriteLine("\t\t\t\t Base: Id = {0}", @base.Id);
                                    }
                                }

                                var displacement = memory.Displacement;
                                Console.WriteLine("\t\t\t\t Displacement Value = {0}", displacement);

                                // ...
                                //
                                // For a memory operand, an irrelevant index register will be a null reference!
                                Arm64Register index = memory.Index;
                                if (index != null) {
                                    if (!index.IsDietModeEnabled) {
                                        Arm64RegisterId indexId = index.Id;
                                        var indexName = index.Name;
                                        Console.WriteLine("\t\t\t\t Index: Id = {0}, Name = {1}", indexId, indexName);
                                    }
                                    else {
                                        Console.WriteLine("\t\t\t\t Index: Id = {0}", index.Id);
                                    }
                                }
                            }
                            else if (operand.Type == Arm64OperandType.MrsSystemRegister) {
                                Arm64MrsSystemRegister mrsRegister = operand.MrsSystemRegister;
                                Console.WriteLine("\t\t\t MRS System Register = {0}", mrsRegister);
                            }
                            else if (operand.Type == Arm64OperandType.MsrSystemRegister) {
                                Arm64MsrSystemRegister msrRegister = operand.MsrSystemRegister;
                                Console.WriteLine("\t\t\t MSR System Register = {0}", msrRegister);
                            }
                            else if (operand.Type == Arm64OperandType.PStateField) {
                                Arm64PStateField pStateField = operand.PStateField;
                                Console.WriteLine("\t\t\t Processor State (PSTATE) Field = {0}", pStateField);
                            }
                            else if (operand.Type == Arm64OperandType.PrefetchOperation) {
                                Arm64PrefetchOperation prefetchOperation = operand.PrefetchOperation;
                                Console.WriteLine("\t\t\t Prefetch Operation = {0}", prefetchOperation);
                            }
                            else if (operand.Type == Arm64OperandType.Register) {
                                Arm64Register register = operand.Register;
                                if (!register.IsDietModeEnabled) {
                                    // ...
                                    //
                                    // A register's name is only available when Diet Mode is disabled. An exception is
                                    // thrown otherwise!
                                    Arm64RegisterId registerId = register.Id;
                                    var name = register.Name;
                                    Console.WriteLine("\t\t\t Register: Id = {0}, Name = {1}", registerId, name);
                                }
                                else {
                                    // ...
                                    //
                                    // A register's unique identifier is always available.
                                    Console.WriteLine("\t\t\t Register: Id = {0}", register.Id);
                                }
                            }

                            if (!operand.IsDietModeEnabled) {
                                // ...
                                //
                                // An operand's access type is only available when Diet Mode is disabled. An exception
                                // is thrown otherwise!
                                OperandAccessType accessType = operand.AccessType;
                                Console.WriteLine("\t\t\t Access Type = {0}", accessType);
                            }

                            Arm64ExtendOperation extendOperation = operand.ExtendOperation;
                            Console.WriteLine("\t\t\t Extend Operation = {0}", extendOperation);

                            Arm64ShiftOperation shiftOperation = operand.ShiftOperation;
                            Console.WriteLine("\t\t\t Shift Operation: {0}", shiftOperation);
                            if (shiftOperation != Arm64ShiftOperation.Invalid) {
                                // ...
                                //
                                // An operand's shift value is only available if the shift operation is not invalid.
                                // An exception is thrown otherwise!
                                var shiftValue = operand.ShiftValue;
                                Console.WriteLine("\t\t\t\t Shift Value = {0}", shiftValue);
                            }

                            Arm64VectorArrangementSpecifier vas = operand.VectorArrangementSpecifier;
                            Console.WriteLine("\t\t\t Vector Arrangement Specifier = {0}", vas);

                            Arm64VectorElementSizeSpecifier vess = operand.VectorElementSizeSpecifier;
                            Console.WriteLine("\t\t\t Vector Element Size Specifier = {0}", vess);

                            var vectorIndex = operand.VectorIndex;
                            Console.WriteLine("\t\t\t Vector Index = {0}", vectorIndex);
                        }
                    }

                    Console.WriteLine();
                    Console.ReadLine();
                }
            }
        }
    }
}