Created
August 20, 2024 18:46
-
-
Save Modder4869/7bc9998c4b0a4a4e5acda0d1f04c2a5a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class StringLiteralExtractor { | |
constructor(gameName, moduleName, version, verbose = false, limitStrings = true) { | |
this.gameName = gameName; | |
this.moduleName = moduleName; | |
this.version = version; | |
this.verbose = verbose; | |
this.limitStrings = limitStrings; | |
this.stringLiteralCount = 0; | |
this.mdl = Process.findModuleByName(this.moduleName); | |
this.metadataRva = ''; | |
this.usageRva = ''; | |
this.initializeRva = ''; | |
this.initializeBelowRva = ''; | |
this.filename = `${this.gameName}_stringliterals.json`; | |
this.stringLiterals = []; | |
this.error = 0; | |
this.showError = false; | |
this.stringCount = 0; | |
this.il2cpp_object_get_class = new NativeFunction( | |
this.mdl.findExportByName("il2cpp_object_get_class"), | |
"pointer", | |
["pointer"] | |
); | |
this.il2cpp_class_get_name = new NativeFunction( | |
this.mdl.findExportByName("il2cpp_class_get_name"), | |
"pointer", | |
["pointer"] | |
); | |
this.il2cpp_thread_attach = new NativeFunction( | |
this.mdl.findExportByName("il2cpp_thread_attach"), | |
"pointer", | |
["pointer"] | |
); | |
this.il2cpp_domain_get = new NativeFunction( | |
this.mdl.findExportByName("il2cpp_domain_get"), | |
"pointer", | |
[] | |
); | |
this.il2cpp_thread_attach(this.il2cpp_domain_get()); | |
if(this.limitStrings){ | |
if (this.metadataRva) { | |
var metaDataHeader = mdl.base.add(metadataRva).readPointer() | |
this.stringLiteralCount = metaDataHeader.add(4 * 4).readU32() / 8 | |
console.warn("found string literal count " , stringLiteralCount) | |
} | |
} | |
} | |
findInitializeRva(pattern, isBelowVersion27 = false) { | |
try { | |
const result = Memory.scanSync(this.mdl.base, this.mdl.size, pattern); | |
if (result.length > 0) { | |
let inst = Instruction.parse(result[0].address); | |
while (true) { | |
if (inst.mnemonic === 'call') { | |
const rva = ptr(inst.opStr).sub(this.mdl.base); | |
if (isBelowVersion27) { | |
this.initializeBelowRva = rva; | |
} else { | |
this.initializeRva = rva; | |
} | |
console.log(`Found RVA ${rva} at ${inst.address} for version ${isBelowVersion27 ? 'below' : 'v27 and above'}`); | |
break; | |
} | |
inst = Instruction.parse(inst.next); | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
} | |
findUsage() { | |
try { | |
const pat1 = Memory.scanSync(this.mdl.base, this.mdl.size, "8b ?? ?? ?? ?? ?? ?? 63 ?? ?? ba ?? ?? ?? ?? ff ?? ?? ?? ?? ?? ?? 89")[0]?.address; | |
const pat2 = Memory.scanSync(this.mdl.base, this.mdl.size, "8b ?? ?? ?? ?? ?? ?? 63 ?? ?? e8 ?? ?? ?? ?? ?? 89")[0]?.address; | |
const addr = pat1 || pat2; | |
if (addr) { | |
const inst = Instruction.parse(addr); | |
const rva = ptr(inst.operands.find(e => e.access === "r").value.disp); | |
return inst.next.add(rva).sub(this.mdl.base); | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
return false; | |
} | |
tryReadString(addr) { | |
try { | |
const length = addr.add(0x10).readU32(); | |
const val = addr.add(0x14).readUtf16String(); | |
return val.length === length ? val : null; | |
} catch (e) { | |
return null; | |
} | |
} | |
tryReadStringApi(addr) { | |
try { | |
const objectClass = this.il2cpp_object_get_class(addr); | |
const objectClassName = this.il2cpp_class_get_name(objectClass).readCString().toString(); | |
if (objectClassName === "String") { | |
return addr.add(0x14).readUtf16String(); | |
} | |
} catch (e) { | |
return null; | |
} | |
} | |
initializeMetaData() { | |
this.error = 0; | |
if (!this.usageRva) { | |
console.error("PLEASE SET USAGES RVA"); | |
return; | |
} | |
const usages = this.version <= 27 | |
? this.mdl.base.add(this.usageRva).readPointer().add(0x78).readPointer() | |
: this.mdl.base.add(this.usageRva).readPointer().add(0x68).readPointer(); | |
if (this.version <= 27) { | |
if (!this.initializeBelowRva) { | |
console.error("PLEASE SET RVA FOR InitializeMethodMetadataRange"); | |
return; | |
} | |
const initializeBelow = new NativeFunction(this.mdl.base.add(this.initializeBelowRva), 'void', ['int', 'int', 'pointer', 'bool']); | |
if (this.limitStrings && this.metadataRva) { | |
const metaDataHeader = this.mdl.base.add(this.metadataRva).readPointer(); | |
this.stringLiteralCount = metaDataHeader.add(4 * 4).readU32() / 8; | |
if (this.stringLiteralCount > 0) { | |
console.warn("Found string literal count:", this.stringLiteralCount); | |
} | |
} | |
try { | |
initializeBelow(0, 2147483647, Memory.alloc(24).writeByteArray(new Array(24).fill(0)), 0); | |
} catch (e) { | |
console.warn("Probably hit limit"); | |
} | |
for (let i = 0; i < 2000000; i++) { | |
this.processStringLiteral(usages, i); | |
if (this.error > 1000) { | |
console.error("Too many errors, stopping process"); | |
break; | |
} | |
} | |
} else { | |
if (!this.initializeRva) { | |
console.error("PLEASE SET RVA FOR InitializeRuntimeMetaData"); | |
return; | |
} | |
const initialize = new NativeFunction(this.mdl.base.add(this.initializeRva), 'pointer', ['pointer', 'bool']); | |
for (let i = 0; i < 2000000; i++) { | |
this.processStringLiteral(usages, i, initialize); | |
if (this.error > 1000) { | |
console.error("Too many errors, stopping process"); | |
break; | |
} | |
} | |
} | |
} | |
processStringLiteral(usages, index, initialize = null) { | |
try { | |
const usagePointer = ptr(usages).add(8 * index); | |
const pointer = initialize ? initialize(usagePointer, 0) : usagePointer.readPointer().readPointer(); | |
const isStringApi = this.tryReadStringApi(pointer); | |
if (isStringApi) { | |
if (this.verbose) { | |
console.error(`Init new string at ${usagePointer}, rva ${usagePointer.sub(this.mdl.base)}, content: ${isStringApi}`); | |
} | |
if (this.stringLiteralCount > 0 && this.stringCount >= this.stringLiteralCount) { | |
console.error("Stopping since string limit option is enabled at count", this.stringCount); | |
} | |
this.stringCount++; | |
this.stringLiterals.push({ | |
"value": isStringApi, | |
"address": usagePointer.sub(this.mdl.base) | |
}); | |
} | |
return true | |
} catch (e) { | |
if(this.showError){ | |
console.error(e, "Processing error", "index", index, "stringCount", this.stringCount, "stringLiteralCount", this.stringLiteralCount); | |
} | |
this.error++; | |
} | |
} | |
writeToFile() { | |
const file = new File(this.filename, "w"); | |
file.write(JSON.stringify(this.stringLiterals, null, 4)); | |
file.flush(); | |
file.close(); | |
console.log(`Done writing to ${this.filename}!`); | |
} | |
run() { | |
this.findInitializeRva("75 ?? ?? 8D ?? ?? ?? ?? ?? E8 ?? ???? ?? ?? 8D ?? ?? ?? ?? ?? E8", false); | |
this.findInitializeRva("75 ?? ?? 8b ?? ?? ?? ?? ?? ?? 63 ?? ?? ?? ?? ?? ?? 8b ?? ?? ?? ?? ?? 8b ?? ?? ?? ?? ?? ?? ?? 33 ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 8d ?? ?? ?? 8b ?? ?? ?? ?? ?? ?? e8", true); | |
if (!this.usageRva) { | |
console.log("Attempting to find usage using pattern, this will likely fail"); | |
this.usageRva = this.findUsage(); | |
console.log("Usage result:", this.usageRva); | |
} | |
this.initializeMetaData(); | |
this.writeToFile(); | |
console.log("done! writing ",this.stringCount) | |
} | |
} | |
const extractor = new StringLiteralExtractor("TEST", "GameAssembly.dll", 27, false, true); | |
extractor.run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment