diff --git a/index.js b/index.js index 198bae6..7e5dafb 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ const Env = require('./lib/env'); const Types = require('./lib/types'); const VM = require('./lib/vm'); const { checkJniResult } = require('./lib/result'); +const Deobfuscator = require('./lib/deobfuscator'); const jsizeSize = 4; const pointerSize = Process.pointerSize; @@ -566,6 +567,10 @@ class Runtime { return result; } + + deobfuscator(mapping) { + return new Deobfuscator(mapping); + } } function initFactoryFromApplication (factory, app) { diff --git a/lib/deobfuscator.js b/lib/deobfuscator.js new file mode 100644 index 0000000..ebcebfa --- /dev/null +++ b/lib/deobfuscator.js @@ -0,0 +1,103 @@ +class Deobfuscator { + #mapping; + #reverseMapping; + + constructor (mapping) { + if (mapping instanceof Map) { + this.#mapping = mapping; + } else { + const o = Object.entries(mapping).map(e => [e[0], { + realSpecifier: e[1].realSpecifier, + properties: new Map(Object.entries(e[1].properties)), + }]); + this.#mapping = new Map(o); + } + + this.#reverseMapping = new Map(); + for (const [key, value] of this.#mapping.entries()) { + this.#reverseMapping.set(value.realSpecifier, key); + } + } + + #getRealSpecifier (deobfuscatedSpecifier) { + const specifierMapping = this.#mapping.get(deobfuscatedSpecifier); + return specifierMapping?.realSpecifier; + } + + use (deobfuscatedSpecifier) { + const realSpecifier = this.#getRealSpecifier(deobfuscatedSpecifier) || deobfuscatedSpecifier; + let realUse; + Java.performNow(() => { + realUse = Java.use(realSpecifier); + }); + return this.wrap(realUse); + } + + choose (deobfuscatedSpecifier, callbacks) { + const realSpecifier = this.#getRealSpecifier(deobfuscatedSpecifier) || deobfuscatedSpecifier; + Java.perform(() => { + Java.choose(realSpecifier, { + onMatch: instance => callbacks.onMatch(this.wrap(instance)), + onComplete: () => callbacks.onComplete(), + }); + }); + } + + wrap (wrapper) { + const realSpecifier = wrapper.$className; + const deobfuscatedSpecifier = this.#reverseMapping.get(realSpecifier) || realSpecifier; + const propertiesMapping = this.#mapping.get(deobfuscatedSpecifier)?.properties || new Map(); + + return new Proxy(wrapper, { + get: (target, prop, receiver) => { + const realProp = propertiesMapping.get(prop) || prop; + const result = Reflect.get(target, realProp, receiver); + + if (result) { + if (result.value && result.value.$className) { + return this.#wrapField(result); + } + + if (result instanceof Function) { + return this.#wrapMethod(target, result); + } + } + + return result; + }, + }); + } + + #wrapField (field) { + return new Proxy(field, { + get: (target, prop, receiver) => { + if (prop === "value") { + return this.wrap(target.value); + } + return Reflect.get(target, prop, receiver); + } + }); + } + + #wrapMethod (wrapper, method) { + return new Proxy(method, { + apply: (_, thisArg, argumentsList) => { + const result = method.apply(thisArg, argumentsList); + if (result && result.$className) { + return this.wrap(result); + } + return result; + }, + set: (target, prop, newValue, receiver) => { + if (prop === "implementation") { + return Reflect.set(target, prop, newValue.bind(wrapper), receiver); + } + + return Reflect.set(target, prop, newValue, receiver); + }, + }); + } +} + + +module.exports = Deobfuscator;