freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Radare2静态分析so文件
2020-11-30 20:32:10

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;
#elsetypedefconststructJNINativeInterface* JNIEnv;
typedefconststructJNIInvokeInterface* JavaVM;
#endif

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

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

struct_JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */conststructJNINativeInterface* functions;

#if defined(__cplusplus)jint GetVersion()
    { returnfunctions->GetVersion(this); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { returnfunctions->DefineClass(this, name, loader, buf, bufLen); }

    . . .
#endif /*__cplusplus*/};

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

structJNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);

    jclass      (*DefineClass)(JNIEnv*, constchar*, jobject, constjbyte*,
                        jsize);
    jclass      (*FindClass)(JNIEnv*, constchar*);

    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(inti = 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.boolzygote = false;
    boolstartSystemServer = false;
    boolapplication = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.while(i < argc) {
        constchar* 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方法的实现如下:

voidAndroidRuntime::start(constchar* className, constVector<String8>& options, boolzygote)
{
    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虚拟机 001if(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_ti = 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");//002if(startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */} else{
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0if(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虚拟机。

intAndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, boolzygote)
{
    . . .
    /*
     * 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;
    }

    return0;
}

参考链接: 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中找到的创建虚拟机的函数returnJNI_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::stringhello = "Hello from C++";
    returnenv->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 () {
          vartargetClass = Java.use("com.example.myapplication.MainActivity");
          targetClass.stringFromJNI.implementation = function(){
            varresult = this.stringFromJNI();
            console.log("burning result "+result);
            returnresult;
          }

          vartargetFunc = 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-fcom.example.myapplication-lanswner.js--no-pause

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

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

在这里插入图片描述

写在最后

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

公众号

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


# 黑客 # 系统安全 # 数据安全 # Android # Radare2
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者