frida-java-bridge 6.0.0 → 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,17 +2378,14 @@ 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);
2382
+ static void art_stack_frame_destroy (ArtStackFrame * frame);
1943
2383
 
1944
2384
  static void append_jni_type_name (GString * s, const gchar * name, gsize length);
1945
2385
 
1946
2386
  static void std_string_destroy (StdString * str);
1947
2387
  static gchar * std_string_get_data (StdString * str);
1948
2388
 
1949
- static void frida_log (const char * format, ...);
1950
-
1951
2389
  void
1952
2390
  init (void)
1953
2391
  {
@@ -1971,7 +2409,7 @@ _create (JNIEnv * env,
1971
2409
  bt->frames = (limit != 0)
1972
2410
  ? g_array_sized_new (FALSE, FALSE, sizeof (ArtStackFrame), limit)
1973
2411
  : g_array_new (FALSE, FALSE, sizeof (ArtStackFrame));
1974
- g_array_set_clear_func (bt->frames, (GDestroyNotify) std_string_destroy);
2412
+ g_array_set_clear_func (bt->frames, (GDestroyNotify) art_stack_frame_destroy);
1975
2413
  bt->frames_json = NULL;
1976
2414
 
1977
2415
  gum_tls_key_set_value (current_backtrace, bt);
@@ -1986,17 +2424,22 @@ _create (JNIEnv * env,
1986
2424
  void
1987
2425
  _on_thread_state_transition_complete (ArtThread * thread)
1988
2426
  {
2427
+ ArtContext * context;
1989
2428
  ArtStackVisitor visitor = {
1990
2429
  .vtable_storage = {
1991
2430
  .visit = visit_frame,
1992
2431
  },
1993
2432
  };
1994
2433
 
1995
- art_stack_visitor_init (&visitor, thread, art_thread_get_long_jump_context (thread), STACK_WALK_SKIP_INLINED_FRAMES, 0, true);
2434
+ context = art_thread_get_long_jump_context (thread);
2435
+
2436
+ art_stack_visitor_init (&visitor, thread, context, STACK_WALK_SKIP_INLINED_FRAMES, 0, true);
1996
2437
  visitor.vtable = &visitor.vtable_storage;
1997
2438
  visitor.backtrace = gum_tls_key_get_value (current_backtrace);
1998
2439
 
1999
2440
  art_stack_visitor_walk_stack (&visitor, false);
2441
+
2442
+ cxx_delete (context);
2000
2443
  }
2001
2444
 
2002
2445
  static bool
@@ -2031,6 +2474,12 @@ skip:
2031
2474
  return true;
2032
2475
  }
2033
2476
 
2477
+ static void
2478
+ art_stack_frame_destroy (ArtStackFrame * frame)
2479
+ {
2480
+ std_string_destroy (&frame->description);
2481
+ }
2482
+
2034
2483
  void
2035
2484
  _destroy (ArtBacktrace * backtrace)
2036
2485
  {
@@ -2322,7 +2771,13 @@ function revertGlobalPatches () {
2322
2771
  });
2323
2772
  patchedClasses.clear();
2324
2773
 
2325
- 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
+ }
2326
2781
  }
2327
2782
 
2328
2783
  function unwrapMethodId (methodId) {
@@ -2695,7 +3150,7 @@ function writeArtQuickCodePrologueArm64 (target, trampoline, redirectSize) {
2695
3150
 
2696
3151
  const artQuickCodeHookRedirectSize = {
2697
3152
  ia32: 5,
2698
- x64: 14,
3153
+ x64: 16,
2699
3154
  arm: 8,
2700
3155
  arm64: 16
2701
3156
  };
@@ -3149,9 +3604,12 @@ function requestDeoptimization (vm, env, kind, method) {
3149
3604
  throw new Error('Unable to find Instrumentation class in ART; please file a bug');
3150
3605
  }
3151
3606
 
3152
- const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
3153
- if (!deoptimizationEnabled) {
3154
- 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
+ }
3155
3613
  }
3156
3614
 
3157
3615
  switch (kind) {
@@ -3532,8 +3990,9 @@ function recompileExceptionClearForX86 (buffer, pc, exceptionClearImpl, nextFunc
3532
3990
  if (pointerSize === 4) {
3533
3991
  writer.putAndRegU32('esp', 0xfffffff0);
3534
3992
  } else {
3535
- writer.putMovRegU64('rax', uint64('0xfffffffffffffff0'));
3536
- writer.putAndRegReg('rsp', 'rax');
3993
+ const scratchReg = (threadReg !== 'rdi') ? 'rdi' : 'rsi';
3994
+ writer.putMovRegU64(scratchReg, uint64('0xfffffffffffffff0'));
3995
+ writer.putAndRegReg('rsp', scratchReg);
3537
3996
  }
3538
3997
  writer.putCallAddressWithAlignedArguments(callback, [threadReg]);
3539
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.0",
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",