From ff8849671af5ac14fc9cc9d37da30f53d3f13d89 Mon Sep 17 00:00:00 2001 From: Caian Benedicto Date: Wed, 4 Aug 2021 17:05:17 -0300 Subject: [PATCH] Update TamperMachine and disable write-to-code prevention (#2506) * Enable write to memory and improve logging * Update tamper machine opcodes and improve reporting * Add Else support * Add missing private statement --- .../Exceptions/CodeRegionTamperedException.cs | 9 --- .../HOS/Kernel/Process/ProcessTamperInfo.cs | 12 ++-- Ryujinx.HLE/HOS/ModLoader.cs | 2 +- Ryujinx.HLE/HOS/ProgramLoader.cs | 3 +- Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs | 36 +++++++++--- Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs | 5 +- .../CodeEmitters/EndConditionalBlock.cs | 55 ++++++++++++++++--- Ryujinx.HLE/HOS/Tamper/CompilationContext.cs | 20 ++++--- Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs | 2 + Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs | 3 + Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs | 6 ++ Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs | 12 +++- Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs | 19 +++---- Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs | 12 ++-- Ryujinx.HLE/HOS/TamperMachine.cs | 26 +++++---- 15 files changed, 156 insertions(+), 66 deletions(-) delete mode 100644 Ryujinx.HLE/Exceptions/CodeRegionTamperedException.cs diff --git a/Ryujinx.HLE/Exceptions/CodeRegionTamperedException.cs b/Ryujinx.HLE/Exceptions/CodeRegionTamperedException.cs deleted file mode 100644 index 7a61273e..00000000 --- a/Ryujinx.HLE/Exceptions/CodeRegionTamperedException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Exceptions -{ - public class CodeRegionTamperedException : TamperExecutionException - { - public CodeRegionTamperedException(string message) : base(message) { } - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs index fd10ee98..556703cf 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs @@ -8,13 +8,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public IEnumerable BuildIds { get; } public IEnumerable CodeAddresses { get; } public ulong HeapAddress { get; } + public ulong AliasAddress { get; } + public ulong AslrAddress { get; } - public ProcessTamperInfo(KProcess process, IEnumerable buildIds, IEnumerable codeAddresses, ulong heapAddress) + public ProcessTamperInfo(KProcess process, IEnumerable buildIds, IEnumerable codeAddresses, ulong heapAddress, ulong aliasAddress, ulong aslrAddress) { - Process = process; - BuildIds = buildIds; + Process = process; + BuildIds = buildIds; CodeAddresses = codeAddresses; - HeapAddress = heapAddress; + HeapAddress = heapAddress; + AliasAddress = aliasAddress; + AslrAddress = aslrAddress; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index a2e9af18..9cea42a4 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs @@ -658,7 +658,7 @@ namespace Ryujinx.HLE.HOS Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'"); - tamperMachine.InstallAtmosphereCheat(cheat.Instructions, tamperInfo, exeAddress); + tamperMachine.InstallAtmosphereCheat(cheat.Name, cheat.Instructions, tamperInfo, exeAddress); } } diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index 93ddd7ee..385b4af5 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -280,7 +280,8 @@ namespace Ryujinx.HLE.HOS // Keep the build ids because the tamper machine uses them to know which process to associate a // tamper to and also keep the starting address of each executable inside a process because some // memory modifications are relative to this address. - tamperInfo = new ProcessTamperInfo(process, buildIds, nsoBase, process.MemoryManager.HeapRegionStart); + tamperInfo = new ProcessTamperInfo(process, buildIds, nsoBase, process.MemoryManager.HeapRegionStart, + process.MemoryManager.AliasRegionStart, process.MemoryManager.CodeRegionStart); return true; } diff --git a/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs b/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs index 05e248c8..7d7af208 100644 --- a/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs +++ b/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs @@ -9,14 +9,36 @@ namespace Ryujinx.HLE.HOS.Tamper { class AtmosphereCompiler { - public ITamperProgram Compile(IEnumerable rawInstructions, ulong exeAddress, ulong heapAddress, ITamperedProcess process) + private ulong _exeAddress; + private ulong _heapAddress; + private ulong _aliasAddress; + private ulong _aslrAddress; + private ITamperedProcess _process; + + public AtmosphereCompiler(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process) { - Logger.Debug?.Print(LogClass.TamperMachine, $"Executable address: {exeAddress:X16}"); - Logger.Debug?.Print(LogClass.TamperMachine, $"Heap address: {heapAddress:X16}"); + _exeAddress = exeAddress; + _heapAddress = heapAddress; + _aliasAddress = aliasAddress; + _aslrAddress = aslrAddress; + _process = process; + } + + public ITamperProgram Compile(string name, IEnumerable rawInstructions) + { + string[] addresses = new string[] + { + $" Executable address: 0x{_exeAddress:X16}", + $" Heap address : 0x{_heapAddress:X16}", + $" Alias address : 0x{_aliasAddress:X16}", + $" Aslr address : 0x{_aslrAddress:X16}" + }; + + Logger.Debug?.Print(LogClass.TamperMachine, $"Compiling Atmosphere cheat {name}...\n{string.Join('\n', addresses)}"); try { - return CompileImpl(rawInstructions, exeAddress, heapAddress, process); + return CompileImpl(name, rawInstructions); } catch(TamperCompilationException exception) { @@ -33,9 +55,9 @@ namespace Ryujinx.HLE.HOS.Tamper return null; } - private ITamperProgram CompileImpl(IEnumerable rawInstructions, ulong exeAddress, ulong heapAddress, ITamperedProcess process) + private ITamperProgram CompileImpl(string name, IEnumerable rawInstructions) { - CompilationContext context = new CompilationContext(exeAddress, heapAddress, process); + CompilationContext context = new CompilationContext(_exeAddress, _heapAddress, _aliasAddress, _aslrAddress, _process); context.BlockStack.Push(new OperationBlock(null)); // Parse the instructions. @@ -124,7 +146,7 @@ namespace Ryujinx.HLE.HOS.Tamper throw new TamperCompilationException($"Reached end of compilation with unmatched conditional(s) or loop(s)"); } - return new AtmosphereProgram(process, context.PressedKeys, new Block(context.CurrentOperations)); + return new AtmosphereProgram(name, _process, context.PressedKeys, new Block(context.CurrentOperations)); } } } diff --git a/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs b/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs index 1fd0afb4..dac445b0 100644 --- a/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs +++ b/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs @@ -8,10 +8,13 @@ namespace Ryujinx.HLE.HOS.Tamper private Parameter _pressedKeys; private IOperation _entryPoint; + public string Name { get; } + public bool TampersCodeMemory { get; set; } = false; public ITamperedProcess Process { get; } - public AtmosphereProgram(ITamperedProcess process, Parameter pressedKeys, IOperation entryPoint) + public AtmosphereProgram(string name, ITamperedProcess process, Parameter pressedKeys, IOperation entryPoint) { + Name = name; Process = process; _pressedKeys = pressedKeys; _entryPoint = entryPoint; diff --git a/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs b/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs index 4a01992c..a25dddde 100644 --- a/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs +++ b/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs @@ -10,32 +10,73 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters /// class EndConditionalBlock { + const int TerminationTypeIndex = 1; + + private const byte End = 0; // True end of the conditional. + private const byte Else = 1; // End of the 'then' block and beginning of 'else' block. + public static void Emit(byte[] instruction, CompilationContext context) { - // 20000000 + Emit(instruction, context, null); + } + + private static void Emit(byte[] instruction, CompilationContext context, IEnumerable operationsElse) + { + // 2X000000 + // X: End type (0 = End, 1 = Else). + + byte terminationType = instruction[TerminationTypeIndex]; + + switch (terminationType) + { + case End: + break; + case Else: + // Start a new operation block with the 'else' instruction to signal that there is the 'then' block just above it. + context.BlockStack.Push(new OperationBlock(instruction)); + return; + default: + throw new TamperCompilationException($"Unknown conditional termination type {terminationType}"); + } // Use the conditional begin instruction stored in the stack. - instruction = context.CurrentBlock.BaseInstruction; - CodeType codeType = InstructionHelper.GetCodeType(instruction); + var upperInstruction = context.CurrentBlock.BaseInstruction; + CodeType codeType = InstructionHelper.GetCodeType(upperInstruction); // Pop the current block of operations from the stack so control instructions // for the conditional can be emitted in the upper block. IEnumerable operations = context.CurrentOperations; context.BlockStack.Pop(); + // If the else operations are already set, then the upper block must not be another end. + if (operationsElse != null && codeType == CodeType.EndConditionalBlock) + { + throw new TamperCompilationException($"Expected an upper 'if' conditional instead of 'end conditional'"); + } + ICondition condition; switch (codeType) { case CodeType.BeginMemoryConditionalBlock: - condition = MemoryConditional.Emit(instruction, context); + condition = MemoryConditional.Emit(upperInstruction, context); break; case CodeType.BeginKeypressConditionalBlock: - condition = KeyPressConditional.Emit(instruction, context); + condition = KeyPressConditional.Emit(upperInstruction, context); break; case CodeType.BeginRegisterConditionalBlock: - condition = RegisterConditional.Emit(instruction, context); + condition = RegisterConditional.Emit(upperInstruction, context); break; + case CodeType.EndConditionalBlock: + terminationType = upperInstruction[TerminationTypeIndex]; + // If there is an end instruction above then it must be an else. + if (terminationType != Else) + { + throw new TamperCompilationException($"Expected an upper 'else' conditional instead of {terminationType}"); + } + // Re-run the Emit with the else operations set. + Emit(instruction, context, operations); + return; default: throw new TamperCompilationException($"Conditional end does not match code type {codeType} in Atmosphere cheat"); } @@ -43,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters // Create a conditional block with the current operations and nest it in the upper // block of the stack. - IfBlock block = new IfBlock(condition, operations); + IfBlock block = new IfBlock(condition, operations, operationsElse); context.CurrentOperations.Add(block); } } diff --git a/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs b/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs index 71e64bb8..2dd4029a 100644 --- a/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs +++ b/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs @@ -20,17 +20,21 @@ namespace Ryujinx.HLE.HOS.Tamper public Dictionary StaticRegisters { get; } public ulong ExeAddress { get; } public ulong HeapAddress { get; } + public ulong AliasAddress { get; } + public ulong AslrAddress { get; } - public CompilationContext(ulong exeAddress, ulong heapAddress, ITamperedProcess process) + public CompilationContext(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process) { - Process = process; - PressedKeys = new Parameter(0); - BlockStack = new Stack(); - Registers = new Dictionary(); - SavedRegisters = new Dictionary(); + Process = process; + PressedKeys = new Parameter(0); + BlockStack = new Stack(); + Registers = new Dictionary(); + SavedRegisters = new Dictionary(); StaticRegisters = new Dictionary(); - ExeAddress = exeAddress; - HeapAddress = heapAddress; + ExeAddress = exeAddress; + HeapAddress = heapAddress; + AliasAddress = aliasAddress; + AslrAddress = aslrAddress; } public Register GetRegister(byte index) diff --git a/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs b/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs index 06bc2243..63702bf7 100644 --- a/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs +++ b/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs @@ -4,6 +4,8 @@ namespace Ryujinx.HLE.HOS.Tamper { interface ITamperProgram { + string Name { get; } + bool TampersCodeMemory { get; set; } ITamperedProcess Process { get; } void Execute(ControllerKeys pressedKeys); } diff --git a/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs b/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs index d9da5d00..c86e1021 100644 --- a/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs +++ b/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs @@ -5,6 +5,9 @@ namespace Ryujinx.HLE.HOS.Tamper interface ITamperedProcess { ProcessState State { get; } + + bool TamperedCodeMemory { get; set; } + T ReadMemory(ulong va) where T : unmanaged; void WriteMemory(ulong va, T value) where T : unmanaged; void PauseProcess(); diff --git a/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs b/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs index 277b3841..1260ed9a 100644 --- a/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs +++ b/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs @@ -15,6 +15,12 @@ namespace Ryujinx.HLE.HOS.Tamper case MemoryRegion.Heap: // Memory address is relative to the heap. return context.HeapAddress; + case MemoryRegion.Alias: + // Memory address is relative to the alias region. + return context.AliasAddress; + case MemoryRegion.Asrl: + // Memory address is relative to the asrl region, which matches the code region. + return context.AslrAddress; default: throw new TamperCompilationException($"Invalid memory source {source} in Atmosphere cheat"); } diff --git a/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs b/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs index 13ba6f18..fb4b25ff 100644 --- a/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs +++ b/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs @@ -20,6 +20,16 @@ namespace Ryujinx.HLE.HOS.Tamper /// /// The address of the heap, as determined by the kernel. /// - Heap = 0x1 + Heap = 0x1, + + /// + /// The address of the alias region, as determined by the kernel. + /// + Alias = 0x2, + + /// + /// The address of the code region with address space layout randomization included. + /// + Asrl = 0x3, } } diff --git a/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs b/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs index 0ba0f8c3..b7c5684e 100644 --- a/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs +++ b/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs @@ -6,27 +6,26 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations class IfBlock : IOperation { private ICondition _condition; - private IEnumerable _operations; + private IEnumerable _operationsThen; + private IEnumerable _operationsElse; - public IfBlock(ICondition condition, IEnumerable operations) + public IfBlock(ICondition condition, IEnumerable operationsThen, IEnumerable operationsElse) { _condition = condition; - _operations = operations; - } - - public IfBlock(ICondition condition, params IOperation[] operations) - { - _operations = operations; + _operationsThen = operationsThen; + _operationsElse = operationsElse; } public void Execute() { - if (!_condition.Evaluate()) + IEnumerable operations = _condition.Evaluate() ? _operationsThen : _operationsElse; + + if (operations == null) { return; } - foreach (IOperation op in _operations) + foreach (IOperation op in operations) { op.Execute(); } diff --git a/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs b/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs index e27c371a..be51264a 100644 --- a/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs +++ b/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs @@ -11,9 +11,11 @@ namespace Ryujinx.HLE.HOS.Tamper public ProcessState State => _process.State; + public bool TamperedCodeMemory { get; set; } = false; + public TamperedKProcess(KProcess process) { - this._process = process; + _process = process; } private void AssertMemoryRegion(ulong va, bool isWrite) where T : unmanaged @@ -32,11 +34,11 @@ namespace Ryujinx.HLE.HOS.Tamper return; } - // TODO (Caian): It is unknown how PPTC behaves if the tamper modifies memory regions - // belonging to code. So for now just prevent code tampering. - if ((va >= _process.MemoryManager.CodeRegionStart) && (va + size <= _process.MemoryManager.CodeRegionEnd)) + // TODO (Caian): The JIT does not support invalidating a code region so writing to code memory may not work + // as intended, so taint the operation to issue a warning later. + if (isWrite && (va >= _process.MemoryManager.CodeRegionStart) && (va + size <= _process.MemoryManager.CodeRegionEnd)) { - throw new CodeRegionTamperedException($"Writing {size} bytes to address 0x{va:X16} alters code"); + TamperedCodeMemory = true; } } diff --git a/Ryujinx.HLE/HOS/TamperMachine.cs b/Ryujinx.HLE/HOS/TamperMachine.cs index 9cdea94a..6044368e 100644 --- a/Ryujinx.HLE/HOS/TamperMachine.cs +++ b/Ryujinx.HLE/HOS/TamperMachine.cs @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS } } - internal void InstallAtmosphereCheat(IEnumerable rawInstructions, ProcessTamperInfo info, ulong exeAddress) + internal void InstallAtmosphereCheat(string name, IEnumerable rawInstructions, ProcessTamperInfo info, ulong exeAddress) { if (!CanInstallOnPid(info.Process.Pid)) { @@ -39,11 +39,13 @@ namespace Ryujinx.HLE.HOS } ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process); - AtmosphereCompiler compiler = new AtmosphereCompiler(); - ITamperProgram program = compiler.Compile(rawInstructions, exeAddress, info.HeapAddress, tamperedProcess); + AtmosphereCompiler compiler = new AtmosphereCompiler(exeAddress, info.HeapAddress, info.AliasAddress, info.AslrAddress, tamperedProcess); + ITamperProgram program = compiler.Compile(name, rawInstructions); if (program != null) { + program.TampersCodeMemory = false; + _programs.Enqueue(program); } @@ -116,27 +118,27 @@ namespace Ryujinx.HLE.HOS // Re-enqueue the tampering program because the process is still valid. _programs.Enqueue(program); - Logger.Debug?.Print(LogClass.TamperMachine, "Running tampering program"); + Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}"); try { ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys); + program.Process.TamperedCodeMemory = false; program.Execute(pressedKeys); - } - catch (CodeRegionTamperedException ex) - { - Logger.Debug?.Print(LogClass.TamperMachine, $"Prevented tampering program from modifing code memory"); - if (!String.IsNullOrEmpty(ex.Message)) + // Detect the first attempt to tamper memory and log it. + if (!program.TampersCodeMemory && program.Process.TamperedCodeMemory) { - Logger.Debug?.Print(LogClass.TamperMachine, ex.Message); + program.TampersCodeMemory = true; + + Logger.Warning?.Print(LogClass.TamperMachine, $"Tampering program {program.Name} modifies code memory so it may not work properly"); } } catch (Exception ex) { - Logger.Debug?.Print(LogClass.TamperMachine, $"The tampering program crashed, this can happen while the game is starting"); + Logger.Debug?.Print(LogClass.TamperMachine, $"The tampering program {program.Name} crashed, this can happen while the game is starting"); - if (!String.IsNullOrEmpty(ex.Message)) + if (!string.IsNullOrEmpty(ex.Message)) { Logger.Debug?.Print(LogClass.TamperMachine, ex.Message); }