Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deobfuscator method #279

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -566,6 +567,10 @@ class Runtime {

return result;
}

deobfuscator(mapping) {
return new Deobfuscator(mapping);
}
}

function initFactoryFromApplication (factory, app) {
Expand Down
2 changes: 1 addition & 1 deletion lib/class-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2166,7 +2166,7 @@ Object.defineProperties(Field.prototype, {
\tvalue: ${this.value},
}`;
return multilineString.split('\n').map(l => l.length > 200 ? l.slice(0, l.indexOf(' ') + 1) + '...,' : l).join('\n');
},
}
}
});

Expand Down
135 changes: 135 additions & 0 deletions lib/deobfuscator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// const Java = require('./lib/class-factory');

class UnexpectedPropertyError extends Error {
}

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,
fields: new Map(Object.entries(e[1].fields)),
methods: new Map(Object.entries(e[1].methods))
}]);
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 = Java.use(realSpecifier);
return this.wrap(realUse);
}

choose (deobfuscatedSpecifier, callbacks) {
const realSpecifier = this.#getRealSpecifier(deobfuscatedSpecifier) ?? deobfuscatedSpecifier;
Java.choose(realSpecifier, {
onMatch: instance => callbacks.onMatch(this.wrap(instance)),
onComplete: () => callbacks.onComplete()
});
}

cast (obj, klass, owned) {
const casted = Java.cast(obj, klass, owned);
return this.wrap(casted);
}

wrap (wrapper) {
const realSpecifier = wrapper.$n;
const deobfuscatedSpecifier = this.#reverseMapping.get(realSpecifier) ?? realSpecifier;

const methodsMapping = this.#mapping.get(deobfuscatedSpecifier)?.methods ?? new Map();
const fieldsMapping = this.#mapping.get(deobfuscatedSpecifier)?.fields ?? new Map();

return new Proxy(wrapper, {
get: (target, prop, receiver) => {
if ((methodProp = methodsMapping.get(prop)) !== undefined) {
const result = Reflect.get(target, methodProp, receiver);
if (!(result instanceof Function)) {
throw new UnexpectedPropertyError(`Mapped property was expected to be a method, got ${result} of type ${typeof result} instead`);
}
return this.#wrapMethod(target, result)
}

if ((fieldProp = fieldsMapping.get(prop)) !== undefined) {
let result = Reflect.get(target, realProp, receiver);
while (result instanceof Function) {
fieldProp = "_" + fieldProp;
result = Reflect.get(target, realProp, receiver);
}

if (!(obj.value && obj.value.$className)) {
throw new UnexpectedPropertyError(`Mapped property was expected to be a field, got ${result} of type ${typeof result} instead`);
}

return this.#wrapField(result)
}

const result = Reflect.get(target, prop, receiver);
return this.#wrapUnknown(target, result);
}
});
}

#wrapUnknown(target, obj) {
if (obj) {
if (obj.value && obj.value.$className) {
return this.#wrapField(obj);
}

if (obj instanceof Function) {
return this.#wrapMethod(target, obj);
}
}

// This code path should never be reached, however, returning it makes sure to have some sort of forward compatability.
return obj;
}

#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;