From f5bacc018cbe252ddd0658ef4d46911516c50bf1 Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sat, 12 Apr 2025 16:42:44 +0300 Subject: [PATCH] test --- ParkanPlayground.sln | 13 +- ParkanPlayground.sln.DotSettings.user | 11 + ParkanPlayground/ParkanPlayground.csproj | 4 + ParkanPlayground/Program.cs | 95 ++- Visualisator/Program.cs | 180 +++++ Visualisator/Visualisator.csproj | 18 + X86Disassembler/PEFormat.cs | 834 +++++++++++++++++++++++ X86Disassembler/Program.cs | 153 +++++ X86Disassembler/X86Disassembler.csproj | 10 + 9 files changed, 1313 insertions(+), 5 deletions(-) create mode 100644 ParkanPlayground.sln.DotSettings.user create mode 100644 Visualisator/Program.cs create mode 100644 Visualisator/Visualisator.csproj create mode 100644 X86Disassembler/PEFormat.cs create mode 100644 X86Disassembler/Program.cs create mode 100644 X86Disassembler/X86Disassembler.csproj diff --git a/ParkanPlayground.sln b/ParkanPlayground.sln index c09d3a1..932cc48 100644 --- a/ParkanPlayground.sln +++ b/ParkanPlayground.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}" EndProject @@ -27,6 +26,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrLib", "ScrLib\ScrLib.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VarsetLib", "VarsetLib\VarsetLib.csproj", "{0EC800E2-1444-40D5-9EDD-93276F4D1FF5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Visualisator", "Visualisator\Visualisator.csproj", "{667A7E03-5CAA-4591-9980-F6C722911A35}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X86Disassembler", "X86Disassembler\X86Disassembler.csproj", "{B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,5 +80,13 @@ Global {0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EC800E2-1444-40D5-9EDD-93276F4D1FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {667A7E03-5CAA-4591-9980-F6C722911A35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {667A7E03-5CAA-4591-9980-F6C722911A35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {667A7E03-5CAA-4591-9980-F6C722911A35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {667A7E03-5CAA-4591-9980-F6C722911A35}.Release|Any CPU.Build.0 = Release|Any CPU + {B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5C2E94A-0F63-4E09-BC04-F2518E2CC1F0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ParkanPlayground.sln.DotSettings.user b/ParkanPlayground.sln.DotSettings.user new file mode 100644 index 0000000..780c784 --- /dev/null +++ b/ParkanPlayground.sln.DotSettings.user @@ -0,0 +1,11 @@ + + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/ParkanPlayground/ParkanPlayground.csproj b/ParkanPlayground/ParkanPlayground.csproj index a0a96e4..527979a 100644 --- a/ParkanPlayground/ParkanPlayground.csproj +++ b/ParkanPlayground/ParkanPlayground.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/ParkanPlayground/Program.cs b/ParkanPlayground/Program.cs index 289364b..367ee58 100644 --- a/ParkanPlayground/Program.cs +++ b/ParkanPlayground/Program.cs @@ -1,4 +1,9 @@ -using VarsetLib; +using System.Buffers.Binary; +using System.Numerics; +using System.Text.Json; +using ScrLib; +using SharpDisasm; +using VarsetLib; // var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\default.scr"; @@ -6,7 +11,7 @@ // var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream.scr"; // var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream1.scr"; // var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS"; -var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\varset.var"; +// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\varset.var"; // var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\preload.lda"; // // var fs = new FileStream(path, FileMode.Open); @@ -24,6 +29,88 @@ var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\v // fs.Position == fs.Length // ); -var items = VarsetParser.Parse(path); +// var items = VarsetParser.Parse(path); -Console.WriteLine(items.Count); \ No newline at end of file +// Console.WriteLine(items.Count); + +// Span flt = stackalloc byte[4]; +// flt[0] = 0x7f; +// flt[1] = 0x7f; +// flt[2] = 0xff; +// flt[3] = 0xff; +// var f = BinaryPrimitives.ReadSingleBigEndian(flt); +// +// Console.WriteLine(f); + +// return; + +// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MisLoad.dll"; +var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin"; + +var fs = new FileStream(path, FileMode.Open); +var outputFs = new FileStream("Land.obj", FileMode.Create); +var sw = new StreamWriter(outputFs); + +List points = []; +var count = 0; +while (fs.Position < fs.Length) +{ + var x = fs.ReadFloatLittleEndian(); + var y = fs.ReadFloatLittleEndian(); + var z = fs.ReadFloatLittleEndian(); + + var vertex = new Vector3D(x, y, z); + sw.WriteLine($"v {x} {y} {z}"); + + var seenIndex = points.FindIndex(vec => vec == vertex); + if (seenIndex != -1) + { + vertex.Duplicates = seenIndex; + } + + points.Add(vertex); + count++; +} + +File.WriteAllText("human-readable.json", JsonSerializer.Serialize(points, new JsonSerializerOptions() +{ + WriteIndented = true +})); + +Console.WriteLine($"Total vertices: {count}"); + + +// for (int i = 0; i < count / 4; i++) + +public record Vector3D(float X, float Y, float Z) +{ + public int Duplicates { get; set; } +} +// var indices = string.Join(" ", Enumerable.Range(1, count)); +// +// sw.WriteLine($"l {indices}"); + +// +// fs.Seek(0x1000, SeekOrigin.Begin); +// +// byte[] buf = new byte[34]; +// fs.ReadExactly(buf); +// +// var disassembler = new SharpDisasm.Disassembler(buf, ArchitectureMode.x86_32); +// foreach (var instruction in disassembler.Disassemble()) +// { +// Console.WriteLine($"{instruction.PC - instruction.Offset}: {instruction}"); +// +// new Instruction() +// { +// Action = instruction.Mnemonic.ToString(), +// Arguments = {instruction.Operands[0].ToString()} +// }; +// } + +public class Instruction +{ + public string Action { get; set; } = ""; + + public List Arguments { get; set; } = []; +} diff --git a/Visualisator/Program.cs b/Visualisator/Program.cs new file mode 100644 index 0000000..7933aff --- /dev/null +++ b/Visualisator/Program.cs @@ -0,0 +1,180 @@ +// Configure window options + +using System.Buffers.Binary; +using System.Numerics; +using Silk.NET.OpenGL; +using Silk.NET.Windowing; + +public static class Program +{ + private static string vertexShaderSource = @" + #version 330 core + layout (location = 0) in vec3 aPos; + uniform mat4 uMVP; + + void main() + { + gl_Position = uMVP * vec4(aPos, 1.0); + gl_PointSize = 8.0; + } + "; + + private static string fragmentShaderSource = @" + #version 330 core + out vec4 FragColor; + + void main() + { + FragColor = vec4(1.0, 1.0, 1.0, 1.0); // White points + } + "; + + + private static IWindow? window; + private static GL? gl = null; + + + private static uint shaderProgram = uint.MaxValue; + private static uint vao = uint.MaxValue; + private static uint vbo = uint.MaxValue; + private static Matrix4x4 mvp = new Matrix4x4(); + private static float[] points = []; + + public static void Main(string[] args) + { + var path = "C:\\ParkanUnpacked\\Land.msh\\2_03 00 00 00_Land.bin"; + var bytes = File.ReadAllBytes(path); + + points = new float[bytes.Length / 4]; + for (int i = 0; i < bytes.Length / 4; i++) + { + points[i] = BinaryPrimitives.ReadSingleBigEndian(bytes.AsSpan()[(i * 4)..]); + } + + var options = WindowOptions.Default; + options.API = new GraphicsAPI(ContextAPI.OpenGL, new APIVersion(3, 3)); + options.Title = "3D Points with Silk.NET"; + window = Window.Create(options); + + window.Load += OnLoad; + window.Render += OnRender; + window.Run(); + } + + + unsafe static void OnLoad() + { + gl = window.CreateOpenGL(); + // Compile shaders + uint vertexShader = gl.CreateShader(ShaderType.VertexShader); + gl.ShaderSource(vertexShader, vertexShaderSource); + gl.CompileShader(vertexShader); + CheckShaderCompile(vertexShader); + + uint fragmentShader = gl.CreateShader(ShaderType.FragmentShader); + gl.ShaderSource(fragmentShader, fragmentShaderSource); + gl.CompileShader(fragmentShader); + CheckShaderCompile(fragmentShader); + + // Create shader program + shaderProgram = gl.CreateProgram(); + gl.AttachShader(shaderProgram, vertexShader); + gl.AttachShader(shaderProgram, fragmentShader); + gl.LinkProgram(shaderProgram); + CheckProgramLink(shaderProgram); + + gl.DeleteShader(vertexShader); + gl.DeleteShader(fragmentShader); + + // Create VAO and VBO + vao = gl.GenVertexArray(); + gl.BindVertexArray(vao); + + vbo = gl.GenBuffer(); + gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); + unsafe + { + fixed (float* ptr = points) + { + gl.BufferData( + BufferTargetARB.ArrayBuffer, + (nuint) (points.Length * sizeof(float)), + ptr, + BufferUsageARB.StaticDraw + ); + } + } + + gl.VertexAttribPointer( + 0, + 3, + VertexAttribPointerType.Float, + false, + 3 * sizeof(float), + (void*) 0 + ); + gl.EnableVertexAttribArray(0); + + gl.BindVertexArray(0); // Unbind VAO + + gl.Enable(EnableCap.DepthTest); + } + + unsafe static void OnRender(double dt) + { + gl.ClearColor( + 0.1f, + 0.1f, + 0.1f, + 1.0f + ); + gl.Clear((uint) (ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit)); + + // Set up MVP matrix + Matrix4x4 view = Matrix4x4.CreateLookAt( + new Vector3(100, 100, 40), // Camera position + Vector3.Zero, // Look at origin + Vector3.UnitY + ); // Up direction + Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView( + (float) Math.PI / 4f, // 45 degrees + (float) window.Size.X / window.Size.Y, + 0.1f, + 100f + ); + mvp = view * projection; + + gl.UseProgram(shaderProgram); + + // Set MVP matrix (transpose=true for column-major format) + int mvpLocation = gl.GetUniformLocation(shaderProgram, "uMVP"); + + fixed (Matrix4x4* ptr = &mvp) + { + gl.UniformMatrix4( + mvpLocation, + 1, + true, + (float*) ptr + ); + } + + gl.BindVertexArray(vao); + gl.DrawArrays(PrimitiveType.Points, 0, (uint) (points.Length / 3)); + } + + // Error checking methods + static void CheckShaderCompile(uint shader) + { + gl.GetShader(shader, ShaderParameterName.CompileStatus, out int success); + if (success == 0) + Console.WriteLine(gl.GetShaderInfoLog(shader)); + } + + static void CheckProgramLink(uint program) + { + gl.GetProgram(program, ProgramPropertyARB.LinkStatus, out int success); + if (success == 0) + Console.WriteLine(gl.GetProgramInfoLog(program)); + } +} \ No newline at end of file diff --git a/Visualisator/Visualisator.csproj b/Visualisator/Visualisator.csproj new file mode 100644 index 0000000..b9b125f --- /dev/null +++ b/Visualisator/Visualisator.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + diff --git a/X86Disassembler/PEFormat.cs b/X86Disassembler/PEFormat.cs new file mode 100644 index 0000000..a66ddc2 --- /dev/null +++ b/X86Disassembler/PEFormat.cs @@ -0,0 +1,834 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace X86Disassembler +{ + /// + /// Represents a Portable Executable (PE) file format parser + /// + public class PEFormat + { + // DOS Header constants + private const ushort DOS_SIGNATURE = 0x5A4D; // 'MZ' + private const uint PE_SIGNATURE = 0x00004550; // 'PE\0\0' + + // Optional Header Magic values + private const ushort PE32_MAGIC = 0x10B; // 32-bit executable + private const ushort PE32PLUS_MAGIC = 0x20B; // 64-bit executable + + // Section characteristics flags + private const uint IMAGE_SCN_CNT_CODE = 0x00000020; // Section contains code + private const uint IMAGE_SCN_MEM_EXECUTE = 0x20000000; // Section is executable + private const uint IMAGE_SCN_MEM_READ = 0x40000000; // Section is readable + private const uint IMAGE_SCN_MEM_WRITE = 0x80000000; // Section is writable + + // Data directories + private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0; // Export Directory + private const int IMAGE_DIRECTORY_ENTRY_IMPORT = 1; // Import Directory + private const int IMAGE_DIRECTORY_ENTRY_RESOURCE = 2; // Resource Directory + private const int IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3; // Exception Directory + private const int IMAGE_DIRECTORY_ENTRY_SECURITY = 4; // Security Directory + private const int IMAGE_DIRECTORY_ENTRY_BASERELOC = 5; // Base Relocation Table + private const int IMAGE_DIRECTORY_ENTRY_DEBUG = 6; // Debug Directory + private const int IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7; // Architecture Specific Data + private const int IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8; // RVA of GP + private const int IMAGE_DIRECTORY_ENTRY_TLS = 9; // TLS Directory + private const int IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10; // Load Configuration Directory + private const int IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11; // Bound Import Directory + private const int IMAGE_DIRECTORY_ENTRY_IAT = 12; // Import Address Table + private const int IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13; // Delay Load Import Descriptors + private const int IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14; // COM Runtime descriptor + + // PE file data + private byte[] _fileData; + + // Parsed headers + public DOSHeader DosHeader { get; private set; } + public FileHeader FileHeader { get; private set; } + public OptionalHeader OptionalHeader { get; private set; } + public List SectionHeaders { get; private set; } + public bool Is64Bit { get; private set; } + + // Export and Import information + public ExportDirectory ExportDirectory { get; private set; } + public List ExportedFunctions { get; private set; } + public List ImportDescriptors { get; private set; } + + /// + /// Parses a PE file from the given byte array + /// + /// The raw file data + public PEFormat(byte[] fileData) + { + _fileData = fileData; + SectionHeaders = new List(); + ExportedFunctions = new List(); + ImportDescriptors = new List(); + Parse(); + } + + /// + /// Parses the PE file structure + /// + private void Parse() + { + using (MemoryStream stream = new MemoryStream(_fileData)) + using (BinaryReader reader = new BinaryReader(stream)) + { + // Parse DOS header + DosHeader = ParseDOSHeader(reader); + + // Move to PE header + reader.BaseStream.Seek(DosHeader.e_lfanew, SeekOrigin.Begin); + + // Verify PE signature + uint peSignature = reader.ReadUInt32(); + if (peSignature != PE_SIGNATURE) + { + throw new InvalidDataException("Invalid PE signature"); + } + + // Parse File Header + FileHeader = ParseFileHeader(reader); + + // Parse Optional Header + OptionalHeader = ParseOptionalHeader(reader); + + // Parse Section Headers + for (int i = 0; i < FileHeader.NumberOfSections; i++) + { + SectionHeaders.Add(ParseSectionHeader(reader)); + } + + // Parse Export Directory + if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_EXPORT && + OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0) + { + ExportDirectory = ParseExportDirectory(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + ParseExportedFunctions(reader); + } + + // Parse Import Descriptors + if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_IMPORT && + OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0) + { + ImportDescriptors = ParseImportDescriptors(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + } + } + } + + /// + /// Parses the DOS header + /// + private DOSHeader ParseDOSHeader(BinaryReader reader) + { + DOSHeader header = new DOSHeader(); + + header.e_magic = reader.ReadUInt16(); + if (header.e_magic != DOS_SIGNATURE) + { + throw new InvalidDataException("Invalid DOS signature (MZ)"); + } + + header.e_cblp = reader.ReadUInt16(); + header.e_cp = reader.ReadUInt16(); + header.e_crlc = reader.ReadUInt16(); + header.e_cparhdr = reader.ReadUInt16(); + header.e_minalloc = reader.ReadUInt16(); + header.e_maxalloc = reader.ReadUInt16(); + header.e_ss = reader.ReadUInt16(); + header.e_sp = reader.ReadUInt16(); + header.e_csum = reader.ReadUInt16(); + header.e_ip = reader.ReadUInt16(); + header.e_cs = reader.ReadUInt16(); + header.e_lfarlc = reader.ReadUInt16(); + header.e_ovno = reader.ReadUInt16(); + + header.e_res = new ushort[4]; + for (int i = 0; i < 4; i++) + { + header.e_res[i] = reader.ReadUInt16(); + } + + header.e_oemid = reader.ReadUInt16(); + header.e_oeminfo = reader.ReadUInt16(); + + header.e_res2 = new ushort[10]; + for (int i = 0; i < 10; i++) + { + header.e_res2[i] = reader.ReadUInt16(); + } + + header.e_lfanew = reader.ReadUInt32(); + + return header; + } + + /// + /// Parses the File header + /// + private FileHeader ParseFileHeader(BinaryReader reader) + { + FileHeader header = new FileHeader(); + + header.Machine = reader.ReadUInt16(); + header.NumberOfSections = reader.ReadUInt16(); + header.TimeDateStamp = reader.ReadUInt32(); + header.PointerToSymbolTable = reader.ReadUInt32(); + header.NumberOfSymbols = reader.ReadUInt32(); + header.SizeOfOptionalHeader = reader.ReadUInt16(); + header.Characteristics = reader.ReadUInt16(); + + return header; + } + + /// + /// Parses the Optional header + /// + private OptionalHeader ParseOptionalHeader(BinaryReader reader) + { + OptionalHeader header = new OptionalHeader(); + + // Standard fields + header.Magic = reader.ReadUInt16(); + + // Determine if this is a PE32 or PE32+ file + Is64Bit = header.Magic == PE32PLUS_MAGIC; + + header.MajorLinkerVersion = reader.ReadByte(); + header.MinorLinkerVersion = reader.ReadByte(); + header.SizeOfCode = reader.ReadUInt32(); + header.SizeOfInitializedData = reader.ReadUInt32(); + header.SizeOfUninitializedData = reader.ReadUInt32(); + header.AddressOfEntryPoint = reader.ReadUInt32(); + header.BaseOfCode = reader.ReadUInt32(); + + // PE32 has BaseOfData, PE32+ doesn't + if (!Is64Bit) + { + header.BaseOfData = reader.ReadUInt32(); + } + + // Windows-specific fields + if (Is64Bit) + { + header.ImageBase = reader.ReadUInt64(); + } + else + { + header.ImageBase = reader.ReadUInt32(); + } + + header.SectionAlignment = reader.ReadUInt32(); + header.FileAlignment = reader.ReadUInt32(); + header.MajorOperatingSystemVersion = reader.ReadUInt16(); + header.MinorOperatingSystemVersion = reader.ReadUInt16(); + header.MajorImageVersion = reader.ReadUInt16(); + header.MinorImageVersion = reader.ReadUInt16(); + header.MajorSubsystemVersion = reader.ReadUInt16(); + header.MinorSubsystemVersion = reader.ReadUInt16(); + header.Win32VersionValue = reader.ReadUInt32(); + header.SizeOfImage = reader.ReadUInt32(); + header.SizeOfHeaders = reader.ReadUInt32(); + header.CheckSum = reader.ReadUInt32(); + header.Subsystem = reader.ReadUInt16(); + header.DllCharacteristics = reader.ReadUInt16(); + + // Size fields differ between PE32 and PE32+ + if (Is64Bit) + { + header.SizeOfStackReserve = reader.ReadUInt64(); + header.SizeOfStackCommit = reader.ReadUInt64(); + header.SizeOfHeapReserve = reader.ReadUInt64(); + header.SizeOfHeapCommit = reader.ReadUInt64(); + } + else + { + header.SizeOfStackReserve = reader.ReadUInt32(); + header.SizeOfStackCommit = reader.ReadUInt32(); + header.SizeOfHeapReserve = reader.ReadUInt32(); + header.SizeOfHeapCommit = reader.ReadUInt32(); + } + + header.LoaderFlags = reader.ReadUInt32(); + header.NumberOfRvaAndSizes = reader.ReadUInt32(); + + // Data directories + int numDirectories = (int)Math.Min(header.NumberOfRvaAndSizes, 16); // Maximum of 16 directories + header.DataDirectories = new DataDirectory[numDirectories]; + + for (int i = 0; i < numDirectories; i++) + { + DataDirectory dir = new DataDirectory(); + dir.VirtualAddress = reader.ReadUInt32(); + dir.Size = reader.ReadUInt32(); + header.DataDirectories[i] = dir; + } + + return header; + } + + /// + /// Parses a section header + /// + private SectionHeader ParseSectionHeader(BinaryReader reader) + { + SectionHeader header = new SectionHeader(); + + // Read section name (8 bytes) + byte[] nameBytes = reader.ReadBytes(8); + // Convert to string, removing any null characters + header.Name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0'); + + header.VirtualSize = reader.ReadUInt32(); + header.VirtualAddress = reader.ReadUInt32(); + header.SizeOfRawData = reader.ReadUInt32(); + header.PointerToRawData = reader.ReadUInt32(); + header.PointerToRelocations = reader.ReadUInt32(); + header.PointerToLinenumbers = reader.ReadUInt32(); + header.NumberOfRelocations = reader.ReadUInt16(); + header.NumberOfLinenumbers = reader.ReadUInt16(); + header.Characteristics = reader.ReadUInt32(); + + return header; + } + + /// + /// Parses the Export Directory + /// + private ExportDirectory ParseExportDirectory(BinaryReader reader, uint rva) + { + ExportDirectory directory = new ExportDirectory(); + + reader.BaseStream.Seek(RvaToOffset(rva), SeekOrigin.Begin); + + directory.Characteristics = reader.ReadUInt32(); + directory.TimeDateStamp = reader.ReadUInt32(); + directory.MajorVersion = reader.ReadUInt16(); + directory.MinorVersion = reader.ReadUInt16(); + directory.Name = reader.ReadUInt32(); + directory.Base = reader.ReadUInt32(); + directory.NumberOfFunctions = reader.ReadUInt32(); + directory.NumberOfNames = reader.ReadUInt32(); + directory.AddressOfFunctions = reader.ReadUInt32(); + directory.AddressOfNames = reader.ReadUInt32(); + directory.AddressOfNameOrdinals = reader.ReadUInt32(); + + // Read the DLL name + uint dllNameRVA = directory.Name; + reader.BaseStream.Seek(RvaToOffset(dllNameRVA), SeekOrigin.Begin); + byte[] dllNameBytes = reader.ReadBytes(256); + directory.DllName = Encoding.ASCII.GetString(dllNameBytes).TrimEnd('\0'); + + return directory; + } + + /// + /// Parses the Import Descriptors + /// + private List ParseImportDescriptors(BinaryReader reader, uint rva) + { + List descriptors = new List(); + + try + { + reader.BaseStream.Seek(RvaToOffset(rva), SeekOrigin.Begin); + + while (true) + { + ImportDescriptor descriptor = new ImportDescriptor(); + + descriptor.OriginalFirstThunk = reader.ReadUInt32(); + descriptor.TimeDateStamp = reader.ReadUInt32(); + descriptor.ForwarderChain = reader.ReadUInt32(); + descriptor.Name = reader.ReadUInt32(); + descriptor.FirstThunk = reader.ReadUInt32(); + + // Check if we've reached the end of the import descriptors + if (descriptor.OriginalFirstThunk == 0 && descriptor.Name == 0 && descriptor.FirstThunk == 0) + { + break; + } + + try + { + // Read the DLL name + uint dllNameOffset = RvaToOffset(descriptor.Name); + reader.BaseStream.Seek(dllNameOffset, SeekOrigin.Begin); + + List nameBytes = new List(); + byte b; + while ((b = reader.ReadByte()) != 0) + { + nameBytes.Add(b); + } + descriptor.DllName = Encoding.ASCII.GetString(nameBytes.ToArray()); + + // Read the imported functions (use FirstThunk if OriginalFirstThunk is 0) + uint thunkRVA = descriptor.OriginalFirstThunk != 0 ? descriptor.OriginalFirstThunk : descriptor.FirstThunk; + + if (thunkRVA != 0) + { + try + { + uint thunkOffset = RvaToOffset(thunkRVA); + uint currentThunkOffset = thunkOffset; + + while (true) + { + reader.BaseStream.Seek(currentThunkOffset, SeekOrigin.Begin); + uint thunk = reader.ReadUInt32(); + + if (thunk == 0) + { + break; + } + + ImportedFunction function = new ImportedFunction(); + function.ThunkRVA = thunkRVA + (currentThunkOffset - thunkOffset); + + // Check if the function is imported by ordinal + if ((thunk & 0x80000000) != 0) + { + function.IsOrdinal = true; + function.Ordinal = (ushort)(thunk & 0xFFFF); + function.Name = $"Ordinal_{function.Ordinal}"; + } + else + { + // Read the function name and hint + try + { + uint nameOffset = RvaToOffset(thunk); + reader.BaseStream.Seek(nameOffset, SeekOrigin.Begin); + + function.Hint = reader.ReadUInt16(); + + List funcNameBytes = new List(); + byte c; + while ((c = reader.ReadByte()) != 0) + { + funcNameBytes.Add(c); + } + function.Name = Encoding.ASCII.GetString(funcNameBytes.ToArray()); + } + catch (Exception) + { + function.Name = $"Function_at_{thunk:X8}"; + } + } + + descriptor.Functions.Add(function); + + currentThunkOffset += 4; // Move to the next thunk + } + } + catch (Exception) + { + // Skip this thunk table if there's an error + } + } + } + catch (Exception) + { + // If we can't read the DLL name, use a placeholder + descriptor.DllName = $"DLL_at_{descriptor.Name:X8}"; + } + + descriptors.Add(descriptor); + } + } + catch (Exception) + { + // Return whatever descriptors we've managed to parse + } + + return descriptors; + } + + /// + /// Parses the exported functions using the export directory information + /// + private void ParseExportedFunctions(BinaryReader reader) + { + if (ExportDirectory == null) + { + return; + } + + // Read the array of function addresses (RVAs) + uint[] functionRVAs = new uint[ExportDirectory.NumberOfFunctions]; + reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfFunctions), SeekOrigin.Begin); + for (int i = 0; i < ExportDirectory.NumberOfFunctions; i++) + { + functionRVAs[i] = reader.ReadUInt32(); + } + + // Read the array of name RVAs + uint[] nameRVAs = new uint[ExportDirectory.NumberOfNames]; + reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNames), SeekOrigin.Begin); + for (int i = 0; i < ExportDirectory.NumberOfNames; i++) + { + nameRVAs[i] = reader.ReadUInt32(); + } + + // Read the array of name ordinals + ushort[] nameOrdinals = new ushort[ExportDirectory.NumberOfNames]; + reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNameOrdinals), SeekOrigin.Begin); + for (int i = 0; i < ExportDirectory.NumberOfNames; i++) + { + nameOrdinals[i] = reader.ReadUInt16(); + } + + // Create a dictionary to map ordinals to names + Dictionary ordinalToName = new Dictionary(); + for (int i = 0; i < ExportDirectory.NumberOfNames; i++) + { + // Read the function name + reader.BaseStream.Seek(RvaToOffset(nameRVAs[i]), SeekOrigin.Begin); + List nameBytes = new List(); + byte b; + while ((b = reader.ReadByte()) != 0) + { + nameBytes.Add(b); + } + string name = Encoding.ASCII.GetString(nameBytes.ToArray()); + + // Map the ordinal to the name + ordinalToName[nameOrdinals[i]] = name; + } + + // Create the exported functions + for (ushort i = 0; i < ExportDirectory.NumberOfFunctions; i++) + { + uint functionRVA = functionRVAs[i]; + if (functionRVA == 0) + { + continue; // Skip empty entries + } + + ExportedFunction function = new ExportedFunction(); + function.Ordinal = (ushort)(i + ExportDirectory.Base); + function.Address = functionRVA; + + // Check if this function has a name + if (ordinalToName.TryGetValue(i, out string name)) + { + function.Name = name; + } + else + { + function.Name = $"Ordinal_{function.Ordinal}"; + } + + // Check if this is a forwarder + uint exportDirStart = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + uint exportDirEnd = exportDirStart + OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + if (functionRVA >= exportDirStart && functionRVA < exportDirEnd) + { + function.IsForwarder = true; + + // Read the forwarder string + reader.BaseStream.Seek(RvaToOffset(functionRVA), SeekOrigin.Begin); + List forwarderBytes = new List(); + byte b; + while ((b = reader.ReadByte()) != 0) + { + forwarderBytes.Add(b); + } + function.ForwarderName = Encoding.ASCII.GetString(forwarderBytes.ToArray()); + } + + ExportedFunctions.Add(function); + } + } + + /// + /// Gets the raw data for a specific section + /// + /// Index of the section + /// Byte array containing the section data + public byte[] GetSectionData(int sectionIndex) + { + if (sectionIndex < 0 || sectionIndex >= SectionHeaders.Count) + { + throw new ArgumentOutOfRangeException(nameof(sectionIndex)); + } + + SectionHeader section = SectionHeaders[sectionIndex]; + byte[] sectionData = new byte[section.SizeOfRawData]; + + Array.Copy(_fileData, section.PointerToRawData, sectionData, 0, section.SizeOfRawData); + + return sectionData; + } + + /// + /// Gets the raw data for a section by name + /// + /// Name of the section + /// Byte array containing the section data + public byte[] GetSectionData(string sectionName) + { + for (int i = 0; i < SectionHeaders.Count; i++) + { + if (SectionHeaders[i].Name == sectionName) + { + return GetSectionData(i); + } + } + + throw new ArgumentException($"Section '{sectionName}' not found"); + } + + /// + /// Checks if a section contains code + /// + /// The section to check + /// True if the section contains code, false otherwise + public bool IsSectionContainsCode(SectionHeader section) + { + return (section.Characteristics & IMAGE_SCN_CNT_CODE) != 0 || + (section.Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + } + + /// + /// Gets all code sections + /// + /// List of section indices that contain code + public List GetCodeSections() + { + List codeSections = new List(); + + for (int i = 0; i < SectionHeaders.Count; i++) + { + if (IsSectionContainsCode(SectionHeaders[i])) + { + codeSections.Add(i); + } + } + + return codeSections; + } + + /// + /// Converts a Relative Virtual Address (RVA) to a file offset + /// + /// The RVA to convert + /// The corresponding file offset + public uint RvaToOffset(uint rva) + { + if (rva == 0) + { + return 0; + } + + foreach (var section in SectionHeaders) + { + // Check if the RVA is within this section + if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) + { + // Calculate the offset within the section + uint offsetInSection = rva - section.VirtualAddress; + + // Make sure we don't exceed the raw data size + if (offsetInSection < section.SizeOfRawData) + { + return section.PointerToRawData + offsetInSection; + } + } + } + + // If the RVA is not within any section, it might be in the headers + if (rva < OptionalHeader.SizeOfHeaders) + { + return rva; + } + + throw new ArgumentException($"RVA {rva:X8} is not within any section"); + } + } + + #region PE Format Structures + + /// + /// DOS Header structure + /// + public class DOSHeader + { + public ushort e_magic; // Magic number ("MZ") + public ushort e_cblp; // Bytes on last page of file + public ushort e_cp; // Pages in file + public ushort e_crlc; // Relocations + public ushort e_cparhdr; // Size of header in paragraphs + public ushort e_minalloc; // Minimum extra paragraphs needed + public ushort e_maxalloc; // Maximum extra paragraphs needed + public ushort e_ss; // Initial (relative) SS value + public ushort e_sp; // Initial SP value + public ushort e_csum; // Checksum + public ushort e_ip; // Initial IP value + public ushort e_cs; // Initial (relative) CS value + public ushort e_lfarlc; // File address of relocation table + public ushort e_ovno; // Overlay number + public ushort[] e_res; // Reserved words + public ushort e_oemid; // OEM identifier + public ushort e_oeminfo; // OEM information + public ushort[] e_res2; // Reserved words + public uint e_lfanew; // File address of new exe header + } + + /// + /// File Header structure + /// + public class FileHeader + { + public ushort Machine; // Target machine type + public ushort NumberOfSections; // Number of sections + public uint TimeDateStamp; // Time stamp + public uint PointerToSymbolTable; // File offset of symbol table + public uint NumberOfSymbols; // Number of symbols + public ushort SizeOfOptionalHeader; // Size of optional header + public ushort Characteristics; // Characteristics + } + + /// + /// Optional Header structure + /// + public class OptionalHeader + { + // Standard fields + public ushort Magic; // Magic number (PE32 or PE32+) + public byte MajorLinkerVersion; // Major linker version + public byte MinorLinkerVersion; // Minor linker version + public uint SizeOfCode; // Size of code section + public uint SizeOfInitializedData; // Size of initialized data + public uint SizeOfUninitializedData; // Size of uninitialized data + public uint AddressOfEntryPoint; // Entry point RVA + public uint BaseOfCode; // Base of code section + public uint BaseOfData; // Base of data section (PE32 only) + + // Windows-specific fields + public dynamic ImageBase; // Preferred image base (uint for PE32, ulong for PE32+) + public uint SectionAlignment; // Section alignment + public uint FileAlignment; // File alignment + public ushort MajorOperatingSystemVersion; // Major OS version + public ushort MinorOperatingSystemVersion; // Minor OS version + public ushort MajorImageVersion; // Major image version + public ushort MinorImageVersion; // Minor image version + public ushort MajorSubsystemVersion; // Major subsystem version + public ushort MinorSubsystemVersion; // Minor subsystem version + public uint Win32VersionValue; // Win32 version value + public uint SizeOfImage; // Size of image + public uint SizeOfHeaders; // Size of headers + public uint CheckSum; // Checksum + public ushort Subsystem; // Subsystem + public ushort DllCharacteristics; // DLL characteristics + public dynamic SizeOfStackReserve; // Size of stack reserve (uint for PE32, ulong for PE32+) + public dynamic SizeOfStackCommit; // Size of stack commit (uint for PE32, ulong for PE32+) + public dynamic SizeOfHeapReserve; // Size of heap reserve (uint for PE32, ulong for PE32+) + public dynamic SizeOfHeapCommit; // Size of heap commit (uint for PE32, ulong for PE32+) + public uint LoaderFlags; // Loader flags + public uint NumberOfRvaAndSizes; // Number of data directories + + // Data directories + public DataDirectory[] DataDirectories; // Data directories + } + + /// + /// Data Directory structure + /// + public class DataDirectory + { + public uint VirtualAddress; // RVA of the directory + public uint Size; // Size of the directory + } + + /// + /// Section Header structure + /// + public class SectionHeader + { + public string Name; // Section name + public uint VirtualSize; // Virtual size + public uint VirtualAddress; // Virtual address (RVA) + public uint SizeOfRawData; // Size of raw data + public uint PointerToRawData; // File pointer to raw data + public uint PointerToRelocations; // File pointer to relocations + public uint PointerToLinenumbers; // File pointer to line numbers + public ushort NumberOfRelocations; // Number of relocations + public ushort NumberOfLinenumbers; // Number of line numbers + public uint Characteristics; // Characteristics + } + + #endregion + + #region Export and Import Structures + + /// + /// Export Directory structure + /// + public class ExportDirectory + { + public uint Characteristics; + public uint TimeDateStamp; + public ushort MajorVersion; + public ushort MinorVersion; + public uint Name; // RVA to the DLL name + public string DllName; // Actual DLL name + public uint Base; // Ordinal base + public uint NumberOfFunctions; // Number of exported functions + public uint NumberOfNames; // Number of exported names + public uint AddressOfFunctions; // RVA to function addresses + public uint AddressOfNames; // RVA to function names + public uint AddressOfNameOrdinals; // RVA to ordinals + } + + /// + /// Represents an exported function + /// + public class ExportedFunction + { + public string Name; // Function name + public uint Address; // Function RVA + public ushort Ordinal; // Function ordinal + public bool IsForwarder; // True if this is a forwarder + public string ForwarderName; // Name of the forwarded function (if IsForwarder is true) + } + + /// + /// Import Descriptor structure + /// + public class ImportDescriptor + { + public uint OriginalFirstThunk; // RVA to Import Lookup Table + public uint TimeDateStamp; + public uint ForwarderChain; + public uint Name; // RVA to the DLL name + public string DllName; // Actual DLL name + public uint FirstThunk; // RVA to Import Address Table + public List Functions; // List of imported functions + + public ImportDescriptor() + { + Functions = new List(); + } + } + + /// + /// Represents an imported function + /// + public class ImportedFunction + { + public bool IsOrdinal; // True if imported by ordinal + public ushort Ordinal; // Ordinal value (if IsOrdinal is true) + public string Name; // Function name (if IsOrdinal is false) + public ushort Hint; // Hint value (if IsOrdinal is false) + public uint ThunkRVA; // RVA in the Import Address Table + } + + #endregion +} diff --git a/X86Disassembler/Program.cs b/X86Disassembler/Program.cs new file mode 100644 index 0000000..752401a --- /dev/null +++ b/X86Disassembler/Program.cs @@ -0,0 +1,153 @@ +using System; +using System.IO; + +namespace X86Disassembler +{ + internal class Program + { + // Path to the DLL file to disassemble + private const string DllPath = @"C:\Windows\SysWOW64\msvcrt.dll"; // Example path, replace with your target DLL + + static void Main(string[] args) + { + Console.WriteLine("X86 Disassembler and Decompiler"); + Console.WriteLine("--------------------------------"); + + Console.WriteLine($"Loading file: {DllPath}"); + + // Load the DLL file + byte[] binaryData = File.ReadAllBytes(DllPath); + + Console.WriteLine($"Successfully loaded {DllPath}"); + Console.WriteLine($"File size: {binaryData.Length} bytes"); + + // Parse the PE format + Console.WriteLine("\nParsing PE format..."); + PEFormat peFile = new PEFormat(binaryData); + + // Display basic PE information + DisplayPEInfo(peFile); + + // Display exported functions + DisplayExportedFunctions(peFile); + + // Display imported functions + DisplayImportedFunctions(peFile); + + // Find code sections for disassembly + var codeSections = peFile.GetCodeSections(); + Console.WriteLine($"\nFound {codeSections.Count} code section(s):"); + + foreach (int sectionIndex in codeSections) + { + var section = peFile.SectionHeaders[sectionIndex]; + Console.WriteLine($" - {section.Name}: Size={section.SizeOfRawData} bytes, RVA=0x{section.VirtualAddress:X8}"); + + // Get the section data for disassembly + byte[] sectionData = peFile.GetSectionData(sectionIndex); + + // TODO: Implement disassembling logic here + // This is where we would pass the section data to our disassembler + } + + Console.WriteLine("\nPress any key to exit..."); + Console.ReadKey(); + } + + private static void DisplayPEInfo(PEFormat peFile) + { + Console.WriteLine("\nPE File Information:"); + Console.WriteLine($"Architecture: {(peFile.Is64Bit ? "64-bit" : "32-bit")}"); + Console.WriteLine($"Entry Point: 0x{peFile.OptionalHeader.AddressOfEntryPoint:X8}"); + Console.WriteLine($"Image Base: 0x{peFile.OptionalHeader.ImageBase:X}"); + Console.WriteLine($"Number of Sections: {peFile.FileHeader.NumberOfSections}"); + + // Display section information + Console.WriteLine("\nSections:"); + for (int i = 0; i < peFile.SectionHeaders.Count; i++) + { + var section = peFile.SectionHeaders[i]; + string flags = ""; + + if ((section.Characteristics & 0x00000020) != 0) flags += "Code "; // IMAGE_SCN_CNT_CODE + if ((section.Characteristics & 0x20000000) != 0) flags += "Exec "; // IMAGE_SCN_MEM_EXECUTE + if ((section.Characteristics & 0x40000000) != 0) flags += "Read "; // IMAGE_SCN_MEM_READ + if ((section.Characteristics & 0x80000000) != 0) flags += "Write"; // IMAGE_SCN_MEM_WRITE + + Console.WriteLine($" {i}: {section.Name,-8} VA=0x{section.VirtualAddress:X8} Size={section.SizeOfRawData,-8} [{flags}]"); + } + } + + private static void DisplayExportedFunctions(PEFormat peFile) + { + if (peFile.ExportDirectory == null) + { + Console.WriteLine("\nNo exported functions found."); + return; + } + + Console.WriteLine("\nExported Functions:"); + Console.WriteLine($"DLL Name: {peFile.ExportDirectory.DllName}"); + Console.WriteLine($"Number of Functions: {peFile.ExportDirectory.NumberOfFunctions}"); + Console.WriteLine($"Number of Names: {peFile.ExportDirectory.NumberOfNames}"); + + // Display the first 10 exported functions (if any) + int count = Math.Min(10, peFile.ExportedFunctions.Count); + for (int i = 0; i < count; i++) + { + var function = peFile.ExportedFunctions[i]; + Console.WriteLine($" {i}: {function.Name} (Ordinal={function.Ordinal}, RVA=0x{function.Address:X8})"); + } + + if (peFile.ExportedFunctions.Count > 10) + { + Console.WriteLine($" ... and {peFile.ExportedFunctions.Count - 10} more"); + } + } + + private static void DisplayImportedFunctions(PEFormat peFile) + { + if (peFile.ImportDescriptors.Count == 0) + { + Console.WriteLine("\nNo imported functions found."); + return; + } + + Console.WriteLine("\nImported Functions:"); + Console.WriteLine($"Number of Imported DLLs: {peFile.ImportDescriptors.Count}"); + + // Display the first 5 imported DLLs and their functions + int dllCount = Math.Min(5, peFile.ImportDescriptors.Count); + for (int i = 0; i < dllCount; i++) + { + var descriptor = peFile.ImportDescriptors[i]; + Console.WriteLine($" DLL: {descriptor.DllName}"); + + // Display the first 5 functions from this DLL + int funcCount = Math.Min(5, descriptor.Functions.Count); + for (int j = 0; j < funcCount; j++) + { + var function = descriptor.Functions[j]; + if (function.IsOrdinal) + { + Console.WriteLine($" {j}: Ordinal {function.Ordinal}"); + } + else + { + Console.WriteLine($" {j}: {function.Name} (Hint={function.Hint})"); + } + } + + if (descriptor.Functions.Count > 5) + { + Console.WriteLine($" ... and {descriptor.Functions.Count - 5} more"); + } + } + + if (peFile.ImportDescriptors.Count > 5) + { + Console.WriteLine($" ... and {peFile.ImportDescriptors.Count - 5} more DLLs"); + } + } + } +} diff --git a/X86Disassembler/X86Disassembler.csproj b/X86Disassembler/X86Disassembler.csproj new file mode 100644 index 0000000..91b464a --- /dev/null +++ b/X86Disassembler/X86Disassembler.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + +