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 +502 -43
- package/lib/class-factory.js +10 -3
- package/lib/class-model.js +1 -1
- package/lib/jvm.js +12 -53
- package/lib/jvmti.js +62 -0
- package/lib/vm.js +10 -2
- package/package.json +2 -1
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 >=
|
|
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 =
|
|
611
|
-
if (cur ===
|
|
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 !==
|
|
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
|
-
|
|
633
|
-
if (mnemonic !== 'mov' && mnemonic !== 'add') {
|
|
686
|
+
if (insn.mnemonic !== 'lea') {
|
|
634
687
|
return null;
|
|
635
688
|
}
|
|
636
689
|
|
|
637
|
-
const
|
|
638
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1848
|
+
if (!maybeInstrumentGetOatQuickMethodHeaderInlineCopies()) {
|
|
1849
|
+
const { getOatQuickMethodHeaderImpl } = artController;
|
|
1850
|
+
if (getOatQuickMethodHeaderImpl === null) {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1792
1853
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
3153
|
-
if (
|
|
3154
|
-
|
|
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
|
-
|
|
3536
|
-
writer.
|
|
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');
|
package/lib/class-factory.js
CHANGED
|
@@ -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 (
|
|
490
|
-
this.
|
|
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
|
-
|
|
555
|
+
_chooseObjectsArtPreA12 (className, env, thread, callbacks) {
|
|
549
556
|
const classWrapper = this.use(className);
|
|
550
557
|
|
|
551
558
|
const scope = android.VariableSizedHandleScope.$new(thread, vm);
|
package/lib/class-model.js
CHANGED
|
@@ -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
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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,
|
|
119
|
+
const result = getEnv(handle, envBuf, version);
|
|
112
120
|
if (result !== JNI_OK) {
|
|
113
121
|
return null;
|
|
114
122
|
}
|
|
115
|
-
return
|
|
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.
|
|
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",
|