Esempio n. 1
0
		void CreateTest(int bitness, string hexBytes, Func<Instruction> create) {
			var bytes = HexUtils.ToByteArray(hexBytes);
			var decoder = Decoder.Create(bitness, new ByteArrayCodeReader(bytes));
			switch (bitness) {
			case 16: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP16; break;
			case 32: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP32; break;
			case 64: decoder.InstructionPointer = DecoderConstants.DEFAULT_IP64; break;
			default: throw new InvalidOperationException();
			}
			var origRip = decoder.InstructionPointer;
			decoder.Decode(out var decodedInstr);
			decodedInstr.CodeSize = 0;
			decodedInstr.ByteLength = 0;
			decodedInstr.NextIP64 = 0;

			var createdInstr = create();
			Assert.True(Instruction.TEST_BitByBitEquals(ref decodedInstr, ref createdInstr));

			var writer = new CodeWriterImpl();
			var encoder = decoder.CreateEncoder(writer);
			bool result = encoder.TryEncode(ref createdInstr, origRip, out _, out var errorMessage);
			Assert.Null(errorMessage);
			Assert.True(result);
			Assert.Equal(bytes, writer.ToArray());
		}
        public static IntPtr RunRemoteCode(IntPtr hProc, IReadOnlyList <Instruction> instructions, bool x86)
        {
            var cw = new CodeWriterImpl();
            var ib = new InstructionBlock(cw, new List <Instruction>(instructions), 0);

            if (!BlockEncoder.TryEncode(x86 ? 32 : 64, ib, out string?errMsg, out _))
            {
                throw new Exception("Error during Iced encode: " + errMsg);
            }
            byte[] bytes = cw.ToArray();

            var ptrStub = Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint)bytes.Length, 0x1000, 0x40);

            Native.WriteProcessMemory(hProc, ptrStub, bytes, (uint)bytes.Length, out _);
            Console.WriteLine("Written to 0x" + ptrStub.ToInt64().ToString("X8"));

#if DEBUG
            Console.WriteLine("Press ENTER to start remote thread (you have the chance to place a breakpoint now)");
            Console.ReadLine();
#endif

            var thread = Native.CreateRemoteThread(hProc, IntPtr.Zero, 0u, ptrStub, IntPtr.Zero, 0u, IntPtr.Zero);

            // NOTE: could wait for thread to finish with WaitForSingleObject
            Debug.Assert(thread != IntPtr.Zero);

            return(thread);
        }
Esempio n. 3
0
        void Test_zero_bytes()
        {
            var a = new Assembler(64);

            var lblf = a.CreateLabel();
            var lbll = a.CreateLabel();
            var lbl1 = a.CreateLabel();
            var lbl2 = a.CreateLabel();

            a.Label(ref lblf);
            a.zero_bytes();

            a.je(lbl1);
            a.je(lbl2);
            a.Label(ref lbl1);
            a.zero_bytes();
            a.Label(ref lbl2);
            a.nop();
            [email protected]_bytes();

            a.Label(ref lbll);
            a.zero_bytes();

            var writer = new CodeWriterImpl();

            a.Assemble(writer, 0);
            var bytes = writer.ToArray();

            Assert.Equal(new byte[] { 0x74, 0x02, 0x74, 0x00, 0x90 }, bytes);
        }
Esempio n. 4
0
        /// <summary>
        /// Encodes the original bytes for a new address fixing e.g. branches, calls, jumps for execution at a given address.
        /// </summary>
        /// <param name="newAddress">The new address to encode the original instructions for.</param>
        public byte[] EncodeForNewAddress(IntPtr newAddress)
        {
            var writer = new CodeWriterImpl(_bytes.Length * 2);
            var block  = new InstructionBlock(writer, DecodePrologue(), (ulong)newAddress);

            BlockEncoder.TryEncode(_bitness, block, out _);
            return(writer.ToArray());
        }
