diff --git a/src/Chip8.Analysis/SelfModifyingCodeDetector.cs b/src/Chip8.Analysis/SelfModifyingCodeDetector.cs new file mode 100644 index 0000000..ced928c --- /dev/null +++ b/src/Chip8.Analysis/SelfModifyingCodeDetector.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Chip8.Core; + +namespace Chip8.Analysis; + +public sealed class SelfModifyingCodeReport +{ + public SelfModifyingCodeReport(IReadOnlyList events, IReadOnlyList staticWriters, IReadOnlyList staticTargets) + { + this.events = events; + this.staticWriters = staticWriters; + this.staticTargets = staticTargets; + } + + public IReadOnlyList events { get; } + public IReadOnlyList staticWriters { get; } + public IReadOnlyList staticTargets { get; } + + public bool detected => events.Count > 0; + public bool hasStaticSuspicion => staticWriters.Count > 0; + + public IReadOnlyList modifiedAddresses => + events.Select(item => item.targetAddress).Distinct().OrderBy(value => value).ToList(); + + public string render() + { + var builder = new StringBuilder(); + builder.Append("self-modifying code: ").Append(detected ? "detected" : "none").Append('\n'); + foreach (var smcEvent in events) + { + builder.Append(" ").Append(smcEvent).Append('\n'); + } + return builder.ToString(); + } +} + +public sealed class SelfModifyingCodeDetector +{ + public SelfModifyingCodeDetector(ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress) + { + this.entry = entry; + this.baseAddress = baseAddress; + } + + public ushort entry { get; } + public ushort baseAddress { get; } + + public SelfModifyingCodeReport detect(byte[] program, int maxSteps, Platform platform = Platform.Chip8, Quirks? quirks = null, int seed = 1, bool stopOnSpin = true) + { + var cfg = ControlFlowGraph.build(program, entry, baseAddress); + var watched = cfg.reachableByteAddresses(); + var staticFindings = staticSuspects(cfg, watched); + + var machine = new Machine(quirks, seed, platform); + machine.loadRom(program); + + var snapshot = new Dictionary(); + foreach (ushort address in watched) + { + snapshot[address] = machine.memory[address]; + } + + var events = new List(); + int spinRun = 0; + + for (int step = 0; step < maxSteps; step++) + { + if (machine.exited) + { + break; + } + + ushort writerAddress = machine.pc; + machine.step(); + + foreach (ushort address in watched) + { + byte current = machine.memory[address]; + if (current != snapshot[address]) + { + events.Add(new SmcEvent(step, writerAddress, address, snapshot[address], current)); + snapshot[address] = current; + } + } + + if (stopOnSpin && machine.pc == writerAddress) + { + spinRun++; + if (spinRun >= 2) + { + break; + } + } + else + { + spinRun = 0; + } + } + + var staticTargets = staticFindings.targets; + return new SelfModifyingCodeReport(events, staticFindings.writers, staticTargets); + } + + static (IReadOnlyList writers, IReadOnlyList targets) staticSuspects(ControlFlowGraph cfg, HashSet codeBytes) + { + var writers = new List(); + var indexTargets = new SortedSet(); + + foreach (var block in cfg.blocksInOrder) + { + foreach (var info in block.instructions) + { + if (info.loadsIndex && info.indexValue.HasValue && codeBytes.Contains(info.indexValue.Value)) + { + indexTargets.Add(info.indexValue.Value); + } + if (info.writesMemory) + { + writers.Add(info); + } + } + } + + bool codeTargeted = indexTargets.Count > 0; + var flaggedWriters = codeTargeted ? writers : new List(); + return (flaggedWriters, indexTargets.ToList()); + } +}