From 057925184fbf4cdd7c334da37759d869f3017eb5 Mon Sep 17 00:00:00 2001 From: Yotam Nachum Date: Tue, 24 Jan 2023 00:03:34 +0200 Subject: [PATCH] Add deobfuscator method --- index.js | 5 +++ lib/deobfuscator.js | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 lib/deobfuscator.js diff --git a/index.js b/index.js index 198bae65..7e5dafbc 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 00000000..1c6fea28 --- /dev/null +++ b/lib/deobfuscator.js @@ -0,0 +1,105 @@ +const ClassFactory = require('./lib/class-factory'); + +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; + const realUse = ClassFactory.use(realSpecifier); + return this.wrap(realUse); + } + + choose (deobfuscatedSpecifier, callbacks) { + const realSpecifier = this.#getRealSpecifier(deobfuscatedSpecifier) || deobfuscatedSpecifier; + ClassFactory.choose(realSpecifier, { + onMatch: instance => callbacks.onMatch(this.wrap(instance)), + onComplete: () => callbacks.onComplete() + }); + } + + cast (obj, klass, owned) { + const casted = ClassFactory.cast(obj, klass, owned); + return this.wrap(casted); + } + + wrap (wrapper) { + const realSpecifier = wrapper.$n; + 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); + } + } + + // This code path should never be reached, however, returning it makes sure to have some sort of forward compatability. + 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;