Esempio n. 5
0
        /// <summary>
        /// Encodes the original bytes for a new address fixing e.g. branches, calls, jumps for execution at a given address.
        /// </summary>
        /// <param name="newAddress">The new address to encode the original instructions for.</param>
        public byte[] EncodeForNewAddress(nuint newAddress)
        {
            var writer = new CodeWriterImpl(_bytes.Length * 2);
            var block  = new InstructionBlock(writer, DecodePrologue(), newAddress);

            if (!BlockEncoder.TryEncode(_bitness, block, out var error, out _))
            {
                throw new Exception($"Reloaded Hooks: Internal Error in {nameof(Reloaded.Hooks.Internal)}/{nameof(IcedPatcher)}. " +
                                    $"Failed to re-encode code for new address. Process will probably die." +
                                    $"Error: {error}");
            }

            return(writer.ToArray());
        }
Esempio n. 6
0
        void Anonymous_labels()
        {
            var c = new Assembler(64);

            var lbl1 = c.CreateLabel();
            var lbl2 = c.CreateLabel();
            var lbl3 = c.CreateLabel();
            var lbl4 = c.CreateLabel();

            c.Label(ref lbl1);
            c.inc(eax);
            c.nop();
            c.AnonymousLabel();
            c.je(c.@B);
            c.nop();
            c.Label(ref lbl2);
            c.je(c.@B);
            c.nop();
            c.jmp(lbl1);
            c.nop();
            c.jmp(lbl2);
            c.nop();
            c.jmp(lbl3);
            c.nop();
            c.jmp(lbl4);
            c.nop();
            c.jne(c.@F);
            c.nop();
            c.Label(ref lbl3);
            c.jne(c.@F);
            c.nop();
            c.AnonymousLabel();
            c.inc(eax);
            c.nop();
            c.Label(ref lbl4);
            c.nop();
            c.nop();

            var expectedData = new byte[] {
                0xFF, 0xC0, 0x90, 0x74, 0xFE, 0x90, 0x74, 0xFB, 0x90, 0xEB, 0xF5, 0x90, 0xEB, 0xF8, 0x90, 0xEB,
                0x07, 0x90, 0xEB, 0x0A, 0x90, 0x75, 0x04, 0x90, 0x75, 0x01, 0x90, 0xFF, 0xC0, 0x90, 0x90, 0x90,
            };
            var writer = new CodeWriterImpl();

            c.Assemble(writer, 0);
            Assert.Equal(expectedData, writer.ToArray());
        }
Esempio n. 7
0
 public void TestInvalidStateAssembler()
 {
     {
         var assembler = new Assembler(Bitness);
         var writer    = new CodeWriterImpl();
         var ex        = Assert.Throws <InvalidOperationException>(() => assembler.rep.Assemble(writer, 0));
         Assert.Contains("Unused prefixes", ex.Message);
     }
     {
         var assembler = new Assembler(Bitness);
         var label     = assembler.CreateLabel(("BadLabel"));
         assembler.Label(ref label);
         var writer = new CodeWriterImpl();
         var ex     = Assert.Throws <InvalidOperationException>(() => assembler.Assemble(writer, 0));
         Assert.Contains("Unused label", ex.Message);
     }
 }
Esempio n. 8
0
        void Reset_works()
        {
            var c = new Assembler(Bitness);

            c.CreateLabel();
            c.add(rax, rcx);
            _                   = c.@lock;
            c.PreferVex         = false;
            c.PreferBranchShort = false;
            c.Reset();
            Assert.False(c.PreferVex);
            Assert.False(c.PreferBranchShort);
            Assert.Empty(c.Instructions);
            Assert.True(c.CurrentLabel.IsEmpty);
            var writer = new CodeWriterImpl();
            var result = c.Assemble(writer, 0);

            Assert.Single(result.Result);
            Assert.Empty(writer.ToArray());
        }
