首页
论坛
课程
招聘
[原创][分享]Radare2静态分析so
2020-11-30 20:28 1679

[原创][分享]Radare2静态分析so

2020-11-30 20:28
1679

Radare2静态分析apk(1) 对Radare静态分析apk进行了简单的介绍。 补充一下: 通过r2 apk://URI可以直接对apk中的dex进行分析。

Android的so文件

前面的文章中,对so文件进行了基本的介绍。Android的so有点不一样。

Android平台pic(位置无关代码)编译的原因,所有全局变量的引用都是通过got(全局偏移表)完成的,加载器会根据加载基址来修正,并向got填入正确的全局变量的地址。如某重定位数据a=S,app运行时的基址是A,pBuf的地址是B,则重定位a的值为S-A+B,这样便相当于从pBuf处加载so。

通过readelf -d来获取数据重定位的信息。后面会对android的so文件进行专门的分析。在这里插入图片描述

JNIEnv在什么时候创建

开机的时候。

JNIEnv是什么

参考链接: http://androidxref.com/9.0.0_r3/xref/libnativehelper/include_jni/jni.h和https://www.androidos.net.cn/android/10.0.0_r6/xref/libnativehelper/include_jni/jni.h


#if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif


如果用C++编译器,那么JNIEnv就是_JNIEnv如果用C编译器,那么JNIEnv就是JNINativeInterface

那_JNIEnv又是什么? 通过下面的代码可知*最终还是还是JNINativeInterface**


struct _JNIEnv {     /* do not rename this; it does not seem to be entirely opaque */     const struct JNINativeInterface* functions; #if defined(__cplusplus)     jint GetVersion()     { return functions->GetVersion(this); }     jclass DefineClass(const char *name, jobject loader, const jbyte* buf,         jsize bufLen)     { return functions->DefineClass(this, name, loader, buf, bufLen); }     . . . #endif /*__cplusplus*/ };


JNINativeInterface又是什么?通过下面的代码可知定义了一系列的函数指针


struct JNINativeInterface {     void*       reserved0;     void*       reserved1;     void*       reserved2;     void*       reserved3;     jint        (*GetVersion)(JNIEnv *);     jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,                         jsize);     jclass      (*FindClass)(JNIEnv*, const char*);     jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);     jfieldID    (*FromReflectedField)(JNIEnv*, jobject);     /* spec doesn't show jboolean parameter */     jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);     jclass      (*GetSuperclass)(JNIEnv*, jclass);     . . .  };


创建JNIEnv

参考链接: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/cmds/app_process/app_main.cpp

Zygote进程是在Init进程启动的时候创建的。至于Init进程是怎么来的,可参考Android启动流程。


int main(int argc, char* const argv[]) {     if (!LOG_NDEBUG) {       String8 argv_String;       for (int i = 0; i < argc; ++i) {         argv_String.append("\"");         argv_String.append(argv[i]);         argv_String.append("\" ");       }       ALOGV("app_process main with argv: %s", argv_String.string());     }     AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));     . . .     // Parse runtime arguments.  Stop at first unrecognized option.     bool zygote = false;     bool startSystemServer = false;     bool application = false;     String8 niceName;     String8 className;     ++i;  // Skip unused "parent dir" argument.     while (i < argc) {         const char* arg = argv[i++];         if (strcmp(arg, "--zygote") == 0) {             zygote = true;             niceName = ZYGOTE_NICE_NAME;             . . .     }     . . .     if (zygote) {         runtime.start("com.android.internal.os.ZygoteInit", args, zygote);     } else if (className) {         runtime.start("com.android.internal.os.RuntimeInit", args, zygote);     } else {         fprintf(stderr, "Error: no class name or --zygote supplied.\n");         app_usage();         LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");     } }


如果zygote是true,则说明当前运行在Zygote进程。走runtime.start("com.android.internal.os.ZygoteInit", args, zygote),start方法的实现如下:


void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {     ALOGD(">>>>>> START %s uid %d <<<<<<\n",             className != NULL ? className : "(unknown)", getuid());     . . .      /* start the virtual machine */     JniInvocation jni_invocation;     jni_invocation.Init(NULL);     JNIEnv* env;     //启动Java虚拟机 001     if (startVm(&mJavaVM, &env, zygote) != 0) {         return;     }     onVmCreated(env);     /*      * Register android functions.      */      //为Java虚拟机注册JNI方法     if (startReg(env) < 0) {         ALOGE("Unable to register all android natives\n");         return;     }     /*      * We want to call main() with a String array with arguments in it.      * At present we have two arguments, the class name and an option string.      * Create an array to hold them.      */     jclass stringClass;     jobjectArray strArray;     jstring classNameStr;     stringClass = env->FindClass("java/lang/String");     assert(stringClass != NULL);     strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);     assert(strArray != NULL);     classNameStr = env->NewStringUTF(className);     assert(classNameStr != NULL);     env->SetObjectArrayElement(strArray, 0, classNameStr);     for (size_t i = 0; i < options.size(); ++i) {         jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());         assert(optionsStr != NULL);         env->SetObjectArrayElement(strArray, i + 1, optionsStr);     }     /*      * Start VM.  This thread becomes the main thread of the VM, and will      * not return until the VM exits.      */     char* slashClassName = toSlashClassName(className != NULL ? className : "");     jclass startClass = env->FindClass(slashClassName);     if (startClass == NULL) {         ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);         /* keep going */     } else {         //调ZygoteInit的main方法         jmethodID startMeth = env->GetStaticMethodID(startClass, "main",             "([Ljava/lang/String;)V");//002         if (startMeth == NULL) {             ALOGE("JavaVM unable to find main() in '%s'\n", className);             /* keep going */         } else {             env->CallStaticVoidMethod(startClass, startMeth, strArray); #if 0             if (env->ExceptionCheck())                 threadExitUncaughtException(env); #endif         }     }     free(slashClassName);     ALOGD("Shutting down VM\n");     if (mJavaVM->DetachCurrentThread() != JNI_OK)         ALOGW("Warning: unable to detach main thread\n");     if (mJavaVM->DestroyJavaVM() != 0)         ALOGW("Warning: VM did not shut down cleanly\n"); }


在上文的注释002处,进入ygoteInit的main方法。Zygote便进入了Java框架层,也就是说Zygote开创了Java框架层。

在注释001处,调用startVM启动Java虚拟机。


int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) {     . . .     /*      * Initialize the VM.      *      * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.      * If this call succeeds, the VM is ready, and we can start issuing      * JNI calls.      */     if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {         ALOGE("JNI_CreateJavaVM failed\n");         return -1;     }     return 0; }


