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

android: alternative offset to ExceptionClear in libart (https://github.com/frida/frida/issues/2958)(https://github.com/frida/frida-java-bridge/issues/336) #337

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
188 changes: 156 additions & 32 deletions lib/android.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const X86_JMP_MAX_DISTANCE = 0x7fffbfff;
const ARM64_ADRP_MAX_DISTANCE = 0xfffff000;

const ENV_VTABLE_OFFSET_EXCEPTION_CLEAR = 17 * pointerSize;
const ENV_VTABLE_OFFSET_EXCEPTION_CLEAR_ALT = 250 * pointerSize;
const ENV_VTABLE_OFFSET_FATAL_ERROR = 18 * pointerSize;

const DVM_JNI_ENV_OFFSET_SELF = 12;
Expand Down Expand Up @@ -501,8 +502,11 @@ function _getApi () {
}
if (temporaryApi['art::interpreter::GetNterpEntryPoint'] !== undefined) {
temporaryApi.artNterpEntryPoint = temporaryApi['art::interpreter::GetNterpEntryPoint']();
} else {
if (Process.arch === 'arm64' && getAndroidApiLevel() >= 30) {
temporaryApi.artNterpEntryPoint = findExecuteNterpImpl();
}
}

artController = makeArtController(vm);

fixupArtQuickDeliverExceptionBug(temporaryApi);
Expand Down Expand Up @@ -532,18 +536,117 @@ function _getApi () {
return temporaryApi;
}

function stringToBytesHex (str) {
const bytes = [];
for (let i = 0; i < str.length; i++) {
let byteHex = str.charCodeAt(i).toString(16).toUpperCase();
if (byteHex.length === 1) {
byteHex = '0' + byteHex;
}
bytes.push(byteHex);
}
return bytes.join(' ');
}

function findString (string, moduleName) {
const module = Process.findModuleByName(moduleName);
const ranges = module.enumerateRanges('r--');
const pattern = stringToBytesHex(string);
return ranges.map((range) => {
const base = range.base;
const size = range.size;
const result = Memory.scanSync(base, size, pattern);
if (result.length > 0) return result;
return null;
}).filter(element => element !== null).flat();
}

function createAdd (address, register) {
return (0x244 << 12) + (address & 0xfff) << 10 + register << 5 + register;
}

function getPattern (instr) {
return ptr(instr).toMatchPattern().replace('ff ff ff ff', '');
}

function findPatternInModule (pattern, moduleName) {
const module = Process.findModuleByName(moduleName);
const ranges = module.enumerateRanges('r-x');
return ranges.map((range) => {
const base = range.base;
const size = range.size;
const result = Memory.scanSync(base, size, pattern);
if (result.length !== 0) return result;
return null;
}).filter(element => element !== null).flat();
}

function findEnsurePlugIn () {
const results = findString('libopenjdkjvmti.so', 'libart.so');
const pattern = getPattern(createAdd(results[0].address, 1));
const patternResults = findPatternInModule(pattern, 'libart.so');
return patternResults.map((match) => {
const possibleBranch = match.address.add(4);
const disasm = Instruction.parse(possibleBranch);
if (disasm.mnemonic === 'b') {
return new NativePointer(disasm.operands[0].value);
}
return null;
}).filter(element => element !== null).flat();
}

function findPrologue (address) {
for (let off = 0; ; off += 4) {
let disasm = Instruction.parse(address.sub(0x4).sub(off));
if (disasm.mnemonic === 'str') {
disasm = Instruction.parse(disasm.next);
if (disasm.mnemonic === 'stp') {
return disasm.address.sub(0x4);
}
}
}
}

function findExecuteNterpImpl () {
const artMethodCopyfrom = Module.findExportByName('libart.so', '_ZN3art9ArtMethod8CopyFromEPS0_NS_11PointerSizeE');
for (let off = 0; off < 0x300; off += 4) {
const disasm = Instruction.parse(artMethodCopyfrom.add(off));
const nextInstr = Instruction.parse(artMethodCopyfrom.add(off).add(0x4));
if (disasm.mnemonic === 'adrp' && nextInstr.mnemonic === 'add') {
const base = ptr(disasm.operands[1].value);
const offset = nextInstr.operands[2].value;
const result = base.add(offset);
const dest = Instruction.parse(result);
if (dest.mnemonic === 'sub') {
return result;
}
}
}
}

function tryGetEnvJvmti (vm, runtime) {
let env = null;

vm.perform(() => {
const ensurePluginLoaded = new NativeFunction(
Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
'bool',
['pointer', 'pointer', 'pointer']);
let ensurePluginLoaded;
if (Module.findExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE') === null) {
// on some devices calling the ensurePluginLoaded make the app really slow so for is commented
if (env !== null && Process.arch === 'arm64') {
const candidates = findEnsurePlugIn();
ensurePluginLoaded = new NativeFunction(candidates[0],
'bool',
['pointer', 'pointer', 'pointer']);
}
return;
} else {
ensurePluginLoaded = new NativeFunction(Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
'bool',
['pointer', 'pointer', 'pointer']);
}
const errorPtr = Memory.alloc(pointerSize);
const success = ensurePluginLoaded(runtime, Memory.allocUtf8String('libopenjdkjvmti.so'), errorPtr);
if (!success) {
// FIXME: Avoid leaking error
// FIXME: Avoid leaking error
return;
}

Expand Down Expand Up @@ -623,7 +726,7 @@ function _getArtRuntimeSpec (api) {

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

let spec = null;

Expand Down Expand Up @@ -1852,6 +1955,23 @@ function instrumentArtQuickEntrypoints (vm) {
});
}

function findDoCalls () {
const results = findString('Invoking %s with bad arg %d', 'libart.so');
const pattern = getPattern(createAdd(results[0].address, 2));
const patternResults = findPatternInModule(pattern, 'libart.so');

return patternResults.map((match) => {
const possibleBranch = match.address;
const instr = Instruction.parse(possibleBranch.sub(4));
if (instr.mnemonic === 'adrp') {
if (new NativePointer(instr.operands[1].value).equals(results[0].address.and(0xfffffffffff000))) {
return findPrologue(possibleBranch);
}
}
return null;
}).filter(element => element !== null).flat();
}

function instrumentArtMethodInvocationFromInterpreter () {
const apiLevel = getAndroidApiLevel();

Expand All @@ -1864,9 +1984,17 @@ function instrumentArtMethodInvocationFromInterpreter () {
artInterpreterDoCallExportRegex = /^_ZN3art11interpreter6DoCallILb[0-1]EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtbPNS_6JValueE$/;
}

let docallFound = true;
for (const exp of Module.enumerateExports('libart.so').filter(exp => artInterpreterDoCallExportRegex.test(exp.name))) {
docallFound = false;
Interceptor.attach(exp.address, artController.hooks.Interpreter.doCall);
}
if (docallFound && Process.arch === 'arm64') {
const doCallsCandidates = findDoCalls();
for (const address of doCallsCandidates) {
Interceptor.attach(address, artController.hooks.Interpreter.doCall);
}
}
}

function ensureArtKnowsHowToHandleReplacementMethods (vm) {
Expand All @@ -1893,29 +2021,23 @@ 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;
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);
}

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 = Module.findExportByName('libart.so', '_ZN3art6Thread15RunFlipFunctionEPS0_b');
if (runFlip === null) {
runFlip = Module.findExportByName('libart.so', '_ZN3art6Thread15RunFlipFunctionEPS0_'); // api 35
}
if (runFlip !== null) {
Interceptor.attach(runFlip, artController.hooks.Gc.runFlip);
}
}

Expand Down Expand Up @@ -3414,7 +3536,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 !== undefined && artNterpEntryPoint !== 0 && quickCode.equals(artNterpEntryPoint)) {
patchArtMethod(hookedMethodId, {
quickCode: api.artQuickToInterpreterBridge
}, vm);
Expand Down Expand Up @@ -3926,9 +4048,11 @@ const threadStateTransitionRecompilers = {

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

if (Module.findExportByName('libart.so', '_ZN3art6Thread15RunFlipFunctionEPS0_')) {
exceptionClearImpl = envVtable.add(ENV_VTABLE_OFFSET_EXCEPTION_CLEAR_ALT).readPointer();
}
const recompile = threadStateTransitionRecompilers[Process.arch];
if (recompile === undefined) {
throw new Error('Not yet implemented for ' + Process.arch);
Expand Down
Loading