Esempio n. 9
0
        public void TestLabelRIP()
        {
            {
                var c      = new Assembler(Bitness);
                var label0 = c.CreateLabel();
                var label1 = c.CreateLabel();
                c.nop();
                c.nop();
                c.nop();
                c.Label(ref label1);
                c.nop();

                var writer    = new CodeWriterImpl();
                var result    = c.Assemble(writer, 0x100, BlockEncoderOptions.ReturnNewInstructionOffsets);
                var label1RIP = result.GetLabelRIP(label1);
                Assert.Equal((ulong)0x103, label1RIP);
                Assert.Throws <ArgumentOutOfRangeException>(() => result.GetLabelRIP(label1, 1));
                Assert.Throws <ArgumentException>(() => result.GetLabelRIP(label0));
            }
            {
                var c      = new Assembler(Bitness);
                var label1 = c.CreateLabel();
                c.nop();
                c.Label(ref label1);
                c.nop();

                // Cannot use a label not created via CreateLabel
                var emptyLabel = new Label();
                Assert.Throws <ArgumentException>(() => c.Label(ref emptyLabel));

                // Cannot use a label already emitted
                Assert.Throws <ArgumentException>(() => c.Label(ref label1));

                var writer = new CodeWriterImpl();
                var result = c.Assemble(writer, 0);
                // Will throw without BlockEncoderOptions.ReturnNewInstructionOffsets
                Assert.Throws <ArgumentOutOfRangeException>(() => result.GetLabelRIP(label1));
                Assert.Throws <ArgumentOutOfRangeException>(() => default(AssemblerResult).GetLabelRIP(label1));
                Assert.Throws <ArgumentException>(() => result.GetLabelRIP(new Label()));
            }
        }
Esempio n. 10
0
        public void TestNops()
        {
            foreach (var bitness in new[] { 16, 32, 64 })
            {
                for (int i = 0; i <= 128; i++)
                {
                    var a = new Assembler(bitness);
                    a.nop(i);
                    var writer = new CodeWriterImpl();
                    a.Assemble(writer, 0);
                    var data = writer.ToArray();
                    Assert.Equal(i, data.Length);
                    var reader  = new ByteArrayCodeReader(data);
                    var decoder = Decoder.Create(bitness, reader);
                    while (reader.CanReadByte)
                    {
                        var instr = decoder.Decode();
                        switch (instr.Code)
                        {
                        case Code.Nopw:
                        case Code.Nopd:
                        case Code.Nopq:
                        case Code.Nop_rm16:
                        case Code.Nop_rm32:
                        case Code.Nop_rm64:
                            break;

                        default:
                            Assert.True(false, $"Expected a NOP but got {instr.Code}");
                            break;
                        }
                    }
                }

                {
                    var a = new Assembler(bitness);
                    Assert.Throws <ArgumentOutOfRangeException>(() => a.nop(-1));
                }
            }
        }
Esempio n. 11
0
        void TestVexEvexPrefixes()
        {
            var a = new Assembler(64);

            a.PreferVex = true;
            Assert.True(a.PreferVex);
            a.vaddpd(xmm1, xmm2, xmm3);
            a.vex.vaddpd(xmm1, xmm2, xmm3);
            a.vaddpd(xmm1, xmm2, xmm3);
            a.evex.vaddpd(xmm1, xmm2, xmm3);
            a.vaddpd(xmm1, xmm2, xmm3);
            Assert.True(a.PreferVex);

            a.PreferVex = false;
            Assert.False(a.PreferVex);
            a.vaddpd(xmm1, xmm2, xmm3);
            a.vex.vaddpd(xmm1, xmm2, xmm3);
            a.vaddpd(xmm1, xmm2, xmm3);
            a.evex.vaddpd(xmm1, xmm2, xmm3);
            a.vaddpd(xmm1, xmm2, xmm3);
            Assert.False(a.PreferVex);

            var writer = new CodeWriterImpl();

            a.Assemble(writer, 0);
            var bytes = writer.ToArray();

            Assert.Equal(new byte[] {
                0xC5, 0xE9, 0x58, 0xCB,
                0xC5, 0xE9, 0x58, 0xCB,
                0xC5, 0xE9, 0x58, 0xCB,
                0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB,
                0xC5, 0xE9, 0x58, 0xCB,
                0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB,
                0xC5, 0xE9, 0x58, 0xCB,
                0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB,
                0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB,
                0x62, 0xF1, 0xED, 0x08, 0x58, 0xCB,
            }, bytes);
        }
