Skip to content

Instantly share code, notes, and snippets.

@Modder4869
Created August 20, 2024 18:46
Show Gist options
  • Save Modder4869/7bc9998c4b0a4a4e5acda0d1f04c2a5a to your computer and use it in GitHub Desktop.
Save Modder4869/7bc9998c4b0a4a4e5acda0d1f04c2a5a to your computer and use it in GitHub Desktop.
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