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
This commit is contained in:
Caian Benedicto 2021-08-04 17:05:17 -03:00 committed by GitHub
parent a27986c311
commit ff8849671a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 156 additions and 66 deletions

View file

@ -1,9 +0,0 @@
using System;
namespace Ryujinx.HLE.Exceptions
{
public class CodeRegionTamperedException : TamperExecutionException
{
public CodeRegionTamperedException(string message) : base(message) { }
}
}

View file

@ -8,13 +8,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IEnumerable<string> BuildIds { get; } public IEnumerable<string> BuildIds { get; }
public IEnumerable<ulong> CodeAddresses { get; } public IEnumerable<ulong> CodeAddresses { get; }
public ulong HeapAddress { get; } public ulong HeapAddress { get; }
public ulong AliasAddress { get; }
public ulong AslrAddress { get; }
public ProcessTamperInfo(KProcess process, IEnumerable<string> buildIds, IEnumerable<ulong> codeAddresses, ulong heapAddress) public ProcessTamperInfo(KProcess process, IEnumerable<string> buildIds, IEnumerable<ulong> codeAddresses, ulong heapAddress, ulong aliasAddress, ulong aslrAddress)
{ {
Process = process; Process = process;
BuildIds = buildIds; BuildIds = buildIds;
CodeAddresses = codeAddresses; CodeAddresses = codeAddresses;
HeapAddress = heapAddress; HeapAddress = heapAddress;
AliasAddress = aliasAddress;
AslrAddress = aslrAddress;
} }
} }
} }

View file

@ -658,7 +658,7 @@ namespace Ryujinx.HLE.HOS
Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'"); Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'");
tamperMachine.InstallAtmosphereCheat(cheat.Instructions, tamperInfo, exeAddress); tamperMachine.InstallAtmosphereCheat(cheat.Name, cheat.Instructions, tamperInfo, exeAddress);
} }
} }

View file

@ -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 // 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 // tamper to and also keep the starting address of each executable inside a process because some
// memory modifications are relative to this address. // 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; return true;
} }

View file

@ -9,14 +9,36 @@ namespace Ryujinx.HLE.HOS.Tamper
{ {
class AtmosphereCompiler class AtmosphereCompiler
{ {
public ITamperProgram Compile(IEnumerable<string> 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}"); _exeAddress = exeAddress;
Logger.Debug?.Print(LogClass.TamperMachine, $"Heap address: {heapAddress:X16}"); _heapAddress = heapAddress;
_aliasAddress = aliasAddress;
_aslrAddress = aslrAddress;
_process = process;
}
public ITamperProgram Compile(string name, IEnumerable<string> 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 try
{ {
return CompileImpl(rawInstructions, exeAddress, heapAddress, process); return CompileImpl(name, rawInstructions);
} }
catch(TamperCompilationException exception) catch(TamperCompilationException exception)
{ {
@ -33,9 +55,9 @@ namespace Ryujinx.HLE.HOS.Tamper
return null; return null;
} }
private ITamperProgram CompileImpl(IEnumerable<string> rawInstructions, ulong exeAddress, ulong heapAddress, ITamperedProcess process) private ITamperProgram CompileImpl(string name, IEnumerable<string> rawInstructions)
{ {
CompilationContext context = new CompilationContext(exeAddress, heapAddress, process); CompilationContext context = new CompilationContext(_exeAddress, _heapAddress, _aliasAddress, _aslrAddress, _process);
context.BlockStack.Push(new OperationBlock(null)); context.BlockStack.Push(new OperationBlock(null));
// Parse the instructions. // 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)"); 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));
} }
} }
} }

View file

