轉載自Sakura的博客:https://eternalsakura13.com/2020/07/04/fridahtml
-
Android逆向
致謝
本篇文章學到的內容來自且徹底來自r0ysue的知識星球,推薦一下(這個男人啥都會,還能陪你在線撩騷)。java
Frida native hook : NDK開發入門
https://www.jianshu.com/p/87ce6f565d37python
-
extern "C"與名稱修飾 -
經過c++filt工具能夠直接還原獲得原來的函數名 -
https://zh.wikipedia.org/zh-hans/%E5%90%8D%E5%AD%97%E4%BF%AE%E9%A5%B0 -
經過extern "C"導出的JNI函數不會被name mangling -
JNI參數與基本類型 -
第一個NDK程序 -
JNI log
#define TAG "sakura1328"#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) #define TAG "sakura1328"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGD("sakura1328");
return env->NewStringUTF(hello.c_str());
}
...
public class MainActivity extends AppCompatActivity {
private static final String TAG = "sakura";
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.d(TAG, stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
extern "C" JNIEXPORT jstring JNICALLJava_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; LOGD("sakura1328"); return env->NewStringUTF(hello.c_str());}...public class MainActivity extends AppCompatActivity {
private static final String TAG = "sakura";
// Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
// Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); Log.d(TAG, stringFromJNI()); }
/** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI();}
Frida native hook : JNIEnv和反射
以jni字符串來掌握基本的JNIEnv用法
public native String stringWithJNI(String context);...
extern "C"JNIEXPORT jstring JNICALLJava_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance, jstring context_) { const char *context = env->GetStringUTFChars(context_, 0);
int context_size = env->GetStringUTFLength(context_);
if (context_size > 0) { LOGD("%s\n", context); }
env->ReleaseStringUTFChars(context_, context);
return env->NewStringUTF("sakura1328");}
12-26 22:30:00.548 15764-15764/myapplication.example.com.ndk_demo D/sakura1328: sakura
Java反射
總結: 多去讀一下java的反射API。linux
Java高級特性——反射android
-
查找調用各類API接口、JNI、frida/xposed原理的一部分 -
反射基本API -
反射修改訪問控制、修改屬性值 -
JNI so調用反射進入java世界 -
xposed/Frida hook原理
這裏其實有一個伏筆,就是爲何咱們要trace artmethod,hook artmethod是由於有些so混淆得很是厲害,而後也就很難靜態分析看出so裏面調用了哪些java函數,也不是經過相似JNI的GetMethodID這樣來調用的。 而是經過相似findclass這種方法先獲得類,而後再反射調用app裏面的某個java函數。c++
因此去hook它執行的位置,每個java函數對於Android源碼而言都是一個artmethod結構體,而後hook拿到artmethod實例之後調用類函數,打印這個函數的名稱。git
public class MainActivity extends AppCompatActivity {
private static final String TAG = "sakura";
// Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
// Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringWithJNI("sakura"));// Log.d(TAG, stringFromJNI());// Log.d(TAG, stringWithJNI("sakura")); try { testClass(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
public void testClass() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Test sakuraTest = new Test(); // 得到Class的方法(三種) Class testClazz = MainActivity.class.getClassLoader().loadClass("myapplication.example.com.ndk_demo.Test"); Class testClazz2 = Class.forName("myapplication.example.com.ndk_demo.Test"); Class testClazz3 = Test.class; Log.i(TAG, "Classloader.loadClass->" + testClazz); Log.i(TAG, "Classloader.loadClass->" + testClazz2); Log.i(TAG, "Classloader.loadClass->" + testClazz3.getName());
// 得到類中屬性相關的方法 Field publicStaticField = testClazz3.getDeclaredField("publicStaticField"); Log.i(TAG, "testClazz3.getDeclaredField->" + publicStaticField);
Field publicField = testClazz3.getDeclaredField("publicField"); Log.i(TAG, "testClazz3.getDeclaredField->" + publicField);
//對於Field的get方法,若是是static,則傳入null便可;若是不是,則須要傳入一個類的實例 String valueStaticPublic = (String) publicStaticField.get(null); Log.i(TAG, "publicStaticField.get->" + valueStaticPublic);
String valuePublic = (String) publicField.get(sakuraTest); Log.i(TAG, "publicField.get->" + valuePublic);
//對於private屬性,須要設置Accessible Field privateStaticField = testClazz3.getDeclaredField("privateStaticField"); privateStaticField.setAccessible(true);
String valuePrivte = (String) privateStaticField.get(null); Log.i(TAG, "modified before privateStaticField.get->" + valuePrivte);
privateStaticField.set(null, "modified");
valuePrivte = (String) privateStaticField.get(null); Log.i(TAG, "modified after privateStaticField.get->" + valuePrivte);
Field[] fields = testClazz3.getDeclaredFields(); for (Field i : fields) { Log.i(TAG, "testClazz3.getDeclaredFields->" + i); }
// 得到類中method相關的方法 Method publicStaticMethod = testClazz3.getDeclaredMethod("publicStaticFunc"); Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicStaticMethod);
publicStaticMethod.invoke(null);
Method publicMethod = testClazz3.getDeclaredMethod("publicFunc", java.lang.String.class); Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicMethod);
publicMethod.invoke(sakuraTest, " sakura"); }
/** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI();
public native String stringWithJNI(String context);}...public class Test { private static final String TAG = "sakura_test";
public static String publicStaticField = "i am a publicStaticField"; public String publicField = "i am a publicField";
private static String privateStaticField = "i am a privateStaticField"; private String privateField = "i am a privateField";
public static void publicStaticFunc() { Log.d(TAG, "I`m from publicStaticFunc"); }
public void publicFunc(String str) { Log.d(TAG, "I`m from publicFunc" + str); }
private static void privateStaticFunc() { Log.i(TAG, "I`m from privateFunc"); }
private void privateFunc() { Log.i(TAG, "I`m from privateFunc"); }}......12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->myapplication.example.com.ndk_demo.Test12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicStaticField.get->i am a publicStaticField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicField.get->i am a publicField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified before privateStaticField.get->i am a privateStaticField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified after privateStaticField.get->modified12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private java.lang.String myapplication.example.com.ndk_demo.Test.privateField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static final java.lang.String myapplication.example.com.ndk_demo.Test.TAG12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static java.lang.String myapplication.example.com.ndk_demo.Test.privateStaticField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public static void myapplication.example.com.ndk_demo.Test.publicStaticFunc()12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicStaticFunc12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public void myapplication.example.com.ndk_demo.Test.publicFunc(java.lang.String)12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicFunc sakura
memory list modules
github
Frida反調試
這一節的主要內容就是關於反調試的原理和如何破解反調試,重要內容仍是看文章理解便可。 由於我並不須要作反調試相關的工做,因此部份內容略過。web
-
Frida反調試與反反調試基本思路 (Java層API、Native層API、Syscall) -
AntiFrida -
frida-detection-demo -
多種特徵檢測Frida -
來自高維的對抗 - 逆向TinyTool自制 -
Unicorn 在 Android 的應用
Frida native hook : 符號hook JNI、art&libc
Native函數的Java Hook及主動調用
對native函數的java層hook和主動調用和普通java函數徹底一致,略過。正則表達式
jni.h
頭文件導入
導入jni.h,先search一下這個文件在哪。
sakura@sakuradeMacBook-Pro:~/Library/Android/sdk$ find ./ -name "jni.h".//ndk-bundle/sysroot/usr/include/jni.h
![](http://static.javashuo.com/static/loading.gif)
Error /Users/sakura/Library/Android/sdk/ndk-bundle/sysroot/usr/include/jni.h,27: Can't open include file 'stdarg.h'Total 1 errorsCaching 'Exports'... ok
報錯,因此拷貝一份jni.h出來
將這兩個頭文件導入刪掉
導入成功
如今就能識別_JNIEnv了,如圖
JNI函數符號hook
先查看一下導出了哪些函數。
extern "C" JNIEXPORT jstring JNICALLJava_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; LOGD("sakura1328"); return env->NewStringUTF(hello.c_str());}extern "C"JNIEXPORT jstring JNICALLJava_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance, jstring context_) { const char *context = env->GetStringUTFChars(context_, 0); extern "C" JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGD("sakura1328");
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance,
jstring context_) {
const char *context = env->GetStringUTFChars(context_, 0);
int context_size = env->GetStringUTFLength(context_);
if (context_size > 0) {
LOGD("%s\n", context);
}
env->ReleaseStringUTFChars(context_, context);
return env->NewStringUTF("sakura1328");
}
int context_size = env->GetStringUTFLength(context_);
if (context_size > 0) { LOGD("%s\n", context); }
env->ReleaseStringUTFChars(context_, context);
return env->NewStringUTF("sakura1328");}
這裏有幾個須要的API。
-
首先是找到是否so被加載,經過 Process.enumerateModules()
,這個API能夠枚舉被加載到內存的modules。 -
而後經過 Module.findBaseAddress(module name)
來查找要hook的函數所在的so的基地址,若是找不到就返回null。 -
而後能夠經過 findExportByName(moduleName: string, exportName: string): NativePointer
來查找導出函數的絕對地址。若是不知道moduleName是什麼,能夠傳入一個null進入,可是會花費一些時間遍歷全部的module。若是找不到就返回null。 -
找到地址以後,就能夠攔截function/instruction的執行。經過 Interceptor.attach
。使用方法見下代碼。 -
另外爲了將jstring的值打印出來,可使用jenv的函數getStringUtfChars,就像正常的寫native程序同樣。 Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()
這裏我是循環調用的string_with_jni,若是不循環調用,那就要主動調用一下這個函數,或者hook dlopen。 hook dlopen的方法在這個代碼能夠參考。
function hook_native() { // console.log(JSON.stringify(Process.enumerateModules())); var libnative_addr = Module.findBaseAddress("libnative-lib.so") console.log("libnative_addr is: " + libnative_addr)
if (libnative_addr) { var string_with_jni_addr = Module.findExportByName("libnative-lib.so", "Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI") console.log("string_with_jni_addr is: " + string_with_jni_addr) }
Interceptor.attach(string_with_jni_addr, { onEnter: function (args) { console.log("string_with_jni args: " + args[0], args[1], args[2]) console.log(Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()) }, onLeave: function (retval) { console.log("retval:", retval) console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString()) var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native"); retval.replace(ptr(newRetval)); } })}
libnative_addr is: 0x7a0842f000string_with_jni_addr is: 0x7a08436194[Google Pixel::myapplication.example.com.ndk_demo]-> string_with_jni args: 0x7a106cc1c0 0x7ff0b71da4 0x7ff0b71da8sakuraretval: 0x75sakura1328
這裏還寫了一個hook env裏的GetStringUTFChars的代碼,和上面同樣,不贅述了。
function hook_art(){ var addr_GetStringUTFChars = null; //console.log( JSON.stringify(Process.enumerateModules())); var symbols = Process.findModuleByName("libart.so").enumerateSymbols(); for(var i = 0;i<symbols.length;i++){ var symbol = symbols[i].name; if((symbol.indexOf("CheckJNI")==-1)&&(symbol.indexOf("JNI")>=0)){ if(symbol.indexOf("GetStringUTFChars")>=0){ console.log(symbols[i].name); console.log(symbols[i].address); addr_GetStringUTFChars = symbols[i].address; } } } console.log("addr_GetStringUTFChars:", addr_GetStringUTFChars); Java.perform(function (){ Interceptor.attach(addr_GetStringUTFChars, { onEnter: function (args) { console.log("addr_GetStringUTFChars OnEnter args[0],args[1]",args[0],args[1]); //console.log(hexdump(args[0].readPointer())); //console.log(Java.vm.tryGetEnv().getStringUtfChars(args[0]).readCString()); }, onLeave: function (retval) { console.log("addr_GetStringUTFChars OnLeave",ptr(retval).readCString()); } }) })}
JNI函數參數、返回值打印和替換
-
libc函數符號hook -
libc函數參數、返回值打印和替換 hook libc的也和上面的徹底同樣,也不贅述了。 因此看到這裏,究其本質就是找到導出符號和它所在的so基地址了。
function hook_libc(){ var pthread_create_addr = null; var symbols = Process.findModuleByName("libc.so").enumerateSymbols(); for(var i = 0;i<symbols.length;i++){ var symbol = symbols[i].name; if(symbol.indexOf("pthread_create")>=0){ //console.log(symbols[i].name); //console.log(symbols[i].address); pthread_create_addr = symbols[i].address; } } console.log("pthread_create_addr,",pthread_create_addr); Interceptor.attach(pthread_create_addr,{ onEnter:function(args){ console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);
},onLeave:function(retval){ console.log("retval is:",retval) } })}
Frida native hook : JNI_Onload/動態註冊/inline_hook/native層調用棧打印
https://github.com/android/ndk-samples
JNI_Onload/動態註冊原理
-
JNI_Onload/動態註冊/Frida hook RegisterNative -
JNI與動態註冊 -
native 方法的動態註冊 -
Frida hook art
詳細的內容參見我寫的文章,這裏只給出栗子。
Log.d(TAG,stringFromJNI2());public native String stringFromJNI2();
JNIEXPORT jstring JNICALL stringFromJNI2( JNIEnv *env, jclass clazz) { jclass testClass = env->FindClass("myapplication/example/com/ndk_demo/Test"); jfieldID publicStaticField = env->GetStaticFieldID(testClass, "publicStaticField", "Ljava/lang/String;"); jstring publicStaticFieldValue = (jstring) env->GetStaticObjectField(testClass, publicStaticField); const char *value_ptr = env->GetStringUTFChars(publicStaticFieldValue, NULL); LOGD("now content is %s", value_ptr); std::string hello = "Hello from C++ stringFromJNI2"; return env->NewStringUTF(hello.c_str());}...JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; vm->GetEnv((void **) &env, JNI_VERSION_1_6); JNINativeMethod methods[] = { {"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2}, }; env->RegisterNatives(env->FindClass("myapplication/example/com/ndk_demo/MainActivity"), methods, 1); return JNI_VERSION_1_6;} JNIEXPORT jstring JNICALL stringFromJNI2(
JNIEnv *env,
jclass clazz) {
jclass testClass = env->FindClass("myapplication/example/com/ndk_demo/Test");
jfieldID publicStaticField = env->GetStaticFieldID(testClass, "publicStaticField",
"Ljava/lang/String;");
jstring publicStaticFieldValue = (jstring) env->GetStaticObjectField(testClass,
publicStaticField);
const char *value_ptr = env->GetStringUTFChars(publicStaticFieldValue, NULL);
LOGD("now content is %s", value_ptr);
std::string hello = "Hello from C++ stringFromJNI2";
return env->NewStringUTF(hello.c_str());
}
...
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
JNINativeMethod methods[] = {
{"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2},
};
env->RegisterNatives(env->FindClass("myapplication/example/com/ndk_demo/MainActivity"), methods,
1);
return JNI_VERSION_1_6;
}
Frida hook RegisterNative
使用下面這個腳原本打印出RegisterNatives的參數,這裏須要注意的是使用了enumerateSymbolsSync,它是enumerateSymbols的同步版本。 另外和咱們以前經過Java.vm.tryGetEnv().getStringUtfChars
來調用env裏的方法不一樣。 這裏則是經過將以前找到的getStringUtfChars函數地址和參數信息封裝起來,直接調用,具體的原理我沒有深刻分析,先記住用法。 原理實際上是同樣的,都是根據符號找到地址,而後hook符號地址,而後打印參數。
declare const NativeFunction: NativeFunctionConstructor;
interface NativeFunctionConstructor { new(address: NativePointerValue, retType: NativeType, argTypes: NativeType[], abiOrOptions?: NativeABI | NativeFunctionOptions): NativeFunction; readonly prototype: NativeFunction;}...var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer", ["pointer", "pointer", "pointer"]);
var ishook_libart = false;
function hook_libart() { if (ishook_libart === true) { return; } var symbols = Module.enumerateSymbolsSync("libart.so"); var addrGetStringUTFChars = null; var addrNewStringUTF = null; var addrFindClass = null; var addrGetMethodID = null; var addrGetStaticMethodID = null; var addrGetFieldID = null; var addrGetStaticFieldID = null; var addrRegisterNatives = null; var addrAllocObject = null; var addrCallObjectMethod = null; var addrGetObjectClass = null; var addrReleaseStringUTFChars = null; for (var i = 0; i < symbols.length; i++) { var symbol = symbols[i]; if (symbol.name == "_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh") { addrGetStringUTFChars = symbol.address; console.log("GetStringUTFChars is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc") { addrNewStringUTF = symbol.address; console.log("NewStringUTF is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI9FindClassEP7_JNIEnvPKc") { addrFindClass = symbol.address; console.log("FindClass is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_") { addrGetMethodID = symbol.address; console.log("GetMethodID is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_") { addrGetStaticMethodID = symbol.address; console.log("GetStaticMethodID is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_") { addrGetFieldID = symbol.address; console.log("GetFieldID is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_") { addrGetStaticFieldID = symbol.address; console.log("GetStaticFieldID is at ", symbol.address, symbol.name); } else if (symbol.name == "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") { addrRegisterNatives = symbol.address; console.log("RegisterNatives is at ", symbol.address, symbol.name); } else if (symbol.name.indexOf("_ZN3art3JNI11AllocObjectEP7_JNIEnvP7_jclass") >= 0) { addrAllocObject = symbol.address; console.log("AllocObject is at ", symbol.address, symbol.name); } else if (symbol.name.indexOf("_ZN3art3JNI16CallObjectMethodEP7_JNIEnvP8_jobjectP10_jmethodIDz") >= 0) { addrCallObjectMethod = symbol.address; console.log("CallObjectMethod is at ", symbol.address, symbol.name); } else if (symbol.name.indexOf("_ZN3art3JNI14GetObjectClassEP7_JNIEnvP8_jobject") >= 0) { addrGetObjectClass = symbol.address; console.log("GetObjectClass is at ", symbol.address, symbol.name); } else if (symbol.name.indexOf("_ZN3art3JNI21ReleaseStringUTFCharsEP7_JNIEnvP8_jstringPKc") >= 0) { addrReleaseStringUTFChars = symbol.address; console.log("ReleaseStringUTFChars is at ", symbol.address, symbol.name); } }
if (addrRegisterNatives != null) { Interceptor.attach(addrRegisterNatives, { onEnter: function (args) { console.log("[RegisterNatives] method_count:", args[3]); var env = args[0]; var java_class = args[1]; var funcAllocObject = new NativeFunction(addrAllocObject, "pointer", ["pointer", "pointer"]); var funcGetMethodID = new NativeFunction(addrGetMethodID, "pointer", ["pointer", "pointer", "pointer", "pointer"]); var funcCallObjectMethod = new NativeFunction(addrCallObjectMethod, "pointer", ["pointer", "pointer", "pointer"]); var funcGetObjectClass = new NativeFunction(addrGetObjectClass, "pointer", ["pointer", "pointer"]); var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer", ["pointer", "pointer", "pointer"]); var funcReleaseStringUTFChars = new NativeFunction(addrReleaseStringUTFChars, "void", ["pointer", "pointer", "pointer"]);
var clz_obj = funcAllocObject(env, java_class); var mid_getClass = funcGetMethodID(env, java_class, Memory.allocUtf8String("getClass"), Memory.allocUtf8String("()Ljava/lang/Class;")); var clz_obj2 = funcCallObjectMethod(env, clz_obj, mid_getClass); var cls = funcGetObjectClass(env, clz_obj2); var mid_getName = funcGetMethodID(env, cls, Memory.allocUtf8String("getName"), Memory.allocUtf8String("()Ljava/lang/String;")); var name_jstring = funcCallObjectMethod(env, clz_obj2, mid_getName); var name_pchar = funcGetStringUTFChars(env, name_jstring, ptr(0)); var class_name = ptr(name_pchar).readCString(); funcReleaseStringUTFChars(env, name_jstring, name_pchar);
//console.log(class_name);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]); for (var i = 0; i < method_count; i++) { var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr); var sig = Memory.readCString(sig_ptr); var find_module = Process.findModuleByAddress(fnPtr_ptr); console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
} }, onLeave: function (retval) { } }); }
ishook_libart = true;}
hook_libart();
結果很明顯的打印了出來,包括動態註冊的函數的名字,函數簽名,加載地址和在so裏的偏移量,
[RegisterNatives] java_class: myapplication.example.com.ndk_demo.MainActivity name: stringFromJNI2 sig: ()Ljava/lang/String; fnPtr: 0x79f8698484 module_name: libnative-lib.so module_base: 0x79f8691000 offset: 0x7484
最後測試一下yang開源的一個hook art的腳本,頗有意思,trace出了很是多的須要的信息。
frida -U --no-pause -f package_name -l hook_art.js...[FindClass] name:myapplication/example/com/ndk_demo/Test[GetStaticFieldID] name:publicStaticField, sig:Ljava/lang/String;[GetStringUTFChars] result:i am a publicStaticField[NewStringUTF] bytes:Hello from C++ stringFromJNI2[GetStringUTFChars] result:sakura
native層調用棧打印
直接使用frida提供的接口打印棧回溯。
Interceptor.attach(f, { onEnter: function (args) { console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }});
效果以下,我加到了hook registerNative的地方。
[Google Pixel::myapplication.example.com.ndk_demo]-> RegisterNatives called from:0x7a100be03c libart.so!0xe103c0x7a100be038 libart.so!0xe10380x79f85699a0 libnative-lib.so!_ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi+0x440x79f85698e0 libnative-lib.so!JNI_OnLoad+0x900x7a102b9fd4 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP8_jstringPS9_+0x6380x7a08e3820c libopenjdkjvm.so!JVM_NativeLoad+0x1100x70b921c4 boot.oat!oatexec+0xa81c4
主動調用去進行方法參數替換
使用Interceptor.replace
,不贅述。主要目的仍是爲了改掉函數本來的執行行爲,而不是僅僅打印一些信息。
inline hook
inline hook簡單理解就是否是hook函數開始執行的地方,而是hook函數中間執行的指令 總體來講沒什麼區別,就是把找函數符號地址改爲從so裏找到偏移,而後加到so基地址上就行,注意一下它的attach的callback。
/** * Callback to invoke when an instruction is about to be executed. */type InstructionProbeCallback = (this: InvocationContext, args: InvocationArguments) => void;type InvocationContext = PortableInvocationContext | WindowsInvocationContext | UnixInvocationContext;
interface PortableInvocationContext { /** * Return address. */ returnAddress: NativePointer;
/** * CPU registers. You may also update register values by assigning to these keys. */ context: CpuContext;
/** * OS thread ID. */ threadId: ThreadId;
/** * Call depth of relative to other invocations. */ depth: number;
/** * User-defined invocation data. Useful if you want to read an argument in `onEnter` and act on it in `onLeave`. */ [x: string]: any;}......interface Arm64CpuContext extends PortableCpuContext { x0: NativePointer; x1: NativePointer; x2: NativePointer; x3: NativePointer; x4: NativePointer; x5: NativePointer; x6: NativePointer; x7: NativePointer; x8: NativePointer; x9: NativePointer; x10: NativePointer; x11: NativePointer; x12: NativePointer; x13: NativePointer; x14: NativePointer; x15: NativePointer; x16: NativePointer; x17: NativePointer; x18: NativePointer; x19: NativePointer; x20: NativePointer; x21: NativePointer; x22: NativePointer; x23: NativePointer; x24: NativePointer; x25: NativePointer; x26: NativePointer; x27: NativePointer; x28: NativePointer;
fp: NativePointer; lr: NativePointer;}
個人so是本身編譯的,具體的彙編代碼以下,總之這裏很明顯在775C時,x0裏保存的是一個指向"sakura"這個字符串的指針。(其實我也不是很看得懂arm64了已經,就隨便hook了一下) 因此hook這個指令,而後Memory.readCString(this.context.x0);
打印出來,結果以下
.text:000000000000772C ; __unwind {.text:000000000000772C SUB SP, SP, #0x40.text:0000000000007730 STP X29, X30, [SP,#0x30+var_s0].text:0000000000007734 ADD X29, SP, #0x30.text:0000000000007738 ; 6: v6 = a1;.text:0000000000007738 MOV X8, XZR.text:000000000000773C STUR X0, [X29,#var_8].text:0000000000007740 ; 7: v5 = a3;.text:0000000000007740 STUR X1, [X29,#var_10].text:0000000000007744 STR X2, [SP,#0x30+var_18].text:0000000000007748 ; 8: v4 = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL);.text:0000000000007748 LDUR X0, [X29,#var_8].text:000000000000774C LDR X1, [SP,#0x30+var_18].text:0000000000007750 MOV X2, X8.text:0000000000007754 BL ._ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *).text:0000000000007758 STR X0, [SP,#0x30+var_20].text:000000000000775C ; 9: if ( (signed int)_JNIEnv::GetStringUTFLength(v6, v5) > 0 ).text:000000000000775C LDUR X0, [X29,#var_8].text:0000000000007760 LDR X1, [SP,#0x30+var_18]
function inline_hook() { var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so"); if (libnative_lib_addr) { console.log("libnative_lib_addr:", libnative_lib_addr); var addr_775C = libnative_lib_addr.add(0x775C); console.log("addr_775C:", addr_775C);
Java.perform(function () { Interceptor.attach(addr_775C, { onEnter: function (args) { var name = this.context.x0.readCString() console.log("addr_775C OnEnter :", this.returnAddress, name); }, onLeave: function (retval) { console.log("retval is :", retval) } }) }) }}setImmediate(inline_hook())
Attaching... libnative_lib_addr: 0x79fabe0000addr_775C: 0x79fabe775cTypeError: cannot read property 'apply' of undefined at [anon] (../../../frida-gum/bindings/gumjs/duktape.c:56618) at frida/runtime/core.js:55[Google Pixel::myapplication.example.com.ndk_demo]-> addr_775C OnEnter : 0x79fabe7758 sakuraaddr_775C OnEnter : 0x79fabe7758 sakura
到這裏已經能夠總結一下我目前的學習了,須要補充一些frida api的學習,好比NativePointr裏竟然有個readCString,這些API是須要再看看的。
Frida native hook : Frida hook native app實戰
-
破解Frida全端口檢測的native層反調試 -
hook libc的pthread_create函數 -
破解TracePid的native反調試 -
target: https://gtoad.github.io/2017/06/25/Android-Anti-Debug/ -
solve : hook libc的fgets函數 -
native層修改參數、返回值 -
靜態分析 JNI_Onload
-
動態trace主動註冊 & IDA溯源 -
動態trace JNI、libc函數 & IDA溯源 -
native層主動調用、打調用棧 -
主動調用libc讀寫文件
看下logcat
n/u0a128 for activity com.gdufs.xman/.MainActivity12-28 05:53:26.898 26615 26615 V com.gdufs.xman: JNI_OnLoad()12-28 05:53:26.898 26615 26615 V com.gdufs.xman: RegisterNatives() --> nativeMethod() ok12-28 05:53:26.898 26615 26615 D com.gdufs.xman m=: 012-28 05:53:26.980 26615 26615 D com.gdufs.xman m=: Xman
sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ frida -U --no-pause -f com.gdufs.xman -l hook_reg.js...[Google Pixel::com.gdufs.xman]-> [RegisterNatives] method_count: 0x3[RegisterNatives] java_class: com.gdufs.xman.MyApp name: initSN sig: ()V fnPtr: 0xd4ddf3b1 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x13b1[RegisterNatives] java_class: com.gdufs.xman.MyApp name: saveSN sig: (Ljava/lang/String;)V fnPtr: 0xd4ddf1f9 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x11f9[RegisterNatives] java_class: com.gdufs.xman.MyApp name: work sig: ()V fnPtr: 0xd4ddf4cd module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x14cd
-
initSN 感受意思應該是從 /sdcard/reg.dat
裏讀一個值,而後和EoPAoY62@ElRD
進行比較。 最後setValue,從導出函數看一下,最後推測第一個參數應該是JNIEnv *env,而後就看到了給字段m賦值。 -
saveSN 這個看上去就是根據str的值,去變換"W3_arE_whO_we_ARE"字符串,而後寫入到 /sdcard/reg.dat
裏
結合一下看,只要initSN檢查到/sdcard/reg.dat
裏是EoPAoY62@ElRD
,應該就會給m設置成1。 只要m的值是1,就能走到work()函數的邏輯。
參考frida的file api
function main() { var file = new File("/sdcard/reg.dat",'w') file.write("EoPAoY62@ElRD") file.flush() file.close()}setImmediate(main())
這樣咱們繼續看work的邏輯
v2是從getValue獲得的,看上去就是m字段的值,此時應該是1,一會hook一下看看。
[NewStringUTF] bytes:輸入便是flag,格式爲xman{……}!
callWork裏又調用了work函數,死循環了。
那看來看去最後仍是回到了initSN,那其實咱們看的順序彷佛錯了。 理一下邏輯,n2執行完保存到文件,而後n1 check一下,因此最後仍是要逆n2的算法,pass。
Frida trace四件套
jni trace : trace jni
https://github.com/chame1eon/jnitrace
pip install jnitrace
Requirement already satisfied: frida>=12.5.0 in /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages (from jnitrace) (12.8.0)Requirement already satisfied: colorama in /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages (from jnitrace) (0.4.3)Collecting hexdump (from jnitrace) Downloading https://files.pythonhosted.org/packages/55/b3/279b1d57fa3681725d0db8820405cdcb4e62a9239c205e4ceac4391c78e4/hexdump-3.3.zipInstalling collected packages: hexdump, jnitrace Running setup.py install for hexdump ... done Running setup.py install for jnitrace ... doneSuccessfully installed hexdump-3.3 jnitrace-3.0.8
usage: jnitrace [options] -l libname target
默認應該是spawn運行的,
-
-m
來指定是spawn
仍是attach
-
-b
指定是fuzzy
仍是accurate
-
-i <regex>
指定一個正則表達式來過濾出方法名,例如-i Get -i RegisterNatives
就會只打印出名字裏包含Get或者RegisterNatives的JNI methods。 -
-e <regex>
和-i
相反,一樣經過正則表達式來過濾,但此次會將指定的內容忽略掉。 -
-I <string>
trace導出的方法,jnitrace認爲導出的函數應該是從Java端可以直接調用的函數,因此能夠包括使用RegisterNatives來註冊的函數,例如-I stringFromJNI -I nativeMethod([B)V
,就包括導出名裏有stringFromJNI,以及使用RegisterNames來註冊,並帶有nativeMethod([B)V簽名的函數。 -
-o path/output.json
,導出輸出到文件裏。 -
-p path/to/script.js
,用於在加載jnitrace腳本以前將指定路徑的Frida腳本加載到目標進程中,這能夠用於在jnitrace啓動以前對抗反調試。 -
-a path/to/script.js
,用於在加載jnitrace腳本以後將指定路徑的Frida腳本加載到目標進程中 -
--ignore-env
,不打印全部的JNIEnv函數 -
--ignore-vm
,不打印全部的JavaVM函數
sakura@sakuradeMacBook-Pro:~/Desktop/lab/alpha/tools/android/frida_learn/0620/0620/xman/resources/lib/armeabi-v7a$ jnitrace -l libmyjni.so com.gdufs.xmanTracing. Press any key to quit...Traced library "libmyjni.so" loaded from path "/data/app/com.gdufs.xman-X0HkzLhbptSc0tjGZ3yQ2g==/lib/arm".
/* TID 28890 */ 355 ms [+] JavaVM->GetEnv 355 ms |- JavaVM* : 0xefe99140 355 ms |- void** : 0xda13e028 355 ms |: 0xeff312a0 355 ms |- jint : 65542 355 ms |= jint : 0
355 ms ------------------------Backtrace------------------------ 355 ms |-> 0xda13a51b: JNI_OnLoad+0x12 (libmyjni.so:0xda139000)
/* TID 28890 */ 529 ms [+] JNIEnv->FindClass 529 ms |- JNIEnv* : 0xeff312a0 529 ms |- char* : 0xda13bdef 529 ms |: com/gdufs/xman/MyApp 529 ms |= jclass : 0x81 { com/gdufs/xman/MyApp }
529 ms ------------------------Backtrace------------------------ 529 ms |-> 0xda13a539: JNI_OnLoad+0x30 (libmyjni.so:0xda139000)
/* TID 28890 */ 584 ms [+] JNIEnv->RegisterNatives 584 ms |- JNIEnv* : 0xeff312a0 584 ms |- jclass : 0x81 { com/gdufs/xman/MyApp } 584 ms |- JNINativeMethod* : 0xda13e004 584 ms |: 0xda13a3b1 - initSN()V 584 ms |: 0xda13a1f9 - saveSN(Ljava/lang/String;)V 584 ms |: 0xda13a4cd - work()V 584 ms |- jint : 3 584 ms |= jint : 0
584 ms ------------------------Backtrace------------------------ 584 ms |-> 0xda13a553: JNI_OnLoad+0x4a (libmyjni.so:0xda139000)
/* TID 28890 */ 638 ms [+] JNIEnv->FindClass 638 ms |- JNIEnv* : 0xeff312a0 638 ms |- char* : 0xda13bdef 638 ms |: com/gdufs/xman/MyApp 638 ms |= jclass : 0x71 { com/gdufs/xman/MyApp }
638 ms -----------------------Backtrace----------------------- 638 ms |-> 0xda13a377: setValue+0x12 (libmyjni.so:0xda139000)
/* TID 28890 */ 688 ms [+] JNIEnv->GetStaticFieldID 688 ms |- JNIEnv* : 0xeff312a0 688 ms |- jclass : 0x71 { com/gdufs/xman/MyApp } 688 ms |- char* : 0xda13be04 688 ms |: m 688 ms |- char* : 0xda13be06 688 ms |: I 688 ms |= jfieldID : 0xf1165004 { m:I }
688 ms -----------------------Backtrace----------------------- 688 ms |-> 0xda13a38d: setValue+0x28 (libmyjni.so:0xda139000)
strace : trace syscall
https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/strace.html
frida-trace : trace libc(or more)
https://frida.re/docs/frida-trace/
Usage:frida-trace [options] target
frida-trace -U -i "strcmp" -f com.gdufs.xman... 5634 ms strcmp(s1="fi", s2="es-US") 5635 ms strcmp(s1="da", s2="es-US") 5635 ms strcmp(s1="es", s2="es-US") 5635 ms strcmp(s1="eu-ES", s2="es-US") 5635 ms strcmp(s1="et-EE", s2="es-US") 5635 ms strcmp(s1="et-EE", s2="es-US")
-
art trace: hook artmethod
hook_artmethod : trace java函數調用
https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_artmethod.js
修改AOSP源碼打印
改aosp源碼trace信息
Frida native hook : init_array開發和自動化逆向
init_array原理
常見的保護都會在init_array裏面作,關於其原理,主要閱讀如下文章便可。
-
IDA調試android so的.init_array數組 -
Android NDK中.init段和.init_array段函數的定義方式 -
Linker學習筆記
IDA靜態分析init_array
// 編譯生成後在.init段 [名字不可更改]extern "C" void _init(void) { LOGD("Enter init......");} // 編譯生成後在.init段 [名字不可更改]
extern "C" void _init(void) {
LOGD("Enter init......");
}
// 編譯生成後在.init_array段 [名字能夠更改]
__attribute__((__constructor__)) static void sakura_init() {
LOGD("Enter sakura_init......");
}
...
...
2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter init......
2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter sakura_init......
// 編譯生成後在.init_array段 [名字能夠更改]__attribute__((__constructor__)) static void sakura_init() { LOGD("Enter sakura_init......");}......2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter init......2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter sakura_init......
IDA快捷鍵
shift+F7
找到segment,而後就能夠找到.init_array
段,而後就能夠找到裏面保存的函數地址。
IDA動態調試so
-
打開要調試的apk,找到入口
sakura@sakuradeMacBook-Pro:~/.gradle/caches$ adb shell dumpsys activity top | grep TASKTASK com.android.systemui id=29 userId=0TASK null id=26 userId=0TASK com.example.ndk_demo id=161 userId=0
-
啓動apk,並讓設備將處於一個Waiting For Debugger的狀態
adb shell am start -D -n com.example.ndk_demo/.MainActivity
-
執行android_server64
sailfish:/data/local/tmp # ./android_server64IDA Android 64-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017Listening on 0.0.0.0:23946...
-
新開一個窗口使用forward程序進行端口轉發: adb forward tcp:23946 tcp:23946
adb forward tcp:<本地機器的網絡端口號> tcp:<模擬器或是真機的網絡端口號>
例:adb [-d|-e|-s
-
打開IDA,選擇菜單Debugger -> Attach -> Remote ARM Linux/Android debugger
-
打開IDA,選擇菜單Debugger -> Process options, 填好,而後選擇進程去attach。
-
查看待調試的進程
adb jdwp
sakura@sakuradeMacBook-Pro:~$ adb jdwp10436
-
轉發端口
adb forward tcp:8700 jdwp:10436
,將該進程的調試端口和本機的8700綁定。 -
jdb鏈接調試端口,從而讓程序繼續運行
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
-
找到斷點並斷下。
打開module找到linker64
找到call array函數
下斷並按F9斷下
最終我確實能夠調試到.init_array
的初始化,具體的代碼分析見Linker學習筆記這裏。
init_array && JNI_Onload 「自吐」
JNI_Onload
目標是找到動態註冊的函數的地址,由於這種函數沒有導出。
JNINativeMethod methods[] = { {"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2}, }; env->RegisterNatives(env->FindClass("com/example/ndk_demo/MainActivity"), methods, 1); JNINativeMethod methods[] = {
{"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2},
};
env->RegisterNatives(env->FindClass("com/example/ndk_demo/MainActivity"), methods,
1);
首先jnitrace -m spawn -i "RegisterNatives" -l libnative-lib.so com.example.ndk_demo
525 ms [+] JNIEnv->RegisterNatives 525 ms |- JNIEnv* : 0x7a106cc1c0 525 ms |- jclass : 0x89 { com/example/ndk_demo/MainActivity } 525 ms |- JNINativeMethod* : 0x7ff0b71120 525 ms |: 0x79f00d36b0 - stringFromJNI2()Ljava/lang/String;
而後objection -d -g com.example.ndk_demo run memory list modules explore | grep demo
sakura@sakuradeMacBook-Pro:~$ objection -d -g com.example.ndk_demo run memory list modules explore | grep demo[debug] Attempting to attach to process: `com.example.ndk_demo`Warning: Output is not to a terminal (fd=1).base.odex 0x79f0249000 106496 (104.0 KiB) /data/app/com.example.ndk_demo-HGAFhnKyKCSIpzn227pwXw==/oat/arm64/base.odexlibnative-lib.so 0x79f00c4000 221184 (216.0 KiB) /data/app/com.example.ndk_demo-HGAFhnKyKCSIpzn227pwXw==/lib/arm64/libnative...
offset = 0x79f00d36b0 - 0x79f00c4000 = 0xf6b0
這樣就找到了
init_array
沒有支持arm64,能夠在安裝app的時候adb install --abi armeabi-v7a
強制讓app運行在32位模式
這個腳本總體來講就是hook callfunction,而後打印出init_array裏面的函數地址和參數等。
從源碼看,關鍵就是call_array這裏調用的call_function,第一個參數表明這是註冊的init_array裏面的function,第二個參數則是init_array裏存儲的函數的地址。
template <typename F>static void call_array(const char* array_name __unused, F* functions, size_t count, bool reverse, const char* realpath) { if (functions == nullptr) { return; } template <typename F>
static void call_array(const char* array_name __unused,
F* functions,
size_t count,
bool reverse,
const char* realpath) {
if (functions == nullptr) {
return;
}
TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? -1 : count;
int step = reverse ? -1 : 1;
for (int i = begin; i != end; i += step) {
TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
call_function("function", functions[i], realpath);
}
TRACE("[ Done calling %s for '%s' ]", array_name, realpath);
}
TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
int begin = reverse ? (count - 1) : 0; int end = reverse ? -1 : count; int step = reverse ? -1 : 1;
for (int i = begin; i != end; i += step) { TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]); call_function("function", functions[i], realpath); }
TRACE("[ Done calling %s for '%s' ]", array_name, realpath);}
function LogPrint(log) { var theDate = new Date(); var hour = theDate.getHours(); var minute = theDate.getMinutes(); var second = theDate.getSeconds(); var mSecond = theDate.getMilliseconds()
hour < 10 ? hour = "0" + hour : hour; minute < 10 ? minute = "0" + minute : minute; second < 10 ? second = "0" + second : second; mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond; var threadid = Process.getCurrentThreadId(); console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklinker() { var linkername = "linker"; var call_function_addr = null; var arch = Process.arch; LogPrint("Process run in:" + arch); if (arch.endsWith("arm")) { linkername = "linker"; } else { linkername = "linker64"; LogPrint("arm64 is not supported yet!"); }
var symbols = Module.enumerateSymbolsSync(linkername); for (var i = 0; i < symbols.length; i++) { var symbol = symbols[i]; //LogPrint(linkername + "->" + symbol.name + "---" + symbol.address); if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) { call_function_addr = symbol.address; LogPrint("linker->" + symbol.name + "---" + symbol.address)
} }
if (call_function_addr != null) { var func_call_function = new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']); Interceptor.replace(new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']), new NativeCallback(function (arg0, arg1, arg2) { var functiontype = null; var functionaddr = null; var sopath = null; if (arg0 != null) { functiontype = Memory.readCString(arg0); } if (arg1 != null) { functionaddr = arg1;
} if (arg2 != null) { sopath = Memory.readCString(arg2); } var modulebaseaddr = Module.findBaseAddress(sopath); LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr); if (sopath.indexOf('libnative-lib.so') >= 0 && functiontype == "DT_INIT") { LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
} else { func_call_function(arg0, arg1, arg2); LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
}
}, 'void', ['pointer', 'pointer', 'pointer'])); }}
setImmediate(hooklinker)
我調試了一下linker64,由於沒有導出call_function的地址,因此不能直接hook符號名,而是要根據偏移去hook,之後再說。 其實要看init_array
,直接shift+F7去segment裏面找.init_array
段就能夠了,這裏主要是爲了反反調試,由於可能反調試會加在init_array裏,hook call_function就可讓它不加載反調試程序。
native層未導出函數主動調用(任意符號和地址)
如今我想要主動調用sakura_add來打印值,能夠ida打開找符號,或者根據偏移,總之最終用這個NativePointer指針來初始化一個NativeFunction來調用。
extern "C"JNIEXPORT jint JNICALLJava_com_example_ndk_1demo_MainActivity_sakuraWithInt(JNIEnv *env, jobject thiz, jint a, jint b) { // TODO: implement sakuraWithInt() return sakura_add(a,b);}...int sakura_add(int a, int b){ int sum = a+b; LOGD("sakura add a+b:",sum); return sum;} extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndk_1demo_MainActivity_sakuraWithInt(JNIEnv *env, jobject thiz, jint a, jint b) {
// TODO: implement sakuraWithInt()
return sakura_add(a,b);
}
...
int sakura_add(int a, int b){
int sum = a+b;
LOGD("sakura add a+b:",sum);
return sum;
}
![](http://static.javashuo.com/static/loading.gif)
function main() { var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so"); console.log("libnative_lib_addr is :", libnative_lib_addr); if (libnative_lib_addr) { var sakura_add_addr1 = Module.findExportByName("libnative-lib.so", "_Z10sakura_addii"); var sakura_add_addr2 = libnative_lib_addr.add(0x0F56C) ; console.log("sakura_add_addr1 ", sakura_add_addr1); console.log("sakura_add_addr2 ", sakura_add_addr2) }
var sakura_add1 = new NativeFunction(sakura_add_addr1, "int", ["int", "int"]); var sakura_add2 = new NativeFunction(sakura_add_addr2, "int", ["int", "int"]);
console.log("sakura_add1 result is :", sakura_add1(200, 33)); console.log("sakura_add2 result is :", sakura_add2(100, 133));}setImmediate(main())......libnative_lib_addr is : 0x79fa1c5000sakura_add_addr1 0x79fa1d456csakura_add_addr2 0x79fa1d456csakura_add1 result is : 233sakura_add2 result is : 233
C/C++ hook
//todo
Native/JNI層參數打印和主動調用參數構造
jni的基本類型要經過調用jni相關的api轉化成c++對象,才能打印和調用。 jni主動調用的時候,參數構造有兩種方式,一種是Java.vm.getenv
,另外一種是hook獲取env以後來調用jni相關的api構造參數。
C/C++編成so並引入Frida調用其中的函數
![](http://static.javashuo.com/static/loading.gif)
關注小白技術社,開啓爬蟲工程師的app逆向之路。
恭喜你完成Frida三部曲的第三部(大結局),點個在看告訴你們吧
本文分享自微信公衆號 - 小白技術社(xbjss123)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。