Skip to content

Commit

Permalink
android: Improve ART compatibility
Browse files Browse the repository at this point in the history
- Look for symbols when exports are missing, now that Frida supports
  parsing `.gnu_debugdata`.
- Handle change of signature of runFlip.

Co-authored-by: matbrik <[email protected]>
  • Loading branch information
oleavr and matbrik committed Jan 7, 2025
1 parent 4532afa commit 628d306
Showing 1 changed file with 100 additions and 89 deletions.
189 changes: 100 additions & 89 deletions lib/android.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,20 @@ function _getApi () {

const temporaryApi = {
module: vmModule,
find (name) {
const { module } = this;
let address = module.findExportByName(name);
if (address === null) {
address = module.findSymbolByName(name);
}
return address;
},
flavor,
addLocalReference: null
};

const pending = isArt
? [{
module: vmModule.path,
? {
functions: {
JNI_GetCreatedJavaVMs: ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']],

Expand Down Expand Up @@ -312,7 +319,7 @@ function _getApi () {
this.isDebuggerActive = () => !!address.readU8();
}
},
optionals: [
optionals: new Set([
'artInterpreterToCompiledCodeBridge',
'_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadENS_6ObjPtrINS_6mirror6ObjectEEE',
'_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE',
Expand Down Expand Up @@ -360,10 +367,9 @@ function _getApi () {
'_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID',
'_ZN3art11interpreter18GetNterpEntryPointEv',
'_ZN3art7Monitor17TranslateLocationEPNS_9ArtMethodEjPPKcPi'
]
}]
: [{
module: vmModule.path,
])
}
: {
functions: {
_Z20dvmDecodeIndirectRefP6ThreadP8_jobject: ['dvmDecodeIndirectRef', 'pointer', ['pointer', 'pointer']],
_Z15dvmUseJNIBridgeP6MethodPv: ['dvmUseJNIBridge', 'void', ['pointer', 'pointer']],
Expand All @@ -380,52 +386,41 @@ function _getApi () {
this.gDvm = address;
}
}
}];
};

const {
functions = {},
variables = {},
optionals = new Set()
} = pending;

const missing = [];

pending.forEach(function (api) {
const functions = api.functions || {};
const variables = api.variables || {};
const optionals = new Set(api.optionals || []);

const exportByName = Module
.enumerateExports(api.module)
.reduce(function (result, exp) {
result[exp.name] = exp;
return result;
}, {});

Object.keys(functions)
.forEach(function (name) {
const exp = exportByName[name];
if (exp !== undefined && exp.type === 'function') {
const signature = functions[name];
if (typeof signature === 'function') {
signature.call(temporaryApi, exp.address);
} else {
temporaryApi[signature[0]] = new NativeFunction(exp.address, signature[1], signature[2], nativeFunctionOptions);
}
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
});
for (const [name, signature] of Object.entries(functions)) {
const address = temporaryApi.find(name);
if (address !== null) {
if (typeof signature === 'function') {
signature.call(temporaryApi, address);
} else {
temporaryApi[signature[0]] = new NativeFunction(address, signature[1], signature[2], nativeFunctionOptions);
}
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
}

Object.keys(variables)
.forEach(function (name) {
const exp = exportByName[name];
if (exp !== undefined && exp.type === 'variable') {
const handler = variables[name];
handler.call(temporaryApi, exp.address);
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
});
});
for (const [name, handler] of Object.entries(variables)) {
const address = temporaryApi.find(name);
if (address !== null) {
handler.call(temporaryApi, address);
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
}

if (missing.length > 0) {
throw new Error('Java API only partially available; please file a bug. Missing: ' + missing.join(', '));
Expand Down Expand Up @@ -501,9 +496,11 @@ function _getApi () {
}
if (temporaryApi['art::interpreter::GetNterpEntryPoint'] !== undefined) {
temporaryApi.artNterpEntryPoint = temporaryApi['art::interpreter::GetNterpEntryPoint']();
} else {
temporaryApi.artNterpEntryPoint = temporaryApi.find('ExecuteNterpImpl');
}

artController = makeArtController(vm);
artController = makeArtController(temporaryApi, vm);

fixupArtQuickDeliverExceptionBug(temporaryApi);

Expand All @@ -518,7 +515,7 @@ function _getApi () {
});
}

const cxxImports = Module.enumerateImports(vmModule.path)
const cxxImports = vmModule.enumerateImports()
.filter(imp => imp.name.indexOf('_Z') === 0)
.reduce((result, imp) => {
result[imp.name] = imp.address;
Expand All @@ -536,8 +533,11 @@ function tryGetEnvJvmti (vm, runtime) {
let env = null;

vm.perform(() => {
const ensurePluginLoaded = new NativeFunction(
Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
const ensurePluginLoadedAddr = getApi().find('_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE');
if (ensurePluginLoadedAddr === null) {
return;
}
const ensurePluginLoaded = new NativeFunction(ensurePluginLoadedAddr,
'bool',
['pointer', 'pointer', 'pointer']);
const errorPtr = Memory.alloc(pointerSize);
Expand Down Expand Up @@ -623,7 +623,8 @@ function _getArtRuntimeSpec (api) {

const apiLevel = getAndroidApiLevel();
const codename = getAndroidCodename();
const isApiLevel34OrApexEquivalent = Module.findExportByName('libart.so', '_ZN3art7AppInfo29GetPrimaryApkReferenceProfileEv') !== null;
const isApiLevel34OrApexEquivalent = api.find('_ZN3art7AppInfo29GetPrimaryApkReferenceProfileEv') !== null ||
api.find('_ZN3art6Thread15RunFlipFunctionEPS0_') !== null;

let spec = null;

Expand Down Expand Up @@ -685,7 +686,7 @@ function _getArtRuntimeSpec (api) {
}

spec.offset.instrumentation = tryDetectInstrumentationOffset(api);
spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset();
spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset(api);

return spec;
}
Expand Down Expand Up @@ -771,8 +772,8 @@ const jniIdsIndirectionOffsetParsers = {
arm64: parseArm64JniIdsIndirectionOffset
};

function tryDetectJniIdsIndirectionOffset () {
const impl = Module.findExportByName('libart.so', '_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
function tryDetectJniIdsIndirectionOffset (api) {
const impl = api.find('_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
if (impl === null) {
return null;
}
Expand Down Expand Up @@ -1568,7 +1569,7 @@ function notifyArtMethodHooked (method, vm) {
ensureArtKnowsHowToHandleReplacementMethods(vm);
}

function makeArtController (vm) {
function makeArtController (api, vm) {
const threadOffsets = getArtThreadSpec(vm).offset;
const managedStackOffsets = getArtManagedStackSpec().offset;

Expand Down Expand Up @@ -1776,10 +1777,9 @@ on_leave_gc_concurrent_copying_copying_phase (GumInvocationContext * ic)
const replacements = methods.add(methodsSize);
const lastSeenArtMethod = replacements.add(replacementsSize);

const getOatQuickMethodHeaderImpl = Module.findExportByName('libart.so',
(pointerSize === 4)
? '_ZN3art9ArtMethod23GetOatQuickMethodHeaderEj'
: '_ZN3art9ArtMethod23GetOatQuickMethodHeaderEm');
const getOatQuickMethodHeaderImpl = api.find((pointerSize === 4)
? '_ZN3art9ArtMethod23GetOatQuickMethodHeaderEj'
: '_ZN3art9ArtMethod23GetOatQuickMethodHeaderEm');

const cm = new CModule(code, {
lock,
Expand Down Expand Up @@ -1864,8 +1864,9 @@ function instrumentArtMethodInvocationFromInterpreter () {
artInterpreterDoCallExportRegex = /^_ZN3art11interpreter6DoCallILb[0-1]EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtbPNS_6JValueE$/;
}

for (const exp of Module.enumerateExports('libart.so').filter(exp => artInterpreterDoCallExportRegex.test(exp.name))) {
Interceptor.attach(exp.address, artController.hooks.Interpreter.doCall);
const art = getApi().module;
for (const entry of [...art.enumerateExports(), ...art.enumerateSymbols()].filter(entry => artInterpreterDoCallExportRegex.test(entry.name))) {
Interceptor.attach(entry.address, artController.hooks.Interpreter.doCall);
}
}

Expand Down Expand Up @@ -1893,29 +1894,24 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {

const apiLevel = getAndroidApiLevel();

const mayUseCollector = (apiLevel > 28)
? (type) => {
const impl = Module.findExportByName('libart.so', '_ZNK3art2gc4Heap15MayUseCollectorENS0_13CollectorTypeE');
if (impl === null) {
return false;
}
return new NativeFunction(impl, 'bool', ['pointer', 'int'])(getApi().artHeap, type);
}
: () => false;
const kCollectorTypeCMC = 3;
let copyingPhase = null;
const api = getApi();
if (apiLevel > 28) {
copyingPhase = api.find('_ZN3art2gc9collector17ConcurrentCopying12CopyingPhaseEv');
} else if (apiLevel > 22) {
copyingPhase = api.find('_ZN3art2gc9collector17ConcurrentCopying12MarkingPhaseEv');
}
if (copyingPhase !== null) {
Interceptor.attach(copyingPhase, artController.hooks.Gc.copyingPhase);
}

if (mayUseCollector(kCollectorTypeCMC)) {
Interceptor.attach(Module.getExportByName('libart.so', '_ZN3art6Thread15RunFlipFunctionEPS0_b'), artController.hooks.Gc.runFlip);
} else {
let copyingPhase = null;
if (apiLevel > 28) {
copyingPhase = Module.findExportByName('libart.so', '_ZN3art2gc9collector17ConcurrentCopying12CopyingPhaseEv');
} else if (apiLevel > 22) {
copyingPhase = Module.findExportByName('libart.so', '_ZN3art2gc9collector17ConcurrentCopying12MarkingPhaseEv');
}
if (copyingPhase !== null) {
Interceptor.attach(copyingPhase, artController.hooks.Gc.copyingPhase);
}
let runFlip = null;
runFlip = api.find('_ZN3art6Thread15RunFlipFunctionEPS0_');
if (runFlip === null) {
runFlip = api.find('_ZN3art6Thread15RunFlipFunctionEPS0_b');
}
if (runFlip !== null) {
Interceptor.attach(runFlip, artController.hooks.Gc.runFlip);
}
}

Expand Down Expand Up @@ -3414,7 +3410,7 @@ class ArtMethodMangler {

// Replace Nterp quick entrypoints with art_quick_to_interpreter_bridge to force stepping out
// of ART's next-generation interpreter and use the quick stub instead.
if (artNterpEntryPoint !== undefined && quickCode.equals(artNterpEntryPoint)) {
if (artNterpEntryPoint !== null && quickCode.equals(artNterpEntryPoint)) {
patchArtMethod(hookedMethodId, {
quickCode: api.artQuickToInterpreterBridge
}, vm);
Expand Down Expand Up @@ -3925,9 +3921,24 @@ const threadStateTransitionRecompilers = {
};

function makeArtThreadStateTransitionImpl (vm, env, callback) {
const api = getApi();
const envVtable = env.handle.readPointer();
const exceptionClearImpl = envVtable.add(ENV_VTABLE_OFFSET_EXCEPTION_CLEAR).readPointer();
const nextFuncImpl = envVtable.add(ENV_VTABLE_OFFSET_FATAL_ERROR).readPointer();

let exceptionClearImpl;
const innerExceptionClearImpl = api.find('_ZN3art3JNIILb1EE14ExceptionClearEP7_JNIEnv');
if (innerExceptionClearImpl !== null) {
exceptionClearImpl = innerExceptionClearImpl;
} else {
exceptionClearImpl = envVtable.add(ENV_VTABLE_OFFSET_EXCEPTION_CLEAR).readPointer();
}

let nextFuncImpl;
const innerNextFuncImpl = api.find('_ZN3art3JNIILb1EE10FatalErrorEP7_JNIEnvPKc');
if (innerNextFuncImpl !== null) {
nextFuncImpl = innerNextFuncImpl;
} else {
nextFuncImpl = envVtable.add(ENV_VTABLE_OFFSET_FATAL_ERROR).readPointer();
}

const recompile = threadStateTransitionRecompilers[Process.arch];
if (recompile === undefined) {
Expand Down

0 comments on commit 628d306

Please sign in to comment.