转载自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

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;
}

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调用其中的函数

关注小白技术社,开启爬虫工程师的app逆向之路。
恭喜你完成Frida三部曲的第三部(大结局),点个在看告诉你们吧
本文分享自微信公众号 - 小白技术社(xbjss123)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。