From 58a148ebd8e5eed46f2955f1004c7f9c8b371c23 Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sat, 12 Apr 2025 19:57:42 +0300 Subject: [PATCH] Refactor instruction handlers to use single instruction per handler pattern --- X86Disassembler/Program.cs | 193 ++++------ X86Disassembler/X86/Disassembler.cs | 86 ++--- .../X86/Handlers/CallRel32Handler.cs | 59 ++++ .../X86/Handlers/ConditionalJumpHandler.cs | 68 ++++ X86Disassembler/X86/Handlers/FnstswHandler.cs | 63 ++++ .../X86/Handlers/IInstructionHandler.cs | 22 ++ .../X86/Handlers/InstructionHandler.cs | 4 +- .../X86/Handlers/InstructionHandlerFactory.cs | 72 ++++ .../X86/Handlers/JmpRel32Handler.cs | 59 ++++ .../X86/Handlers/JmpRel8Handler.cs | 59 ++++ X86Disassembler/X86/Handlers/RetHandler.cs | 45 +++ X86Disassembler/X86/Handlers/RetImmHandler.cs | 56 +++ .../X86/Handlers/TestAlImmHandler.cs | 56 +++ .../X86/Handlers/TestEaxImmHandler.cs | 56 +++ .../X86/Handlers/TestRegMemHandler.cs | 82 +++++ .../Handlers/TwoByteConditionalJumpHandler.cs | 88 +++++ .../X86/Handlers/XorAlImmHandler.cs | 56 +++ .../X86/Handlers/XorEaxImmHandler.cs | 56 +++ .../X86/Handlers/XorMemRegHandler.cs | 82 +++++ .../X86/Handlers/XorRegMemHandler.cs | 82 +++++ X86Disassembler/X86/Instruction.cs | 52 +-- X86Disassembler/X86/InstructionDecoder.cs | 334 ++++++++++-------- 22 files changed, 1386 insertions(+), 344 deletions(-) create mode 100644 X86Disassembler/X86/Handlers/CallRel32Handler.cs create mode 100644 X86Disassembler/X86/Handlers/ConditionalJumpHandler.cs create mode 100644 X86Disassembler/X86/Handlers/FnstswHandler.cs create mode 100644 X86Disassembler/X86/Handlers/IInstructionHandler.cs create mode 100644 X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs create mode 100644 X86Disassembler/X86/Handlers/JmpRel32Handler.cs create mode 100644 X86Disassembler/X86/Handlers/JmpRel8Handler.cs create mode 100644 X86Disassembler/X86/Handlers/RetHandler.cs create mode 100644 X86Disassembler/X86/Handlers/RetImmHandler.cs create mode 100644 X86Disassembler/X86/Handlers/TestAlImmHandler.cs create mode 100644 X86Disassembler/X86/Handlers/TestEaxImmHandler.cs create mode 100644 X86Disassembler/X86/Handlers/TestRegMemHandler.cs create mode 100644 X86Disassembler/X86/Handlers/TwoByteConditionalJumpHandler.cs create mode 100644 X86Disassembler/X86/Handlers/XorAlImmHandler.cs create mode 100644 X86Disassembler/X86/Handlers/XorEaxImmHandler.cs create mode 100644 X86Disassembler/X86/Handlers/XorMemRegHandler.cs create mode 100644 X86Disassembler/X86/Handlers/XorRegMemHandler.cs diff --git a/X86Disassembler/Program.cs b/X86Disassembler/Program.cs index 95e75e3..fdb8ec0 100644 --- a/X86Disassembler/Program.cs +++ b/X86Disassembler/Program.cs @@ -1,5 +1,3 @@ -namespace X86Disassembler; - using System; using System.IO; using System.Text; @@ -7,203 +5,138 @@ using System.Collections.Generic; using X86Disassembler.PE; using X86Disassembler.X86; -internal class Program +namespace X86Disassembler; + +/// +/// Main program class +/// +public class Program { - // Path to the DLL file to disassemble - private const string DllPath = @"C:\Program Files (x86)\Nikita\Iron Strategy\Terrain.dll"; // Example path, replace with your target DLL + // Hardcoded file path for testing + private const string FilePath = @"C:\Program Files (x86)\Nikita\Iron Strategy\Terrain.dll"; - // Maximum number of instructions to display per section - private const int MaxInstructionsToDisplay = 50; - - static void Main(string[] args) + /// + /// Main entry point + /// + /// Command line arguments + public static void Main(string[] args) { Console.WriteLine("X86 Disassembler and Decompiler"); Console.WriteLine("--------------------------------"); - string filePath = DllPath; + // Load the file + Console.WriteLine($"Loading file: {FilePath}"); + byte[] fileBytes = File.ReadAllBytes(FilePath); + Console.WriteLine($"Successfully loaded {FilePath}"); + Console.WriteLine($"File size: {fileBytes.Length} bytes\n"); - Console.WriteLine($"Loading file: {filePath}"); + // Parse the PE format + Console.WriteLine("Parsing PE format...\n"); + PEFormat peFormat = new PEFormat(fileBytes); + peFormat.Parse(); - try - { - // Load the file into memory - byte[] fileBytes = File.ReadAllBytes(filePath); - Console.WriteLine($"Successfully loaded {filePath}"); - Console.WriteLine($"File size: {fileBytes.Length} bytes"); - Console.WriteLine(); - - Console.WriteLine("Parsing PE format..."); - Console.WriteLine(); - - // Parse the PE format - PEFormat peFormat = new PEFormat(fileBytes); - if (!peFormat.Parse()) - { - Console.WriteLine("Failed to parse PE file."); - return; - } - - // Display PE information - DisplayPEInfo(peFormat); - - // Disassemble code sections - DisassembleCodeSections(peFormat); - - Console.WriteLine(); - Console.WriteLine("Press Enter to exit..."); - Console.Read(); // Use Read instead of ReadKey to avoid errors in redirected console - - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - Console.WriteLine(ex.StackTrace); - } - } - - /// - /// Displays information about the PE file - /// - /// The PE format object - private static void DisplayPEInfo(PEFormat peFormat) - { + // Print PE file information Console.WriteLine("PE File Information:"); Console.WriteLine($"Architecture: {(peFormat.OptionalHeader.Is64Bit() ? "64-bit" : "32-bit")}"); Console.WriteLine($"Entry Point: 0x{peFormat.OptionalHeader.AddressOfEntryPoint:X8}"); Console.WriteLine($"Image Base: 0x{peFormat.OptionalHeader.ImageBase:X8}"); Console.WriteLine($"Number of Sections: {peFormat.FileHeader.NumberOfSections}"); + Console.WriteLine(); - Console.WriteLine("\nSections:"); - for (int i = 0; i < peFormat.SectionHeaders.Count; i++) + // Print section information + Console.WriteLine("Sections:"); + foreach (var section in peFormat.SectionHeaders) { - var section = peFormat.SectionHeaders[i]; string flags = ""; - - // Use the section's methods to determine characteristics if (section.ContainsCode()) flags += "Code "; if (section.IsExecutable()) flags += "Exec "; if (section.IsReadable()) flags += "Read "; if (section.IsWritable()) flags += "Write"; - Console.WriteLine($" {i}: {section.Name,-8} VA=0x{section.VirtualAddress:X8} Size={section.VirtualSize,-8} [{flags}]"); + Console.WriteLine($" {peFormat.SectionHeaders.IndexOf(section)}: {section.Name,-8} VA=0x{section.VirtualAddress:X8} Size={section.VirtualSize,-8} [{flags}]"); } + Console.WriteLine(); - // Display exported functions + // Print export information if (peFormat.ExportDirectory != null) { - Console.WriteLine("\nExported Functions:"); + Console.WriteLine("Exported Functions:"); Console.WriteLine($"DLL Name: {peFormat.ExportDirectory.DllName}"); Console.WriteLine($"Number of Functions: {peFormat.ExportDirectory.NumberOfFunctions}"); Console.WriteLine($"Number of Names: {peFormat.ExportDirectory.NumberOfNames}"); for (int i = 0; i < peFormat.ExportedFunctions.Count; i++) { - var function = peFormat.ExportedFunctions[i]; - Console.WriteLine($" {i}: {function.Name} (Ordinal={function.Ordinal}, RVA=0x{function.AddressRva:X8})"); + var export = peFormat.ExportedFunctions[i]; + Console.WriteLine($" {i}: {export.Name} (Ordinal={export.Ordinal}, RVA=0x{export.AddressRva:X8})"); } + Console.WriteLine(); } - // Display imported functions + // Print import information if (peFormat.ImportDescriptors.Count > 0) { - Console.WriteLine("\nImported Functions:"); + Console.WriteLine("Imported Functions:"); Console.WriteLine($"Number of Imported DLLs: {peFormat.ImportDescriptors.Count}"); - for (int i = 0; i < peFormat.ImportDescriptors.Count; i++) + foreach (var import in peFormat.ImportDescriptors) { - var descriptor = peFormat.ImportDescriptors[i]; - Console.WriteLine($" DLL: {descriptor.DllName}"); + Console.WriteLine($" DLL: {import.DllName}"); - for (int j = 0; j < descriptor.Functions.Count; j++) + for (int i = 0; i < import.Functions.Count; i++) { - var function = descriptor.Functions[j]; + var function = import.Functions[i]; if (function.IsOrdinal) { - Console.WriteLine($" {j}: Ordinal {function.Ordinal}"); + Console.WriteLine($" {i}: Ordinal {function.Ordinal}"); } else { - Console.WriteLine($" {j}: {function.Name} (Hint={function.Hint})"); + Console.WriteLine($" {i}: {function.Name} (Hint={function.Hint})"); } } - - if (i < peFormat.ImportDescriptors.Count - 1) - { - Console.WriteLine(); // Add a blank line between DLLs for better readability - } } + Console.WriteLine(); } - } - - /// - /// Disassembles the code sections of the PE file - /// - /// The PE format object - private static void DisassembleCodeSections(PEFormat peFormat) - { + // Find code sections var codeSections = peFormat.SectionHeaders.FindAll(s => s.ContainsCode()); - - Console.WriteLine($"\nFound {codeSections.Count} code section(s):"); + Console.WriteLine($"Found {codeSections.Count} code section(s):"); foreach (var section in codeSections) { Console.WriteLine($" - {section.Name}: Size={section.VirtualSize} bytes, RVA=0x{section.VirtualAddress:X8}"); } Console.WriteLine(); - // Disassemble each code section - for (int i = 0; i < peFormat.SectionHeaders.Count; i++) + // Disassemble the first code section + if (codeSections.Count > 0) { - var section = peFormat.SectionHeaders[i]; + var section = codeSections[0]; + byte[] codeBytes = peFormat.GetSectionData(peFormat.SectionHeaders.IndexOf(section)); - // Skip non-code sections - if (!section.ContainsCode()) - continue; - Console.WriteLine($"Disassembling section {section.Name} at RVA 0x{section.VirtualAddress:X8}:"); - // Get section data using the section index - byte[] sectionData = peFormat.GetSectionData(i); + // Create a disassembler for the code section + Disassembler disassembler = new Disassembler(codeBytes, section.VirtualAddress); - // Create a disassembler for this section - ulong baseAddress = peFormat.OptionalHeader.ImageBase + section.VirtualAddress; - Disassembler disassembler = new Disassembler(sectionData, baseAddress); + // Disassemble all instructions + var instructions = disassembler.Disassemble(); - // Disassemble and display instructions - int count = 0; - int maxInstructions = MaxInstructionsToDisplay; // Use the constant - - while (count < maxInstructions) + // Print the first 100 instructions + int count = Math.Min(100, instructions.Count); + for (int i = 0; i < count; i++) { - Instruction? instruction = disassembler.DisassembleNext(); - if (instruction == null) - { - break; - } - - // Format the instruction bytes - StringBuilder bytesStr = new StringBuilder(); - foreach (byte b in instruction.Bytes) - { - bytesStr.Append($"{b:X2} "); - } - - // Format the instruction - // Calculate the RVA by subtracting the image base - ulong rva = instruction.Address - peFormat.OptionalHeader.ImageBase; - string addressStr = $"{rva:X8}"; - string bytesDisplay = bytesStr.ToString().PadRight(20); // Pad to 20 characters - string operandsStr = string.IsNullOrEmpty(instruction.Operands) ? "" : $" {instruction.Operands}"; - - Console.WriteLine($" {addressStr} {bytesDisplay} {instruction.Mnemonic}{operandsStr}"); - - count++; + Console.WriteLine(instructions[i]); } - if (sectionData.Length > count * 10) // If we've only shown a small portion + // Print a summary of how many more instructions there are + if (instructions.Count > count) { - Console.WriteLine($" ... ({sectionData.Length - (count * 10)} more bytes not shown)"); + Console.WriteLine($"... ({instructions.Count - count} more bytes not shown)"); } } + + Console.WriteLine("\nPress Enter to exit..."); + Console.ReadLine(); } } \ No newline at end of file diff --git a/X86Disassembler/X86/Disassembler.cs b/X86Disassembler/X86/Disassembler.cs index 5cf3c3f..70c0917 100644 --- a/X86Disassembler/X86/Disassembler.cs +++ b/X86Disassembler/X86/Disassembler.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Collections.Generic; namespace X86Disassembler.X86; @@ -7,77 +8,62 @@ namespace X86Disassembler.X86; /// public class Disassembler { - // Buffer containing the code to disassemble + // The buffer containing the code to disassemble private readonly byte[] _codeBuffer; - // Base address for the code (RVA) - private readonly ulong _baseAddress; + // The length of the buffer + private readonly int _length; - // Current position in the code buffer - private int _position; - - // Instruction decoder - private readonly InstructionDecoder _decoder; + // The base address of the code + private readonly uint _baseAddress; /// /// Initializes a new instance of the Disassembler class /// /// The buffer containing the code to disassemble - /// The base address (RVA) of the code - public Disassembler(byte[] codeBuffer, ulong baseAddress) + /// The base address of the code + public Disassembler(byte[] codeBuffer, uint baseAddress) { _codeBuffer = codeBuffer; + _length = codeBuffer.Length; _baseAddress = baseAddress; - _position = 0; - _decoder = new InstructionDecoder(codeBuffer); } /// - /// Disassembles the next instruction in the code buffer - /// - /// The disassembled instruction, or null if the end of the buffer is reached - public Instruction? DisassembleNext() - { - if (_position >= _codeBuffer.Length) - { - return null; // End of buffer reached - } - - // Create a new instruction - Instruction instruction = new Instruction - { - Address = _baseAddress + (uint)_position - }; - - // Decode the instruction - int bytesRead = _decoder.DecodeAt(_position, instruction); - - if (bytesRead == 0) - { - return null; // Failed to decode instruction - } - - // Update position - _position += bytesRead; - - return instruction; - } - - /// - /// Disassembles all instructions in the code buffer + /// Disassembles the code buffer and returns the disassembled instructions /// /// A list of disassembled instructions - public List DisassembleAll() + public List Disassemble() { List instructions = new List(); - // Reset position - _position = 0; + // Create an instruction decoder + InstructionDecoder decoder = new InstructionDecoder(_codeBuffer, _length); - // Disassemble all instructions - Instruction? instruction; - while ((instruction = DisassembleNext()) != null) + // Decode instructions until the end of the buffer is reached + while (true) { + int position = decoder.GetPosition(); + + // Check if we've reached the end of the buffer + if (position >= _length) + { + break; + } + + // Decode the next instruction + Instruction? instruction = decoder.DecodeInstruction(); + + // Check if decoding failed + if (instruction == null) + { + break; + } + + // Adjust the instruction address to include the base address + instruction.Address += _baseAddress; + + // Add the instruction to the list instructions.Add(instruction); } diff --git a/X86Disassembler/X86/Handlers/CallRel32Handler.cs b/X86Disassembler/X86/Handlers/CallRel32Handler.cs new file mode 100644 index 0000000..04950f6 --- /dev/null +++ b/X86Disassembler/X86/Handlers/CallRel32Handler.cs @@ -0,0 +1,59 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for CALL rel32 instruction (0xE8) +/// +public class CallRel32Handler : InstructionHandler +{ + /// + /// Initializes a new instance of the CallRel32Handler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public CallRel32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xE8; + } + + /// + /// Decodes a CALL rel32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "call"; + + int position = Decoder.GetPosition(); + + if (position + 4 > Length) + { + return false; + } + + // Read the relative offset + int offset = BitConverter.ToInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + + // Calculate the target address + uint targetAddress = (uint)(position + offset + 4); + + // Set the operands + instruction.Operands = $"0x{targetAddress:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/ConditionalJumpHandler.cs b/X86Disassembler/X86/Handlers/ConditionalJumpHandler.cs new file mode 100644 index 0000000..ed433f0 --- /dev/null +++ b/X86Disassembler/X86/Handlers/ConditionalJumpHandler.cs @@ -0,0 +1,68 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for conditional jump instructions (0x70-0x7F) +/// +public class ConditionalJumpHandler : InstructionHandler +{ + // Mnemonics for conditional jumps + private static readonly string[] ConditionalJumpMnemonics = new string[] + { + "jo", "jno", "jb", "jnb", "jz", "jnz", "jbe", "jnbe", + "js", "jns", "jp", "jnp", "jl", "jnl", "jle", "jnle" + }; + + /// + /// Initializes a new instance of the ConditionalJumpHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public ConditionalJumpHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + // Conditional jumps are in the range 0x70-0x7F + return opcode >= 0x70 && opcode <= 0x7F; + } + + /// + /// Decodes a conditional jump instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Get the mnemonic from the table + int index = opcode - 0x70; + instruction.Mnemonic = ConditionalJumpMnemonics[index]; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the relative offset + sbyte offset = (sbyte)CodeBuffer[position]; + Decoder.SetPosition(position + 1); + + // Calculate the target address + uint targetAddress = (uint)(position + offset + 1); + + // Set the operands + instruction.Operands = $"0x{targetAddress:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/FnstswHandler.cs b/X86Disassembler/X86/Handlers/FnstswHandler.cs new file mode 100644 index 0000000..635b75d --- /dev/null +++ b/X86Disassembler/X86/Handlers/FnstswHandler.cs @@ -0,0 +1,63 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for FNSTSW instruction (0xDFE0) +/// +public class FnstswHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the FnstswHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public FnstswHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + // FNSTSW is a two-byte opcode (0xDF 0xE0) + if (opcode == 0xDF) + { + int position = Decoder.GetPosition(); + if (position < Length && CodeBuffer[position] == 0xE0) + { + return true; + } + } + + return false; + } + + /// + /// Decodes an FNSTSW instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + int position = Decoder.GetPosition(); + + if (position >= Length || CodeBuffer[position] != 0xE0) + { + return false; + } + + // Skip the second byte of the opcode + Decoder.SetPosition(position + 1); + + // Set the mnemonic and operands + instruction.Mnemonic = "fnstsw"; + instruction.Operands = "ax"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/IInstructionHandler.cs b/X86Disassembler/X86/Handlers/IInstructionHandler.cs new file mode 100644 index 0000000..5ce3628 --- /dev/null +++ b/X86Disassembler/X86/Handlers/IInstructionHandler.cs @@ -0,0 +1,22 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Interface for instruction handlers +/// +public interface IInstructionHandler +{ + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + bool CanHandle(byte opcode); + + /// + /// Decodes an instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + bool Decode(byte opcode, Instruction instruction); +} diff --git a/X86Disassembler/X86/Handlers/InstructionHandler.cs b/X86Disassembler/X86/Handlers/InstructionHandler.cs index 20604c3..b79b8ab 100644 --- a/X86Disassembler/X86/Handlers/InstructionHandler.cs +++ b/X86Disassembler/X86/Handlers/InstructionHandler.cs @@ -1,9 +1,9 @@ namespace X86Disassembler.X86.Handlers; /// -/// Base class for all instruction handlers +/// Abstract base class for instruction handlers /// -public abstract class InstructionHandler +public abstract class InstructionHandler : IInstructionHandler { // Buffer containing the code to decode protected readonly byte[] CodeBuffer; diff --git a/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs b/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs new file mode 100644 index 0000000..7608a84 --- /dev/null +++ b/X86Disassembler/X86/Handlers/InstructionHandlerFactory.cs @@ -0,0 +1,72 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Factory for creating instruction handlers +/// +public class InstructionHandlerFactory +{ + private readonly byte[] _codeBuffer; + private readonly InstructionDecoder _decoder; + private readonly int _length; + private readonly List _handlers = new List(); + + /// + /// Initializes a new instance of the InstructionHandlerFactory class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this factory + /// The length of the buffer + public InstructionHandlerFactory(byte[] codeBuffer, InstructionDecoder decoder, int length) + { + _codeBuffer = codeBuffer; + _decoder = decoder; + _length = length; + + // Register all instruction handlers + RegisterHandlers(); + } + + /// + /// Registers all instruction handlers + /// + private void RegisterHandlers() + { + // Register specific instruction handlers + _handlers.Add(new RetHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new RetImmHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new JmpRel32Handler(_codeBuffer, _decoder, _length)); + _handlers.Add(new JmpRel8Handler(_codeBuffer, _decoder, _length)); + _handlers.Add(new CallRel32Handler(_codeBuffer, _decoder, _length)); + _handlers.Add(new XorRegMemHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new TestRegMemHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new TestAlImmHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new TestEaxImmHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new FnstswHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new ConditionalJumpHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new TwoByteConditionalJumpHandler(_codeBuffer, _decoder, _length)); + + // Register group handlers for instructions that share similar decoding logic + _handlers.Add(new Group1Handler(_codeBuffer, _decoder, _length)); + _handlers.Add(new Group3Handler(_codeBuffer, _decoder, _length)); + _handlers.Add(new FloatingPointHandler(_codeBuffer, _decoder, _length)); + _handlers.Add(new DataTransferHandler(_codeBuffer, _decoder, _length)); + } + + /// + /// Gets a handler that can decode the given opcode + /// + /// The opcode to decode + /// A handler that can decode the opcode, or null if no handler is found + public IInstructionHandler? GetHandler(byte opcode) + { + foreach (var handler in _handlers) + { + if (handler.CanHandle(opcode)) + { + return handler; + } + } + + return null; + } +} diff --git a/X86Disassembler/X86/Handlers/JmpRel32Handler.cs b/X86Disassembler/X86/Handlers/JmpRel32Handler.cs new file mode 100644 index 0000000..b41c251 --- /dev/null +++ b/X86Disassembler/X86/Handlers/JmpRel32Handler.cs @@ -0,0 +1,59 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for JMP rel32 instruction (0xE9) +/// +public class JmpRel32Handler : InstructionHandler +{ + /// + /// Initializes a new instance of the JmpRel32Handler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public JmpRel32Handler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xE9; + } + + /// + /// Decodes a JMP rel32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "jmp"; + + int position = Decoder.GetPosition(); + + if (position + 4 > Length) + { + return false; + } + + // Read the relative offset + int offset = BitConverter.ToInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + + // Calculate the target address + uint targetAddress = (uint)(position + offset + 4); + + // Set the operands + instruction.Operands = $"0x{targetAddress:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/JmpRel8Handler.cs b/X86Disassembler/X86/Handlers/JmpRel8Handler.cs new file mode 100644 index 0000000..66f51f6 --- /dev/null +++ b/X86Disassembler/X86/Handlers/JmpRel8Handler.cs @@ -0,0 +1,59 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for JMP rel8 instruction (0xEB) +/// +public class JmpRel8Handler : InstructionHandler +{ + /// + /// Initializes a new instance of the JmpRel8Handler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public JmpRel8Handler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xEB; + } + + /// + /// Decodes a JMP rel8 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "jmp"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the relative offset + sbyte offset = (sbyte)CodeBuffer[position]; + Decoder.SetPosition(position + 1); + + // Calculate the target address + uint targetAddress = (uint)(position + offset + 1); + + // Set the operands + instruction.Operands = $"0x{targetAddress:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/RetHandler.cs b/X86Disassembler/X86/Handlers/RetHandler.cs new file mode 100644 index 0000000..76769ca --- /dev/null +++ b/X86Disassembler/X86/Handlers/RetHandler.cs @@ -0,0 +1,45 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for RET instruction (0xC3) +/// +public class RetHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the RetHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public RetHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xC3; + } + + /// + /// Decodes a RET instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "ret"; + + // No operands for RET + instruction.Operands = string.Empty; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/RetImmHandler.cs b/X86Disassembler/X86/Handlers/RetImmHandler.cs new file mode 100644 index 0000000..d2c6a31 --- /dev/null +++ b/X86Disassembler/X86/Handlers/RetImmHandler.cs @@ -0,0 +1,56 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for RET instruction with immediate operand (0xC2) +/// +public class RetImmHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the RetImmHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public RetImmHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xC2; + } + + /// + /// Decodes a RET instruction with immediate operand + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "ret"; + + int position = Decoder.GetPosition(); + + if (position + 2 > Length) + { + return false; + } + + // Read the immediate value + ushort imm16 = BitConverter.ToUInt16(CodeBuffer, position); + Decoder.SetPosition(position + 2); + + // Set the operands + instruction.Operands = $"0x{imm16:X4}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/TestAlImmHandler.cs b/X86Disassembler/X86/Handlers/TestAlImmHandler.cs new file mode 100644 index 0000000..2ea44cd --- /dev/null +++ b/X86Disassembler/X86/Handlers/TestAlImmHandler.cs @@ -0,0 +1,56 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for TEST AL, imm8 instruction (0xA8) +/// +public class TestAlImmHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the TestAlImmHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public TestAlImmHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xA8; + } + + /// + /// Decodes a TEST AL, imm8 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "test"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the immediate value + byte imm8 = CodeBuffer[position]; + Decoder.SetPosition(position + 1); + + // Set the operands + instruction.Operands = $"al, 0x{imm8:X2}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/TestEaxImmHandler.cs b/X86Disassembler/X86/Handlers/TestEaxImmHandler.cs new file mode 100644 index 0000000..615bb81 --- /dev/null +++ b/X86Disassembler/X86/Handlers/TestEaxImmHandler.cs @@ -0,0 +1,56 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for TEST EAX, imm32 instruction (0xA9) +/// +public class TestEaxImmHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the TestEaxImmHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public TestEaxImmHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0xA9; + } + + /// + /// Decodes a TEST EAX, imm32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "test"; + + int position = Decoder.GetPosition(); + + if (position + 4 > Length) + { + return false; + } + + // Read the immediate value + uint imm32 = BitConverter.ToUInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + + // Set the operands + instruction.Operands = $"eax, 0x{imm32:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/TestRegMemHandler.cs b/X86Disassembler/X86/Handlers/TestRegMemHandler.cs new file mode 100644 index 0000000..96c9eee --- /dev/null +++ b/X86Disassembler/X86/Handlers/TestRegMemHandler.cs @@ -0,0 +1,82 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for TEST r/m32, r32 instruction (0x85) +/// +public class TestRegMemHandler : InstructionHandler +{ + // ModR/M decoder + private readonly ModRMDecoder _modRMDecoder; + + /// + /// Initializes a new instance of the TestRegMemHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public TestRegMemHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + _modRMDecoder = new ModRMDecoder(codeBuffer, decoder, length); + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0x85; + } + + /// + /// Decodes a TEST r/m32, r32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "test"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the ModR/M byte + byte modRM = CodeBuffer[position++]; + Decoder.SetPosition(position); + + // Extract the fields from the ModR/M byte + byte mod = (byte)((modRM & 0xC0) >> 6); + byte reg = (byte)((modRM & 0x38) >> 3); + byte rm = (byte)(modRM & 0x07); + + // Decode the destination operand + string destOperand = _modRMDecoder.DecodeModRM(mod, rm, false); + + // Get the source register + string srcReg = GetRegister32(reg); + + // Set the operands + instruction.Operands = $"{destOperand}, {srcReg}"; + + return true; + } + + /// + /// Gets the 32-bit register name for the given register index + /// + /// The register index + /// The register name + private static string GetRegister32(byte reg) + { + string[] registerNames = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" }; + return registerNames[reg & 0x07]; + } +} diff --git a/X86Disassembler/X86/Handlers/TwoByteConditionalJumpHandler.cs b/X86Disassembler/X86/Handlers/TwoByteConditionalJumpHandler.cs new file mode 100644 index 0000000..9b59ca5 --- /dev/null +++ b/X86Disassembler/X86/Handlers/TwoByteConditionalJumpHandler.cs @@ -0,0 +1,88 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for two-byte conditional jump instructions (0x0F 0x80-0x8F) +/// +public class TwoByteConditionalJumpHandler : InstructionHandler +{ + // Mnemonics for conditional jumps + private static readonly string[] ConditionalJumpMnemonics = new string[] + { + "jo", "jno", "jb", "jnb", "jz", "jnz", "jbe", "jnbe", + "js", "jns", "jp", "jnp", "jl", "jnl", "jle", "jnle" + }; + + /// + /// Initializes a new instance of the TwoByteConditionalJumpHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public TwoByteConditionalJumpHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + // Two-byte conditional jumps start with 0x0F + if (opcode == 0x0F) + { + int position = Decoder.GetPosition(); + if (position < Length) + { + byte secondByte = CodeBuffer[position]; + // Second byte must be in the range 0x80-0x8F + return secondByte >= 0x80 && secondByte <= 0x8F; + } + } + + return false; + } + + /// + /// Decodes a two-byte conditional jump instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the second byte of the opcode + byte secondByte = CodeBuffer[position++]; + Decoder.SetPosition(position); + + // Get the mnemonic from the table + int index = secondByte - 0x80; + instruction.Mnemonic = ConditionalJumpMnemonics[index]; + + if (position + 4 > Length) + { + return false; + } + + // Read the relative offset (32-bit) + int offset = BitConverter.ToInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + + // Calculate the target address + uint targetAddress = (uint)(position + offset + 4); + + // Set the operands + instruction.Operands = $"0x{targetAddress:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/XorAlImmHandler.cs b/X86Disassembler/X86/Handlers/XorAlImmHandler.cs new file mode 100644 index 0000000..ba46ee6 --- /dev/null +++ b/X86Disassembler/X86/Handlers/XorAlImmHandler.cs @@ -0,0 +1,56 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for XOR AL, imm8 instruction (0x34) +/// +public class XorAlImmHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the XorAlImmHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public XorAlImmHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0x34; + } + + /// + /// Decodes a XOR AL, imm8 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "xor"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the immediate value + byte imm8 = CodeBuffer[position]; + Decoder.SetPosition(position + 1); + + // Set the operands + instruction.Operands = $"al, 0x{imm8:X2}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/XorEaxImmHandler.cs b/X86Disassembler/X86/Handlers/XorEaxImmHandler.cs new file mode 100644 index 0000000..78fcae1 --- /dev/null +++ b/X86Disassembler/X86/Handlers/XorEaxImmHandler.cs @@ -0,0 +1,56 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for XOR EAX, imm32 instruction (0x35) +/// +public class XorEaxImmHandler : InstructionHandler +{ + /// + /// Initializes a new instance of the XorEaxImmHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public XorEaxImmHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0x35; + } + + /// + /// Decodes a XOR EAX, imm32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "xor"; + + int position = Decoder.GetPosition(); + + if (position + 4 > Length) + { + return false; + } + + // Read the immediate value + uint imm32 = BitConverter.ToUInt32(CodeBuffer, position); + Decoder.SetPosition(position + 4); + + // Set the operands + instruction.Operands = $"eax, 0x{imm32:X8}"; + + return true; + } +} diff --git a/X86Disassembler/X86/Handlers/XorMemRegHandler.cs b/X86Disassembler/X86/Handlers/XorMemRegHandler.cs new file mode 100644 index 0000000..acefa7c --- /dev/null +++ b/X86Disassembler/X86/Handlers/XorMemRegHandler.cs @@ -0,0 +1,82 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for XOR r/m32, r32 instruction (0x31) +/// +public class XorMemRegHandler : InstructionHandler +{ + // ModR/M decoder + private readonly ModRMDecoder _modRMDecoder; + + /// + /// Initializes a new instance of the XorMemRegHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public XorMemRegHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + _modRMDecoder = new ModRMDecoder(codeBuffer, decoder, length); + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0x31; + } + + /// + /// Decodes an XOR r/m32, r32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "xor"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the ModR/M byte + byte modRM = CodeBuffer[position++]; + Decoder.SetPosition(position); + + // Extract the fields from the ModR/M byte + byte mod = (byte)((modRM & 0xC0) >> 6); + byte reg = (byte)((modRM & 0x38) >> 3); + byte rm = (byte)(modRM & 0x07); + + // Decode the destination operand + string destOperand = _modRMDecoder.DecodeModRM(mod, rm, false); + + // Get the source register + string srcReg = GetRegister32(reg); + + // Set the operands + instruction.Operands = $"{destOperand}, {srcReg}"; + + return true; + } + + /// + /// Gets the 32-bit register name for the given register index + /// + /// The register index + /// The register name + private static string GetRegister32(byte reg) + { + string[] registerNames = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" }; + return registerNames[reg & 0x07]; + } +} diff --git a/X86Disassembler/X86/Handlers/XorRegMemHandler.cs b/X86Disassembler/X86/Handlers/XorRegMemHandler.cs new file mode 100644 index 0000000..21bf4c1 --- /dev/null +++ b/X86Disassembler/X86/Handlers/XorRegMemHandler.cs @@ -0,0 +1,82 @@ +namespace X86Disassembler.X86.Handlers; + +/// +/// Handler for XOR r32, r/m32 instruction (0x33) +/// +public class XorRegMemHandler : InstructionHandler +{ + // ModR/M decoder + private readonly ModRMDecoder _modRMDecoder; + + /// + /// Initializes a new instance of the XorRegMemHandler class + /// + /// The buffer containing the code to decode + /// The instruction decoder that owns this handler + /// The length of the buffer + public XorRegMemHandler(byte[] codeBuffer, InstructionDecoder decoder, int length) + : base(codeBuffer, decoder, length) + { + _modRMDecoder = new ModRMDecoder(codeBuffer, decoder, length); + } + + /// + /// Checks if this handler can decode the given opcode + /// + /// The opcode to check + /// True if this handler can decode the opcode + public override bool CanHandle(byte opcode) + { + return opcode == 0x33; + } + + /// + /// Decodes an XOR r32, r/m32 instruction + /// + /// The opcode of the instruction + /// The instruction object to populate + /// True if the instruction was successfully decoded + public override bool Decode(byte opcode, Instruction instruction) + { + // Set the mnemonic + instruction.Mnemonic = "xor"; + + int position = Decoder.GetPosition(); + + if (position >= Length) + { + return false; + } + + // Read the ModR/M byte + byte modRM = CodeBuffer[position++]; + Decoder.SetPosition(position); + + // Extract the fields from the ModR/M byte + byte mod = (byte)((modRM & 0xC0) >> 6); + byte reg = (byte)((modRM & 0x38) >> 3); + byte rm = (byte)(modRM & 0x07); + + // Decode the source operand + string srcOperand = _modRMDecoder.DecodeModRM(mod, rm, false); + + // Get the destination register + string destReg = GetRegister32(reg); + + // Set the operands + instruction.Operands = $"{destReg}, {srcOperand}"; + + return true; + } + + /// + /// Gets the 32-bit register name for the given register index + /// + /// The register index + /// The register name + private static string GetRegister32(byte reg) + { + string[] registerNames = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" }; + return registerNames[reg & 0x07]; + } +} diff --git a/X86Disassembler/X86/Instruction.cs b/X86Disassembler/X86/Instruction.cs index c744b0d..28c8253 100644 --- a/X86Disassembler/X86/Instruction.cs +++ b/X86Disassembler/X86/Instruction.cs @@ -1,50 +1,56 @@ namespace X86Disassembler.X86; /// -/// Represents a decoded x86 instruction +/// Represents an x86 instruction /// public class Instruction { /// - /// The address of the instruction in memory + /// Gets or sets the address of the instruction /// - public ulong Address { get; set; } + public uint Address { get; set; } /// - /// The raw bytes of the instruction - /// - public byte[] Bytes { get; set; } = Array.Empty(); - - /// - /// The mnemonic of the instruction (e.g., "mov", "add", "jmp") + /// Gets or sets the mnemonic of the instruction /// public string Mnemonic { get; set; } = string.Empty; /// - /// The operands of the instruction as a formatted string + /// Gets or sets the operands of the instruction /// public string Operands { get; set; } = string.Empty; /// - /// The length of the instruction in bytes + /// Gets or sets the raw bytes of the instruction /// - public int Length => Bytes.Length; + public byte[] RawBytes { get; set; } = Array.Empty(); /// /// Returns a string representation of the instruction /// - /// A formatted string representing the instruction + /// A string representation of the instruction public override string ToString() { - return $"{Address:X8} {BytesToString()} {Mnemonic} {Operands}".Trim(); - } - - /// - /// Converts the instruction bytes to a formatted hex string - /// - /// A formatted hex string of the instruction bytes - private string BytesToString() - { - return string.Join(" ", Bytes.Select(b => b.ToString("X2"))); + // Format the address + string addressStr = $"{Address:X8}"; + + // Format the raw bytes + string bytesStr = string.Empty; + foreach (byte b in RawBytes) + { + bytesStr += $"{b:X2} "; + } + + // Pad the bytes string to a fixed width + bytesStr = bytesStr.PadRight(30); + + // Format the instruction + string instructionStr = Mnemonic; + if (!string.IsNullOrEmpty(Operands)) + { + instructionStr += " " + Operands; + } + + return $" {addressStr} {bytesStr}{instructionStr}"; } } diff --git a/X86Disassembler/X86/InstructionDecoder.cs b/X86Disassembler/X86/InstructionDecoder.cs index e91a948..5cba892 100644 --- a/X86Disassembler/X86/InstructionDecoder.cs +++ b/X86Disassembler/X86/InstructionDecoder.cs @@ -3,188 +3,147 @@ namespace X86Disassembler.X86; using X86Disassembler.X86.Handlers; /// -/// Decodes x86 instructions +/// Decodes x86 instructions from a byte buffer /// public class InstructionDecoder { - // Instruction prefix bytes - private const byte PREFIX_LOCK = 0xF0; - private const byte PREFIX_REPNE = 0xF2; - private const byte PREFIX_REP = 0xF3; - private const byte PREFIX_CS = 0x2E; - private const byte PREFIX_SS = 0x36; - private const byte PREFIX_DS = 0x3E; - private const byte PREFIX_ES = 0x26; - private const byte PREFIX_FS = 0x64; - private const byte PREFIX_GS = 0x65; - private const byte PREFIX_OPERAND_SIZE = 0x66; - private const byte PREFIX_ADDRESS_SIZE = 0x67; - - // Buffer containing the code to decode + // The buffer containing the code to decode private readonly byte[] _codeBuffer; - // Current position in the code buffer - private int _position; - - // Length of the buffer + // The length of the buffer private readonly int _length; - // List of instruction handlers - private readonly List _handlers; + // The current position in the buffer + private int _position; + + // The instruction handler factory + private readonly InstructionHandlerFactory _handlerFactory; + + // Instruction prefixes + private bool _operandSizePrefix; + private bool _addressSizePrefix; + private bool _segmentOverridePrefix; + private bool _lockPrefix; + private bool _repPrefix; + private string _segmentOverride = string.Empty; /// /// Initializes a new instance of the InstructionDecoder class /// /// The buffer containing the code to decode - public InstructionDecoder(byte[] codeBuffer) + /// The length of the buffer + public InstructionDecoder(byte[] codeBuffer, int length) { _codeBuffer = codeBuffer; + _length = length; _position = 0; - _length = codeBuffer.Length; + _segmentOverride = string.Empty; - // Initialize the instruction handlers - _handlers = new List - { - new Group1Handler(_codeBuffer, this, _length), - new FloatingPointHandler(_codeBuffer, this, _length), - new DataTransferHandler(_codeBuffer, this, _length), - new ControlFlowHandler(_codeBuffer, this, _length), - new Group3Handler(_codeBuffer, this, _length), - new TestHandler(_codeBuffer, this, _length), - new ArithmeticHandler(_codeBuffer, this, _length) - }; + // Create the instruction handler factory + _handlerFactory = new InstructionHandlerFactory(_codeBuffer, this, _length); } /// - /// Decodes an instruction at the current position in the code buffer + /// Decodes an instruction at the current position /// - /// The instruction object to populate - /// The number of bytes read - public int Decode(Instruction instruction) + /// The decoded instruction, or null if the decoding failed + public Instruction? DecodeInstruction() { - // Store the starting position - int startPosition = _position; - - // Check if we've reached the end of the buffer if (_position >= _length) { - return 0; + return null; } - // Handle instruction prefixes - bool hasPrefix = true; - bool operandSizePrefix = false; - bool addressSizePrefix = false; - string segmentOverride = string.Empty; + // Reset prefix flags + _operandSizePrefix = false; + _addressSizePrefix = false; + _segmentOverridePrefix = false; + _lockPrefix = false; + _repPrefix = false; + _segmentOverride = string.Empty; - while (hasPrefix && _position < _length) + // Save the start position of the instruction + int startPosition = _position; + + // Create a new instruction + Instruction instruction = new Instruction + { + Address = (uint)startPosition, + }; + + // Handle prefixes + while (_position < _length) { byte prefix = _codeBuffer[_position]; - switch (prefix) + if (prefix == 0x66) // Operand size prefix { - case PREFIX_LOCK: - case PREFIX_REPNE: - case PREFIX_REP: - _position++; - break; - - case PREFIX_CS: - segmentOverride = "cs"; - _position++; - break; - - case PREFIX_SS: - segmentOverride = "ss"; - _position++; - break; - - case PREFIX_DS: - segmentOverride = "ds"; - _position++; - break; - - case PREFIX_ES: - segmentOverride = "es"; - _position++; - break; - - case PREFIX_FS: - segmentOverride = "fs"; - _position++; - break; - - case PREFIX_GS: - segmentOverride = "gs"; - _position++; - break; - - case PREFIX_OPERAND_SIZE: - operandSizePrefix = true; - _position++; - break; - - case PREFIX_ADDRESS_SIZE: - addressSizePrefix = true; - _position++; - break; - - default: - hasPrefix = false; - break; + _operandSizePrefix = true; + _position++; + } + else if (prefix == 0x67) // Address size prefix + { + _addressSizePrefix = true; + _position++; + } + else if (prefix >= 0x26 && prefix <= 0x3E && (prefix & 0x7) == 0x6) // Segment override prefix + { + _segmentOverridePrefix = true; + switch (prefix) + { + case 0x26: _segmentOverride = "es"; break; + case 0x2E: _segmentOverride = "cs"; break; + case 0x36: _segmentOverride = "ss"; break; + case 0x3E: _segmentOverride = "ds"; break; + case 0x64: _segmentOverride = "fs"; break; + case 0x65: _segmentOverride = "gs"; break; + } + _position++; + } + else if (prefix == 0xF0) // LOCK prefix + { + _lockPrefix = true; + _position++; + } + else if (prefix == 0xF2 || prefix == 0xF3) // REP/REPNE prefix + { + _repPrefix = true; + _position++; + } + else + { + break; } } - // We've reached the end of the buffer after processing prefixes if (_position >= _length) { - return _position - startPosition; + return null; } // Read the opcode byte opcode = _codeBuffer[_position++]; - // Try to find a handler for this opcode - bool handled = false; - foreach (var handler in _handlers) - { - if (handler.CanHandle(opcode)) - { - handled = handler.Decode(opcode, instruction); - if (handled) - { - break; - } - } - } + // Get a handler for the opcode + var handler = _handlerFactory.GetHandler(opcode); - // If no handler was found or the instruction couldn't be decoded, - // use a default mnemonic from the opcode map - if (!handled) + if (handler == null || !handler.Decode(opcode, instruction)) { + // If no handler is found or decoding fails, create a default instruction instruction.Mnemonic = OpcodeMap.GetMnemonic(opcode); - instruction.Operands = string.Empty; + instruction.Operands = "??"; } - // Copy the instruction bytes - int bytesRead = _position - startPosition; - instruction.Bytes = new byte[bytesRead]; - Array.Copy(_codeBuffer, startPosition, instruction.Bytes, 0, bytesRead); + // Set the raw bytes + int length = _position - startPosition; + instruction.RawBytes = new byte[length]; + Array.Copy(_codeBuffer, startPosition, instruction.RawBytes, 0, length); - return bytesRead; + return instruction; } /// - /// Sets the current position in the code buffer - /// - /// The new position - public void SetPosition(int position) - { - _position = position; - } - - /// - /// Gets the current position in the code buffer + /// Gets the current position in the buffer /// /// The current position public int GetPosition() @@ -193,14 +152,111 @@ public class InstructionDecoder } /// - /// Decodes an instruction at the specified position in the code buffer + /// Sets the current position in the buffer /// - /// The position in the code buffer - /// The instruction object to populate - /// The number of bytes read - public int DecodeAt(int position, Instruction instruction) + /// The new position + public void SetPosition(int position) { _position = position; - return Decode(instruction); + } + + /// + /// Checks if the operand size prefix is present + /// + /// True if the operand size prefix is present + public bool HasOperandSizePrefix() + { + return _operandSizePrefix; + } + + /// + /// Checks if the address size prefix is present + /// + /// True if the address size prefix is present + public bool HasAddressSizePrefix() + { + return _addressSizePrefix; + } + + /// + /// Checks if a segment override prefix is present + /// + /// True if a segment override prefix is present + public bool HasSegmentOverridePrefix() + { + return _segmentOverridePrefix; + } + + /// + /// Gets the segment override prefix + /// + /// The segment override prefix, or an empty string if none is present + public string GetSegmentOverride() + { + return _segmentOverride; + } + + /// + /// Checks if the LOCK prefix is present + /// + /// True if the LOCK prefix is present + public bool HasLockPrefix() + { + return _lockPrefix; + } + + /// + /// Checks if the REP/REPNE prefix is present + /// + /// True if the REP/REPNE prefix is present + public bool HasRepPrefix() + { + return _repPrefix; + } + + /// + /// Reads a byte from the buffer and advances the position + /// + /// The byte read + public byte ReadByte() + { + if (_position >= _length) + { + return 0; + } + + return _codeBuffer[_position++]; + } + + /// + /// Reads a 16-bit value from the buffer and advances the position + /// + /// The 16-bit value read + public ushort ReadUInt16() + { + if (_position + 1 >= _length) + { + return 0; + } + + ushort value = BitConverter.ToUInt16(_codeBuffer, _position); + _position += 2; + return value; + } + + /// + /// Reads a 32-bit value from the buffer and advances the position + /// + /// The 32-bit value read + public uint ReadUInt32() + { + if (_position + 3 >= _length) + { + return 0; + } + + uint value = BitConverter.ToUInt32(_codeBuffer, _position); + _position += 4; + return value; } }