@ -8,10 +8,13 @@ namespace Ryujinx.HLE.HOS.Tamper
private Parameter<long> _pressedKeys; private Parameter<long> _pressedKeys;
private IOperation _entryPoint; private IOperation _entryPoint;
public string Name { get; }
public bool TampersCodeMemory { get; set; } = false;
public ITamperedProcess Process { get; } public ITamperedProcess Process { get; }
public AtmosphereProgram(ITamperedProcess process, Parameter<long> pressedKeys, IOperation entryPoint) public AtmosphereProgram(string name, ITamperedProcess process, Parameter<long> pressedKeys, IOperation entryPoint)
{ {
Name = name;
Process = process; Process = process;
_pressedKeys = pressedKeys; _pressedKeys = pressedKeys;
_entryPoint = entryPoint; _entryPoint = entryPoint;

View file

@ -10,32 +10,73 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
/// </summary> /// </summary>
class EndConditionalBlock 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) public static void Emit(byte[] instruction, CompilationContext context)
{ {
// 20000000 Emit(instruction, context, null);
}
private static void Emit(byte[] instruction, CompilationContext context, IEnumerable<IOperation> 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. // Use the conditional begin instruction stored in the stack.
instruction = context.CurrentBlock.BaseInstruction; var upperInstruction = context.CurrentBlock.BaseInstruction;
CodeType codeType = InstructionHelper.GetCodeType(instruction); CodeType codeType = InstructionHelper.GetCodeType(upperInstruction);
// Pop the current block of operations from the stack so control instructions // Pop the current block of operations from the stack so control instructions
// for the conditional can be emitted in the upper block. // for the conditional can be emitted in the upper block.
IEnumerable<IOperation> operations = context.CurrentOperations; IEnumerable<IOperation> operations = context.CurrentOperations;
context.BlockStack.Pop(); 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; ICondition condition;
switch (codeType) switch (codeType)
{ {
case CodeType.BeginMemoryConditionalBlock: case CodeType.BeginMemoryConditionalBlock:
condition = MemoryConditional.Emit(instruction, context); condition = MemoryConditional.Emit(upperInstruction, context);
break; break;
case CodeType.BeginKeypressConditionalBlock: case CodeType.BeginKeypressConditionalBlock:
condition = KeyPressConditional.Emit(instruction, context); condition = KeyPressConditional.Emit(upperInstruction, context);
break; break;
case CodeType.BeginRegisterConditionalBlock: case CodeType.BeginRegisterConditionalBlock:
condition = RegisterConditional.Emit(instruction, context); condition = RegisterConditional.Emit(upperInstruction, context);
break; 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: default:
throw new TamperCompilationException($"Conditional end does not match code type {codeType} in Atmosphere cheat"); 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 // Create a conditional block with the current operations and nest it in the upper
// block of the stack. // block of the stack.
IfBlock block = new IfBlock(condition, operations); IfBlock block = new IfBlock(condition, operations, operationsElse);
context.CurrentOperations.Add(block); context.CurrentOperations.Add(block);
} }
} }

View file

@ -20,17 +20,21 @@ namespace Ryujinx.HLE.HOS.Tamper
public Dictionary<byte, Register> StaticRegisters { get; } public Dictionary<byte, Register> StaticRegisters { get; }
public ulong ExeAddress { get; } public ulong ExeAddress { get; }
public ulong HeapAddress { 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; Process = process;
PressedKeys = new Parameter<long>(0); PressedKeys = new Parameter<long>(0);
BlockStack = new Stack<OperationBlock>(); BlockStack = new Stack<OperationBlock>();
Registers = new Dictionary<byte, Register>(); Registers = new Dictionary<byte, Register>();
SavedRegisters = new Dictionary<byte, Register>(); SavedRegisters = new Dictionary<byte, Register>();
StaticRegisters = new Dictionary<byte, Register>(); StaticRegisters = new Dictionary<byte, Register>();
ExeAddress = exeAddress; ExeAddress = exeAddress;
HeapAddress = heapAddress; HeapAddress = heapAddress;
AliasAddress = aliasAddress;
AslrAddress = aslrAddress;
} }
public Register GetRegister(byte index) public Register GetRegister(byte index)

View file

@ -4,6 +4,8 @@ namespace Ryujinx.HLE.HOS.Tamper
{ {
interface ITamperProgram interface ITamperProgram
{ {
string Name { get; }
bool TampersCodeMemory { get; set; }
ITamperedProcess Process { get; } ITamperedProcess Process { get; }
void Execute(ControllerKeys pressedKeys); void Execute(ControllerKeys pressedKeys);
} }

View file

@ -5,6 +5,9 @@ namespace Ryujinx.HLE.HOS.Tamper
interface ITamperedProcess interface ITamperedProcess
{ {
ProcessState State { get; } ProcessState State { get; }
bool TamperedCodeMemory { get; set; }
T ReadMemory<T>(ulong va) where T : unmanaged; T ReadMemory<T>(ulong va) where T : unmanaged;
void WriteMemory<T>(ulong va, T value) where T : unmanaged; void WriteMemory<T>(ulong va, T value) where T : unmanaged;
void PauseProcess(); void PauseProcess();

View file

@ -15,6 +15,12 @@ namespace Ryujinx.HLE.HOS.Tamper
case MemoryRegion.Heap: case MemoryRegion.Heap:
// Memory address is relative to the heap. // Memory address is relative to the heap.
return context.HeapAddress; 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: default:
throw new TamperCompilationException($"Invalid memory source {source} in Atmosphere cheat"); throw new TamperCompilationException($"Invalid memory source {source} in Atmosphere cheat");
} }

View file

@ -20,6 +20,16 @@ namespace Ryujinx.HLE.HOS.Tamper
/// <summary> /// <summary>
/// The address of the heap, as determined by the kernel. /// The address of the heap, as determined by the kernel.
/// </summary> /// </summary>
Heap = 0x1 Heap = 0x1,
/// <summary>
/// The address of the alias region, as determined by the kernel.
/// </summary>
Alias = 0x2,
/// <summary>
/// The address of the code region with address space layout randomization included.
/// </summary>
Asrl = 0x3,
} }
} }