Esempio n. 12
0
        protected unsafe void TestAssemblerDeclareData <T>(Action <Assembler> fAsm, T[] data) where T : unmanaged
        {
            var assembler = new Assembler(Bitness);
            var sizeOfT   = sizeof(T);

            fAsm(assembler);

            var writer = new CodeWriterImpl();

            assembler.Assemble(writer, 0);
            var buffer = writer.ToArray();

            Assert.Equal(sizeOfT * data.Length, buffer.Length);
            fixed(void *pData = data)
            {
                for (int i = 0; i < buffer.Length; i++)
                {
                    var expectedData = ((byte *)pData)[i];
                    Assert.True(expectedData == buffer[i], $"Invalid data at offset {i}. Expecting {expectedData:x2}. Actual: {buffer[i]}");
                }
            }
        }
Esempio n. 13
0
        /// <summary>
        /// Patches the prologue of a function with iced and returns the new address of the prologue.
        /// </summary>
        /// <returns></returns>
        public IntPtr GetFunctionAddress()
        {
            if (_newPrologueAddress != IntPtr.Zero)
            {
                return(_newPrologueAddress);
            }

            var estimateLength = _bytes.Length * 2;  // Super generous! Exact length not known till relocated, just ensuring the size is enough under any circumstance.
            var buffer         = Utilities.FindOrCreateBufferInRange(estimateLength);

            return(buffer.ExecuteWithLock(() =>
            {
                // Patch prologue
                var newBaseAddress = buffer.Properties.WritePointer;
                var writer = new CodeWriterImpl(estimateLength);
                var block = new InstructionBlock((CodeWriter)writer, DecodePrologue(), (ulong)newBaseAddress);
                BlockEncoder.TryEncode(_bitness, block, out _);

                var data = writer.ToArray();
                _newPrologueAddress = buffer.Add(data, 1);

                return _newPrologueAddress;
            }));
        }
        private static IntPtr AllocateStub(IntPtr hProc, string asmPath, string typeName, string methodName, string?args, IntPtr fnAddr, bool x86, bool isV4)
        {
            const string clrVersion2 = "v2.0.50727";
            const string clrVersion4 = "v4.0.30319";
            string       clrVersion  = isV4 ? clrVersion4 : clrVersion2;

            const string buildFlavor = "wks";    // WorkStation

            var clsidCLRRuntimeHost = new Guid(0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02);
            var iidICLRRuntimeHost  = new Guid(0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02);

            IntPtr ppv             = alloc(IntPtr.Size);
            IntPtr riid            = allocBytes(iidICLRRuntimeHost.ToByteArray());
            IntPtr rcslid          = allocBytes(clsidCLRRuntimeHost.ToByteArray());
            IntPtr pwszBuildFlavor = allocString(buildFlavor);
            IntPtr pwszVersion     = allocString(clrVersion);

            IntPtr pReturnValue    = alloc(4);
            IntPtr pwzArgument     = allocString(args);
            IntPtr pwzMethodName   = allocString(methodName);
            IntPtr pwzTypeName     = allocString(typeName);
            IntPtr pwzAssemblyPath = allocString(asmPath);

            var instructions = new InstructionList();

            if (x86)
            {
                // call CorBindtoRuntimeEx
                instructions.Add(Instruction.Create(Code.Pushd_imm32, ppv.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, riid.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, rcslid.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm8, 0));     // startupFlags
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwszBuildFlavor.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwszVersion.ToInt32()));
                instructions.Add(Instruction.Create(Code.Mov_r32_imm32, Register.EAX, fnAddr.ToInt32()));
                instructions.Add(Instruction.Create(Code.Call_rm32, Register.EAX));

                // call ICLRRuntimeHost::Start
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.None, ppv.ToInt32())));
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.ECX, new MemoryOperand(Register.EAX)));
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EDX, new MemoryOperand(Register.ECX, 0x0C)));
                instructions.Add(Instruction.Create(Code.Push_r32, Register.EAX));
                instructions.Add(Instruction.Create(Code.Call_rm32, Register.EDX));

                // call ICLRRuntimeHost::ExecuteInDefaultAppDomain
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pReturnValue.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzArgument.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzMethodName.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzTypeName.ToInt32()));
                instructions.Add(Instruction.Create(Code.Pushd_imm32, pwzAssemblyPath.ToInt32()));
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.None, ppv.ToInt32())));
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.ECX, new MemoryOperand(Register.EAX)));
                instructions.Add(Instruction.Create(Code.Push_r32, Register.EAX));
                instructions.Add(Instruction.Create(Code.Mov_r32_rm32, Register.EAX, new MemoryOperand(Register.ECX, 0x2C)));
                instructions.Add(Instruction.Create(Code.Call_rm32, Register.EAX));

                instructions.Add(Instruction.Create(Code.Retnd));
            }
            else
            {
                const int maxStackIndex = 3;
                const int stackSize     = 0x20 + maxStackIndex * 8; // 0x20 bytes shadow space, because x64
                MemoryOperand stackAccess(int i) => new MemoryOperand(Register.RSP, stackSize - (maxStackIndex - i) * 8);

                instructions.Add(Instruction.Create(Code.Sub_rm64_imm8, Register.RSP, stackSize));

                // call CorBindtoRuntimeEx
                // calling convention: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, ppv.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(1), Register.RAX));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, riid.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(0), Register.RAX));

                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R9, rcslid.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r32_imm32, Register.R8D, 0));    // startupFlags
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwszBuildFlavor.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, pwszVersion.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RAX, fnAddr.ToInt64()));
                instructions.Add(Instruction.Create(Code.Call_rm64, Register.RAX));

                // call pClrHost->Start();
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX)));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX)));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RDX, new MemoryOperand(Register.RAX, 0x18)));
                instructions.Add(Instruction.Create(Code.Call_rm64, Register.RDX));

                // call pClrHost->ExecuteInDefaultAppDomain()
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pReturnValue.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(1), Register.RDX));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwzArgument.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_rm64_r64, stackAccess(0), Register.RDX));

                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R9, pwzMethodName.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.R8, pwzTypeName.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RDX, pwzAssemblyPath.ToInt64()));

                instructions.Add(Instruction.Create(Code.Mov_r64_imm64, Register.RCX, ppv.ToInt64()));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RCX, new MemoryOperand(Register.RCX)));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RCX)));
                instructions.Add(Instruction.Create(Code.Mov_r64_rm64, Register.RAX, new MemoryOperand(Register.RAX, 0x58)));
                instructions.Add(Instruction.Create(Code.Call_rm64, Register.RAX));

                instructions.Add(Instruction.Create(Code.Add_rm64_imm8, Register.RSP, stackSize));

                instructions.Add(Instruction.Create(Code.Retnq));
            }

            var  cw      = new CodeWriterImpl();
            var  ib      = new InstructionBlock(cw, instructions, 0);
            bool success = BlockEncoder.TryEncode(x86 ? 32 : 64, ib, out string errMsg);

            if (!success)
            {
                throw new Exception("Error during Iced encode: " + errMsg);
            }
            byte[] bytes = cw.ToArray();

            var ptrStub = alloc(bytes.Length, 0x40);    // RWX

            writeBytes(ptrStub, bytes);

            return(ptrStub);

            IntPtr alloc(int size, int protection = 0x04) => Native.VirtualAllocEx(hProc, IntPtr.Zero, (uint)size, 0x1000, protection);
            void writeBytes(IntPtr address, byte[] b) => Native.WriteProcessMemory(hProc, address, b, (uint)b.Length, out _);
            void writeString(IntPtr address, string str) => writeBytes(address, new UnicodeEncoding().GetBytes(str));

            IntPtr allocString(string?str)
            {
                if (str is null)
                {
                    return(IntPtr.Zero);
                }

                IntPtr pString = alloc(str.Length * 2 + 2);

                writeString(pString, str);
                return(pString);
            }

            IntPtr allocBytes(byte[] buffer)
            {
                IntPtr pBuffer = alloc(buffer.Length);

                writeBytes(pBuffer, buffer);
                return(pBuffer);
            }
        }
