Android JNI介绍(九)- 动态库逆向

1,692 阅读3分钟

本系列文章列表:

Android JNI介绍(一)- 第一个Android JNI工程

Android JNI介绍(二)- 第一个JNI工程的详细分析

Android JNI介绍(三)- Java和Native的互相调用

Android JNI介绍(四)- 异常的处理

Android JNI介绍(五)- 函数的注册

Android JNI介绍(六)- 依赖其他库

Android JNI介绍(七)- 引用的管理

Android JNI介绍(八)- CMakeLists的使用

Android JNI介绍(九)- 动态库逆向


经常在一些地方看到类似于这样的介绍:一些重要的字段不要放在Java代码中,需要放在native。 但是事实上,并没有绝对的安全,即使是将数据放在native,我们也可以使用IDA等工具进行查看,对于未加密的常量字段数据,我们可以在.rodata数据段直接看到数据内容,即使是通过插入花指令等方式使数据看着没那么容易理解,有经验的逆向者也可以通过代码阅读、调试动态库等方式了解原数据内容。

一、编译一个动态库

先写一个动态库,CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.6)
add_library(reversetest
        SHARED
        reverse.cpp)
find_library(
        log-lib
        log)
target_link_libraries(reversetest ${log-lib})

reverse.cpp中的代码内容如下:

#include <jni.h>
#include <android/log.h>

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"reservetest" ,__VA_ARGS__)

extern "C" JNIEXPORT void
JNICALL
Java_com_wsy_jnidemo_test_ReverseTest_reverseData(
        JNIEnv
        *env,
        jclass) {
    const char *content = "content";
    LOGI("content is: %s", content);
}
extern "C" JNIEXPORT void
JNICALL
Java_com_wsy_jnidemo_test_ReverseTest_reverseJudge(
        JNIEnv
        *env,
        jclass,
        jint count
) {
    if (count > 1000) {
        LOGI("count is %d, count > 1000", count);
    } else {
        LOGI("count is %d, count <= 1000", count);
    }
}

Java调用:

public class ReverseTest {
    static {
        System.loadLibrary("reversetest");
    }
    public static native void reverseData();

    public static native void reverseJudge(int count);
}
    private void doReverseTest() {
        ReverseTest.reverseData();
        ReverseTest.reverseJudge(10000);
    }

点击externalNativeBuildRelease,将生成的动态库复制到jniLibs目录下,运行。

输出日志如下,很正常:

二、修改动态库

首先我们需要了解的是,无论是exe、so、dll、txt、doc,还是其他格式的文件,它们都只是文件,都是二进制数据,只是生成和解析方式不同。比如我们可以使用文本编辑器将txt解析成我们能够理解的文本内容,也可以将我们输入的文本内容保存成txt;我们可以使用Word将doc解析成我们能够理解的内容,也可以将我们输入的内容保存成doc...

对于so文件,我们可以使用IDA Pro进行解析、修改。

1. IDA界面简介

IDA Pro在安装完成后,会分别有64位和32位的可执行文件。对于刚才生成的libreversetest.so这个动态库文件,使用64位的程序打开,打开后界面如下:

2. 修改函数内容

  • 修改常量字符串

    在左边的函数表中,我们可以使用CTRL + F寻找我们需要查看的函数:

    双击函数名,可查看其汇编指令,再按F5,得到反汇编后的C代码:

    双击右边的content,即可跳转到其定义处:

    这个时候我们可以使用IDA Pro自带的编辑器修改其内容,也可以根据其偏移地址0x6E0使用其他二进制编辑器修改,或者自己写个代码修改文件的内容。

    这里选择用IDA自带的功能修改: 点击Edit->Patch program->Change bytes... 将其修改为61 - 67(也就是a-g): 确认后可以看到,这个字段的内容已经被我们成功修改了 但是这个时候并没有保存数据至原动态库中,我们点击Edit->Patch program->Apply patches to input file..即可保存至原文件。

  • 修改指令

    双击另一个函数,看到以下源码:

    这个动态库里的指令反汇编得到的代码和编写时的内容不是完全一致,但是不影响,表达的含义相同。

    对于指令的修改,汇编视图会更友好,切换到汇编视图,内容如下:

    我们知道,LT代表less thanGT代表greater thanGE代表greater or equals,只要我们将LT修改为GE即可进入判断的另一个分支:

    选中这行指令,点击Edit->Patch program->Assemble...,对于arm架构的动态库,我的电脑会报Sorry, this processor module doesn't support the assembler,但是直接改数据是没问题的,修改指令为以下内容:

    和刚才一样的操作,点击Apply patches to input file..保存修改至原文件。

    对于x86_64的动态库,我的设备点击Edit->Patch program->Assemble...是没问题的,所以这里也对x86_64架构的动态库操作一遍:

    原指令如下,我们将jl修改为jge即可进入另一个逻辑分支:

    原指令: 修改后:

三、运行验证

刚才我们修改了一处常量字符串和一处判断指令,现在将修改好的arm64-v8a的动态库放进工程对应目录,运行,结果如下:

大功告成,成功地修改动态库内容。

四、如何加固

程序本身就是相当于可阅读的东西,因此,无论如何加固,都只是增加攻破的难度,我们可以通过添加花指令、隐藏符号等方式来达到加固的作用,比如我们尝试用IDA Pro反汇编支付宝中的一个动态库: 可以看到,打开后发现函数名都被修改成了这种让人看不下去的名字,大大增加逆向的难度。