View file

@ -6,27 +6,26 @@ namespace Ryujinx.HLE.HOS.Tamper.Operations
class IfBlock : IOperation class IfBlock : IOperation
{ {
private ICondition _condition; private ICondition _condition;
private IEnumerable<IOperation> _operations; private IEnumerable<IOperation> _operationsThen;
private IEnumerable<IOperation> _operationsElse;
public IfBlock(ICondition condition, IEnumerable<IOperation> operations) public IfBlock(ICondition condition, IEnumerable<IOperation> operationsThen, IEnumerable<IOperation> operationsElse)
{ {
_condition = condition; _condition = condition;
_operations = operations; _operationsThen = operationsThen;
} _operationsElse = operationsElse;
public IfBlock(ICondition condition, params IOperation[] operations)
{
_operations = operations;
} }
public void Execute() public void Execute()
{ {
if (!_condition.Evaluate()) IEnumerable<IOperation> operations = _condition.Evaluate() ? _operationsThen : _operationsElse;
if (operations == null)
{ {
return; return;
} }
foreach (IOperation op in _operations) foreach (IOperation op in operations)
{ {
op.Execute(); op.Execute();
} }

View file

@ -11,9 +11,11 @@ namespace Ryujinx.HLE.HOS.Tamper
public ProcessState State => _process.State; public ProcessState State => _process.State;
public bool TamperedCodeMemory { get; set; } = false;
public TamperedKProcess(KProcess process) public TamperedKProcess(KProcess process)
{ {
this._process = process; _process = process;
} }
private void AssertMemoryRegion<T>(ulong va, bool isWrite) where T : unmanaged private void AssertMemoryRegion<T>(ulong va, bool isWrite) where T : unmanaged
@ -32,11 +34,11 @@ namespace Ryujinx.HLE.HOS.Tamper
return; return;
} }
// TODO (Caian): It is unknown how PPTC behaves if the tamper modifies memory regions // TODO (Caian): The JIT does not support invalidating a code region so writing to code memory may not work
// belonging to code. So for now just prevent code tampering. // as intended, so taint the operation to issue a warning later.
if ((va >= _process.MemoryManager.CodeRegionStart) && (va + size <= _process.MemoryManager.CodeRegionEnd)) 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;
} }
} }

View file

@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS
} }
} }
internal void InstallAtmosphereCheat(IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress) internal void InstallAtmosphereCheat(string name, IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress)
{ {
if (!CanInstallOnPid(info.Process.Pid)) if (!CanInstallOnPid(info.Process.Pid))
{ {
@ -39,11 +39,13 @@ namespace Ryujinx.HLE.HOS
} }
ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process); ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process);
AtmosphereCompiler compiler = new AtmosphereCompiler(); AtmosphereCompiler compiler = new AtmosphereCompiler(exeAddress, info.HeapAddress, info.AliasAddress, info.AslrAddress, tamperedProcess);
ITamperProgram program = compiler.Compile(rawInstructions, exeAddress, info.HeapAddress, tamperedProcess); ITamperProgram program = compiler.Compile(name, rawInstructions);
if (program != null) if (program != null)
{ {
program.TampersCodeMemory = false;
_programs.Enqueue(program); _programs.Enqueue(program);
} }
@ -116,27 +118,27 @@ namespace Ryujinx.HLE.HOS
// Re-enqueue the tampering program because the process is still valid. // Re-enqueue the tampering program because the process is still valid.
_programs.Enqueue(program); _programs.Enqueue(program);
Logger.Debug?.Print(LogClass.TamperMachine, "Running tampering program"); Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}");
try try
{ {
ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys); ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys);
program.Process.TamperedCodeMemory = false;
program.Execute(pressedKeys); 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) 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); Logger.Debug?.Print(LogClass.TamperMachine, ex.Message);
} }