Esempio n. 15
0
        protected void TestAssembler(Action <Assembler> fAsm, Instruction expectedInst, LocalOpCodeFlags flags = LocalOpCodeFlags.None)
        {
            var assembler = new Assembler(_bitness);

            // Encode the instruction
            if ((flags & LocalOpCodeFlags.PreferVex) != 0)
            {
                assembler.PreferVex = true;
            }
            else if ((flags & LocalOpCodeFlags.PreferEvex) != 0)
            {
                assembler.PreferVex = false;
            }
            if ((flags & LocalOpCodeFlags.PreferBranchShort) != 0)
            {
                assembler.PreferBranchShort = true;
            }
            else if ((flags & LocalOpCodeFlags.PreferBranchNear) != 0)
            {
                assembler.PreferBranchShort = false;
            }
            fAsm(assembler);

            // Expecting only one instruction
            Assert.Equal(1, assembler.Instructions.Count);

            // Encode the instruction first to get any errors
            var writer = new CodeWriterImpl();

            assembler.Assemble(writer, 0, (flags & LocalOpCodeFlags.BranchUlong) != 0 ? BlockEncoderOptions.None : BlockEncoderOptions.DontFixBranches);

            // Check that the instruction is the one expected
            if ((flags & LocalOpCodeFlags.Broadcast) != 0)
            {
                expectedInst.IsBroadcast = true;
            }
            var inst = assembler.Instructions[0];

            Assert.Equal(expectedInst, inst);

            // Special for decoding options
            DecoderOptions decoderOptions = DecoderOptions.None;

            switch (inst.Code)
            {
            case Code.Umov_rm8_r8:
            case Code.Umov_rm16_r16:
            case Code.Umov_rm32_r32:
            case Code.Umov_r8_rm8:
            case Code.Umov_r16_rm16:
            case Code.Umov_r32_rm32:
                decoderOptions = DecoderOptions.Umov;
                break;

            case Code.Xbts_r16_rm16:
            case Code.Xbts_r32_rm32:
            case Code.Ibts_rm16_r16:
            case Code.Ibts_rm32_r32:
                decoderOptions = DecoderOptions.Xbts;
                break;

            case Code.Frstpm:
            case Code.Fstdw_AX:
            case Code.Fstsg_AX:
                decoderOptions = DecoderOptions.OldFpu;
                break;

            case Code.Pcommit:
                decoderOptions = DecoderOptions.Pcommit;
                break;

            case Code.Loadall386:
                decoderOptions = DecoderOptions.Loadall386;
                break;

            case Code.Cl1invmb:
                decoderOptions = DecoderOptions.Cl1invmb;
                break;

            case Code.Mov_r32_tr:
            case Code.Mov_tr_r32:
                decoderOptions = DecoderOptions.MovTr;
                break;

            case Code.Jmpe_rm16:
            case Code.Jmpe_rm32:
            case Code.Jmpe_disp16:
            case Code.Jmpe_disp32:
                decoderOptions = DecoderOptions.Jmpe;
                break;

            case Code.ReservedNop_rm16_r16_0F0D:
            case Code.ReservedNop_rm32_r32_0F0D:
            case Code.ReservedNop_rm64_r64_0F0D:
            case Code.ReservedNop_rm16_r16_0F18:
            case Code.ReservedNop_rm32_r32_0F18:
            case Code.ReservedNop_rm64_r64_0F18:
            case Code.ReservedNop_rm16_r16_0F19:
            case Code.ReservedNop_rm32_r32_0F19:
            case Code.ReservedNop_rm64_r64_0F19:
            case Code.ReservedNop_rm16_r16_0F1A:
            case Code.ReservedNop_rm32_r32_0F1A:
            case Code.ReservedNop_rm64_r64_0F1A:
            case Code.ReservedNop_rm16_r16_0F1B:
            case Code.ReservedNop_rm32_r32_0F1B:
            case Code.ReservedNop_rm64_r64_0F1B:
            case Code.ReservedNop_rm16_r16_0F1C:
            case Code.ReservedNop_rm32_r32_0F1C:
            case Code.ReservedNop_rm64_r64_0F1C:
            case Code.ReservedNop_rm16_r16_0F1D:
            case Code.ReservedNop_rm32_r32_0F1D:
            case Code.ReservedNop_rm64_r64_0F1D:
            case Code.ReservedNop_rm16_r16_0F1E:
            case Code.ReservedNop_rm32_r32_0F1E:
            case Code.ReservedNop_rm64_r64_0F1E:
            case Code.ReservedNop_rm16_r16_0F1F:
            case Code.ReservedNop_rm32_r32_0F1F:
            case Code.ReservedNop_rm64_r64_0F1F:
                decoderOptions = DecoderOptions.ForceReservedNop;
                break;
            }

            if ((flags & LocalOpCodeFlags.BranchUlong) == 0)
            {
                decoderOptions |= DecoderOptions.AmdBranches;
            }

            // Check decoding back against the original instruction
            var instructionAsBytes = new System.Text.StringBuilder();

            foreach (var b in writer.ToArray())
            {
                instructionAsBytes.Append($"{b:x2} ");
            }

            var decoder     = Decoder.Create(_bitness, new ByteArrayCodeReader(writer.ToArray()), decoderOptions);
            var decodedInst = decoder.Decode();

            if ((flags & LocalOpCodeFlags.Fwait) != 0)
            {
                Assert.Equal(decodedInst, Instruction.Create(Code.Wait));
                decodedInst = decoder.Decode();

                switch (decodedInst.Code)
                {
                case Code.Fnstenv_m14byte:
                    decodedInst.Code = Code.Fstenv_m14byte;
                    break;

                case Code.Fnstenv_m28byte:
                    decodedInst.Code = Code.Fstenv_m28byte;
                    break;

                case Code.Fnstcw_m2byte:
                    decodedInst.Code = Code.Fstcw_m2byte;
                    break;

                case Code.Fneni:
                    decodedInst.Code = Code.Feni;
                    break;

                case Code.Fndisi:
                    decodedInst.Code = Code.Fdisi;
                    break;

                case Code.Fnclex:
                    decodedInst.Code = Code.Fclex;
                    break;

                case Code.Fninit:
                    decodedInst.Code = Code.Finit;
                    break;

                case Code.Fnsetpm:
                    decodedInst.Code = Code.Fsetpm;
                    break;

                case Code.Fnsave_m94byte:
                    decodedInst.Code = Code.Fsave_m94byte;
                    break;

                case Code.Fnsave_m108byte:
                    decodedInst.Code = Code.Fsave_m108byte;
                    break;

                case Code.Fnstsw_m2byte:
                    decodedInst.Code = Code.Fstsw_m2byte;
                    break;

                case Code.Fnstsw_AX:
                    decodedInst.Code = Code.Fstsw_AX;
                    break;
                }
            }

            // Reset IP to 0 when matching against decode
            if (inst.Code != Code.Jmpe_disp16 && inst.Code != Code.Jmpe_disp32 && (flags & LocalOpCodeFlags.Branch) != 0)
            {
                // Check if it's a label ID, if so, replace it with the IP
                if (inst.NearBranch64 == 1)
                {
                    inst.NearBranch64 = 0;
                }
            }

            // Special case for branch via ulong. An instruction like `loopne 000031D0h`
            // could have been encoded with a sequence like:
            //
            // 0:  e0 02                   loopne 0x4
            // 2:  eb 05                   jmp    0x9
            // 4:  e9 c7 31 00 00          jmp    0x31d0
            if ((flags & LocalOpCodeFlags.BranchUlong) != 0)
            {
                Assert.Equal(inst.Code.ToShortBranch(), decodedInst.Code.ToShortBranch());

                if (decodedInst.NearBranch64 == 4)
                {
                    var nextDecodedInst = decoder.Decode();
                    var expectedCode    = Bitness switch {
                        16 => Code.Jmp_rel8_16,
                        32 => Code.Jmp_rel8_32,
                        64 => Code.Jmp_rel8_64,
                        _ => throw new InvalidOperationException(),
                    };

                    Assert.True(nextDecodedInst.Code == expectedCode, $"Branch ulong next decoding failed!\nExpected: {expectedCode} \nActual Decoded: {nextDecodedInst}\n");
                }
                else
                {
                    Assert.True(inst.NearBranch64 == decodedInst.NearBranch64, $"Branch decoding offset failed!\nExpected: {inst} ({instructionAsBytes})\nActual Decoded: {decodedInst}\n");
                }
            }
            else
            {
                Assert.True(inst == decodedInst, $"Decoding failed!\nExpected: {inst} ({instructionAsBytes})\nActual Decoded: {decodedInst}\n");
            }
        }