Skip to content

Instantly share code, notes, and snippets.

@apkunpacker
Forked from commonuserlol/index.ts
Created July 14, 2024 13:54
Show Gist options
  • Save apkunpacker/f9be2a610cce4c35b151a9e6ed2d6085 to your computer and use it in GitHub Desktop.
Save apkunpacker/f9be2a610cce4c35b151a9e6ed2d6085 to your computer and use it in GitHub Desktop.
ACTk ObscuredTypes hax with frida; tested on 2.0.2
import "frida-il2cpp-bridge";
function main() {
const AssemblyCSharp = Il2Cpp.domain.assembly("Assembly-CSharp").image;
// Note that on versions older than 2.x.y this isn't needed
// Since ACTk bundled directly into Assembly-CSharp
const ACTk_Runtime = Il2Cpp.domain.assembly("ACTk.Runtime").image;
// Target class
const PlayerData = AssemblyCSharp.class("PlayerData");
// Here I would show about `ObscuredInt`
// But `ObscuredTypes` are really similar
// So you have to just checkout dump and edit my code a little
const ObscuredInt = ACTk_Runtime.class("CodeStage.AntiCheat.ObscuredTypes.ObscuredInt");
// Cache
const map = new Map<Il2Cpp.Class, Il2Cpp.Object[]>();
const scanWithCache = (klass: Il2Cpp.Class): Il2Cpp.Object[] => {
const value = map.get(klass);
// Value is cached and still valid
if (value && value.every(v => !v.isNull()))
return value;
// No value in cache
const objects = Il2Cpp.gc.choose(klass);
if (objects.length > 0) {
// Object already created
map.set(klass, objects);
return objects;
}
// Found nothing, return empty array
return [];
}
const replaceValue = (instance: Il2Cpp.ValueType, value: number) => {
// `GetEncrypted(out int key)`
// out is reference (System.Int32 &key) but pointer is acceptable too
const key = Il2Cpp.alloc(4); // int32 size
instance.method("GetEncrypted").invoke(key);
// Remember, key is pointer
const keyValue = key.readU32();
// We don't need that pointer anymore
Il2Cpp.free(key);
const encryptedValue = ObscuredInt.method("Encrypt").invoke(value, keyValue);
instance.method("SetEncrypted").invoke(encryptedValue, keyValue);
}
// Every time when game calls this, it will OVERRIDE value
// So nothing will affect value unless you revert method
ObscuredInt.method("GetDecrypted").implementation = function () {
// Replace with yours
const myValue = 1337 * (4 * 2);
const objects = scanWithCache(PlayerData);
for (const instance of objects) {
// Target field
// Note that it's `ValueType`, not `Object`, so to get properties such as `.class`
// You should `.box()` it
// But do NOT use boxed value for replacement
const currentMoney = instance.field<Il2Cpp.ValueType>("currentMoney").value;
const oldValue = currentMoney.method<number>("GetDecrypted").invoke();
// Already replaced
if (oldValue == myValue)
// If target class have more than one instance - we have to check value on them too
continue;
// Stop everything
// Because ACTk in another thread can overwrite key before we change value
// Note that if we get error here, app will be deadlocked
// Let's hope everything will be ok
Il2Cpp.gc.stopWorld();
replaceValue(currentMoney, myValue);
console.log(`Replaced old value (${oldValue}) with new (${myValue})`);
// Start back
Il2Cpp.gc.startWorld();
}
// If we found nothing in `scanWithCache` call or it's not our target - return original value
return this.method<number>("GetDecrypted").invoke();
// Note that game might sub or add to our value
// If you really care about fancy value like `1337`
// Trace this class and find method which performs operation
// Or look into pseudocode using Ghidra / IDA and find how it works
// Personally, I don't care about this and would set just a big number
}
}
Il2Cpp.perform(main);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment