public bool PlusSearch(int methodCount, int typeDefinitionsCount) { Console.WriteLine("Looking for registration functions..."); var execList = new List <SectionHeader>(); var dataList = new List <SectionHeader>(); foreach (var section in sections) { switch (section.Characteristics) { case 0x60000020: Console.WriteLine("\tIdentified execute section " + section.Name); execList.Add(section); break; case 0x40000040: case 0xC0000040: Console.WriteLine("\tIdentified data section " + section.Name); dataList.Add(section); break; } } ulong codeRegistration; ulong metadataRegistration; Console.WriteLine("Attempting to locate code and metadata registration functions..."); var plusSearch = new PlusSearch(this, methodCount, typeDefinitionsCount, maxMetadataUsages); var dataSections = dataList.ToArray(); var execSections = execList.ToArray(); plusSearch.SetSearch(imageBase, dataSections); plusSearch.SetDataSections(imageBase, dataSections); plusSearch.SetExecSections(imageBase, execSections); if (is32Bit) { Console.WriteLine("\t(32-bit PE)"); codeRegistration = plusSearch.FindCodeRegistration(); plusSearch.SetExecSections(imageBase, dataSections); metadataRegistration = plusSearch.FindMetadataRegistration(); } else { Console.WriteLine("\t(64-bit PE)"); codeRegistration = plusSearch.FindCodeRegistration64Bit(); plusSearch.SetExecSections(imageBase, dataSections); metadataRegistration = plusSearch.FindMetadataRegistration64Bit(); } if (codeRegistration == 0 || metadataRegistration == 0) { Console.WriteLine("\tFailed to find code and metadata registration functions using primary location method (probably because we're post-2019), checking if we can use the fallback..."); //Get export for il2cpp_init function var virtualAddrInit = GetVirtualAddressOfUnmanagedExportByName("il2cpp_init"); if (virtualAddrInit <= 0) { Console.WriteLine("\tCould not find exported il2cpp_init function! Fallback method failed, execution will fail!"); goto bailout; } Console.WriteLine($"\tFound il2cpp_init export (resolves to virtual addr 0x{virtualAddrInit:X}), using fallback method to find Code and Metadata registration..."); List <Instruction> initMethodBody = Utils.GetMethodBodyAtRawAddress(this, MapVirtualAddressToRaw(virtualAddrInit), false); //Look for a JMP for older il2cpp versions, on newer ones it appears to be a CALL var callToRuntimeInit = initMethodBody.Find(i => i.Mnemonic == ud_mnemonic_code.UD_Ijmp); if (callToRuntimeInit == null) { callToRuntimeInit = initMethodBody.FindLast(i => i.Mnemonic == ud_mnemonic_code.UD_Icall); } if (callToRuntimeInit == null) { Console.WriteLine("\tCould not find a call to Runtime::Init! Fallback method failed!"); goto bailout; } var virtualAddressRuntimeInit = Utils.GetJumpTarget(callToRuntimeInit, callToRuntimeInit.PC + virtualAddrInit); Console.WriteLine($"\tLocated probable Runtime::Init function at virtual addr 0x{virtualAddressRuntimeInit:X}"); List <Instruction> methodBodyRuntimeInit = Utils.GetMethodBodyAtRawAddress(this, MapVirtualAddressToRaw(virtualAddressRuntimeInit), false); //This is kind of sketchy, but look for a global read (i.e an LEA where the second base is RIP), as that's the framework version read, then there's a MOV, then 4 calls, the third of which is our target. //So as to ensure compat with 2018, ensure we have a call before this LEA. var minimumIndex = methodBodyRuntimeInit.FindIndex(i => i.Mnemonic == ud_mnemonic_code.UD_Icall); var idx = -1; var indexOfFrameworkVersionLoad = methodBodyRuntimeInit.FindIndex(i => idx++ > minimumIndex && i.Mnemonic == ud_mnemonic_code.UD_Ilea && i.Operands.Last().Base == ud_type.UD_R_RIP); var instructionsFromThatPoint = methodBodyRuntimeInit.Skip(indexOfFrameworkVersionLoad + 1).TakeWhile(i => true).ToList(); var calls = instructionsFromThatPoint.Where(i => i.Mnemonic == ud_mnemonic_code.UD_Icall).ToList(); if (calls.Count < 3) { Console.WriteLine("\tRuntime::Init does not call enough methods for us to locate ExecuteInitializations! Fallback failed!"); goto bailout; } var thirdCall = calls[2]; //Now we have the address of the ExecuteInitializations function var virtAddrExecuteInit = Utils.GetJumpTarget(thirdCall, thirdCall.PC + virtualAddressRuntimeInit); Console.WriteLine($"\tLocated probable ExecuteInitializations function at virt addr 0x{virtAddrExecuteInit:X}"); //We peek this as we only want the second instruction. List <Instruction> execInitMethodBody = Utils.GetMethodBodyAtRawAddress(this, MapVirtualAddressToRaw(virtAddrExecuteInit), true); if (execInitMethodBody.Count < 2 || execInitMethodBody[1].Error || execInitMethodBody[1].Mnemonic != ud_mnemonic_code.UD_Imov) { Console.WriteLine("\tMissing or invalid second instruction in ExecuteInitializations, fallback failed!"); goto bailout; } var offset = Utils.GetOffsetFromMemoryAccess(execInitMethodBody[1], execInitMethodBody[1].Operands[1]); //This SHOULD be the address of the global list of callbacks il2cpp executes on boot, which should only contain one item, that being the function which invokes the code + metadata registration var addrGlobalCallbackList = virtAddrExecuteInit + offset; var bytesNotToCheck = execInitMethodBody[1].Bytes; Console.WriteLine($"\tGot what we believe is the address of the global callback list - 0x{addrGlobalCallbackList:X}. Searching for another MOV instruction that references it within the .text segment..."); var textSection = sections.First(s => s.Name == ".text"); var toDisasm = raw.SubArray((int)textSection.PointerToRawData, (int)textSection.SizeOfRawData); var allInstructionsInTextSection = Utils.DisassembleBytes(toDisasm); Console.WriteLine($"\tDisassembled entire .text section, into {allInstructionsInTextSection.Count} instructions."); var allMOVs = allInstructionsInTextSection.Where(i => i.Mnemonic == ud_mnemonic_code.UD_Imov && i.Operands[0].Base == ud_type.UD_R_RIP).ToList(); Console.WriteLine($"\t\t...of which {allMOVs.Count} are MOV instructions with a global/Rip first base"); var references = allMOVs.AsParallel().Where(mov => { var rawMemoryRead = Utils.GetOffsetFromMemoryAccess(mov, mov.Operands[0]); var virtMemoryRead = rawMemoryRead + textSection.VirtualAddress + imageBase; return(virtMemoryRead == addrGlobalCallbackList); }).ToList(); Console.WriteLine($"\t\t...of which {references.Count} have a first parameter as that callback list."); if (references.Count != 1) { Console.WriteLine("\tExpected only one reference, but didn't get that, fallback failed!"); goto bailout; } var callbackListWrite = references[0]; var virtualAddressOfInstruction = callbackListWrite.PC + imageBase + textSection.VirtualAddress - (ulong)callbackListWrite.Length; Console.WriteLine($"\tLocated a single write reference to callback list, therefore identified callback registration function, which must contain the instruction at virt address 0x{virtualAddressOfInstruction:X}"); var instructionIdx = allInstructionsInTextSection.IndexOf(callbackListWrite); var instructionsUpToCallbackListWrite = allInstructionsInTextSection.Take(instructionIdx).ToList(); instructionsUpToCallbackListWrite.Reverse(); var indexOfFirstInt3 = instructionsUpToCallbackListWrite.FindIndex(i => i.Mnemonic == ud_mnemonic_code.UD_Iint3); var firstInstructionInRegisterCallback = instructionsUpToCallbackListWrite[indexOfFirstInt3 - 1]; var virtAddrRegisterCallback = firstInstructionInRegisterCallback.PC + imageBase + textSection.VirtualAddress - (ulong)firstInstructionInRegisterCallback.Length; Console.WriteLine($"\tGot address of register callback function to be 0x{virtAddrRegisterCallback:X}"); var callToRegisterCallback = allInstructionsInTextSection.Find(i => (i.Mnemonic == ud_mnemonic_code.UD_Icall || i.Mnemonic == ud_mnemonic_code.UD_Ijmp) && Utils.GetJumpTarget(i, imageBase + textSection.VirtualAddress + i.PC) == virtAddrRegisterCallback); var addrCallToRegCallback = callToRegisterCallback.PC + imageBase + textSection.VirtualAddress - (ulong)callToRegisterCallback.Length; Console.WriteLine($"\tFound a call to that function at 0x{addrCallToRegCallback:X}"); var indexOfCallToRegisterCallback = allInstructionsInTextSection.IndexOf(callToRegisterCallback); Instruction loadOfAddressToCodegenRegistrationFunction = null; for (var i = indexOfCallToRegisterCallback; i > 0; i--) { if (allInstructionsInTextSection[i].Mnemonic == ud_mnemonic_code.UD_Ilea && allInstructionsInTextSection[i].Operands[0].Base == ud_type.UD_R_RDX) { loadOfAddressToCodegenRegistrationFunction = allInstructionsInTextSection[i]; break; } } if (loadOfAddressToCodegenRegistrationFunction == null) { Console.WriteLine("Failed to find an instruction loading the address of the codegen reg function. Fallback failed."); goto bailout; } Console.WriteLine("\tGot instruction containing the address of the codegen registration function: " + loadOfAddressToCodegenRegistrationFunction); var virtAddrS_Il2CppCodegenRegistration = Utils.GetOffsetFromMemoryAccess(loadOfAddressToCodegenRegistrationFunction, loadOfAddressToCodegenRegistrationFunction.Operands[1]) + imageBase + textSection.VirtualAddress; Console.WriteLine($"\tWhich means s_Il2CppCodegenRegistration is in-binary at 0x{virtAddrS_Il2CppCodegenRegistration:X}"); //This should consist of LEA, LEA, LEA, JMP List <Instruction> methodBodyS_Il2CppCodegenRegistration = Utils.GetMethodBodyAtRawAddress(this, MapVirtualAddressToRaw(virtAddrS_Il2CppCodegenRegistration), false); var loadMetadataRegistration = methodBodyS_Il2CppCodegenRegistration.Find(i => i.Mnemonic == ud_mnemonic_code.UD_Ilea && i.Operands[0].Base == ud_type.UD_R_RDX); var loadCodeRegistration = methodBodyS_Il2CppCodegenRegistration.Find(i => i.Mnemonic == ud_mnemonic_code.UD_Ilea && i.Operands[0].Base == ud_type.UD_R_RCX); //This one's RCX not RDX metadataRegistration = Utils.GetOffsetFromMemoryAccess(loadMetadataRegistration, loadMetadataRegistration.Operands[1]) + virtAddrS_Il2CppCodegenRegistration; codeRegistration = Utils.GetOffsetFromMemoryAccess(loadCodeRegistration, loadCodeRegistration.Operands[1]) + virtAddrS_Il2CppCodegenRegistration; } bailout: Console.WriteLine("Initializing with located addresses:"); return(AutoInit(codeRegistration, metadataRegistration)); }
public void Init(ulong codeRegistration, ulong metadataRegistration) { Console.WriteLine("Initializing PE data..."); this.codeRegistration = ReadClassAtVirtualAddress <Il2CppCodeRegistration>(codeRegistration); this.metadataRegistration = ReadClassAtVirtualAddress <Il2CppMetadataRegistration>(metadataRegistration); Console.Write("\tReading generic instances..."); var start = DateTime.Now; genericInsts = Array.ConvertAll(ReadClassArrayAtVirtualAddress <ulong>(this.metadataRegistration.genericInsts, this.metadataRegistration.genericInstsCount), x => ReadClassAtVirtualAddress <Il2CppGenericInst>(x)); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading generic method pointers..."); start = DateTime.Now; genericMethodPointers = ReadClassArrayAtVirtualAddress <ulong>(this.codeRegistration.genericMethodPointers, (long)this.codeRegistration.genericMethodPointersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading invoker pointers..."); start = DateTime.Now; invokerPointers = ReadClassArrayAtVirtualAddress <ulong>(this.codeRegistration.invokerPointers, (long)this.codeRegistration.invokerPointersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading custom attribute generators..."); start = DateTime.Now; customAttributeGenerators = ReadClassArrayAtVirtualAddress <ulong>(this.codeRegistration.customAttributeGeneratorListAddress, this.codeRegistration.customAttributeCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading field offsets..."); start = DateTime.Now; fieldOffsets = ReadClassArrayAtVirtualAddress <long>(this.metadataRegistration.fieldOffsetListAddress, this.metadataRegistration.fieldOffsetsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading types..."); start = DateTime.Now; var typesAddress = ReadClassArrayAtVirtualAddress <ulong>(this.metadataRegistration.typeAddressListAddress, this.metadataRegistration.numTypes); types = new Il2CppType[this.metadataRegistration.numTypes]; for (var i = 0; i < this.metadataRegistration.numTypes; ++i) { types[i] = ReadClassAtVirtualAddress <Il2CppType>(typesAddress[i]); types[i].Init(); typesDict.Add(typesAddress[i], types[i]); } Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading metadata usages..."); start = DateTime.Now; metadataUsages = ReadClassArrayAtVirtualAddress <ulong>(this.metadataRegistration.metadataUsages, maxMetadataUsages); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); if (Program.MetadataVersion >= 24.2f) { Console.WriteLine("\tReading code gen modules..."); start = DateTime.Now; var codeGenModulePtrs = ReadClassArrayAtVirtualAddress <ulong>(this.codeRegistration.addrCodeGenModulePtrs, (long)this.codeRegistration.codeGenModulesCount); codeGenModules = new Il2CppCodeGenModule[codeGenModulePtrs.Length]; codeGenModuleMethodPointers = new ulong[codeGenModulePtrs.Length][]; for (int i = 0; i < codeGenModulePtrs.Length; i++) { var codeGenModule = ReadClassAtVirtualAddress <Il2CppCodeGenModule>(codeGenModulePtrs[i]); codeGenModules[i] = codeGenModule; string name = ReadStringToNull(MapVirtualAddressToRaw(codeGenModule.moduleName)); Console.WriteLine($"\t\t-Read module data for {name}, contains {codeGenModule.methodPointerCount} method pointers starting at 0x{codeGenModule.methodPointers:X}"); if (codeGenModule.methodPointerCount > 0) { try { var ptrs = ReadClassArrayAtVirtualAddress <ulong>(codeGenModule.methodPointers, codeGenModule.methodPointerCount); codeGenModuleMethodPointers[i] = ptrs; Console.WriteLine($"\t\t\t-Read {codeGenModule.methodPointerCount} method pointers."); } catch (Exception e) { Console.WriteLine($"\t\t\tWARNING: Unable to get function pointers for {name}: {e.Message}"); codeGenModuleMethodPointers[i] = new ulong[codeGenModule.methodPointerCount]; } } } Console.WriteLine($"\tOK ({(DateTime.Now - start).TotalMilliseconds} ms)"); } else { Console.Write("\tReading method pointers..."); start = DateTime.Now; methodPointers = ReadClassArrayAtVirtualAddress <ulong>(this.codeRegistration.methodPointers, (long)this.codeRegistration.methodPointersCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); } Console.Write("\tReading generic method tables..."); start = DateTime.Now; genericMethodTables = ReadClassArrayAtVirtualAddress <Il2CppGenericMethodFunctionsDefinitions>(this.metadataRegistration.genericMethodTable, this.metadataRegistration.genericMethodTableCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading method specifications..."); start = DateTime.Now; methodSpecs = ReadClassArrayAtVirtualAddress <Il2CppMethodSpec>(this.metadataRegistration.methodSpecs, this.metadataRegistration.methodSpecsCount); Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); Console.Write("\tReading generic methods..."); start = DateTime.Now; genericMethodDictionary = new Dictionary <int, ulong>(genericMethodTables.Length); foreach (var table in genericMethodTables) { var index = methodSpecs[table.genericMethodIndex].methodDefinitionIndex; if (!genericMethodDictionary.ContainsKey(index)) { genericMethodDictionary.Add(index, genericMethodPointers[table.indices.methodIndex]); } } Console.WriteLine($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)"); }