frida Java对象就是Runtime,是单例的。
const runtime = new Runtime();
Script.bindWeak(runtime, () => { runtime._dispose(); });
对应art虚拟机的Runtime。art虚拟机可以通过调用JNI_GetCreatedJavaVMs获取JavaVM间接获取该单例对象
extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms_buf, jsize buf_len, jsize* vm_count) {
Runtime* runtime = Runtime::Current();
if (runtime == nullptr || buf_len == 0) {
*vm_count = 0;
} else {
*vm_count = 1;
vms_buf[0] = runtime->GetJavaVM();
}
return JNI_OK;
}
const vms = Memory.alloc(pointerSize);
const vmCount = Memory.alloc(jsizeSize);
checkJniResult('JNI_GetCreatedJavaVMs', temporaryApi.JNI_GetCreatedJavaVMs(vms, 1, vmCount));
if (vmCount.readInt() === 0) {
return null;
}
temporaryApi.vm = vms.readPointer();//JavaVMExt
const artRuntime = temporaryApi.vm.add(pointerSize).readPointer();//Runtime
temporaryApi.artRuntime = artRuntime;
frida使用了使用了trick获取Runtim的成员变量instrumentation_和jni_ids_indirection_
GetJniIdType和GetInstrumentation是内联函数,frida通过反编译调用了这些函数的非内联获取偏移
JniIdType GetJniIdType() const {
return jni_ids_indirection_;
}
instrumentation::Instrumentation* GetInstrumentation() {
return &instrumentation_;
}
frida通过反编译Art::Runtime::DeoptimizeBootImage获取instrumentation_的偏移,当然其它调用了GetInstrumentation的函数也可以。
void Runtime::DeoptimizeBootImage() {
// If we've already started and we are setting this runtime to debuggable,
// we patch entry points of methods in boot image to interpreter bridge, as
// boot image code may be AOT compiled as not debuggable.
if (!GetInstrumentation()->IsForcedInterpretOnly()) {
UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
GetClassLinker()->VisitClasses(&visitor);
jit::Jit* jit = GetJit();
if (jit != nullptr) {
// Code previously compiled may not be compiled debuggable.
jit->GetCodeCache()->TransitionToDebuggable();
}
}
}
frida获取Runtime结构体成员变量的偏移也使用了trick,通过找到java_vm_的偏移再计算其它成员变量的偏移。
for (let offset = startOffset; offset !== endOffset; offset += pointerSize) {
const value = runtime.add(offset).readPointer();
if (value.equals(vm)) {
let classLinkerOffset = null;
let jniIdManagerOffset = null;
if (apiLevel >= 33 || getAndroidCodename() === 'Tiramisu') {
classLinkerOffset = offset - (4 * pointerSize);
jniIdManagerOffset = offset - pointerSize;
} else if (apiLevel >= 30 || getAndroidCodename() === 'R') {
classLinkerOffset = offset - (3 * pointerSize);
jniIdManagerOffset = offset - pointerSize;
} else if (apiLevel >= 29) {
classLinkerOffset = offset - (2 * pointerSize);
} else if (apiLevel >= 27) {
classLinkerOffset = offset - STD_STRING_SIZE - (3 * pointerSize);
} else {
classLinkerOffset = offset - STD_STRING_SIZE - (2 * pointerSize);
}
const internTableOffset = classLinkerOffset - pointerSize;
const threadListOffset = internTableOffset - pointerSize;
let heapOffset;
if (apiLevel >= 24) {
heapOffset = threadListOffset - (8 * pointerSize);
} else if (apiLevel >= 23) {
heapOffset = threadListOffset - (7 * pointerSize);
} else {
heapOffset = threadListOffset - (4 * pointerSize);
}
spec = {
offset: {
heap: heapOffset,
threadList: threadListOffset,
internTable: internTableOffset,
classLinker: classLinkerOffset,
jniIdManager: jniIdManagerOffset
}
};
break;
}
}
art/runtime/runtime.h
gc::Heap* heap_;
ThreadList* thread_list_;
InternTable* intern_table_;
ClassLinker* class_linker_;
std::unique_ptr<jni::JniIdManager> jni_id_manager_;
std::unique_ptr<JavaVMExt> java_vm_;
instrumentation::Instrumentation instrumentation_;//line:1218
// True if jmethodID and jfieldID are opaque Indices. When false (the default) these are simply
// pointers. This is set by -Xopaque-jni-ids:{true,false}.
JniIdType jni_ids_indirection_;//line:1379
然后获取ClassLinker结构体的偏移,方法和Runtime类似,不过这次是找到成员变量intern_table_的偏移,再根据intern_table_去计算其它成员变量的偏移。
class ClassLinker {
InternTable* intern_table_;
const void* quick_resolution_trampoline_;
quick_imt_conflict_trampoline_
const void* quick_generic_jni_trampoline_;
const void* quick_to_interpreter_bridge_trampoline_;
}
计算到ClassLinker变量的偏移,就可以获取ClassLinker的相关数据了
const classLinker = artRuntime.add(runtimeOffset.classLinker).readPointer();
const classLinkerOffsets = getArtClassLinkerSpec(temporaryApi).offset;
const quickResolutionTrampoline = classLinker.add(classLinkerOffsets.quickResolutionTrampoline).readPointer();
const quickImtConflictTrampoline = classLinker.add(classLinkerOffsets.quickImtConflictTrampoline).readPointer();
const quickGenericJniTrampoline = classLinker.add(classLinkerOffsets.quickGenericJniTrampoline).readPointer();
const quickToInterpreterBridgeTrampoline = classLinker.add(classLinkerOffsets.quickToInterpreterBridgeTrampoline).readPointer();
temporaryApi.artClassLinker = {
address: classLinker,
quickResolutionTrampoline: quickResolutionTrampoline,
quickImtConflictTrampoline: quickImtConflictTrampoline,
quickGenericJniTrampoline: quickGenericJniTrampoline,
quickToInterpreterBridgeTrampoline: quickToInterpreterBridgeTrampoline
};
然后创建Java世界的主角VM
const vm = new VM(temporaryApi);
先看看temporaryApi有哪些变量
image.png
function VM (api) {
const handle = api.vm;//struct _JavaVM
let attachCurrentThread = null;//jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);JNIEnvExt* jni_env;
let detachCurrentThread = null;//jint (*DetachCurrentThread)(JavaVM*);
let getEnv = null;//tlsPtr_.jni_env;
function initialize () {
const vtable = handle.readPointer();
const options = {
exceptions: 'propagate'
};
attachCurrentThread = new NativeFunction(vtable.add(4 * pointerSize).readPointer(), 'int32', ['pointer', 'pointer', 'pointer'], options);
detachCurrentThread = new NativeFunction(vtable.add(5 * pointerSize).readPointer(), 'int32', ['pointer'], options);
getEnv = new NativeFunction(vtable.add(6 * pointerSize).readPointer(), 'int32', ['pointer', 'pointer', 'int32'], options);
}
this.handle = handle;
重要的perform函数就是在这里实现的。
this.perform = function (fn){}
Runtime对perform函数进行包装
perform (fn) {
this._checkAvailable();
if (!this._isAppProcess() || this.classFactory.loader !== null) {
try {
this.vm.perform(fn);
} catch (e) {
Script.nextTick(() => { throw e; });
}
} else {
this._pendingVmOps.push(fn);
if (this._pendingVmOps.length === 1) {
this._performPendingVmOpsWhenReady();
}
}
}