参考链接: https://www.androidos.net.cn/android/10.0.0_r6/xref/libnativehelper/JniInvocation.cpp


jint JniInvocationImpl::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {   //调用前面从libart.so中找到的创建虚拟机的函数   return JNI_CreateJavaVM_(p_vm, p_env, vm_args); }


NIEnv是在Zygote初始化的时候调用libart.so中的"JNI_CreateJavaVM"方法创建的。JNIEnv来的竟然如此复杂!

JNIEnv基础

1.线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv。JNIEnv 不能跨线程 :

当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;

1. 所有的JNI调用都使用了JNIEnv*类型的指针,习惯上在CPP文件中将这个变量定义为evn,它是任意一个本地方法的第一个参数。env指针指向一个函数指针表,在VC中可以直接用"->"操作符访问其中的函数。2.jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction的一个句柄,相当于this指针。后续的参数就是本地调用中有Java程序传进的参数。以下是我们经常会用到的字符串类型处理的函数:

const char* GetStringUTFChars (jstring string,jboolean* isCopy) 返回指向字符串UTF编码的指针,如果不能创建这个字符数组,返回null。这个指针在调用ReleaseStringUTFChar()函数之前一直有效。 参数: string Java字符串对象 isCopy 如果进行拷贝,指向以JNI_TRUE填充的jboolean,否则指向以JNI_FALSE填充的jboolean。void ReleaseStringUTFChars(jstring str, const char* chars) 通知虚拟机本地代码不再需要通过chars访问Java字符串。 参数: string Java字符串对象 chars 由GetStringChars返回的指针

jstring NewStringUTF(const char *utf) 返回一个新的Java字符串并将utf内容拷贝入新串,如果不能创建字符串对象,返回null。通常在反值类型为string型时用到。 参数: utf UTF编码的字符串指针,对于数值型参数,在C/C++中可直接使用

在这里插入图片描述

JNIEnv编程

通过ANdroid studio 新建c++项目,需要下载ndk和CMakelist。 核心代码:

MainActivity:


    static {         System.loadLibrary("native-lib");     }     . . .     tv.setText(stringFromJNI());


native-lib.cpp


#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI(         JNIEnv* env,         jobject /* this */) {     std::string hello = "Hello from C++";     return env->NewStringUTF(hello.c_str()); }


CMakeList.txt


add_library( # Sets the name of the library.              native-lib              # Sets the library as a shared library.              SHARED              # Provides a relative path to your source file(s).              native-lib.cpp )


使用Radare2静态分析

使用pdf看一下,只能看出有字符串"Hello from c++"。至于怎么处理的看不出来。在这里插入图片描述decompiler看一下效果:在这里插入图片描述还是不能直接看出来怎么处理的“hello from c++”。 如果能把int64_t转换为JNIEnv *,同时相关的函数也可以带出来。我查询了一些资料,通过ESIL能够模拟出JNIEnv的地址,不过我没有成功,然后通过tl命令进行链接。类型处理是Radare2做得不太好的地方。不过问题总有解决办法,既然静态分析看不出来什么,那就使用动态分析。动态分析的内容会在后续文章中介绍。

这个使用Frida走一波,看看最终的返回值是什么。


function frida_Java() {     Java.perform(function () {           var targetClass = Java.use("com.example.myapplication.MainActivity");           targetClass.stringFromJNI.implementation = function(){             var result = this.stringFromJNI();             console.log("burning result "+result);             return result;           }           var targetFunc = Module.getExportByName("libnative-lib.so", 'Java_com_example_myapplication_MainActivity_stringFromJNI');           console.log("burning"+targetFunc);           Interceptor.attach(targetFunc, {             onEnter: function (args) {                 console.log(args[0]);                 console.log("burning Env"+JSON.stringify(Java.vm.getEnv()));             },             onLeave: function (retval) {               console.log("retval "+retval.toString());             }           });     });   }          setImmediate(frida_Java,0);


通过如下的命令运行,这样才能hook onCreate方法。


frida -U  -f  com.example.myapplication -l answner.js  --no-pause


运行结果如下:在这里插入图片描述如果我想改返回值,


          targetClass.stringFromJNI.implementation = function(){             var result = this.stringFromJNI();             console.log("burning result "+result);             result = "欢迎关注我的微信公众号:无情剑客";             return result;           }


在这里插入图片描述

写在最后

通过Radare2静态分析so文件,不能看出直观的逻辑,即使使用decompiler也不是非常友好,使用ESIL模拟定位JNIEnv没能成功定位。希望Radare2在类型处理方面加强一些。动态调式在后续文章中会更新。

公众号

更多内容,欢迎关注我的微信公众号:无情剑客。在这里插入图片描述


[招聘] 欢迎你加入看雪团队!

最后于 2020-12-6 13:29 被无情剑客_bur编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回