public void Enable(bool enable = true) { MemoryProtection oldProtect; long patchSize = Marshal.SizeOf(typeof(JMPCALL_REL)); IntPtr patchTarget = trampoline.Target; if (trampoline.PatchAbove) { patchTarget = patchTarget - Marshal.SizeOf(typeof(JMPCALL_REL)); patchSize += Marshal.SizeOf(typeof(JMP_REL_SHORT)); } if (!VirtualProtect(patchTarget, (UIntPtr)patchSize, MemoryProtection.ExecuteReadWrite, out oldProtect)) { throw new Exception("Failed to change memory protection"); } if (enable) { JMPCALL_REL jmp = new JMPCALL_REL(0xE9); if (IntPtr.Size == 8) { long rel = ((long)trampoline.Relay - ((long)patchTarget + Marshal.SizeOf(jmp))); jmp.SetOperand((uint)rel); } else { jmp.SetOperand((uint)((ulong)trampoline.Detour - ((ulong)patchTarget + (ulong)Marshal.SizeOf(jmp)))); } Marshal.StructureToPtr(jmp, patchTarget, false); if (trampoline.PatchAbove) { JMP_REL_SHORT shortJmp = new JMP_REL_SHORT(0xEB, (byte)(0 - (Marshal.SizeOf(typeof(JMP_REL_SHORT)) + Marshal.SizeOf(jmp)))); Marshal.StructureToPtr(shortJmp, trampoline.Target, false); } } else { if (trampoline.PatchAbove) { Marshal.Copy(trampoline.Backup, 0, patchTarget, Marshal.SizeOf(typeof(JMPCALL_REL)) + Marshal.SizeOf(typeof(JMP_REL_SHORT))); } else { Marshal.Copy(trampoline.Backup, 0, patchTarget, Marshal.SizeOf(typeof(JMPCALL_REL))); } } VirtualProtect(patchTarget, (UIntPtr)patchSize, oldProtect, out _); //TODO: flush instruction cache Enabled = true; }
public Trampoline(IntPtr target, IntPtr detour, MemorySlot memorySlot) { Target = target; Detour = detour; Tramp = memorySlot.Address; this.memorySlot = memorySlot; uint oldPos = 0; uint newPos = 0; IntPtr jmpDest = IntPtr.Zero; IntPtr instBuf = Marshal.AllocHGlobal(16); bool finished = false; bool is64 = IntPtr.Size == 8; object jmp; object call; object jcc; if (is64) { call = new CALL_ABS(IntPtr.Zero); jmp = new JMP_ABS(IntPtr.Zero); jcc = new JCC_ABS(IntPtr.Zero); } else { call = new JMPCALL_REL(0xE8); jmp = new JMPCALL_REL(0xE9); jcc = new JCC_REL(0); } do { object srcObj = null; uint copySize; IntPtr copySrc; IntPtr oldInst = (IntPtr)((long)Target + oldPos); IntPtr newInst = (IntPtr)((long)Tramp + newPos); Disassembler disasm = new Disassembler(oldInst, 16, is64 ? ArchitectureMode.x86_64 : ArchitectureMode.x86_32, 0, true); disasm.Disassemble(); Instruction inst = disasm.NextInstruction(); if (inst.Error) { throw new Exception(inst.ErrorMessage); } copySrc = oldInst; copySize = (uint)inst.Length; if (oldPos >= Marshal.SizeOf(typeof(JMPCALL_REL))) { if (is64) { ((AddressSupport)jmp).SetAddress(oldInst); } else { ((OperandSupport)jmp).SetOperand((uint)((uint)oldInst - ((uint)newInst + Marshal.SizeOf(jmp)))); } srcObj = jmp; copySize = (uint)Marshal.SizeOf(jmp); finished = true; } else if (is64 && inst.HasModRM && ((inst.ModRM & 0xC7) == 0x5)) { byte modRMReg = (byte)((inst.ModRM & 0x3f) >> 3); copySrc = instBuf; CopyMemory(instBuf, oldInst, (int)copySize); //TODO: figure out how to calculate imm length with udisasm engine uint immLen = 0; IntPtr relAddr = (IntPtr)((long)instBuf + inst.Length - immLen - 4); uint updatedRel = (uint)((ulong)oldInst + (ulong)inst.Length + inst.Operands[0].LvalUDWord - ((ulong)newInst + (ulong)inst.Length)); Marshal.WriteInt32(relAddr, (int)updatedRel); // Complete function if JMP (FF /4) if (inst.Bytes[0] == 0xFF & modRMReg == 4) { finished = true; } } else if (inst.Mnemonic == ud_mnemonic_code.UD_Icall) { IntPtr dest = (IntPtr)((long)oldInst + inst.Length + inst.Operands[0].LvalSDWord); if (is64) { ((AddressSupport)call).SetAddress(dest); } else { ((OperandSupport)call).SetOperand((uint)dest - (uint)(newInst + Marshal.SizeOf(call))); } srcObj = call; copySize = (uint)Marshal.SizeOf(call); } else if ((inst.Bytes[0] & 0xFD) == 0xE9) { IntPtr dest = oldInst + inst.Length; if ((inst.Bytes[0] == 0xE9)) //short JMP { dest = IntPtr.Add(dest, inst.Operands[0].LvalByte); } else { dest = IntPtr.Add(dest, inst.Operands[0].LvalSDWord); } if ((long)Target <= (long)dest && (long)dest < ((long)Target + Marshal.SizeOf(typeof(JMPCALL_REL)))) { if ((long)jmpDest < (long)dest) { jmpDest = dest; } } else { if (is64) { ((AddressSupport)jmp).SetAddress(dest); } else { ((OperandSupport)jmp).SetOperand((uint)((uint)dest - ((uint)newInst + Marshal.SizeOf(jmp)))); } srcObj = jmp; copySize = (uint)Marshal.SizeOf(jmp); finished = ((long)oldInst >= (long)jmpDest); } } else if (inst.Length >= 2 && ((inst.Bytes[0] & 0xF0) == 0x70) || ((inst.Bytes[0] & 0xFC) == 0xE0) || (inst.Bytes[0] == 0xF0 && (inst.Bytes[1] & 0xF0) == 0x80)) { //Jcc Handler IntPtr dest = oldInst + inst.Length; if ((inst.Bytes[0] & 0xF0) == 0x70 || (inst.Bytes[0] & 0xFC) == 0xE0) { dest = IntPtr.Add(dest, inst.Operands[0].LvalByte); } else { dest = IntPtr.Add(dest, (int)inst.Operands[0].LvalUDWord); } if ((long)Target <= (long)dest && (long)dest < ((long)Target + Marshal.SizeOf(typeof(JMPCALL_REL)))) { if ((long)jmpDest < (long)dest) { jmpDest = dest; } } else if ((inst.Bytes[0] & 0xFC) == 0xE0) { throw new Exception("LOOPx/Jxx to the outside not supported"); } else { byte cond = (byte)((inst.Bytes[0] != 0x0F ? inst.Bytes[0] : inst.Bytes[1]) & 0xF0); if (is64) { ((OpcodeSupport)jcc).SetOpcode((byte)(0x71 ^ cond)); ((AddressSupport)jcc).SetAddress(dest); } else { ((OpcodeSupport)jcc).SetOpcode((byte)(0x80 ^ cond)); ((OperandSupport)jcc).SetOperand((uint)((uint)dest - ((uint)newInst + Marshal.SizeOf(jcc)))); } srcObj = jcc; copySize = (uint)Marshal.SizeOf(jcc); } } else if (inst.Mnemonic == ud_mnemonic_code.UD_Iret || inst.Mnemonic == ud_mnemonic_code.UD_Iretf) { finished = ((long)oldInst >= (long)jmpDest); } if ((long)oldInst < (long)jmpDest && copySize != inst.Length) { throw new Exception("Can't alter intruction length in a branch"); } if ((newPos + copySize) > TrampolineMaxSize) { throw new Exception("Trampoline function too big"); } if (InstructionBoundaries > OldInstructionBoundaries.Length) { throw new Exception("Trampoline has too many instructions"); } OldInstructionBoundaries[InstructionBoundaries] = (byte)oldPos; NewInstructionBoundaries[InstructionBoundaries] = (byte)newPos; InstructionBoundaries++; if (srcObj != null) { IntPtr data = Marshal.AllocHGlobal((int)copySize); Marshal.StructureToPtr(srcObj, data, false); CopyMemory((IntPtr)((long)Tramp + newPos), data, (int)copySize); Marshal.FreeHGlobal(data); } else { CopyMemory((IntPtr)((long)Tramp + newPos), copySrc, (int)copySize); } newPos += copySize; oldPos += (uint)inst.Length; } while (!finished); if (oldPos < Marshal.SizeOf(typeof(JMPCALL_REL)) && IsCodePadding((IntPtr)((long)Target + oldPos), (int)(Marshal.SizeOf(typeof(JMPCALL_REL)) - oldPos))) { if (oldPos < Marshal.SizeOf(typeof(JMP_REL_SHORT)) && IsCodePadding((IntPtr)((long)Target + oldPos), (int)(Marshal.SizeOf(typeof(JMP_REL_SHORT)) - oldPos))) { throw new Exception("Not enough space for patch"); } if (!IsExecutableAddress((IntPtr)((long)Target - Marshal.SizeOf(typeof(JMPCALL_REL))))) { throw new Exception("Not enought space for patch"); } if (!IsCodePadding((IntPtr)((long)Target - Marshal.SizeOf(typeof(JMPCALL_REL))), Marshal.SizeOf(typeof(JMPCALL_REL)))) { throw new Exception("Not enought space for patch"); } PatchAbove = true; } if (is64) { ((AddressSupport)jmp).SetAddress(Detour); Relay = (IntPtr)((long)Tramp + newPos); IntPtr data = Marshal.AllocHGlobal((int)Marshal.SizeOf(jmp)); Marshal.StructureToPtr(jmp, data, false); CopyMemory(Relay, data, Marshal.SizeOf(jmp)); Marshal.FreeHGlobal(data); } BackupPrologue(); }