frida-java-bridge 6.0.1 → 6.1.1

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/index.js CHANGED
@@ -233,7 +233,7 @@ class Runtime {
233
233
 
234
234
  const visitClassLoaders = api['art::ClassLinker::VisitClassLoaders'];
235
235
  if (visitClassLoaders === undefined) {
236
- throw new Error('This API is only available on Nougat and above');
236
+ throw new Error('This API is only available on Android >= 7.0');
237
237
  }
238
238
 
239
239
  const ClassLoader = factory.use('java.lang.ClassLoader');
package/lib/android.js CHANGED
@@ -1,6 +1,12 @@
1
1
  const makeCodeAllocator = require('./alloc');
2
+ const {
3
+ jvmtiVersion,
4
+ jvmtiCapabilities,
5
+ EnvJvmti
6
+ } = require('./jvmti');
7
+ const { parseInstructionsAt } = require('./machine-code');
2
8
  const memoize = require('./memoize');
3
- const { checkJniResult } = require('./result');
9
+ const { checkJniResult, JNI_OK } = require('./result');
4
10
  const VM = require('./vm');
5
11
 
6
12
  const jsizeSize = 4;
@@ -99,6 +105,7 @@ const artThreadStateTransitions = {};
99
105
  let cachedApi = null;
100
106
  let MethodMangler = null;
101
107
  let artController = null;
108
+ const inlineHooks = [];
102
109
  const patchedClasses = new Map();
103
110
  const artQuickInterceptors = [];
104
111
  let thunkPage = null;
@@ -330,6 +337,7 @@ function _getApi () {
330
337
  '_ZN3art3Dbg13ConfigureJdwpERKNS_4JDWP11JdwpOptionsE',
331
338
  '_ZN3art31InternalDebuggerControlCallback13StartDebuggerEv',
332
339
  '_ZN3art3Dbg15gDebuggerActiveE',
340
+ '_ZN3art15instrumentation15Instrumentation20EnableDeoptimizationEv',
333
341
  '_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEPKc',
334
342
  '_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEv',
335
343
  '_ZN3art7Runtime19DeoptimizeBootImageEv',
@@ -474,6 +482,16 @@ function _getApi () {
474
482
  artController = makeArtController(vm);
475
483
 
476
484
  fixupArtQuickDeliverExceptionBug(temporaryApi);
485
+
486
+ let cachedJvmti = null;
487
+ Object.defineProperty(temporaryApi, 'jvmti', {
488
+ get () {
489
+ if (cachedJvmti === null) {
490
+ cachedJvmti = [tryGetEnvJvmti(vm, this.artRuntime)];
491
+ }
492
+ return cachedJvmti[0];
493
+ }
494
+ });
477
495
  }
478
496
 
479
497
  const cxxImports = Module.enumerateImports(vmModule.path)
@@ -490,6 +508,39 @@ function _getApi () {
490
508
  return temporaryApi;
491
509
  }
492
510
 
511
+ function tryGetEnvJvmti (vm, runtime) {
512
+ let env = null;
513
+
514
+ vm.perform(() => {
515
+ const ensurePluginLoaded = new NativeFunction(
516
+ Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
517
+ 'bool',
518
+ ['pointer', 'pointer', 'pointer']);
519
+ const errorPtr = Memory.alloc(pointerSize);
520
+ const success = ensurePluginLoaded(runtime, Memory.allocUtf8String('libopenjdkjvmti.so'), errorPtr);
521
+ if (!success) {
522
+ // FIXME: Avoid leaking error
523
+ return;
524
+ }
525
+
526
+ const kArtTiVersion = jvmtiVersion.v1_2 | 0x40000000;
527
+ const handle = vm.tryGetEnvHandle(kArtTiVersion);
528
+ if (handle === null) {
529
+ return;
530
+ }
531
+ env = new EnvJvmti(handle, vm);
532
+
533
+ const capaBuf = Memory.alloc(8);
534
+ capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
535
+ const result = env.addCapabilities(capaBuf);
536
+ if (result !== JNI_OK) {
537
+ env = null;
538
+ }
539
+ });
540
+
541
+ return env;
542
+ }
543
+
493
544
  function ensureClassInitialized (env, classRef) {
494
545
  const api = getApi();
495
546
  if (api.flavor !== 'art') {
@@ -530,6 +581,7 @@ function _getArtRuntimeSpec (api) {
530
581
  * InternTable* intern_table_; <--/
531
582
  * ClassLinker* class_linker_; <-/
532
583
  * SignalCatcher* signal_catcher_;
584
+ * SmallIrtAllocator* small_irt_allocator_; <------------ API level >= 33 or Android Tiramisu Developer Preview
533
585
  * std::unique_ptr<jni::JniIdManager> jni_id_manager_; <- API level >= 30 or Android R Developer Preview
534
586
  * bool use_tombstoned_traces_; <-------------------- API level 27/28
535
587
  * std::string stack_trace_file_; <-------------------- API level <= 28
@@ -553,7 +605,10 @@ function _getArtRuntimeSpec (api) {
553
605
  if (value.equals(vm)) {
554
606
  let classLinkerOffset = null;
555
607
  let jniIdManagerOffset = null;
556
- if (apiLevel >= 30 || getAndroidCodename() === 'R') {
608
+ if (apiLevel >= 33 || getAndroidCodename() === 'Tiramisu') {
609
+ classLinkerOffset = offset - (4 * pointerSize);
610
+ jniIdManagerOffset = offset - pointerSize;
611
+ } else if (apiLevel >= 30 || getAndroidCodename() === 'R') {
557
612
  classLinkerOffset = offset - (3 * pointerSize);
558
613
  jniIdManagerOffset = offset - pointerSize;
559
614
  } else if (apiLevel >= 29) {
@@ -593,7 +648,7 @@ function _getArtRuntimeSpec (api) {
593
648
  throw new Error('Unable to determine Runtime field offsets');
594
649
  }
595
650
 
596
- spec.offset.instrumentation = tryDetectInstrumentationOffset();
651
+ spec.offset.instrumentation = tryDetectInstrumentationOffset(api);
597
652
  spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset();
598
653
 
599
654
  return spec;
@@ -606,43 +661,26 @@ const instrumentationOffsetParsers = {
606
661
  arm64: parseArm64InstrumentationOffset
607
662
  };
608
663
 
609
- function tryDetectInstrumentationOffset () {
610
- let cur = Module.findExportByName('libart.so', 'MterpHandleException');
611
- if (cur === null) {
664
+ function tryDetectInstrumentationOffset (api) {
665
+ const impl = api['art::Runtime::DeoptimizeBootImage'];
666
+ if (impl === undefined) {
612
667
  return null;
613
668
  }
614
669
 
615
- const tryParse = instrumentationOffsetParsers[Process.arch];
616
-
617
- for (let i = 0; i !== 20; i++) {
618
- const insn = Instruction.parse(cur);
619
-
620
- const offset = tryParse(insn);
621
- if (offset !== null) {
622
- return offset;
623
- }
624
-
625
- cur = insn.next;
626
- }
627
-
628
- return null;
670
+ return parseInstructionsAt(impl, instrumentationOffsetParsers[Process.arch], { limit: 30 });
629
671
  }
630
672
 
631
673
  function parsex86InstrumentationOffset (insn) {
632
- const { mnemonic } = insn;
633
- if (mnemonic !== 'mov' && mnemonic !== 'add') {
674
+ if (insn.mnemonic !== 'lea') {
634
675
  return null;
635
676
  }
636
677
 
637
- const op1 = insn.operands[1];
638
- if (op1.type !== 'imm') {
639
- return null;
640
- }
641
- if (op1.value < 0x100 || op1.value > 0x400) {
678
+ const offset = insn.operands[1].value.disp;
679
+ if (offset < 0x100 || offset > 0x400) {
642
680
  return null;
643
681
  }
644
682
 
645
- return op1.value;
683
+ return offset;
646
684
  }
647
685
 
648
686
  function parseArmInstrumentationOffset (insn) {
@@ -673,12 +711,21 @@ function parseArm64InstrumentationOffset (insn) {
673
711
  return null;
674
712
  }
675
713
 
714
+ if (ops[0].value === 'sp' || ops[1].value === 'sp') {
715
+ return null;
716
+ }
717
+
676
718
  const op2 = ops[2];
677
719
  if (op2.type !== 'imm') {
678
720
  return null;
679
721
  }
680
722
 
681
- return op2.value;
723
+ const offset = op2.value.valueOf();
724
+ if (offset < 0x100 || offset > 0x400) {
725
+ return null;
726
+ }
727
+
728
+ return offset;
682
729
  }
683
730
 
684
731
  const jniIdsIndirectionOffsetParsers = {
@@ -689,27 +736,17 @@ const jniIdsIndirectionOffsetParsers = {
689
736
  };
690
737
 
691
738
  function tryDetectJniIdsIndirectionOffset () {
692
- let cur = Module.findExportByName('libart.so', '_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
693
- if (cur === null) {
739
+ const impl = Module.findExportByName('libart.so', '_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
740
+ if (impl === null) {
694
741
  return null;
695
742
  }
696
743
 
697
- const tryParse = jniIdsIndirectionOffsetParsers[Process.arch];
698
-
699
- let prevInsn = null;
700
- for (let i = 0; i !== 20; i++) {
701
- const insn = Instruction.parse(cur);
702
-
703
- const offset = tryParse(insn, prevInsn);
704
- if (offset !== null) {
705
- return offset;
706
- }
707
-
708
- cur = insn.next;
709
- prevInsn = insn;
744
+ const offset = parseInstructionsAt(impl, jniIdsIndirectionOffsetParsers[Process.arch], { limit: 20 });
745
+ if (offset === null) {
746
+ throw new Error('Unable to determine Runtime.jni_ids_indirection_ offset');
710
747
  }
711
748
 
712
- throw new Error('Unable to determine Runtime.jni_ids_indirection_ offset');
749
+ return offset;
713
750
  }
714
751
 
715
752
  function parsex86JniIdsIndirectionOffset (insn) {
@@ -902,7 +939,7 @@ function _getArtMethodSpec (vm) {
902
939
 
903
940
  vm.perform(env => {
904
941
  const process = env.findClass('android/os/Process');
905
- const setArgV0 = unwrapMethodId(env.getStaticMethodId(process, 'setArgV0', '(Ljava/lang/String;)V'));
942
+ const getElapsedCpuTime = unwrapMethodId(env.getStaticMethodId(process, 'getElapsedCpuTime', '()J'));
906
943
  env.deleteLocalRef(process);
907
944
 
908
945
  const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
@@ -914,13 +951,13 @@ function _getArtMethodSpec (vm) {
914
951
  const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;
915
952
 
916
953
  const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;
917
- const relevantAccessFlagsMask = ~(kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
954
+ const relevantAccessFlagsMask = ~(kAccFastInterpreterToInterpreterInvoke | kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
918
955
 
919
956
  let jniCodeOffset = null;
920
957
  let accessFlagsOffset = null;
921
958
  let remaining = 2;
922
959
  for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
923
- const field = setArgV0.add(offset);
960
+ const field = getElapsedCpuTime.add(offset);
924
961
 
925
962
  if (jniCodeOffset === null) {
926
963
  const address = field.readPointer();
@@ -1711,6 +1748,7 @@ on_leave_gc_concurrent_copying_copying_phase (GumInvocationContext * ic)
1711
1748
  return {
1712
1749
  handle: cm,
1713
1750
  replacedMethods: {
1751
+ isReplacement: new NativeFunction(cm.is_replacement_method, 'bool', ['pointer'], fastOptions),
1714
1752
  get: new NativeFunction(cm.get_replacement_method, 'pointer', ['pointer'], fastOptions),
1715
1753
  set: new NativeFunction(cm.set_replacement_method, 'void', ['pointer', 'pointer'], fastOptions),
1716
1754
  delete: new NativeFunction(cm.delete_replacement_method, 'void', ['pointer'], fastOptions),
@@ -1785,18 +1823,20 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
1785
1823
  }
1786
1824
  taughtArtAboutReplacementMethods = true;
1787
1825
 
1788
- const { getOatQuickMethodHeaderImpl } = artController;
1789
- if (getOatQuickMethodHeaderImpl === null) {
1790
- return;
1791
- }
1826
+ if (!maybeInstrumentGetOatQuickMethodHeaderInlineCopies()) {
1827
+ const { getOatQuickMethodHeaderImpl } = artController;
1828
+ if (getOatQuickMethodHeaderImpl === null) {
1829
+ return;
1830
+ }
1792
1831
 
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
- */
1832
+ try {
1833
+ Interceptor.replace(getOatQuickMethodHeaderImpl, artController.hooks.ArtMethod.getOatQuickMethodHeader);
1834
+ } catch (e) {
1835
+ /*
1836
+ * Already replaced by another script. For now we don't support replacing methods from multiple scripts,
1837
+ * but we'll allow users to try it if they're feeling adventurous.
1838
+ */
1839
+ }
1800
1840
  }
1801
1841
 
1802
1842
  const apiLevel = getAndroidApiLevel();
@@ -1813,6 +1853,381 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
1813
1853
  }
1814
1854
  }
1815
1855
 
1856
+ const artGetOatQuickMethodHeaderInlinedCopyHandler = {
1857
+ arm: {
1858
+ signatures: [
1859
+ {
1860
+ pattern: [
1861
+ 'b0 68', // ldr r0, [r6, #8]
1862
+ '01 30', // adds r0, #1
1863
+ '0c d0', // beq #0x16fcd4
1864
+ '1b 98', // ldr r0, [sp, #0x6c]
1865
+ ':',
1866
+ 'c0 ff',
1867
+ 'c0 ff',
1868
+ '00 ff',
1869
+ '00 2f'
1870
+ ],
1871
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1872
+ },
1873
+ {
1874
+ pattern: [
1875
+ 'd8 f8 08 00', // ldr r0, [r8, #8]
1876
+ '01 30', // adds r0, #1
1877
+ '0c d0', // beq #0x16fcd4
1878
+ '1b 98', // ldr r0, [sp, #0x6c]
1879
+ ':',
1880
+ 'f0 ff ff 0f',
1881
+ 'ff ff',
1882
+ '00 ff',
1883
+ '00 2f'
1884
+ ],
1885
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1886
+ },
1887
+ {
1888
+ pattern: [
1889
+ 'b0 68', // ldr r0, [r6, #8]
1890
+ '01 30', // adds r0, #1
1891
+ '40 f0 c3 80', // bne #0x203bf0
1892
+ '00 25', // movs r5, #0
1893
+ ':',
1894
+ 'c0 ff',
1895
+ 'c0 ff',
1896
+ 'c0 fb 00 d0',
1897
+ 'ff f8'
1898
+ ],
1899
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
1900
+ }
1901
+ ],
1902
+ instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm
1903
+ },
1904
+ arm64: {
1905
+ signatures: [
1906
+ {
1907
+ pattern: [
1908
+ /* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
1909
+ '1f 05 00 31', // cmn w8, #0x1
1910
+ '40 01 00 54', // b.eq 0x2e4204
1911
+ '88 39 00 f0', // adrp x8, 0xa17000
1912
+ ':',
1913
+ /* 00 */ 'fc ff ff',
1914
+ '1f fc ff ff',
1915
+ '1f 00 00 ff',
1916
+ '00 00 00 9f'
1917
+ ],
1918
+ offset: 1,
1919
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
1920
+ },
1921
+ {
1922
+ pattern: [
1923
+ /* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
1924
+ '1f 05 00 31', // cmn w8, #0x1
1925
+ '01 34 00 54', // b.ne 0x3d8e50
1926
+ 'e0 03 1f aa', // mov x0, xzr
1927
+ ':',
1928
+ /* 00 */ 'fc ff ff',
1929
+ '1f fc ff ff',
1930
+ '1f 00 00 ff',
1931
+ 'e0 ff ff ff'
1932
+ ],
1933
+ offset: 1,
1934
+ validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
1935
+ }
1936
+ ],
1937
+ instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm64
1938
+ }
1939
+ };
1940
+
1941
+ function validateGetOatQuickMethodHeaderInlinedMatchArm ({ address, size }) {
1942
+ const ldr = Instruction.parse(address.or(1));
1943
+ const [ldrDst, ldrSrc] = ldr.operands;
1944
+ const methodReg = ldrSrc.value.base;
1945
+ const scratchReg = ldrDst.value;
1946
+
1947
+ const branch = Instruction.parse(ldr.next.add(2));
1948
+ const targetWhenTrue = ptr(branch.operands[0].value);
1949
+ const targetWhenFalse = branch.address.add(branch.size);
1950
+
1951
+ let targetWhenRegularMethod, targetWhenRuntimeMethod;
1952
+ if (branch.mnemonic === 'beq') {
1953
+ targetWhenRegularMethod = targetWhenFalse;
1954
+ targetWhenRuntimeMethod = targetWhenTrue;
1955
+ } else {
1956
+ targetWhenRegularMethod = targetWhenTrue;
1957
+ targetWhenRuntimeMethod = targetWhenFalse;
1958
+ }
1959
+
1960
+ return parseInstructionsAt(targetWhenRegularMethod.or(1), tryParse, { limit: 3 });
1961
+
1962
+ function tryParse (insn) {
1963
+ const { mnemonic } = insn;
1964
+ if (!(mnemonic === 'ldr' || mnemonic === 'ldr.w')) {
1965
+ return null;
1966
+ }
1967
+
1968
+ const { base, disp } = insn.operands[1].value;
1969
+ if (!(base === methodReg && disp === 0x14)) {
1970
+ return null;
1971
+ }
1972
+
1973
+ return {
1974
+ methodReg,
1975
+ scratchReg,
1976
+ target: {
1977
+ whenTrue: targetWhenTrue,
1978
+ whenRegularMethod: targetWhenRegularMethod,
1979
+ whenRuntimeMethod: targetWhenRuntimeMethod
1980
+ }
1981
+ };
1982
+ }
1983
+ }
1984
+
1985
+ function validateGetOatQuickMethodHeaderInlinedMatchArm64 ({ address, size }) {
1986
+ const [ldrDst, ldrSrc] = Instruction.parse(address).operands;
1987
+ const methodReg = ldrSrc.value.base;
1988
+ const scratchReg = 'x' + ldrDst.value.substring(1);
1989
+
1990
+ const branch = Instruction.parse(address.add(8));
1991
+ const targetWhenTrue = ptr(branch.operands[0].value);
1992
+ const targetWhenFalse = address.add(12);
1993
+
1994
+ let targetWhenRegularMethod, targetWhenRuntimeMethod;
1995
+ if (branch.mnemonic === 'b.eq') {
1996
+ targetWhenRegularMethod = targetWhenFalse;
1997
+ targetWhenRuntimeMethod = targetWhenTrue;
1998
+ } else {
1999
+ targetWhenRegularMethod = targetWhenTrue;
2000
+ targetWhenRuntimeMethod = targetWhenFalse;
2001
+ }
2002
+
2003
+ return parseInstructionsAt(targetWhenRegularMethod, tryParse, { limit: 3 });
2004
+
2005
+ function tryParse (insn) {
2006
+ if (insn.mnemonic !== 'ldr') {
2007
+ return null;
2008
+ }
2009
+
2010
+ const { base, disp } = insn.operands[1].value;
2011
+ if (!(base === methodReg && disp === 0x18)) {
2012
+ return null;
2013
+ }
2014
+
2015
+ return {
2016
+ methodReg,
2017
+ scratchReg,
2018
+ target: {
2019
+ whenTrue: targetWhenTrue,
2020
+ whenRegularMethod: targetWhenRegularMethod,
2021
+ whenRuntimeMethod: targetWhenRuntimeMethod
2022
+ }
2023
+ };
2024
+ }
2025
+ }
2026
+
2027
+ function maybeInstrumentGetOatQuickMethodHeaderInlineCopies () {
2028
+ if (getAndroidApiLevel() < 31) {
2029
+ return false;
2030
+ }
2031
+
2032
+ const handler = artGetOatQuickMethodHeaderInlinedCopyHandler[Process.arch];
2033
+ if (handler === undefined) {
2034
+ // Not needed on x86 and x64, at least not for now...
2035
+ return false;
2036
+ }
2037
+
2038
+ const signatures = handler.signatures.map(({ pattern, offset = 0, validateMatch = returnEmptyObject }) => {
2039
+ return {
2040
+ pattern: new MatchPattern(pattern.join('')),
2041
+ offset,
2042
+ validateMatch
2043
+ };
2044
+ });
2045
+
2046
+ const impls = [];
2047
+ for (const { base, size } of getApi().module.enumerateRanges('--x')) {
2048
+ for (const { pattern, offset, validateMatch } of signatures) {
2049
+ const matches = Memory.scanSync(base, size, pattern)
2050
+ .map(({ address, size }) => {
2051
+ return { address: address.sub(offset), size: size + offset };
2052
+ })
2053
+ .filter(match => {
2054
+ const validationResult = validateMatch(match);
2055
+ if (validationResult === null) {
2056
+ return false;
2057
+ }
2058
+ match.validationResult = validationResult;
2059
+ return true;
2060
+ });
2061
+ impls.push(...matches);
2062
+ }
2063
+ }
2064
+
2065
+ impls.forEach(handler.instrument);
2066
+
2067
+ return true;
2068
+ }
2069
+
2070
+ function returnEmptyObject () {
2071
+ return {};
2072
+ }
2073
+
2074
+ class InlineHook {
2075
+ constructor (address, size, trampoline) {
2076
+ this.address = address;
2077
+ this.size = size;
2078
+ this.originalCode = address.readByteArray(size);
2079
+ this.trampoline = trampoline;
2080
+ }
2081
+
2082
+ revert () {
2083
+ Memory.patchCode(this.address, this.size, code => {
2084
+ code.writeByteArray(this.originalCode);
2085
+ });
2086
+ }
2087
+ }
2088
+
2089
+ function instrumentGetOatQuickMethodHeaderInlinedCopyArm ({ address, size, validationResult }) {
2090
+ const { methodReg, target } = validationResult;
2091
+
2092
+ const trampoline = Memory.alloc(Process.pageSize);
2093
+ let redirectCapacity = size;
2094
+
2095
+ Memory.patchCode(trampoline, 256, code => {
2096
+ const writer = new ThumbWriter(code, { pc: trampoline });
2097
+
2098
+ const relocator = new ThumbRelocator(address, writer);
2099
+ for (let i = 0; i !== 2; i++) {
2100
+ relocator.readOne();
2101
+ }
2102
+ relocator.writeAll();
2103
+
2104
+ relocator.readOne();
2105
+ relocator.skipOne();
2106
+ writer.putBCondLabel('eq', 'runtime_or_replacement_method');
2107
+
2108
+ const vpushFpRegs = [0x2d, 0xed, 0x10, 0x0a]; /* vpush {s0-s15} */
2109
+ writer.putBytes(vpushFpRegs);
2110
+
2111
+ const savedRegs = ['r0', 'r1', 'r2', 'r3'];
2112
+ writer.putPushRegs(savedRegs);
2113
+
2114
+ writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
2115
+ writer.putCmpRegImm('r0', 0);
2116
+
2117
+ writer.putPopRegs(savedRegs);
2118
+
2119
+ const vpopFpRegs = [0xbd, 0xec, 0x10, 0x0a]; /* vpop {s0-s15} */
2120
+ writer.putBytes(vpopFpRegs);
2121
+
2122
+ writer.putBCondLabel('ne', 'runtime_or_replacement_method');
2123
+ writer.putBLabel('regular_method');
2124
+
2125
+ relocator.readOne();
2126
+
2127
+ const tailIsRegular = relocator.input.address.equals(target.whenRegularMethod);
2128
+
2129
+ writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
2130
+ relocator.writeOne();
2131
+ while (redirectCapacity < 10) {
2132
+ const offset = relocator.readOne();
2133
+ if (offset === 0) {
2134
+ redirectCapacity = 10;
2135
+ break;
2136
+ }
2137
+ redirectCapacity = offset;
2138
+ }
2139
+ relocator.writeAll();
2140
+ writer.putBranchAddress(address.add(redirectCapacity + 1));
2141
+
2142
+ writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
2143
+ writer.putBranchAddress(target.whenTrue);
2144
+
2145
+ writer.flush();
2146
+ });
2147
+
2148
+ inlineHooks.push(new InlineHook(address, redirectCapacity, trampoline));
2149
+
2150
+ Memory.patchCode(address, redirectCapacity, code => {
2151
+ const writer = new ThumbWriter(code, { pc: address });
2152
+ writer.putLdrRegAddress('pc', trampoline.or(1));
2153
+ writer.flush();
2154
+ });
2155
+ }
2156
+
2157
+ function instrumentGetOatQuickMethodHeaderInlinedCopyArm64 ({ address, size, validationResult }) {
2158
+ const { methodReg, scratchReg, target } = validationResult;
2159
+
2160
+ const trampoline = Memory.alloc(Process.pageSize);
2161
+
2162
+ Memory.patchCode(trampoline, 256, code => {
2163
+ const writer = new Arm64Writer(code, { pc: trampoline });
2164
+
2165
+ const relocator = new Arm64Relocator(address, writer);
2166
+ for (let i = 0; i !== 2; i++) {
2167
+ relocator.readOne();
2168
+ }
2169
+ relocator.writeAll();
2170
+
2171
+ relocator.readOne();
2172
+ relocator.skipOne();
2173
+ writer.putBCondLabel('eq', 'runtime_or_replacement_method');
2174
+
2175
+ const savedRegs = [
2176
+ 'd0', 'd1',
2177
+ 'd2', 'd3',
2178
+ 'd4', 'd5',
2179
+ 'd6', 'd7',
2180
+ 'x0', 'x1',
2181
+ 'x2', 'x3',
2182
+ 'x4', 'x5',
2183
+ 'x6', 'x7',
2184
+ 'x8', 'x9',
2185
+ 'x10', 'x11',
2186
+ 'x12', 'x13',
2187
+ 'x14', 'x15',
2188
+ 'x16', 'x17'
2189
+ ];
2190
+ const numSavedRegs = savedRegs.length;
2191
+
2192
+ for (let i = 0; i !== numSavedRegs; i += 2) {
2193
+ writer.putPushRegReg(savedRegs[i], savedRegs[i + 1]);
2194
+ }
2195
+
2196
+ writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
2197
+ writer.putCmpRegReg('x0', 'xzr');
2198
+
2199
+ for (let i = numSavedRegs - 2; i >= 0; i -= 2) {
2200
+ writer.putPopRegReg(savedRegs[i], savedRegs[i + 1]);
2201
+ }
2202
+
2203
+ writer.putBCondLabel('ne', 'runtime_or_replacement_method');
2204
+ writer.putBLabel('regular_method');
2205
+
2206
+ relocator.readOne();
2207
+ const tailInstruction = relocator.input;
2208
+
2209
+ const tailIsRegular = tailInstruction.address.equals(target.whenRegularMethod);
2210
+
2211
+ writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
2212
+ relocator.writeOne();
2213
+ writer.putBranchAddress(tailInstruction.next);
2214
+
2215
+ writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
2216
+ writer.putBranchAddress(target.whenTrue);
2217
+
2218
+ writer.flush();
2219
+ });
2220
+
2221
+ inlineHooks.push(new InlineHook(address, size, trampoline));
2222
+
2223
+ Memory.patchCode(address, size, code => {
2224
+ const writer = new Arm64Writer(code, { pc: address });
2225
+ writer.putLdrRegAddress(scratchReg, trampoline);
2226
+ writer.putBrReg(scratchReg);
2227
+ writer.flush();
2228
+ });
2229
+ }
2230
+
1816
2231
  function makeMethodMangler (methodId) {
1817
2232
  return new MethodMangler(methodId);
1818
2233
  }
@@ -1937,8 +2352,6 @@ extern void translate_location (ArtMethod * method, guint32 pc, const gchar ** s
1937
2352
  extern void cxx_delete (void * mem);
1938
2353
  extern unsigned long strtoul (const char * str, char ** endptr, int base);
1939
2354
 
1940
- extern void _frida_log (const gchar * message);
1941
-
1942
2355
  static bool visit_frame (ArtStackVisitor * visitor);
1943
2356
  static void art_stack_frame_destroy (ArtStackFrame * frame);
1944
2357
 
@@ -1947,8 +2360,6 @@ static void append_jni_type_name (GString * s, const gchar * name, gsize length)
1947
2360
  static void std_string_destroy (StdString * str);
1948
2361
  static gchar * std_string_get_data (StdString * str);
1949
2362
 
1950
- static void frida_log (const char * format, ...);
1951
-
1952
2363
  void
1953
2364
  init (void)
1954
2365
  {
@@ -2334,7 +2745,13 @@ function revertGlobalPatches () {
2334
2745
  });
2335
2746
  patchedClasses.clear();
2336
2747
 
2337
- artQuickInterceptors.forEach((interceptor) => interceptor.deactivate());
2748
+ for (const interceptor of artQuickInterceptors.splice(0)) {
2749
+ interceptor.deactivate();
2750
+ }
2751
+
2752
+ for (const hook of inlineHooks.splice(0)) {
2753
+ hook.revert();
2754
+ }
2338
2755
  }
2339
2756
 
2340
2757
  function unwrapMethodId (methodId) {
@@ -2707,7 +3124,7 @@ function writeArtQuickCodePrologueArm64 (target, trampoline, redirectSize) {
2707
3124
 
2708
3125
  const artQuickCodeHookRedirectSize = {
2709
3126
  ia32: 5,
2710
- x64: 14,
3127
+ x64: 16,
2711
3128
  arm: 8,
2712
3129
  arm64: 16
2713
3130
  };
@@ -3116,6 +3533,10 @@ function deoptimizeEverything (vm, env) {
3116
3533
  function deoptimizeBootImage (vm, env) {
3117
3534
  const api = getApi();
3118
3535
 
3536
+ if (getAndroidApiLevel() < 26) {
3537
+ throw new Error('This API is only available on Android >= 8.0');
3538
+ }
3539
+
3119
3540
  withRunnableArtThread(vm, env, thread => {
3120
3541
  api['art::Runtime::DeoptimizeBootImage'](api.artRuntime);
3121
3542
  });
@@ -3125,7 +3546,7 @@ function requestDeoptimization (vm, env, kind, method) {
3125
3546
  const api = getApi();
3126
3547
 
3127
3548
  if (getAndroidApiLevel() < 24) {
3128
- throw new Error('This API is only available on Nougat and above');
3549
+ throw new Error('This API is only available on Android >= 7.0');
3129
3550
  }
3130
3551
 
3131
3552
  withRunnableArtThread(vm, env, thread => {
@@ -3161,9 +3582,12 @@ function requestDeoptimization (vm, env, kind, method) {
3161
3582
  throw new Error('Unable to find Instrumentation class in ART; please file a bug');
3162
3583
  }
3163
3584
 
3164
- const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
3165
- if (!deoptimizationEnabled) {
3166
- api['art::Instrumentation::EnableDeoptimization'](instrumentation);
3585
+ const enableDeopt = api['art::Instrumentation::EnableDeoptimization'];
3586
+ if (enableDeopt !== undefined) {
3587
+ const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
3588
+ if (!deoptimizationEnabled) {
3589
+ enableDeopt(instrumentation);
3590
+ }
3167
3591
  }
3168
3592
 
3169
3593
  switch (kind) {
@@ -3544,8 +3968,9 @@ function recompileExceptionClearForX86 (buffer, pc, exceptionClearImpl, nextFunc
3544
3968
  if (pointerSize === 4) {
3545
3969
  writer.putAndRegU32('esp', 0xfffffff0);
3546
3970
  } else {
3547
- writer.putMovRegU64('rax', uint64('0xfffffffffffffff0'));
3548
- writer.putAndRegReg('rsp', 'rax');
3971
+ const scratchReg = (threadReg !== 'rdi') ? 'rdi' : 'rsi';
3972
+ writer.putMovRegU64(scratchReg, uint64('0xfffffffffffffff0'));
3973
+ writer.putAndRegReg('rsp', scratchReg);
3549
3974
  }
3550
3975
  writer.putCallAddressWithAlignedArguments(callback, [threadReg]);
3551
3976
  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,9 @@
1
+ const {
2
+ jvmtiVersion,
3
+ jvmtiCapabilities,
4
+ EnvJvmti
5
+ } = require('./jvmti');
6
+ const { parseInstructionsAt } = require('./machine-code');
1
7
  const memoize = require('./memoize');
2
8
  const { checkJniResult } = require('./result');
3
9
  const VM = require('./vm');
@@ -11,17 +17,13 @@ const JVM_ACC_IS_OBSOLETE = 0x00020000;
11
17
  const JVM_ACC_NOT_C2_COMPILABLE = 0x02000000;
12
18
  const JVM_ACC_NOT_C1_COMPILABLE = 0x04000000;
13
19
  const JVM_ACC_NOT_C2_OSR_COMPILABLE = 0x08000000;
14
- const JVMTI_VERSION_1_0 = 0x30010000;
15
-
16
- const jvmtiCapabilities = {
17
- canTagObjects: 1
18
- };
19
20
 
20
21
  const nativeFunctionOptions = {
21
22
  exceptions: 'propagate'
22
23
  };
23
24
 
24
25
  const getJvmMethodSpec = memoize(_getJvmMethodSpec);
26
+ const getJvmInstanceKlassSpec = memoize(_getJvmInstanceKlassSpec);
25
27
  const getJvmThreadSpec = memoize(_getJvmThreadSpec);
26
28
 
27
29
  let cachedApi = null;
@@ -57,6 +59,9 @@ function _getApi () {
57
59
  _ZN6Method4sizeEb: ['Method::size', 'int', ['int']],
58
60
  _ZN6Method19set_native_functionEPhb: ['Method::set_native_function', 'void', ['pointer', 'pointer', 'int']],
59
61
  _ZN6Method21clear_native_functionEv: ['Method::clear_native_function', 'void', ['pointer']],
62
+ // JDK >= 17
63
+ _ZN6Method24restore_unshareable_infoEP10JavaThread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
64
+ // JDK < 17
60
65
  _ZN6Method24restore_unshareable_infoEP6Thread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
61
66
  _ZN6Method10jmethod_idEv: ['Method::jmethod_id', 'pointer', ['pointer']],
62
67
  _ZN6Method10clear_codeEv: function (address) {
@@ -73,8 +78,6 @@ function _getApi () {
73
78
  };
74
79
  },
75
80
 
76
- _ZNK5Klass15start_of_vtableEv: ['Klass::start_of_vtable', 'pointer', ['pointer']],
77
- _ZNK13InstanceKlass6vtableEv: ['InstanceKlass::vtable', 'pointer', ['pointer']],
78
81
  // JDK >= 13
79
82
  _ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass: ['VM_RedefineClasses::mark_dependent_code', 'void', ['pointer', 'pointer']],
80
83
  _ZN18VM_RedefineClasses20flush_dependent_codeEv: ['VM_RedefineClasses::flush_dependent_code', 'void', []],
@@ -157,6 +160,16 @@ function _getApi () {
157
160
  _ZNK18VM_RedefineClasses14print_on_errorEP12outputStream: function (address) {
158
161
  this.redefineClassesOnError = address;
159
162
  },
163
+
164
+ // JDK >= 17
165
+ _ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread: function (address) {
166
+ this.createNewDefaultVtableIndices = address;
167
+ },
168
+ // JDK < 17
169
+ _ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread: function (address) {
170
+ this.createNewDefaultVtableIndices = address;
171
+ },
172
+
160
173
  _ZN19Abstract_VM_Version19jre_release_versionEv: function (address) {
161
174
  const getVersion = new NativeFunction(address, 'pointer', [], nativeFunctionOptions);
162
175
  const versionS = getVersion().readCString();
@@ -167,6 +180,7 @@ function _getApi () {
167
180
  : parseInt(versionS.slice(0, 2), 10);
168
181
  this.versionS = versionS;
169
182
  },
183
+
170
184
  _ZN14NMethodSweeper11_traversalsE: function (address) {
171
185
  this.traversals = address;
172
186
  },
@@ -178,36 +192,41 @@ function _getApi () {
178
192
  }
179
193
  },
180
194
  optionals: [
195
+ '_ZN6Method24restore_unshareable_infoEP10JavaThread',
196
+ '_ZN6Method24restore_unshareable_infoEP6Thread',
197
+ '_ZN6Method10clear_codeEv',
198
+ '_ZN6Method10clear_codeEb',
199
+
181
200
  '_ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass',
182
201
  '_ZN18VM_RedefineClasses20flush_dependent_codeEv',
183
202
  '_ZN18VM_RedefineClasses20flush_dependent_codeEP13InstanceKlassP6Thread',
184
203
  '_ZN18VM_RedefineClasses20flush_dependent_codeE19instanceKlassHandleP6Thread',
185
204
 
186
- '_ZNK5Klass15start_of_vtableEv',
187
- '_ZNK13InstanceKlass6vtableEv',
188
-
189
205
  '_ZN19ResolvedMethodTable21adjust_method_entriesEPb',
190
206
  '_ZN15MemberNameTable21adjust_method_entriesEP13InstanceKlassPb',
191
207
 
192
208
  '_ZN17ConstantPoolCache21adjust_method_entriesEPb',
193
209
  '_ZN17ConstantPoolCache21adjust_method_entriesEP13InstanceKlassPb',
194
210
 
211
+ '_ZN20ClassLoaderDataGraph22clean_deallocate_listsEb',
212
+
213
+ '_ZN10JavaThread27thread_from_jni_environmentEP7JNIEnv_',
214
+
215
+ '_ZN14NMethodSweeper11force_sweepEv',
216
+ '_ZN14NMethodSweeper17sweep_in_progressEv',
217
+
195
218
  '_ZN18VM_RedefineClasses14_the_class_oopE',
196
219
  '_ZN18VM_RedefineClasses10_the_classE',
197
220
  '_ZN18VM_RedefineClasses25AdjustCpoolCacheAndVtable8do_klassEP5Klass',
198
221
  '_ZN18VM_RedefineClasses22AdjustAndCleanMetadata8do_klassEP5Klass',
199
-
200
222
  '_ZN18VM_RedefineClassesD0Ev',
201
223
  '_ZN18VM_RedefineClassesD1Ev',
202
224
  '_ZNK18VM_RedefineClasses14print_on_errorEP12outputStream',
203
225
 
204
- '_ZN6Method10clear_codeEv',
205
- '_ZN6Method10clear_codeEb',
226
+ '_ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread',
227
+ '_ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread',
206
228
 
207
- '_ZN20ClassLoaderDataGraph22clean_deallocate_listsEb',
208
- '_ZN14NMethodSweeper11force_sweepEv',
209
- '_ZN14NMethodSweeper21_sweep_fractions_leftE',
210
- '_ZN14NMethodSweeper17sweep_in_progressEv'
229
+ '_ZN14NMethodSweeper21_sweep_fractions_leftE'
211
230
  ]
212
231
  }];
213
232
 
@@ -291,6 +310,11 @@ function _getApi () {
291
310
  }
292
311
 
293
312
  temporaryApi.jvmti = getEnvJvmti(temporaryApi);
313
+
314
+ if (temporaryApi['JavaThread::thread_from_jni_environment'] === undefined) {
315
+ temporaryApi['JavaThread::thread_from_jni_environment'] = makeThreadFromJniHelper(temporaryApi);
316
+ }
317
+
294
318
  return temporaryApi;
295
319
  }
296
320
 
@@ -299,63 +323,59 @@ function getEnvJvmti (api) {
299
323
 
300
324
  let env;
301
325
  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);
326
+ const handle = vm.tryGetEnvHandle(jvmtiVersion.v1_0);
327
+ if (handle === null) {
328
+ throw new Error('JVMTI not available');
329
+ }
330
+ env = new EnvJvmti(handle, vm);
308
331
 
309
332
  const capaBuf = Memory.alloc(8);
310
333
  capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
311
- result = env.addCapabilities(capaBuf);
334
+ const result = env.addCapabilities(capaBuf);
312
335
  checkJniResult('getEnvJvmti::AddCapabilities', result);
313
336
  });
314
337
 
315
338
  return env;
316
339
  }
317
340
 
318
- function EnvJvmti (handle, vm) {
319
- this.handle = handle;
320
- this.vm = vm;
321
- this.vtable = handle.readPointer();
322
- }
341
+ const threadOffsetParsers = {
342
+ x64: parseX64ThreadOffset
343
+ };
323
344
 
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);
345
+ function makeThreadFromJniHelper (api) {
346
+ let offset = null;
347
+
348
+ const tryParse = threadOffsetParsers[Process.arch];
349
+ if (tryParse !== undefined) {
350
+ const vm = new VM(api);
351
+ const findClassImpl = vm.perform(env => env.handle.readPointer().add(6 * pointerSize).readPointer());
352
+ offset = parseInstructionsAt(findClassImpl, tryParse, { limit: 10 });
353
+ }
354
+
355
+ if (offset === null) {
356
+ return () => {
357
+ throw new Error('Unable to make thread_from_jni_environment() helper for the current architecture');
358
+ };
359
+ }
360
+
361
+ return env => {
362
+ return env.add(offset);
356
363
  };
357
364
  }
358
365
 
366
+ function parseX64ThreadOffset (insn) {
367
+ if (insn.mnemonic !== 'lea') {
368
+ return null;
369
+ }
370
+
371
+ const { base, disp } = insn.operands[1].value;
372
+ if (!(base === 'rdi' && disp < 0)) {
373
+ return null;
374
+ }
375
+
376
+ return disp;
377
+ }
378
+
359
379
  function ensureClassInitialized (env, classRef) {
360
380
  }
361
381
 
@@ -530,13 +550,15 @@ function withJvmThread (fn, fnPrologue, fnEpilogue) {
530
550
  const doIt = new NativeCallback(fn, 'void', ['pointer']);
531
551
  vtableDup.add(doItOffset).writePointer(doIt);
532
552
 
553
+ let prologue = null;
533
554
  if (fnPrologue !== undefined) {
534
- const prologue = new NativeCallback(fnPrologue, 'int', ['pointer']);
555
+ prologue = new NativeCallback(fnPrologue, 'int', ['pointer']);
535
556
  vtableDup.add(prologueOffset).writePointer(prologue);
536
557
  }
537
558
 
559
+ let epilogue = null;
538
560
  if (fnEpilogue !== undefined) {
539
- const epilogue = new NativeCallback(fnEpilogue, 'void', ['pointer']);
561
+ epilogue = new NativeCallback(fnEpilogue, 'void', ['pointer']);
540
562
  vtableDup.add(epilogueOffset).writePointer(epilogue);
541
563
  }
542
564
 
@@ -727,24 +749,19 @@ function readJvmMethod (method, constMethod, constMethodSize) {
727
749
  const instanceKlass = constantPool.add(spec.constantPool.instanceKlassOffset).readPointer();
728
750
  const cache = constantPool.add(spec.constantPool.cacheOffset).readPointer();
729
751
 
730
- if (spec.instanceKlass === undefined) {
731
- const klassVtable = api['InstanceKlass::vtable'](instanceKlass);
732
- const vtableOffset = klassVtable.add(pointerSize).readS32();
733
- const klassSpec = getJvmKlassSpec(vtableOffset);
734
- spec.instanceKlass = klassSpec;
735
- }
752
+ const instanceKlassSpec = getJvmInstanceKlassSpec();
736
753
 
737
- const methods = instanceKlass.add(spec.instanceKlass.methodsOffset).readPointer();
754
+ const methods = instanceKlass.add(instanceKlassSpec.methodsOffset).readPointer();
738
755
  const methodsCount = methods.readS32();
739
756
  const methodsArray = methods.add(pointerSize);
740
757
  const methodIndex = constMethod.add(spec.constMethod.methodIdnumOffset).readU16();
741
758
  const vtableIndexPtr = method.add(spec.method.vtableIndexOffset);
742
759
  const vtableIndex = vtableIndexPtr.readS32();
743
- const vtable = instanceKlass.add(spec.instanceKlass.vtableOffset);
744
- const oopMapCache = instanceKlass.add(spec.instanceKlass.oopMapCacheOffset).readPointer();
760
+ const vtable = instanceKlass.add(instanceKlassSpec.vtableOffset);
761
+ const oopMapCache = instanceKlass.add(instanceKlassSpec.oopMapCacheOffset).readPointer();
745
762
 
746
763
  const memberNames = (api.version >= 10)
747
- ? instanceKlass.add(spec.instanceKlass.memberNamesOffset).readPointer()
764
+ ? instanceKlass.add(instanceKlassSpec.memberNamesOffset).readPointer()
748
765
  : NULL;
749
766
 
750
767
  return {
@@ -782,39 +799,48 @@ function revertJvmMethod (method) {
782
799
 
783
800
  function _getJvmMethodSpec () {
784
801
  const api = getApi();
802
+ const { version } = api;
785
803
 
786
- const adapterInConstMethod = (api.version > 8) ? 1 : 0;
804
+ let adapterHandlerLocation;
805
+ if (version >= 17) {
806
+ adapterHandlerLocation = 'method:early';
807
+ } else if (version >= 9 && version <= 16) {
808
+ adapterHandlerLocation = 'const-method';
809
+ } else {
810
+ adapterHandlerLocation = 'method:late';
811
+ }
787
812
 
788
813
  const isNative = 1;
789
814
  const methodSize = api['Method::size'](isNative) * pointerSize;
790
815
  const constMethodOffset = pointerSize;
791
816
  const methodDataOffset = 2 * pointerSize;
792
817
  const methodCountersOffset = 3 * pointerSize;
793
- const accessFlagsOffset = 4 * pointerSize;
818
+ const adapterInMethodEarlyOffset = 4 * pointerSize;
819
+ const adapterInMethodEarlySize = (adapterHandlerLocation === 'method:early') ? pointerSize : 0;
820
+ const accessFlagsOffset = adapterInMethodEarlyOffset + adapterInMethodEarlySize;
794
821
  const vtableIndexOffset = accessFlagsOffset + 4;
795
- const i2iEntryOffset = vtableIndexOffset + 4 + pointerSize;
822
+ const i2iEntryOffset = vtableIndexOffset + 4 + 8;
823
+ const adapterInMethodLateOffset = i2iEntryOffset + pointerSize;
824
+ const adapterInMethodOffset = (adapterInMethodEarlySize !== 0) ? adapterInMethodEarlyOffset : adapterInMethodLateOffset;
796
825
  const nativeFunctionOffset = methodSize - 2 * pointerSize;
797
826
  const signatureHandlerOffset = methodSize - pointerSize;
798
827
 
799
- const constantPoolOffset = pointerSize;
800
- const stackmapDataOffset = 2 * pointerSize;
801
- const constMethodSizeOffset = (3 + adapterInConstMethod) * pointerSize;
828
+ const constantPoolOffset = 8;
829
+ const stackmapDataOffset = constantPoolOffset + pointerSize;
830
+ const adapterInConstMethodOffset = stackmapDataOffset + pointerSize;
831
+ const adapterInConstMethodSize = (adapterHandlerLocation === 'const-method') ? pointerSize : 0;
832
+ const constMethodSizeOffset = adapterInConstMethodOffset + adapterInConstMethodSize;
802
833
  const methodIdnumOffset = constMethodSizeOffset + 0xe;
803
834
 
804
835
  const cacheOffset = 2 * pointerSize;
805
836
  const instanceKlassOffset = 3 * pointerSize;
806
- let klassSpec;
807
- if ('Klass::start_of_vtable' in api) {
808
- const vtableOffset = api['Klass::start_of_vtable'](NULL).toInt32();
809
- klassSpec = getJvmKlassSpec(vtableOffset);
810
- }
811
837
 
812
- const getAdapterPointer = adapterInConstMethod
838
+ const getAdapterPointer = (adapterInConstMethodSize !== 0)
813
839
  ? function (method, constMethod) {
814
- return constMethod.add(constantPoolOffset + 2 * pointerSize);
840
+ return constMethod.add(adapterInConstMethodOffset);
815
841
  }
816
842
  : function (method, constMethod) {
817
- return method.add(i2iEntryOffset + pointerSize);
843
+ return method.add(adapterInMethodOffset);
818
844
  };
819
845
 
820
846
  return {
@@ -839,15 +865,28 @@ function _getJvmMethodSpec () {
839
865
  constantPool: {
840
866
  cacheOffset,
841
867
  instanceKlassOffset
842
- },
843
- instanceKlass: klassSpec
868
+ }
844
869
  };
845
870
  }
846
871
 
847
- function getJvmKlassSpec (vtableOffset) {
848
- const { version: jvmVersion } = getApi();
872
+ const vtableOffsetParsers = {
873
+ x64: parseX64VTableOffset
874
+ };
875
+
876
+ function _getJvmInstanceKlassSpec () {
877
+ const { version: jvmVersion, createNewDefaultVtableIndices } = getApi();
878
+
879
+ const tryParse = vtableOffsetParsers[Process.arch];
880
+ if (tryParse === undefined) {
881
+ throw new Error(`Missing vtable offset parser for ${Process.arch}`);
882
+ }
883
+
884
+ const vtableOffset = parseInstructionsAt(createNewDefaultVtableIndices, tryParse, { limit: 32 });
885
+ if (vtableOffset === null) {
886
+ throw new Error('Unable to deduce vtable offset');
887
+ }
849
888
 
850
- const oopMultiplier = (jvmVersion >= 10 && jvmVersion <= 11) ? 17 : 18;
889
+ const oopMultiplier = ((jvmVersion >= 10 && jvmVersion <= 11) || jvmVersion >= 15) ? 17 : 18;
851
890
 
852
891
  const methodsOffset = vtableOffset - (7 * pointerSize);
853
892
  const memberNamesOffset = vtableOffset - (17 * pointerSize);
@@ -861,6 +900,31 @@ function getJvmKlassSpec (vtableOffset) {
861
900
  };
862
901
  }
863
902
 
903
+ function parseX64VTableOffset (insn) {
904
+ if (insn.mnemonic !== 'mov') {
905
+ return null;
906
+ }
907
+
908
+ const dst = insn.operands[0];
909
+ if (dst.type !== 'mem') {
910
+ return null;
911
+ }
912
+
913
+ const { value: dstValue } = dst;
914
+ if (dstValue.scale !== 1) {
915
+ return null;
916
+ }
917
+
918
+ const { disp } = dstValue;
919
+ if (disp < 0x100) {
920
+ return null;
921
+ }
922
+
923
+ const defaultVtableIndicesOffset = disp;
924
+
925
+ return defaultVtableIndicesOffset + 16;
926
+ }
927
+
864
928
  function deoptimizeEverything (vm, env) {
865
929
  }
866
930
 
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
+ };
@@ -0,0 +1,22 @@
1
+ function parseInstructionsAt (address, tryParse, { limit }) {
2
+ let cursor = address;
3
+ let prevInsn = null;
4
+
5
+ for (let i = 0; i !== limit; i++) {
6
+ const insn = Instruction.parse(cursor);
7
+
8
+ const value = tryParse(insn, prevInsn);
9
+ if (value !== null) {
10
+ return value;
11
+ }
12
+
13
+ cursor = insn.next;
14
+ prevInsn = insn;
15
+ }
16
+
17
+ return null;
18
+ }
19
+
20
+ module.exports = {
21
+ parseInstructionsAt
22
+ };
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.1",
3
+ "version": "6.1.1",
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",