diff --git a/X86Emulator/EmulatorException.cs b/X86Emulator/EmulatorException.cs new file mode 100644 index 0000000..15032e5 --- /dev/null +++ b/X86Emulator/EmulatorException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator +{ + internal class EmulatorException : Exception + { + public EmulatorException() + { + } + + public EmulatorException(string message) + : base(message) + { + } + + public EmulatorException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/X86Emulator/Instructions/Add.cs b/X86Emulator/Instructions/Add.cs new file mode 100644 index 0000000..a32292b --- /dev/null +++ b/X86Emulator/Instructions/Add.cs @@ -0,0 +1,24 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class Add : Instruction + { + private Operand[] Operands; + + public Add(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int src = ReadOperand(Operands[1], registers); + int dst = ReadOperand(Operands[0], registers); + + int result = dst + src; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Instructions/IMul.cs b/X86Emulator/Instructions/IMul.cs new file mode 100644 index 0000000..2b2e041 --- /dev/null +++ b/X86Emulator/Instructions/IMul.cs @@ -0,0 +1,45 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class IMul : Instruction + { + private Operand[] Operands; + + public IMul(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + if (Operands.Length == 1) + { + long eax = registers.GetValue(Registers.Register.EAX); + long val = ReadOperand(Operands[0], registers); + + long result = eax * val; + + registers.SetValue(SharpDisasm.Udis86.ud_type.UD_R_EDX, (int)(result >> 32)); + registers.SetValue(SharpDisasm.Udis86.ud_type.UD_R_EAX, (int)result); + } + else if (Operands.Length == 2) + { + int dst = registers.GetValue(Operands[0].Base); + int src = ReadOperand(Operands[1], registers); + + int result = dst * src; + registers.SetValue(Operands[0].Base, result); + } + else + { + int val0 = ReadOperand(Operands[1], registers); + int val1 = ReadOperand(Operands[2], registers); + + int result = val0 * val1; + registers.SetValue(Operands[0].Base, result); + } + } + } +} diff --git a/X86Emulator/Instructions/Instruction.cs b/X86Emulator/Instructions/Instruction.cs new file mode 100644 index 0000000..dd27a21 --- /dev/null +++ b/X86Emulator/Instructions/Instruction.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using SharpDisasm; +using SharpDisasm.Udis86; + +namespace X86Emulator.Instructions +{ + internal abstract class Instruction + { + public abstract void Emulate(Stack stack, Registers registers); + + protected static int ReadOperand(Operand operand, Registers registers) + { + return operand.Type == ud_type.UD_OP_REG + ? registers.GetValue(operand.Base) + : operand.LvalSDWord; + } + } +} diff --git a/X86Emulator/Instructions/Mov.cs b/X86Emulator/Instructions/Mov.cs new file mode 100644 index 0000000..67af326 --- /dev/null +++ b/X86Emulator/Instructions/Mov.cs @@ -0,0 +1,23 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class Mov : Instruction + { + private Operand[] Operands; + + public Mov(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + Operand to = Operands[0]; + int val = ReadOperand(Operands[1], registers); + + registers.SetValue(to.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Neg.cs b/X86Emulator/Instructions/Neg.cs new file mode 100644 index 0000000..fef13cf --- /dev/null +++ b/X86Emulator/Instructions/Neg.cs @@ -0,0 +1,28 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Neg : Instruction + { + private Operand Operand; + + public Neg(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = registers.GetValue(Operand.Base); + + val = -val; + + registers.SetValue(Operand.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Not.cs b/X86Emulator/Instructions/Not.cs new file mode 100644 index 0000000..ea69155 --- /dev/null +++ b/X86Emulator/Instructions/Not.cs @@ -0,0 +1,28 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Not : Instruction + { + private Operand Operand; + + public Not(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = registers.GetValue(Operand.Base); + + val = ~val; + + registers.SetValue(Operand.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Pop.cs b/X86Emulator/Instructions/Pop.cs new file mode 100644 index 0000000..c9ac39e --- /dev/null +++ b/X86Emulator/Instructions/Pop.cs @@ -0,0 +1,29 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Pop : Instruction + { + private Operand? Operand = null; + + public Pop(Operand? operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = stack.Pop(); + + if (Operand != null) + { + registers.SetValue(Operand.Base, val); + } + } + } +} diff --git a/X86Emulator/Instructions/Push.cs b/X86Emulator/Instructions/Push.cs new file mode 100644 index 0000000..8d90c2d --- /dev/null +++ b/X86Emulator/Instructions/Push.cs @@ -0,0 +1,20 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class Push : Instruction + { + private Operand Operand; + + public Push(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + stack.Push(ReadOperand(Operand, registers)); + } + } +} diff --git a/X86Emulator/Instructions/Sub.cs b/X86Emulator/Instructions/Sub.cs new file mode 100644 index 0000000..299d654 --- /dev/null +++ b/X86Emulator/Instructions/Sub.cs @@ -0,0 +1,24 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class Sub : Instruction + { + private Operand[] Operands; + + public Sub(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int src = ReadOperand(Operands[1], registers); + int dst = ReadOperand(Operands[0], registers); + + int result = dst - src; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Instructions/Xor.cs b/X86Emulator/Instructions/Xor.cs new file mode 100644 index 0000000..1976eaf --- /dev/null +++ b/X86Emulator/Instructions/Xor.cs @@ -0,0 +1,24 @@ +using SharpDisasm; +using System.Collections.Generic; + +namespace X86Emulator.Instructions +{ + internal class Xor : Instruction + { + private Operand[] Operands; + + public Xor(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int dst = registers.GetValue(Operands[0].Base); + int src = ReadOperand(Operands[1], registers); + + int result = src ^ dst; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Registers.cs b/X86Emulator/Registers.cs new file mode 100644 index 0000000..d7d6f8e --- /dev/null +++ b/X86Emulator/Registers.cs @@ -0,0 +1,83 @@ +using SharpDisasm.Udis86; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator +{ + internal class Registers + { + public int[] Values = new int[(int)Register.MAX]; + + + public int GetValue(Register register) + { + return Values[(int)register]; + } + + public int GetValue(ud_type register) + { + return register switch + { + ud_type.UD_R_EAX => Values[(int)Register.EAX], + ud_type.UD_R_ECX => Values[(int)Register.ECX], + ud_type.UD_R_EDX => Values[(int)Register.EDX], + ud_type.UD_R_EBX => Values[(int)Register.EBX], + ud_type.UD_R_ESP => Values[(int)Register.ESP], + ud_type.UD_R_EBP => Values[(int)Register.EBP], + ud_type.UD_R_ESI => Values[(int)Register.ESI], + ud_type.UD_R_EDI => Values[(int)Register.EDI], + _ => throw new EmulatorException($"Unhandled register type {register}"), + }; + } + + public void SetValue(ud_type register, int value) + { + switch (register) + { + case ud_type.UD_R_EAX: + Values[(int)Register.EAX] = value; + break; + case ud_type.UD_R_ECX: + Values[(int)Register.ECX] = value; + break; + case ud_type.UD_R_EDX: + Values[(int)Register.EDX] = value; + break; + case ud_type.UD_R_EBX: + Values[(int)Register.EBX] = value; + break; + case ud_type.UD_R_ESP: + Values[(int)Register.ESP] = value; + break; + case ud_type.UD_R_EBP: + Values[(int)Register.EBP] = value; + break; + case ud_type.UD_R_ESI: + Values[(int)Register.ESI] = value; + break; + case ud_type.UD_R_EDI: + Values[(int)Register.EDI] = value; + break; + default: + throw new EmulatorException($"Unhandled register type {register}"); + }; + } + + public enum Register + { + EAX, + ECX, + EDX, + EBX, + ESP, + EBP, + ESI, + EDI, + + MAX + } + } +} diff --git a/X86Emulator/X86Emulator.csproj b/X86Emulator/X86Emulator.csproj new file mode 100644 index 0000000..e28e33b --- /dev/null +++ b/X86Emulator/X86Emulator.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + + + + + + + + diff --git a/X86Emulator/X86Method.cs b/X86Emulator/X86Method.cs new file mode 100644 index 0000000..d137309 --- /dev/null +++ b/X86Emulator/X86Method.cs @@ -0,0 +1,133 @@ +using dnlib.DotNet; +using dnlib.PE; +using System.Collections.Generic; +using X86Emulator.Instructions; +using System.Linq; +using SharpDisasm.Udis86; + +namespace X86Emulator +{ + public class X86Method + { + private ModuleDefMD Module; + private MethodDef Method; + private List Instructions = new(); + private int NumArgs = 0; + + public X86Method(ModuleDefMD module, MethodDef method) + { + Module = module; + Method = method; + + Initialize(); + } + + public int Emulate(int[] args) + { + if (args.Length != NumArgs) + { + throw new EmulatorException($"Incorrect number of args passed to method. Expected {NumArgs} got {args.Length}"); + } + + var stack = new Stack(); + var registers = new Registers(); + + if (args.Length == 1) + { + registers.SetValue(ud_type.UD_R_ECX, args[0]); + stack.Push(args[0]); + } + else if (args.Length > 1) + { + registers.SetValue(ud_type.UD_R_ECX, args[0]); + registers.SetValue(ud_type.UD_R_EDX, args[1]); + foreach (var arg in args.Skip(2)) + { + stack.Push(arg); + } + } + + foreach (var instr in Instructions) + { + instr.Emulate(stack, registers); + } + + return registers.GetValue(Registers.Register.EAX); + } + + private void Initialize() + { + NumArgs = Method.Parameters.Count; + var body = ReadNativeBody(); + + var disassembler = new SharpDisasm.Disassembler(body, SharpDisasm.ArchitectureMode.x86_32); + var instructions = disassembler.Disassemble(); + + foreach (var instr in instructions) + { + if (instr.Mnemonic == ud_mnemonic_code.UD_Iret) + { + // Need to remove the final pops before a ret + Instructions.RemoveRange(Instructions.Count - 3, 3); + break; + } + + switch (instr.Mnemonic) + { + case ud_mnemonic_code.UD_Ipush: + Instructions.Add(new Push(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Ipop: + Instructions.Add(new Pop(instr.Operands.Length == 0 ? null : instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Imov: + Instructions.Add(new Mov(instr.Operands)); + break; + case ud_mnemonic_code.UD_Inot: + Instructions.Add(new Not(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Iimul: + Instructions.Add(new IMul(instr.Operands)); + break; + case ud_mnemonic_code.UD_Ineg: + Instructions.Add(new Neg(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Ixor: + Instructions.Add(new Xor(instr.Operands)); + break; + case ud_mnemonic_code.UD_Isub: + Instructions.Add(new Sub(instr.Operands)); + break; + case ud_mnemonic_code.UD_Iadd: + Instructions.Add(new Add(instr.Operands)); + break; + + + case ud_mnemonic_code.UD_Inop: + // no-op ... + break; + + + default: + throw new EmulatorException($"Unhandled instruction type {instr.Mnemonic}"); + } + } + + } + + private byte[] ReadNativeBody() + { + var reader = Module.Metadata.PEImage.CreateReader(); + var fileOffset = Module.Metadata.PEImage.ToFileOffset(Method.NativeBody.RVA) + 0x14; + reader.Position = (uint)fileOffset; + + Module.TablesStream.TryReadMethodRow(Method.Rid + 1, out var nextMethod); + var size = Module.Metadata.PEImage.ToFileOffset((RVA)nextMethod.RVA) - fileOffset; + var bytes = ((nextMethod.ImplFlags & (ushort)MethodImplAttributes.Native) != 0) ? new byte[size] : new byte[2048]; + + + reader.ReadBytes(bytes, 0, bytes.Length); + return bytes; + } + } +} \ No newline at end of file