Skip to content

Commit 176100f

Browse files
committedMay 30, 2019
Revive multi-instance support
There could be more than one script using frida-java, so we cannot assume that: A) We know of all the hooked methods. B) GetOatQuickMethodHeader() hasn't already been replaced. The challenge with A) is that ArtMethod doesn't have any unused bits we can use to tag the patched instances. We mitigate A) by detecting the entrypoint of Frida's libffi closures, which is going to be the same assuming agent and gadget aren't loaded at the same time and both using frida-java. For now we gracefully handle B) by assuming that the first script stays around for as long as the other scripts depending on this. It's not ideal, but it will have to do for now.
1 parent abe1ee2 commit 176100f

File tree

2 files changed

+67
-12
lines changed

2 files changed

+67
-12
lines changed
 

‎lib/android.js

+67-11
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const artThreadStateTransitions = {};
4444
let cachedApi = null;
4545
let thunkPage = null;
4646
let thunkOffset = 0;
47-
const hookedArtMethods = new Set();
4847
let taughtArtAboutHookedMethods = false;
4948
let jdwpSession = null;
5049
let socketpair = null;
@@ -1075,14 +1074,19 @@ function makeThunk (size, write) {
10751074
}
10761075

10771076
function notifyArtMethodHooked (method) {
1078-
hookedArtMethods.add(method.toString());
1079-
10801077
ensureArtKnowsHowToHandleHookedMethods();
10811078
}
10821079

1083-
function notifyArtMethodReverted (method) {
1084-
hookedArtMethods.delete(method.toString());
1085-
}
1080+
const LIBFFI_CLOSURE_MAGIC_X64 = uint64('0xfffffffff9158d4c');
1081+
const LIBFFI_CLOSURE_MAGIC_ARM = uint64('0xe51ff004e24fc008');
1082+
const LIBFFI_CLOSURE_MAGIC_ARM64 = uint64('0x10fffff158000090');
1083+
1084+
const libffiClosureEntrypointParsers = {
1085+
ia32: parseLibffiClosureEntrypointForIA32,
1086+
x64: parseLibffiClosureEntrypointForX64,
1087+
arm: parseLibffiClosureEntrypointForArm,
1088+
arm64: parseLibffiClosureEntrypointForArm64,
1089+
};
10861090

10871091
const goqmhFilterFuncGenerators = {
10881092
ia32: makeGoqmhFilterFuncForIA32,
@@ -1098,6 +1102,8 @@ function ensureArtKnowsHowToHandleHookedMethods () {
10981102
taughtArtAboutHookedMethods = true;
10991103

11001104
const api = getApi();
1105+
const methodOffsets = getArtMethodSpec(api.vm).offset;
1106+
const jniCodeOffset = methodOffsets.jniCode;
11011107

11021108
const getOatQuickMethodHeaderImpl = Module.findExportByName('libart.so',
11031109
(pointerSize === 4)
@@ -1111,21 +1117,72 @@ function ensureArtKnowsHowToHandleHookedMethods () {
11111117

11121118
const original = new NativeFunction(getOatQuickMethodHeaderImpl, ...signature);
11131119

1120+
const arch = Process.arch;
1121+
const parseClosure = libffiClosureEntrypointParsers[arch];
1122+
const fridaLibffiClosureEntrypoint = parseClosure(new NativeCallback(() => {}, 'void', []));
1123+
11141124
const replacement = new NativeCallback(function (method, pc) {
1115-
const isHookedMethod = hookedArtMethods.has(method.toString());
1125+
const jniImpl = method.add(jniCodeOffset).readPointer();
1126+
const entrypoint = parseClosure(jniImpl);
1127+
const isHookedMethod = entrypoint.equals(fridaLibffiClosureEntrypoint);
11161128
if (isHookedMethod) {
11171129
return NULL;
11181130
}
11191131

11201132
return original(method, pc);
11211133
}, ...signature);
11221134

1123-
const generateFilterFunc = goqmhFilterFuncGenerators[Process.arch];
1124-
const filter = generateFilterFunc(original, replacement, getArtMethodSpec(api.vm).offset.quickCode,
1135+
const generateFilterFunc = goqmhFilterFuncGenerators[arch];
1136+
const filter = generateFilterFunc(original, replacement, methodOffsets.quickCode,
11251137
api.artQuickGenericJniTrampoline);
11261138
filter._replacement = replacement;
11271139

1128-
Interceptor.replace(original, filter);
1140+
try {
1141+
Interceptor.replace(original, filter);
1142+
} catch (e) {
1143+
/*
1144+
* Already replaced by another script. For now we count on the other script
1145+
* not getting unloaded before ours.
1146+
*/
1147+
}
1148+
}
1149+
1150+
function parseLibffiClosureEntrypointForIA32 (closure) {
1151+
if (closure.readU8() !== 0xb8 || closure.add(5).readU8() !== 0xe9) {
1152+
return NULL;
1153+
}
1154+
1155+
const offset = closure.add(6).readS32();
1156+
1157+
return closure.add(10).add(offset);
1158+
}
1159+
1160+
function parseLibffiClosureEntrypointForX64 (closure) {
1161+
if (!closure.readU64().equals(LIBFFI_CLOSURE_MAGIC_X64)) {
1162+
return NULL;
1163+
}
1164+
1165+
if (closure.add(8).readU8() !== 0x25 || closure.add(9).readS32() !== 3) {
1166+
return NULL;
1167+
}
1168+
1169+
return closure.add(16).readPointer();
1170+
}
1171+
1172+
function parseLibffiClosureEntrypointForArm (closure) {
1173+
if (!closure.readU64().equals(LIBFFI_CLOSURE_MAGIC_ARM)) {
1174+
return NULL;
1175+
}
1176+
1177+
return closure.add(8).readPointer();
1178+
}
1179+
1180+
function parseLibffiClosureEntrypointForArm64 (closure) {
1181+
if (!closure.readU64().equals(LIBFFI_CLOSURE_MAGIC_ARM64)) {
1182+
return NULL;
1183+
}
1184+
1185+
return closure.add(16).readPointer();
11291186
}
11301187

11311188
function makeGoqmhFilterFuncForIA32 (original, replacement, quickCodeOffset, quickJniTrampoline) {
@@ -2366,7 +2423,6 @@ module.exports = {
23662423
ArtStackVisitor,
23672424
ArtMethod,
23682425
notifyArtMethodHooked,
2369-
notifyArtMethodReverted,
23702426
cloneArtMethod,
23712427
HandleVector,
23722428
deoptimizeEverything

‎lib/class-factory.js

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const {
1010
getArtThreadSpec,
1111
withRunnableArtThread,
1212
notifyArtMethodHooked,
13-
notifyArtMethodReverted,
1413
cloneArtMethod,
1514
HandleVector
1615
} = require('./android');

0 commit comments

Comments
 (0)
Please sign in to comment.