要hook java方法,首先要找到ArtMethod相关成员的对应的偏移量
std::atomic<std::uint32_t> access_flags_;
struct PtrSizedFields {
void* data_;
void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
(1) frida使用Process.getElapsedCpuTime方法来定位偏移。这个方法是native方法,
(2)该方法具体实现是在libandroid_runtime.so库中,ptr_sized_fields_.data保存了该nativie方法地址。通过比较地址值是否位于libandroid_runtime中,来定位data_偏移
(3)entry_point_from_quick_compiled_code_是data_下一个字段,entry_point_from_quick_compiled_code_的偏移为data_偏移加一个字的大小
(4)然后ptr_sized_fields_一定是ArtMethod最后的成员变量,所以ArtMethod结构体的大小也可以确定。
function _getArtMethodSpec (vm) {
const api = getApi();
let spec;
vm.perform(env => {
const process = env.findClass('android/os/Process');
//(1)
const getElapsedCpuTime = unwrapMethodId(env.getStaticMethodId(process, 'getElapsedCpuTime', '()J'));
env.deleteLocalRef(process);
//(2)
const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
const runtimeStart = runtimeModule.base;
const runtimeEnd = runtimeStart.add(runtimeModule.size);
const apiLevel = getAndroidApiLevel();
const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;
const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;
const relevantAccessFlagsMask = ~(kAccFastInterpreterToInterpreterInvoke | kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
let jniCodeOffset = null;
let accessFlagsOffset = null;
let remaining = 2;
for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
const field = getElapsedCpuTime.add(offset);
if (jniCodeOffset === null) {
const address = field.readPointer();
if (address.compare(runtimeStart) >= 0 && address.compare(runtimeEnd) < 0) { //(1)
jniCodeOffset = offset;
remaining--;
}
}
if (accessFlagsOffset === null) {
const flags = field.readU32();
if ((flags & relevantAccessFlagsMask) === expectedAccessFlags) { //(2)
accessFlagsOffset = offset;
remaining--;
}
}
}
if (remaining !== 0) {
throw new Error('Unable to determine ArtMethod field offsets');
}
const quickCodeOffset = jniCodeOffset + entrypointFieldSize;//(3)
const size = (apiLevel <= 21) ? (quickCodeOffset + 32) : (quickCodeOffset + pointerSize);//(4)
spec = {
size: size,
offset: {
jniCode: jniCodeOffset,
quickCode: quickCodeOffset,
accessFlags: accessFlagsOffset
}
};
if ('artInterpreterToCompiledCodeBridge' in api) {
spec.offset.interpreterCode = jniCodeOffset - entrypointFieldSize;
}
});
return spec;
}
(1)这个步骤很重要,通过查找replacedMethods来判断某个某个方法是否hook了。
replace (impl, isInstanceMethod, argTypes, vm, api) {
const { kAccCompileDontBother, artNterpEntryPoint } = api;
this.originalMethod = fetchArtMethod(this.methodId, vm);
const originalFlags = this.originalMethod.accessFlags;
if ((originalFlags & kAccXposedHookedMethod) !== 0 && xposedIsSupported()) {
const hookInfo = this.originalMethod.jniCode;
this.hookedMethodId = hookInfo.add(2 * pointerSize).readPointer();
this.originalMethod = fetchArtMethod(this.hookedMethodId, vm);
}
const { hookedMethodId } = this;
const replacementMethodId = cloneArtMethod(hookedMethodId, vm);
this.replacementMethodId = replacementMethodId;
patchArtMethod(replacementMethodId, {
jniCode: impl,
accessFlags: ((originalFlags & ~(kAccCriticalNative | kAccFastNative | kAccNterpEntryPointFastPathFlag)) | kAccNative | kAccCompileDontBother) >>> 0,
quickCode: api.artClassLinker.quickGenericJniTrampoline,
interpreterCode: api.artInterpreterToCompiledCodeBridge
}, vm);
//(1)
artController.replacedMethods.set(hookedMethodId, replacementMethodId);
implementation: {
enumerable: true,
get () {
const replacement = this._r;
return (replacement !== undefined) ? replacement : null;
},
set (fn) {
const params = this._p;
const holder = params[1];
const type = params[2];
if (type === CONSTRUCTOR_METHOD) {
throw new Error('Reimplementing $new is not possible; replace implementation of $init instead');
}
const existingReplacement = this._r;
if (existingReplacement !== undefined) {
holder.$f._patchedMethods.delete(this);
const mangler = existingReplacement._m;
mangler.revert(vm);
this._r = undefined;
}
if (fn !== null) {
const [methodName, classWrapper, type, methodId, retType, argTypes] = params;
const replacement = implement(methodName, classWrapper, type, retType, argTypes, fn, this);
const mangler = makeMethodMangler(methodId);
replacement._m = mangler;
this._r = replacement;
mangler.replace(replacement, type === INSTANCE_METHOD, argTypes, vm, api);
holder.$f._patchedMethods.add(this);
}
}
},
function implement (methodName, classWrapper, type, retType, argTypes, handler, fallback = null) {
const pendingCalls = new Set();
const f = makeMethodImplementation([methodName, classWrapper, type, retType, argTypes, handler, fallback, pendingCalls]);
const impl = new NativeCallback(f, retType.type, ['pointer', 'pointer'].concat(argTypes.map(t => t.type)));
impl._c = pendingCalls;
return impl;
}
frida通过将java方法改成native,让跳板函数变成jni,然后hook跳板函数,来实现hook的目的
所以不管方法是否hook了,只要没有编译,都会执行这段跳板。
art_quick_generic_jni_trampoline和art_quick_to_interpreter_bridge都会被重写,改成frida的逻辑。
下面是frida运行后art_quick_generic_jni_trampoline被替换后的指令
(1)保存浮点寄存器
(2) Save core args, callee-saves & LR.
frida其实就是照搬了art虚拟机的代码
.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
INCREASE_FRAME 224
// Ugly compile-time check, but we only have the preprocessor.
#if (FRAME_SIZE_SAVE_REFS_AND_ARGS != 224)
#error "FRAME_SIZE_SAVE_REFS_AND_ARGS(ARM64) size not as expected."
#endif
// Stack alignment filler [sp, #8].
// FP args.
stp d0, d1, [sp, #16]
stp d2, d3, [sp, #32]
stp d4, d5, [sp, #48]
stp d6, d7, [sp, #64]
// Core args.
SAVE_TWO_REGS x1, x2, 80
SAVE_TWO_REGS x3, x4, 96
SAVE_TWO_REGS x5, x6, 112
// x7, Callee-saves.
// Note: We could avoid saving X20 in the case of Baker read
// barriers, as it is overwritten by REFRESH_MARKING_REGISTER
// later; but it's not worth handling this special case.
SAVE_TWO_REGS x7, x20, 128
SAVE_TWO_REGS x21, x22, 144
SAVE_TWO_REGS x23, x24, 160
SAVE_TWO_REGS x25, x26, 176
SAVE_TWO_REGS x27, x28, 192
// x29(callee-save) and LR.
SAVE_TWO_REGS x29, xLR, 208
.endm
(3)保存ArtMethod到栈底(x0为ArtMethod)
(4)x19寄存器为Thread,0x7fb6ab13b0为函数find_replacement_method_from_quick_code的地址
(5)如果该方法不是hook的方法,取出保存的ArtMethod,还原保存的寄存器。
(6)否则替换ArtMethod为replaceMethod.
(7) 还原寄存器后b.ne为true(x0!=0),跳转到(9)
(8) 拷贝art_quick_to_interpreter_bridge前面的几条指令,跳转执行剩下的逻辑代码
(9)取出replaceMethod->entry_point_from_quick_compiled_code_entry_point_from_quick_compiled_code_,并跳转
//---------------------------
//(1)
0x7fb1da1f00: stp d0, d1, [sp, #-16]!
0x7fb1da1f04: stp d2, d3, [sp, #-16]!
0x7fb1da1f08: stp d4, d5, [sp, #-16]!
0x7fb1da1f0c: stp d6, d7, [sp, #-16]!
//(2)
0x7fb1da1f10: stp x1, x2, [sp, #-16]!
0x7fb1da1f14: stp x3, x4, [sp, #-16]!
0x7fb1da1f18: stp x5, x6, [sp, #-16]!
0x7fb1da1f1c: stp x7, x20, [sp, #-16]!
0x7fb1da1f20: stp x21, x22, [sp, #-16]!
0x7fb1da1f24: stp x23, x24, [sp, #-16]!
0x7fb1da1f28: stp x25, x26, [sp, #-16]!
0x7fb1da1f2c: stp x27, x28, [sp, #-16]!
0x7fb1da1f30: stp x29, x30, [sp, #-16]!
//(3)
0x7fb1da1f34: sub sp, sp, #0x10
0x7fb1da1f38: str x0, [sp]
// (4)
0x7fb1da1f3c: mov x1, x19
0x7fb1da1f40: bl 0x7fb6ab13b0
0x7fb1da1f44: cmp x0, xzr
0x7fb1da1f48: b.eq 0x7fb1da1f50 // b.none
//(6)
0x7fb1da1f4c: str x0, [sp]
//(5)
0x7fb1da1f50: ldr x0, [sp]
0x7fb1da1f54: add sp, sp, #0x10
0x7fb1da1f58: ldp x29, x30, [sp], #16
0x7fb1da1f5c: ldp x27, x28, [sp], #16
0x7fb1da1f60: ldp x25, x26, [sp], #16
0x7fb1da1f64: ldp x23, x24, [sp], #16
0x7fb1da1f68: ldp x21, x22, [sp], #16
0x7fb1da1f6c: ldp x7, x20, [sp], #16
0x7fb1da1f70: ldp x5, x6, [sp], #16
0x7fb1da1f74: ldp x3, x4, [sp], #16
0x7fb1da1f78: ldp x1, x2, [sp], #16
0x7fb1da1f7c: ldp d6, d7, [sp], #16
0x7fb1da1f80: ldp d4, d5, [sp], #16
0x7fb1da1f84: ldp d2, d3, [sp], #16
0x7fb1da1f88: ldp d0, d1, [sp], #16
//(7)
0x7fb1da1f8c: b.ne 0x7fb1da1fa8 // b.any
//-------------------- 拷贝art_quick_to_interpreter_bridge前面的几条指令
0x7fb1da1f90: ldr x16, 0x7fb1da1fb0
0x7fb1da1f94: ldr x16, [x16, #2664]
0x7fb1da1f98: ldr x16, [x16, #16]
0x7fb1da1f9c: sub sp, sp, #0xe0
//---------------- 取出Runtime::instance_->callee_save_methods_[kSaveRefAndArgs];
//(8)
0x7fb1da1fa0: ldr x17, 0x7fb1da1fb8
0x7fb1da1fa4: br x17
// (9)
0x7fb1da1fa8: ldr x16, [x0, #32]
0x7fb1da1fac: br x16
0x7fb1da1fb0: .inst 0xb33d1000 ; undefined
(1) 查表,没有说明没hook,返回调原方法
(2) 查栈顶,如果top_quick_frame不为空,说明方法走解释层, replacement_method是native方法,所以需要走hook逻辑
(3) frida需要时谁调用的,因为frida第一次调用走hook逻辑,再hook方法即implementation方法内再调用方法时就走原逻辑。
(4) 栈回溯到调用的方法。
gpointer
find_replacement_method_from_quick_code (gpointer method,
gpointer thread)
{
gpointer replacement_method;
gpointer managed_stack;
gpointer top_quick_frame;
gpointer link_managed_stack;
gpointer * link_top_quick_frame;
//(1)
replacement_method = get_replacement_method (method);
if (replacement_method == NULL)
return NULL;
// (2)
managed_stack = thread + ${threadOffsets.managedStack};
top_quick_frame = *((gpointer *) (managed_stack + ${managedStackOffsets.topQuickFrame}));
if (top_quick_frame != NULL)
return replacement_method;
//(3)
link_managed_stack = *((gpointer *) (managed_stack + ${managedStackOffsets.link}));
if (link_managed_stack == NULL)
return replacement_method;
//(4)
link_top_quick_frame = GSIZE_TO_POINTER (*((gsize *) (link_managed_stack + ${managedStackOffsets.topQuickFrame})) & ~((gsize) 1));
if (link_top_quick_frame == NULL || *link_top_quick_frame != replacement_method)
return replacement_method;
return NULL;
}