diff --git a/lib/android.js b/lib/android.js index f5dcfbe..910c873 100644 --- a/lib/android.js +++ b/lib/android.js @@ -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; @@ -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); @@ -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; } @@ -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; @@ -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(); @@ -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) { @@ -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); } } @@ -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); @@ -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);