frida-java-bridge 6.0.2 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/android.js CHANGED
@@ -1,6 +1,11 @@
1
1
  const makeCodeAllocator = require('./alloc');
2
+ const {
3
+ jvmtiVersion,
4
+ jvmtiCapabilities,
5
+ EnvJvmti
6
+ } = require('./jvmti');
2
7
  const memoize = require('./memoize');
3
- const { checkJniResult } = require('./result');
8
+ const { checkJniResult, JNI_OK } = require('./result');
4
9
  const VM = require('./vm');
5
10
 
6
11
  const jsizeSize = 4;
@@ -99,6 +104,7 @@ const artThreadStateTransitions = {};
99
104
  let cachedApi = null;
100
105
  let MethodMangler = null;
101
106
  let artController = null;
107
+ const inlineHooks = [];
102
108
  const patchedClasses = new Map();
103
109
  const artQuickInterceptors = [];
104
110
  let thunkPage = null;
@@ -330,6 +336,7 @@ function _getApi () {
330
336
  '_ZN3art3Dbg13ConfigureJdwpERKNS_4JDWP11JdwpOptionsE',
331
337
  '_ZN3art31InternalDebuggerControlCallback13StartDebuggerEv',
332
338
  '_ZN3art3Dbg15gDebuggerActiveE',
339
+ '_ZN3art15instrumentation15Instrumentation20EnableDeoptimizationEv',
333
340
  '_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEPKc',
334
341
  '_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEv',
335
342
  '_ZN3art7Runtime19DeoptimizeBootImageEv',
@@ -474,6 +481,16 @@ function _getApi () {
474
481
  artController = makeArtController(vm);
475
482
 
476
483
  fixupArtQuickDeliverExceptionBug(temporaryApi);
484
+
485
+ let cachedJvmti = null;
486
+ Object.defineProperty(temporaryApi, 'jvmti', {
487
+ get () {
488
+ if (cachedJvmti === null) {
489
+ cachedJvmti = [tryGetEnvJvmti(vm, this.artRuntime)];
490
+ }
491
+ return cachedJvmti[0];
492
+ }
493
+ });
477
494
  }
478
495
 
479
496
  const cxxImports = Module.enumerateImports(vmModule.path)
@@ -490,6 +507,39 @@ function _getApi () {
490
507
  return temporaryApi;
491
508
  }
492
509
 
510
+ function tryGetEnvJvmti (vm, runtime) {
511
+ let env = null;
512
+
513
+ vm.perform(() => {
514
+ const ensurePluginLoaded = new NativeFunction(
515
+ Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
516
+ 'bool',
517
+ ['pointer', 'pointer', 'pointer']);
518
+ const errorPtr = Memory.alloc(pointerSize);
519
+ const success = ensurePluginLoaded(runtime, Memory.allocUtf8String('libopenjdkjvmti.so'), errorPtr);
520
+ if (!success) {
521
+ // FIXME: Avoid leaking error
522
+ return;
523
+ }
524
+
525
+ const kArtTiVersion = jvmtiVersion.v1_2 | 0x40000000;
526
+ const handle = vm.tryGetEnvHandle(kArtTiVersion);
527
+ if (handle === null) {
528
+ return;
529
+ }
530
+ env = new EnvJvmti(handle, vm);
531
+
532
+ const capaBuf = Memory.alloc(8);
533
+ capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
534
+ const result = env.addCapabilities(capaBuf);
535
+ if (result !== JNI_OK) {
536
+ env = null;
537
+ }
538
+ });
539
+
540
+ return env;
541
+ }
542
+
493
543
  function ensureClassInitialized (env, classRef) {
494
544
  const api = getApi();
495
545
  if (api.flavor !== 'art') {
@@ -530,6 +580,7 @@ function _getArtRuntimeSpec (api) {
530
580
  * InternTable* intern_table_; <--/
531
581
  * ClassLinker* class_linker_; <-/
532
582
  * SignalCatcher* signal_catcher_;
583
+ * SmallIrtAllocator* small_irt_allocator_; <------------ API level >= 33 or Android Tiramisu Developer Preview
533
584
  * std::unique_ptr<jni::JniIdManager> jni_id_manager_; <- API level >= 30 or Android R Developer Preview
534
585
  * bool use_tombstoned_traces_; <-------------------- API level 27/28
535
586
  * std::string stack_trace_file_; <-------------------- API level <= 28
@@ -553,7 +604,10 @@ function _getArtRuntimeSpec (api) {
553
604
  if (value.equals(vm)) {
554
605
  let classLinkerOffset = null;
555
606
  let jniIdManagerOffset = null;
556
- if (apiLevel >= 30 || getAndroidCodename() === 'R') {
607
+ if (apiLevel >= 33 || getAndroidCodename() === 'Tiramisu') {
608
+ classLinkerOffset = offset - (4 * pointerSize);
609
+ jniIdManagerOffset = offset - pointerSize;
610
+ } else if (apiLevel >= 30 || getAndroidCodename() === 'R') {
557
611
  classLinkerOffset = offset - (3 * pointerSize);
558
612
  jniIdManagerOffset = offset - pointerSize;
559
613
  } else if (apiLevel >= 29) {
@@ -593,7 +647,7 @@ function _getArtRuntimeSpec (api) {
593
647
  throw new Error('Unable to determine Runtime field offsets');
594
648
  }
595
649
 
596
- spec.offset.instrumentation = tryDetectInstrumentationOffset();
650
+ spec.offset.instrumentation = tryDetectInstrumentationOffset(api);
597
651
  spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset();
598
652
 
599
653
  return spec;
@@ -606,15 +660,15 @@ const instrumentationOffsetParsers = {
606
660
  arm64: parseArm64InstrumentationOffset
607
661
  };
608
662
 
609
- function tryDetectInstrumentationOffset () {
610
- let cur = Module.findExportByName('libart.so', 'MterpHandleException');
611
- if (cur === null) {
663
+ function tryDetectInstrumentationOffset (api) {
664
+ let cur = api['art::Runtime::DeoptimizeBootImage'];
665
+ if (cur === undefined) {
612
666
  return null;
613
667
  }
614
668
 
615
669
  const tryParse = instrumentationOffsetParsers[Process.arch];
616
670
 
617
- for (let i = 0; i !== 20; i++) {
671
+ for (let i = 0; i !== 30; i++) {
618
672
  const insn = Instruction.parse(cur);
619
673
 
620
674
  const offset = tryParse(insn);
@@ -629,20 +683,16 @@ function tryDetectInstrumentationOffset () {
629
683
  }
630
684
 
631
685
  function parsex86InstrumentationOffset (insn) {
632
- const { mnemonic } = insn;
633
- if (mnemonic !== 'mov' && mnemonic !== 'add') {
686
+ if (insn.mnemonic !== 'lea') {
634
687
  return null;
635
688
  }
636
689
 
637
- const op1 = insn.operands[1];
638
- if (op1.type !== 'imm') {
639
- return null;
640
- }
641
- if (op1.value < 0x100 || op1.value > 0x400) {
690
+ const offset = insn.operands[1].value.disp;
691
+ if (offset < 0x100 || offset > 0x400) {
642
692
  return null;
643
693
  }
644
694
 
645
- return op1.value;
695
+ return offset;
646
696
  }
647
697
 
648
698
  function parseArmInstrumentationOffset (insn) {
@@ -673,12 +723,21 @@ function parseArm64InstrumentationOffset (insn) {
673
723
  return null;
674
724
  }
675
725
 
726
+ if (ops[0].value === 'sp' || ops[1].value === 'sp') {
727
+ return null;
728
+ }
729
+
676
730
  const op2 = ops[2];
677
731
  if (op2.type !== 'imm') {
678
732
  return null;
679
733
  }
680
734
 
681
- return op2.value;
735
+ const offset = op2.value.valueOf();
736
+ if (offset < 0x100 || offset > 0x400) {
737
+ return null;
738
+ }
739
+
740
+ return offset;
682
741
  }
683
742
 
684
743
  const jniIdsIndirectionOffsetParsers = {
@@ -902,7 +961,7 @@ function _getArtMethodSpec (vm) {
902
961
 
903
962
  vm.perform(env => {
904
963
  const process = env.findClass('android/os/Process');
905
- const setArgV0 = unwrapMethodId(env.getStaticMethodId(process, 'setArgV0', '(Ljava/lang/String;)V'));
964
+ const getElapsedCpuTime = unwrapMethodId(env.getStaticMethodId(process, 'getElapsedCpuTime', '()J'));
906
965
  env.deleteLocalRef(process);
907
966
 
908
967
  const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
@@ -914,13 +973,13 @@ function _getArtMethodSpec (vm) {
914
973
  const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;
915
974
 
916
975
  const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;
917
- const relevantAccessFlagsMask = ~(kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
976
+ const relevantAccessFlagsMask = ~(kAccFastInterpreterToInterpreterInvoke | kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
918
977
 
919
978
  let jniCodeOffset = null;
920
979
  let accessFlagsOffset = null;
921
980
  let remaining = 2;
922
981
  for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
923
- const field = setArgV0.add(offset);
982
+ const field = getElapsedCpuTime.add(offset);
924
983
 
925
984
  if (jniCodeOffset === null) {
926
985
  const address = field.readPointer();
@@ -1711,6 +1770,7 @@ on_leave_gc_concurrent_copying_copying_phase (GumInvocationContext * ic)
1711
1770
  return {
1712
1771
  handle: cm,
1713
1772
  replacedMethods: {
1773
+ isReplacement: new NativeFunction(cm.is_replacement_method, 'bool', ['pointer'], fastOptions),
1714
1774
  get: new NativeFunction(cm.get_replacement_method, 'pointer', ['pointer'], fastOptions),
1715
1775
  set: new NativeFunction(cm.set_replacement_method, 'void', ['pointer', 'pointer'], fastOptions),
1716
1776
  delete: new NativeFunction(cm.delete_replacement_method, 'void', ['pointer'], fastOptions),
@@ -1785,18 +1845,20 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
1785
1845
  }
1786
1846
  taughtArtAboutReplacementMethods = true;
1787
1847
 
1788
- const { getOatQuickMethodHeaderImpl } = artController;
1789
- if (getOatQuickMethodHeaderImpl === null) {
1790
- return;
1791
- }
1848
+ if (!maybeInstrumentGetOatQuickMethodHeaderInlineCopies()) {
1849
+ const { getOatQuickMethodHeaderImpl } = artController;
1850
+ if (getOatQuickMethodHeaderImpl === null) {
1851
+ return;
1852
+ }
1792
1853
 
1793
- try {
1794
- Interceptor.replace(getOatQuickMethodHeaderImpl, artController.hooks.ArtMethod.getOatQuickMethodHeader);
1795
- } catch (e) {
1796
- /*
1797
- * Already replaced by another script. For now we don't support replacing methods from multiple scripts,
1798
- * but we'll allow users to try it if they're feeling adventurous.
1799
- */
1854
+ try {
1855
+ Interceptor.replace(getOatQuickMethodHeaderImpl, artController.hooks.ArtMethod.getOatQuickMethodHeader);
1856
+ } catch (e) {
1857
+ /*
1858
+ * Already replaced by another script. For now we don't support replacing methods from multiple scripts,
1859
+ * but we'll allow users to try it if they're feeling adventurous.
1860
+ */
1861
+ }
1800
1862
  }
1801
1863
 
1802
1864
  const apiLevel = getAndroidApiLevel();
@@ -1813,6 +1875,385 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
1813
1875
  }
1814
1876
  }
1815
1877
 
1878
+ const artGetOatQuickMethodHeaderInlinedCopyHandler = {
1879
+ arm: {
1880
+ signatures: [
1881
+ {
1882
+ pattern: [
1883
+ 'b0 68', // ldr r0, [r6, #8]
1884
+ '01 30', // adds r0, #1
1885
+ '0c d0', // beq #0x16fcd4
1886
+ '1b 98', // ldr r0, [sp, #0x6c]
1887
+ ':',
1888
+ 'c0 ff',
1889
+ 'c0 ff',
1890
+ '00 ff',
1891
+ '00 2f'
1892
+ ],
1893
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1894
+ },
1895
+ {
1896
+ pattern: [
1897
+ 'd8 f8 08 00', // ldr r0, [r8, #8]
1898
+ '01 30', // adds r0, #1
1899
+ '0c d0', // beq #0x16fcd4
1900
+ '1b 98', // ldr r0, [sp, #0x6c]
1901
+ ':',
1902
+ 'f0 ff ff 0f',
1903
+ 'ff ff',
1904
+ '00 ff',
1905
+ '00 2f'
1906
+ ],
1907
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1908
+ },
1909
+ {
1910
+ pattern: [
1911
+ 'b0 68', // ldr r0, [r6, #8]
1912
+ '01 30', // adds r0, #1
1913
+ '40 f0 c3 80', // bne #0x203bf0
1914
+ '00 25', // movs r5, #0
1915
+ ':',
1916
+ 'c0 ff',
1917
+ 'c0 ff',
1918
+ 'c0 fb 00 d0',
1919
+ 'ff f8'
1920
+ ],
1921
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1922
+ }
1923
+ ],
1924
+ instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm
1925
+ },
1926
+ arm64: {
1927
+ signatures: [
1928
+ {
1929
+ pattern: [
1930
+ /* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
1931
+ '1f 05 00 31', // cmn w8, #0x1
1932
+ '40 01 00 54', // b.eq 0x2e4204
1933
+ '88 39 00 f0', // adrp x8, 0xa17000
1934
+ ':',
1935
+ /* 00 */ 'fc ff ff',
1936
+ '1f fc ff ff',
1937
+ '1f 00 00 ff',
1938
+ '00 00 00 9f'
1939
+ ],
1940
+ offset: 1,
1941
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
1942
+ },
1943
+ {
1944
+ pattern: [
1945
+ /* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
1946
+ '1f 05 00 31', // cmn w8, #0x1
1947
+ '01 34 00 54', // b.ne 0x3d8e50
1948
+ 'e0 03 1f aa', // mov x0, xzr
1949
+ ':',
1950
+ /* 00 */ 'fc ff ff',
1951
+ '1f fc ff ff',
1952
+ '1f 00 00 ff',
1953
+ 'e0 ff ff ff'
1954
+ ],
1955
+ offset: 1,
1956
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
1957
+ }
1958
+ ],
1959
+ instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm64
1960
+ }
1961
+ };
1962
+
1963
+ function validateGetOatQuickMethodHeaderInlinedMatchArm ({ address, size }) {
1964
+ const ldr = Instruction.parse(address.or(1));
1965
+ const [ldrDst, ldrSrc] = ldr.operands;
1966
+ const methodReg = ldrSrc.value.base;
1967
+ const scratchReg = ldrDst.value;
1968
+
1969
+ const branch = Instruction.parse(ldr.next.add(2));
1970
+ const targetWhenTrue = ptr(branch.operands[0].value);
1971
+ const targetWhenFalse = branch.address.add(branch.size);
1972
+
1973
+ let targetWhenRegularMethod, targetWhenRuntimeMethod;
1974
+ if (branch.mnemonic === 'beq') {
1975
+ targetWhenRegularMethod = targetWhenFalse;
1976
+ targetWhenRuntimeMethod = targetWhenTrue;
1977
+ } else {
1978
+ targetWhenRegularMethod = targetWhenTrue;
1979
+ targetWhenRuntimeMethod = targetWhenFalse;
1980
+ }
1981
+
1982
+ let cursor = targetWhenRegularMethod.or(1);
1983
+ for (let i = 0; i !== 3; i++) {
1984
+ const insn = Instruction.parse(cursor);
1985
+ cursor = insn.next;
1986
+
1987
+ const { mnemonic } = insn;
1988
+ if (!(mnemonic === 'ldr' || mnemonic === 'ldr.w')) {
1989
+ continue;
1990
+ }
1991
+
1992
+ const { base, disp } = insn.operands[1].value;
1993
+ if (base === methodReg && disp === 0x14) {
1994
+ return {
1995
+ methodReg,
1996
+ scratchReg,
1997
+ target: {
1998
+ whenTrue: targetWhenTrue,
1999
+ whenRegularMethod: targetWhenRegularMethod,
2000
+ whenRuntimeMethod: targetWhenRuntimeMethod
2001
+ }
2002
+ };
2003
+ }
2004
+ }
2005
+
2006
+ return null;
2007
+ }
2008
+
2009
+ function validateGetOatQuickMethodHeaderInlinedMatchArm64 ({ address, size }) {
2010
+ const [ldrDst, ldrSrc] = Instruction.parse(address).operands;
2011
+ const methodReg = ldrSrc.value.base;
2012
+ const scratchReg = 'x' + ldrDst.value.substring(1);
2013
+
2014
+ const branch = Instruction.parse(address.add(8));
2015
+ const targetWhenTrue = ptr(branch.operands[0].value);
2016
+ const targetWhenFalse = address.add(12);
2017
+
2018
+ let targetWhenRegularMethod, targetWhenRuntimeMethod;
2019
+ if (branch.mnemonic === 'b.eq') {
2020
+ targetWhenRegularMethod = targetWhenFalse;
2021
+ targetWhenRuntimeMethod = targetWhenTrue;
2022
+ } else {
2023
+ targetWhenRegularMethod = targetWhenTrue;
2024
+ targetWhenRuntimeMethod = targetWhenFalse;
2025
+ }
2026
+
2027
+ let cursor = targetWhenRegularMethod;
2028
+ for (let i = 0; i !== 3; i++) {
2029
+ const insn = Instruction.parse(cursor);
2030
+ cursor = insn.next;
2031
+
2032
+ if (insn.mnemonic !== 'ldr') {
2033
+ continue;
2034
+ }
2035
+
2036
+ const { base, disp } = insn.operands[1].value;
2037
+ if (base === methodReg && disp === 0x18) {
2038
+ return {
2039
+ methodReg,
2040
+ scratchReg,
2041
+ target: {
2042
+ whenTrue: targetWhenTrue,
2043
+ whenRegularMethod: targetWhenRegularMethod,
2044
+ whenRuntimeMethod: targetWhenRuntimeMethod
2045
+ }
2046
+ };
2047
+ }
2048
+ }
2049
+
2050
+ return null;
2051
+ }
2052
+
2053
+ function maybeInstrumentGetOatQuickMethodHeaderInlineCopies () {
2054
+ if (getAndroidApiLevel() < 31) {
2055
+ return false;
2056
+ }
2057
+
2058
+ const handler = artGetOatQuickMethodHeaderInlinedCopyHandler[Process.arch];
2059
+ if (handler === undefined) {
2060
+ // Not needed on x86 and x64, at least not for now...
2061
+ return false;
2062
+ }
2063
+
2064
+ const signatures = handler.signatures.map(({ pattern, offset = 0, validateMatch = returnEmptyObject }) => {
2065
+ return {
2066
+ pattern: new MatchPattern(pattern.join('')),
2067
+ offset,
2068
+ validateMatch
2069
+ };
2070
+ });
2071
+
2072
+ const impls = [];
2073
+ for (const { base, size } of getApi().module.enumerateRanges('--x')) {
2074
+ for (const { pattern, offset, validateMatch } of signatures) {
2075
+ const matches = Memory.scanSync(base, size, pattern)
2076
+ .map(({ address, size }) => {
2077
+ return { address: address.sub(offset), size: size + offset };
2078
+ })
2079
+ .filter(match => {
2080
+ const validationResult = validateMatch(match);
2081
+ if (validationResult === null) {
2082
+ return false;
2083
+ }
2084
+ match.validationResult = validationResult;
2085
+ return true;
2086
+ });
2087
+ impls.push(...matches);
2088
+ }
2089
+ }
2090
+
2091
+ impls.forEach(handler.instrument);
2092
+
2093
+ return true;
2094
+ }
2095
+
2096
+ function returnEmptyObject () {
2097
+ return {};
2098
+ }
2099
+
2100
+ class InlineHook {
2101
+ constructor (address, size, trampoline) {
2102
+ this.address = address;
2103
+ this.size = size;
2104
+ this.originalCode = address.readByteArray(size);
2105
+ this.trampoline = trampoline;
2106
+ }
2107
+
2108
+ revert () {
2109
+ Memory.patchCode(this.address, this.size, code => {
2110
+ code.writeByteArray(this.originalCode);
2111
+ });
2112
+ }
2113
+ }
2114
+
2115
+ function instrumentGetOatQuickMethodHeaderInlinedCopyArm ({ address, size, validationResult }) {
2116
+ const { methodReg, target } = validationResult;
2117
+
2118
+ const trampoline = Memory.alloc(Process.pageSize);
2119
+ let redirectCapacity = size;
2120
+
2121
+ Memory.patchCode(trampoline, 256, code => {
2122
+ const writer = new ThumbWriter(code, { pc: trampoline });
2123
+
2124
+ const relocator = new ThumbRelocator(address, writer);
2125
+ for (let i = 0; i !== 2; i++) {
2126
+ relocator.readOne();
2127
+ }
2128
+ relocator.writeAll();
2129
+
2130
+ relocator.readOne();
2131
+ relocator.skipOne();
2132
+ writer.putBCondLabel('eq', 'runtime_or_replacement_method');
2133
+
2134
+ const vpushFpRegs = [0x2d, 0xed, 0x10, 0x0a]; /* vpush {s0-s15} */
2135
+ writer.putBytes(vpushFpRegs);
2136
+
2137
+ const savedRegs = ['r0', 'r1', 'r2', 'r3'];
2138
+ writer.putPushRegs(savedRegs);
2139
+
2140
+ writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
2141
+ writer.putCmpRegImm('r0', 0);
2142
+
2143
+ writer.putPopRegs(savedRegs);
2144
+
2145
+ const vpopFpRegs = [0xbd, 0xec, 0x10, 0x0a]; /* vpop {s0-s15} */
2146
+ writer.putBytes(vpopFpRegs);
2147
+
2148
+ writer.putBCondLabel('ne', 'runtime_or_replacement_method');
2149
+ writer.putBLabel('regular_method');
2150
+
2151
+ relocator.readOne();
2152
+
2153
+ const tailIsRegular = relocator.input.address.equals(target.whenRegularMethod);
2154
+
2155
+ writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
2156
+ relocator.writeOne();
2157
+ while (redirectCapacity < 10) {
2158
+ const offset = relocator.readOne();
2159
+ if (offset === 0) {
2160
+ redirectCapacity = 10;
2161
+ break;
2162
+ }
2163
+ redirectCapacity = offset;
2164
+ }
2165
+ relocator.writeAll();
2166
+ writer.putBranchAddress(address.add(redirectCapacity + 1));
2167
+
2168
+ writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
2169
+ writer.putBranchAddress(target.whenTrue);
2170
+
2171
+ writer.flush();
2172
+ });
2173
+
2174
+ inlineHooks.push(new InlineHook(address, redirectCapacity, trampoline));
2175
+
2176
+ Memory.patchCode(address, redirectCapacity, code => {
2177
+ const writer = new ThumbWriter(code, { pc: address });
2178
+ writer.putLdrRegAddress('pc', trampoline.or(1));
2179
+ writer.flush();
2180
+ });
2181
+ }
2182
+
2183
+ function instrumentGetOatQuickMethodHeaderInlinedCopyArm64 ({ address, size, validationResult }) {
2184
+ const { methodReg, scratchReg, target } = validationResult;
2185
+
2186
+ const trampoline = Memory.alloc(Process.pageSize);
2187
+
2188
+ Memory.patchCode(trampoline, 256, code => {
2189
+ const writer = new Arm64Writer(code, { pc: trampoline });
2190
+
2191
+ const relocator = new Arm64Relocator(address, writer);
2192
+ for (let i = 0; i !== 2; i++) {
2193
+ relocator.readOne();
2194
+ }
2195
+ relocator.writeAll();
2196
+
2197
+ relocator.readOne();
2198
+ relocator.skipOne();
2199
+ writer.putBCondLabel('eq', 'runtime_or_replacement_method');
2200
+
2201
+ const savedRegs = [
2202
+ 'd0', 'd1',
2203
+ 'd2', 'd3',
2204
+ 'd4', 'd5',
2205
+ 'd6', 'd7',
2206
+ 'x0', 'x1',
2207
+ 'x2', 'x3',
2208
+ 'x4', 'x5',
2209
+ 'x6', 'x7',
2210
+ 'x8', 'x9',
2211
+ 'x10', 'x11',
2212
+ 'x12', 'x13',
2213
+ 'x14', 'x15',
2214
+ 'x16', 'x17'
2215
+ ];
2216
+ const numSavedRegs = savedRegs.length;
2217
+
2218
+ for (let i = 0; i !== numSavedRegs; i += 2) {
2219
+ writer.putPushRegReg(savedRegs[i], savedRegs[i + 1]);
2220
+ }
2221
+
2222
+ writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
2223
+ writer.putCmpRegReg('x0', 'xzr');
2224
+
2225
+ for (let i = numSavedRegs - 2; i >= 0; i -= 2) {
2226
+ writer.putPopRegReg(savedRegs[i], savedRegs[i + 1]);
2227
+ }
2228
+
2229
+ writer.putBCondLabel('ne', 'runtime_or_replacement_method');
2230
+ writer.putBLabel('regular_method');
2231
+
2232
+ relocator.readOne();
2233
+ const tailInstruction = relocator.input;
2234
+
2235
+ const tailIsRegular = tailInstruction.address.equals(target.whenRegularMethod);
2236
+
2237
+ writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
2238
+ relocator.writeOne();
2239
+ writer.putBranchAddress(tailInstruction.next);
2240
+
2241
+ writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
2242
+ writer.putBranchAddress(target.whenTrue);
2243
+
2244
+ writer.flush();
2245
+ });
2246
+
2247
+ inlineHooks.push(new InlineHook(address, size, trampoline));
2248
+
2249
+ Memory.patchCode(address, size, code => {
2250
+ const writer = new Arm64Writer(code, { pc: address });
2251
+ writer.putLdrRegAddress(scratchReg, trampoline);
2252
+ writer.putBrReg(scratchReg);
2253
+ writer.flush();
2254
+ });
2255
+ }
2256
+
1816
2257
  function makeMethodMangler (methodId) {
1817
2258
  return new MethodMangler(methodId);
1818
2259
  }
@@ -1937,8 +2378,6 @@ extern void translate_location (ArtMethod * method, guint32 pc, const gchar ** s
1937
2378
  extern void cxx_delete (void * mem);
1938
2379
  extern unsigned long strtoul (const char * str, char ** endptr, int base);
1939
2380
 
1940
- extern void _frida_log (const gchar * message);
1941
-
1942
2381
  static bool visit_frame (ArtStackVisitor * visitor);
1943
2382
  static void art_stack_frame_destroy (ArtStackFrame * frame);
1944
2383
 
@@ -1947,8 +2386,6 @@ static void append_jni_type_name (GString * s, const gchar * name, gsize length)
1947
2386
  static void std_string_destroy (StdString * str);
1948
2387
  static gchar * std_string_get_data (StdString * str);
1949
2388
 
1950
- static void frida_log (const char * format, ...);
1951
-
1952
2389
  void
1953
2390
  init (void)
1954
2391
  {
@@ -2334,7 +2771,13 @@ function revertGlobalPatches () {
2334
2771
  });
2335
2772
  patchedClasses.clear();
2336
2773
 
2337
- artQuickInterceptors.forEach((interceptor) => interceptor.deactivate());
2774
+ for (const interceptor of artQuickInterceptors.splice(0)) {
2775
+ interceptor.deactivate();
2776
+ }
2777
+
2778
+ for (const hook of inlineHooks.splice(0)) {
2779
+ hook.revert();
2780
+ }
2338
2781
  }
2339
2782
 
2340
2783
  function unwrapMethodId (methodId) {
@@ -3161,9 +3604,12 @@ function requestDeoptimization (vm, env, kind, method) {
3161
3604
  throw new Error('Unable to find Instrumentation class in ART; please file a bug');
3162
3605
  }
3163
3606
 
3164
- const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
3165
- if (!deoptimizationEnabled) {
3166
- api['art::Instrumentation::EnableDeoptimization'](instrumentation);
3607
+ const enableDeopt = api['art::Instrumentation::EnableDeoptimization'];
3608
+ if (enableDeopt !== undefined) {
3609
+ const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
3610
+ if (!deoptimizationEnabled) {
3611
+ enableDeopt(instrumentation);
3612
+ }
3167
3613
  }
3168
3614
 
3169
3615
  switch (kind) {
@@ -3544,8 +3990,9 @@ function recompileExceptionClearForX86 (buffer, pc, exceptionClearImpl, nextFunc
3544
3990
  if (pointerSize === 4) {
3545
3991
  writer.putAndRegU32('esp', 0xfffffff0);
3546
3992
  } else {
3547
- writer.putMovRegU64('rax', uint64('0xfffffffffffffff0'));
3548
- writer.putAndRegReg('rsp', 'rax');
3993
+ const scratchReg = (threadReg !== 'rdi') ? 'rdi' : 'rsi';
3994
+ writer.putMovRegU64(scratchReg, uint64('0xfffffffffffffff0'));
3995
+ writer.putAndRegReg('rsp', scratchReg);
3549
3996
  }
3550
3997
  writer.putCallAddressWithAlignedArguments(callback, [threadReg]);
3551
3998
  writer.putMovRegReg('xsp', 'xbp');
@@ -485,9 +485,16 @@ class ClassFactory {
485
485
  if (flavor === 'jvm') {
486
486
  this._chooseObjectsJvm(specifier, env, callbacks);
487
487
  } else if (flavor === 'art') {
488
+ const legacyApiMissing = api['art::gc::Heap::VisitObjects'] === undefined;
489
+ if (legacyApiMissing) {
490
+ const preA12ApiMissing = api['art::gc::Heap::GetInstances'] === undefined;
491
+ if (preA12ApiMissing) {
492
+ return this._chooseObjectsJvm(specifier, env, callbacks);
493
+ }
494
+ }
488
495
  android.withRunnableArtThread(vm, env, thread => {
489
- if (api['art::gc::Heap::VisitObjects'] === undefined) {
490
- this._chooseObjectsArtModern(specifier, env, thread, callbacks);
496
+ if (legacyApiMissing) {
497
+ this._chooseObjectsArtPreA12(specifier, env, thread, callbacks);
491
498
  } else {
492
499
  this._chooseObjectsArtLegacy(specifier, env, thread, callbacks);
493
500
  }
@@ -545,7 +552,7 @@ class ClassFactory {
545
552
  }
546
553
  }
547
554
 
548
- _chooseObjectsArtModern (className, env, thread, callbacks) {
555
+ _chooseObjectsArtPreA12 (className, env, thread, callbacks) {
549
556
  const classWrapper = this.use(className);
550
557
 
551
558
  const scope = android.VariableSizedHandleScope.$new(thread, vm);
@@ -1176,7 +1176,7 @@ class Model {
1176
1176
 
1177
1177
  const params = query.match(methodQueryPattern);
1178
1178
  if (params === null) {
1179
- throw new Error('Invalid method query');
1179
+ throw new Error('Invalid query; format is: class!method -- see documentation of Java.enumerateMethods(query) for details');
1180
1180
  }
1181
1181
 
1182
1182
  const classQuery = Memory.allocUtf8String(params[1]);
package/lib/jvm.js CHANGED
@@ -1,3 +1,8 @@
1
+ const {
2
+ jvmtiVersion,
3
+ jvmtiCapabilities,
4
+ EnvJvmti
5
+ } = require('./jvmti');
1
6
  const memoize = require('./memoize');
2
7
  const { checkJniResult } = require('./result');
3
8
  const VM = require('./vm');
@@ -11,11 +16,6 @@ const JVM_ACC_IS_OBSOLETE = 0x00020000;
11
16
  const JVM_ACC_NOT_C2_COMPILABLE = 0x02000000;
12
17
  const JVM_ACC_NOT_C1_COMPILABLE = 0x04000000;
13
18
  const JVM_ACC_NOT_C2_OSR_COMPILABLE = 0x08000000;
14
- const JVMTI_VERSION_1_0 = 0x30010000;
15
-
16
- const jvmtiCapabilities = {
17
- canTagObjects: 1
18
- };
19
19
 
20
20
  const nativeFunctionOptions = {
21
21
  exceptions: 'propagate'
@@ -291,6 +291,7 @@ function _getApi () {
291
291
  }
292
292
 
293
293
  temporaryApi.jvmti = getEnvJvmti(temporaryApi);
294
+
294
295
  return temporaryApi;
295
296
  }
296
297
 
@@ -299,63 +300,21 @@ function getEnvJvmti (api) {
299
300
 
300
301
  let env;
301
302
  vm.perform(() => {
302
- const getEnv = new NativeFunction(vm.handle.readPointer().add(6 * pointerSize).readPointer(), 'int32', ['pointer', 'pointer', 'int32'],
303
- nativeFunctionOptions);
304
- const envBuf = Memory.alloc(pointerSize);
305
- let result = getEnv(vm.handle, envBuf, JVMTI_VERSION_1_0);
306
- checkJniResult('getEnvJvmti::GetEnv', result);
307
- env = new EnvJvmti(envBuf.readPointer(), vm);
303
+ const handle = vm.tryGetEnvHandle(jvmtiVersion.v1_0);
304
+ if (handle === null) {
305
+ throw new Error('JVMTI not available');
306
+ }
307
+ env = new EnvJvmti(handle, vm);
308
308
 
309
309
  const capaBuf = Memory.alloc(8);
310
310
  capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
311
- result = env.addCapabilities(capaBuf);
311
+ const result = env.addCapabilities(capaBuf);
312
312
  checkJniResult('getEnvJvmti::AddCapabilities', result);
313
313
  });
314
314
 
315
315
  return env;
316
316
  }
317
317
 
318
- function EnvJvmti (handle, vm) {
319
- this.handle = handle;
320
- this.vm = vm;
321
- this.vtable = handle.readPointer();
322
- }
323
-
324
- EnvJvmti.prototype.deallocate = proxy(47, 'int32', ['pointer', 'pointer'], function (impl, mem) {
325
- return impl(this.handle, mem);
326
- });
327
-
328
- EnvJvmti.prototype.getLoadedClasses = proxy(78, 'int32', ['pointer', 'pointer', 'pointer'], function (impl, classCountPtr, classesPtr) {
329
- const result = impl(this.handle, classCountPtr, classesPtr);
330
- checkJniResult('EnvJvmti::getLoadedClasses', result);
331
- });
332
-
333
- EnvJvmti.prototype.iterateOverInstancesOfClass = proxy(112, 'int32', ['pointer', 'pointer', 'int', 'pointer', 'pointer'], function (impl, klass, objectFilter, heapObjectCallback, userData) {
334
- const result = impl(this.handle, klass, objectFilter, heapObjectCallback, userData);
335
- checkJniResult('EnvJvmti::iterateOverInstancesOfClass', result);
336
- });
337
-
338
- EnvJvmti.prototype.getObjectsWithTags = proxy(114, 'int32', ['pointer', 'int', 'pointer', 'pointer', 'pointer', 'pointer'], function (impl, tagCount, tags, countPtr, objectResultPtr, tagResultPtr) {
339
- const result = impl(this.handle, tagCount, tags, countPtr, objectResultPtr, tagResultPtr);
340
- checkJniResult('EnvJvmti::getObjectsWithTags', result);
341
- });
342
-
343
- EnvJvmti.prototype.addCapabilities = proxy(142, 'int32', ['pointer', 'pointer'], function (impl, capabilitiesPtr) {
344
- return impl(this.handle, capabilitiesPtr);
345
- });
346
-
347
- function proxy (offset, retType, argTypes, wrapper) {
348
- let impl = null;
349
- return function () {
350
- if (impl === null) {
351
- impl = new NativeFunction(this.vtable.add((offset - 1) * pointerSize).readPointer(), retType, argTypes, nativeFunctionOptions);
352
- }
353
- let args = [impl];
354
- args = args.concat.apply(args, arguments);
355
- return wrapper.apply(this, args);
356
- };
357
- }
358
-
359
318
  function ensureClassInitialized (env, classRef) {
360
319
  }
361
320
 
package/lib/jvmti.js ADDED
@@ -0,0 +1,62 @@
1
+ const { checkJniResult } = require('./result');
2
+
3
+ const jvmtiVersion = {
4
+ v1_0: 0x30010000,
5
+ v1_2: 0x30010200
6
+ };
7
+
8
+ const jvmtiCapabilities = {
9
+ canTagObjects: 1
10
+ };
11
+
12
+ const { pointerSize } = Process;
13
+ const nativeFunctionOptions = {
14
+ exceptions: 'propagate'
15
+ };
16
+
17
+ function EnvJvmti (handle, vm) {
18
+ this.handle = handle;
19
+ this.vm = vm;
20
+ this.vtable = handle.readPointer();
21
+ }
22
+
23
+ EnvJvmti.prototype.deallocate = proxy(47, 'int32', ['pointer', 'pointer'], function (impl, mem) {
24
+ return impl(this.handle, mem);
25
+ });
26
+
27
+ EnvJvmti.prototype.getLoadedClasses = proxy(78, 'int32', ['pointer', 'pointer', 'pointer'], function (impl, classCountPtr, classesPtr) {
28
+ const result = impl(this.handle, classCountPtr, classesPtr);
29
+ checkJniResult('EnvJvmti::getLoadedClasses', result);
30
+ });
31
+
32
+ EnvJvmti.prototype.iterateOverInstancesOfClass = proxy(112, 'int32', ['pointer', 'pointer', 'int', 'pointer', 'pointer'], function (impl, klass, objectFilter, heapObjectCallback, userData) {
33
+ const result = impl(this.handle, klass, objectFilter, heapObjectCallback, userData);
34
+ checkJniResult('EnvJvmti::iterateOverInstancesOfClass', result);
35
+ });
36
+
37
+ EnvJvmti.prototype.getObjectsWithTags = proxy(114, 'int32', ['pointer', 'int', 'pointer', 'pointer', 'pointer', 'pointer'], function (impl, tagCount, tags, countPtr, objectResultPtr, tagResultPtr) {
38
+ const result = impl(this.handle, tagCount, tags, countPtr, objectResultPtr, tagResultPtr);
39
+ checkJniResult('EnvJvmti::getObjectsWithTags', result);
40
+ });
41
+
42
+ EnvJvmti.prototype.addCapabilities = proxy(142, 'int32', ['pointer', 'pointer'], function (impl, capabilitiesPtr) {
43
+ return impl(this.handle, capabilitiesPtr);
44
+ });
45
+
46
+ function proxy (offset, retType, argTypes, wrapper) {
47
+ let impl = null;
48
+ return function () {
49
+ if (impl === null) {
50
+ impl = new NativeFunction(this.vtable.add((offset - 1) * pointerSize).readPointer(), retType, argTypes, nativeFunctionOptions);
51
+ }
52
+ let args = [impl];
53
+ args = args.concat.apply(args, arguments);
54
+ return wrapper.apply(this, args);
55
+ };
56
+ }
57
+
58
+ module.exports = {
59
+ jvmtiVersion,
60
+ jvmtiCapabilities,
61
+ EnvJvmti
62
+ };
package/lib/vm.js CHANGED
@@ -107,12 +107,20 @@ function VM (api) {
107
107
  };
108
108
 
109
109
  this._tryGetEnv = function () {
110
+ const h = this.tryGetEnvHandle(JNI_VERSION_1_6);
111
+ if (h === null) {
112
+ return null;
113
+ }
114
+ return new Env(h, this);
115
+ };
116
+
117
+ this.tryGetEnvHandle = function (version) {
110
118
  const envBuf = Memory.alloc(pointerSize);
111
- const result = getEnv(handle, envBuf, JNI_VERSION_1_6);
119
+ const result = getEnv(handle, envBuf, version);
112
120
  if (result !== JNI_OK) {
113
121
  return null;
114
122
  }
115
- return new Env(envBuf.readPointer(), this);
123
+ return envBuf.readPointer();
116
124
  };
117
125
 
118
126
  this.makeHandleDestructor = function (handle) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frida-java-bridge",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "Java runtime interop from Frida",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -36,6 +36,7 @@
36
36
  "Instruction",
37
37
  "Int64",
38
38
  "Interceptor",
39
+ "MatchPattern",
39
40
  "Memory",
40
41
  "Module",
41
42
  "NULL",