首先寫一個安卓demo如下
findViewById(R.id.sample_text).setOnClickListener(v -> {
try {
Method doWork1 = MainActivity.class.getDeclaredMethod("doWork1");
Method doWork2 = MainActivity.class.getDeclaredMethod("doWork2");
Method doWork3 = MainActivity.class.getDeclaredMethod("doWork3");
calledBefore(doWork1,doWork2,doWork3);
HookMain.backupAndHook(doWork1,doWork2,doWork3);
calledAfter(doWork1,doWork2,doWork3);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
});
}
private static void doWork1() {
Log.i(TAG, "doWork1");
}
private static void doWork2() {
Log.i(TAG, "doWork2");
}
private static void doWork3() {
Log.i(TAG, "doWork3");
}
public native void calledBefore(Method doWork1, Method doWork2, Method doWork3);
public native void calledAfter(Method doWork1, Method doWork2, Method doWork3);
兩個native方法(calledBefore/calledAfter)在native層啥也不用做 只是拿到一個反射方法
順帶附上hook代碼,主要是用frida展示artmethod指針內存的變化
var getArtMethod = new NativeFunction(Module.findExportByName('libnative-lib.so','getArtMethod'),'pointer',['pointer','pointer'])
Interceptor.attach(Module.findExportByName('libnative-lib.so','Java_com_lzy_yahfa_MainActivity_calledBefore'),{
onEnter:function(args){
LOG("\n----------------------- Before -----------------------\n",LogColor.RED)
showLog(args[0],args[2],args[3],args[4])
},
onLeave:function(ret){
}
})
Interceptor.attach(Module.findExportByName('libnative-lib.so','Java_com_lzy_yahfa_MainActivity_calledAfter'),{
onEnter:function(args){
LOG("\n----------------------- After -----------------------\n",LogColor.RED)
showLog(args[0],args[2],args[3],args[4])
},
onLeave:function(ret){
}
})
function showLog(a0,a1,a2,a3){
LOG(" ----- ORG ----- ",LogColor.YELLOW)
var method = getArtMethod(a0,a1)
seeHexA(method,p_size*8)
LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
LOG("\n")
LOG(" ----- Hook ----- ",LogColor.YELLOW)
var method = getArtMethod(a0,a2)
seeHexA(method,p_size*8)
LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
LOG("\n")
LOG(" ----- Back ----- ",LogColor.YELLOW)
var method = getArtMethod(a0,a3)
seeHexA(method,p_size*8)
LOG("entry_point_from_quick_compiled_code -> "+method.add(p_size*7).readPointer()+" ---> "+method.add(p_size*7).readPointer().readPointer())
}
這里我用的是google原生系統 8.1
直接去參考源碼得到 artMethod 704行 結構體長這樣:
4 GcRoot<mirror::Class> declaring_class_;
4 std::atomic<std::uint32_t> access_flags_;
4 uint32_t dex_code_item_offset_;
4 uint32_t dex_method_index_;
2 uint16_t method_index_;
2 uint16_t hotness_count_;
12 struct PtrSizedFields {
4 ArtMethod** dex_cache_resolved_methods_;
4 void* data_;
4 void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
運行起來點擊textview即可得到一下日志
三個java方法的art結構體信息
cpu三級流水線可知程序運行到0xf48bc018的時候
pc應該是往后的兩條指令,即為0xf48bc020
ORG的entry_point_from_quick_compiled_code
這里計算一下跳板地址跳到了哪里
跳轉位置
很明顯這個位置就是 hook 函數的 entry_point_from_quick_compiled_code_
繼續看一下 BackUp 函數的實現
實現
第一條指令
ldr r0, [pc, #0xc]
就是把 pc+0x2+0xc位置的值給到了r0,即等價于
ldr r0,=0xf410307c
第二條指令
stmdb sp!, {r0}
r0壓棧
第三條指令
ldr r0, [pc]
等價于
ldr r0,=0xf36174d1 (這個地址就是他的默認入口)
第四條指令
ldm sp!, {pc}
將sp出到pc,也就是配置r0的值為第一個org method指針,并恢復pc為默認的函數入口 0xf36174d1
tips:
- 這里的 ldm stmdb 進行入棧出棧并不影響棧頂指針
- 這里的函數入口處對sp的讀寫沒關系,不用關心覆蓋問題
總的來說:
替換函數用到了跳板操作
備份函數用到了跳板操作加上一個備份還原,用到了sp,在函數開時候用不用管覆蓋問題,在中途用可以考慮使用超出棧頂指針的部分內存-