mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
135 lines
6.5 KiB
C#
135 lines
6.5 KiB
C#
using System.Reflection;
|
|
using System.Reflection.PortableExecutable;
|
|
using Iced.Intel;
|
|
|
|
// Find the il2cpp methods that reference a given string literal.
|
|
//
|
|
// Neither the bytes nor the index of a literal appear directly in the code: on
|
|
// metadata v27+ the code loads a pointer from a metadata-usage slot, and statically
|
|
// that slot holds the encoded usage token `(type << 29) | (index << 1) | 1` with
|
|
// type 5 for string literals. So the search is:
|
|
// 1. resolve the literal's index from its text,
|
|
// 2. scan the image for the 8-byte token (this finds the slots),
|
|
// 3. scan the executable sections for instructions whose rip-relative operand
|
|
// lands on one of those slots. The displacement is the tail of such an
|
|
// instruction, so a cheap arithmetic pre-check narrows candidates and Iced
|
|
// then confirms by decoding from the candidate's start.
|
|
internal static class LiteralFinder
|
|
{
|
|
const ulong TokenBase = 5UL << 29;
|
|
|
|
static object Prop(object o, string n) => o.GetType().GetProperty(n)!.GetValue(o)!;
|
|
|
|
sealed class LineOutput : FormatterOutput
|
|
{
|
|
public string Value = "";
|
|
public override void Write(string text, FormatterTextKind kind) => Value += text;
|
|
}
|
|
|
|
public static void Run(object main, object metadata, string binaryPath, string needle)
|
|
{
|
|
var defs = (System.Collections.IEnumerable)metadata.GetType().GetField("methodDefs")!.GetValue(metadata)!;
|
|
var methods = new List<(object Method, ulong Address)>();
|
|
foreach (var m in defs.Cast<object>())
|
|
{
|
|
var address = (ulong)Prop(m, "MethodPointer");
|
|
if (address != 0) methods.Add((m, address));
|
|
}
|
|
methods.Sort((a, b) => a.Address.CompareTo(b.Address));
|
|
|
|
var table = metadata.GetType().GetField("stringLiterals",
|
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)!.GetValue(metadata) as Array;
|
|
var getLiteralText = metadata.GetType().GetMethod("GetStringLiteralFromIndex")!;
|
|
|
|
var tokens = new List<ulong>();
|
|
for (int i = 0; i < (table?.Length ?? 0); i++)
|
|
{
|
|
var text = (string)getLiteralText.Invoke(metadata, new object[] { (uint)i })!;
|
|
if (!text.Contains(needle, StringComparison.Ordinal)) continue;
|
|
// Both low-bit spellings resolve to the same index downstream.
|
|
tokens.Add(TokenBase | ((ulong)i << 1) | 1);
|
|
tokens.Add(TokenBase | ((ulong)i << 1));
|
|
Console.WriteLine($" literal #{i} = \"{text}\" token 0x{TokenBase | ((ulong)i << 1):X}");
|
|
}
|
|
if (tokens.Count == 0)
|
|
{
|
|
Console.WriteLine($"no literal contains \"{needle}\"");
|
|
return;
|
|
}
|
|
|
|
var bytes = File.ReadAllBytes(binaryPath);
|
|
using var pe = new PEReader(new MemoryStream(bytes));
|
|
var imageBase = pe.PEHeaders.PEHeader!.ImageBase;
|
|
|
|
// 1. slots: 8-byte tokens, so only probe 8-byte aligned positions.
|
|
var slots = new List<(string Section, ulong Address, ulong Value)>();
|
|
foreach (var section in pe.PEHeaders.SectionHeaders)
|
|
{
|
|
var size = Math.Min(section.SizeOfRawData, Math.Max(0, bytes.Length - section.PointerToRawData));
|
|
for (int i = section.PointerToRawData; i < section.PointerToRawData + size - 8; i += 8)
|
|
{
|
|
var value = BitConverter.ToUInt64(bytes, i);
|
|
if (!tokens.Contains(value)) continue;
|
|
var slot = imageBase + (ulong)section.VirtualAddress + (ulong)(i - section.PointerToRawData);
|
|
slots.Add((section.Name, slot, value));
|
|
Console.WriteLine($" slot 0x{slot:X} ({section.Name}, raw 0x{i:X}) = 0x{value:X}");
|
|
}
|
|
}
|
|
if (slots.Count == 0)
|
|
{
|
|
Console.WriteLine("no metadata-usage slot found");
|
|
return;
|
|
}
|
|
var slotSet = slots.Select(s => s.Address).ToHashSet();
|
|
|
|
// 2. references: pre-check the displacement, confirm by decoding from the start.
|
|
var formatter = new IntelFormatter();
|
|
int hits = 0, candidates = 0;
|
|
foreach (var section in pe.PEHeaders.SectionHeaders.Where(s => (s.SectionCharacteristics & SectionCharacteristics.MemExecute) != 0))
|
|
{
|
|
var rawToVa = (long)imageBase + section.VirtualAddress - section.PointerToRawData;
|
|
var size = Math.Min(section.SizeOfRawData, Math.Max(0, bytes.Length - section.PointerToRawData));
|
|
for (int i = section.PointerToRawData; i < section.PointerToRawData + size - 4; i++)
|
|
{
|
|
var disp = BitConverter.ToInt32(bytes, i);
|
|
var target = (ulong)((long)i + 4 + rawToVa + disp);
|
|
if (!slotSet.Contains(target)) continue;
|
|
candidates++;
|
|
for (int back = 0; back <= 7; back++)
|
|
{
|
|
var candidateRaw = i - back;
|
|
if (candidateRaw < section.PointerToRawData) break;
|
|
var reader = new ByteArrayCodeReader(bytes, candidateRaw, Math.Min(16, bytes.Length - candidateRaw));
|
|
var decoder = Decoder.Create(64, reader);
|
|
decoder.IP = (ulong)((long)candidateRaw + rawToVa);
|
|
decoder.Decode(out var instruction);
|
|
if (instruction.IsInvalid || !instruction.IsIPRelativeMemoryOperand) continue;
|
|
if (instruction.IPRelativeMemoryAddress != target) continue;
|
|
hits++;
|
|
var method = Nearest(methods, instruction.IP);
|
|
var name = method == null
|
|
? "<none>"
|
|
: Prop(Prop(method, "DeclaringType"), "Name") + "::" + Prop(method, "Name");
|
|
var offset = method == null ? 0 : instruction.IP - (ulong)Prop(method, "MethodPointer");
|
|
var output = new LineOutput();
|
|
formatter.Format(instruction, output);
|
|
Console.WriteLine($" REF 0x{instruction.IP:X} -> slot 0x{target:X} in {name} +0x{offset:X} [{output.Value}]");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Console.WriteLine($"slots {slots.Count}; displacement candidates {candidates}; confirmed references {hits}");
|
|
}
|
|
|
|
static object? Nearest(List<(object Method, ulong Address)> methods, ulong address)
|
|
{
|
|
object? best = null;
|
|
foreach (var (method, addr) in methods)
|
|
{
|
|
if (addr > address) break;
|
|
best = method;
|
|
}
|
|
return best;
|
|
}
|
|
}
|