移动测试基础 android 中 ndk 的开发

匿名 · 2018年04月26日 · 810 次阅读

前言(其实是吐槽)

这是我看(android 应用安全防护和逆向分析)遇到的第一个坑了,在章节 2.1 和 2.2 里,虽然作者很贴心的给了步骤教你如何搭建 ndk 的开发环境,但是,我要说的是,如果按照作者在 2.1.2 的五个步骤按部就班的来,你绝对!不可能!完成!

主要的原因我就不再分析了,大约就是少了一堆乱七八糟的说明和步骤,这里我重新写一遍 ndk 开发相关。(如果你不信,可以尝试只按照 2.1.2 章节的五步来尝试)

搭建 NDK 开发环境

NDK 相关概念

首先,普及一下 ndk 的概念,何谓 ndk 开发呢?

简而言之,就是让安卓(java)可以调用前人用 c 语言完成的库,这么做的好处主要有两个,

第一,节约代码量,提高工作(运行)效率,可以用之前 c 写好的很多很棒的库

第二,防止应用被逆向,因为 java 层的代码很容易被反编译逆向突破。

再介绍几个 ndk 里相关概念名词,

.c,.cpp,这几个不用多说了吧,就是 c 或者 c++ 等文件的后缀名;

.so,这个是编译 c 文件得到的库文件的后缀名,.so 文件大概就相当于 windows 上的.dll 文件,他可以方便让别人调用;

.h,也就是通过 javah 命令编译类(class)文件编译出来的头文件,这玩意开始在 c 里用作声明管理,现在在 jni 里已经意义不大了,你可以当做没啥用;

jni,全名 java native interface,由他提供 java 和 c 之间的通信;

一个 NDK 开发的流程

编写 Java 代码 (.java) —————> 编译生成字节码文件 (.class) —————> 产生 C 头文件 (.h) —————> 编写 jni 实现代码 (.c) —————> 编译成链接库 (.so)

基于 Android Studio3 & CMAKE 的 NDK 环境搭建
  1. 打开你的 as(Android Studio),新建一个支持 c++ 的项目(如图),然后一路 next 到结束;
    image
  2. 此时一个基本工程环境已经新建好了,不需要任何改动,我们直接可以点击 build 生成一个系统自带的示例 so 库也就是 cpp 目录里的 native-lib,但在此之前,我们先看一下工程目录:
    image
    可以看到,相对于普通项目,我们的 ndk 的项目其实就是多了 cpp 目录,多了 CMakelists.txt 文件,此外 build.gradle 里多了一些配置;
  3. 因为编译出 so 库,主要看的是 CMakelists 文件里的配置,下面我们重点关注一下 CMakelists 文件的内容,如图:
    image
    默认的 CMakelists 文件里,主要有三个配置需要关注,分别是
    add_library:
    主要用作给生成的 so 库做配置,在三个注释的下面,每一个都代表了一个配置项(其实注释的英文已经说明一切了),
    Sets the name of the library 下,填写你希望生成的 so 库的名称,这里注意的是,你填写的名称,最终会被自动加上 lib 的前缀和.so 的后缀,因此你只需要基础的名称就够了,最终生成的 so 文件会在工程的 app-build-intermediates-cmake-debug-obj 目录下,每个 abi 环境都有个文件夹,里面放着对应的 so 库;
    Sets the library as a shared library 下,填写你要生成的 so 库的类型,这里的 SHARED 是指动态链接库类型;
    Provides a relative path to your source file(s) 下,填写你要编译成 so 文件的对应的 c 源文件的路径;
    find_library:
    主要是用作添加我们本地库的依赖库,
    Sets the name of the path variable 下,填写你引用的库的别名,便于引用;
    you want CMake to locate 下,填写你要引用的依赖库;
    target_link_libraries:
    是为了关联我们自己的库和一些第三方库或者系统库,
    Specifies the target library 下,填写本地库的名称;
    included in the NDK 下,填写你要关联的库的名称。
  4. ok,现在让我们点击一下工具栏的锤子图标,就可以生成 so 库了。
    ##### 补充一些 tips
    ###### 关于环境 android ndk 开发需要设置环境变量以及安装 sdk,ndk,cmake,lldb,如果没有,可以在 as 的 tools 选项里,点击 sdk manager,在 sdk tools 栏里,把这些都勾上,点击确定即可;
    ###### 关于默认生成的例子
    按照常规的 ndk 开发流程,应该是先写 java 类,这个类里可能会引用到 native 的方法也就是拿 c 实现的方法;然后 javah 命令编译这个 java 类,生成.h 的头文件(这一步可以不用);再写一个.c 或者.cpp 实现在你 java 类里引用的这个 native 方法;最后编译你的 c 文件,生成最终的 so 库。
    在系统默认的例子里,你点一下编译按钮就能得到 so 库,可能对这个流程不太明白,下面我们具体说一下:
    先看一下 MainActivity 类的代码:

public class MainActivity extends AppCompatActivity {

    // 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());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

你可以当做这是系统给你先写好的一个引用了 native 方法的 java 类,也就是说这就是 ndk 第一步编写 java 类的产物;

在 static 里,System.loadLibrary("native-lib") 这句会先于 onCreate 函数触发,这句是载入我们生成的动态链接库.so 文件,这里只需要填写 CMakeLists 里面填写的名称即可;然后你会看到下面引用了这个库里的 native 方法,也就是这句 public native String stringFromJNI();这就是系统帮你做好的第一步编写 java 类;

然后我们需要编写实现这个 native 方法的.c 文件,这里系统也帮你写好了就是在 cpp 文件夹里的那个 native-lib.cpp 的文件,这个文件里实现了 stringFromJNI() 这个方法;

最后,我们点击编译,成功生成了 so 文件,这就是系统的例子里,ndk 开发的完整流程。

Hello World 工程引发的被坑事件

前面是介绍了如何在 as3 的环境下进行 ndk 开发以及解析了系统自带的例子,接下来我们肯定要仿照系统的例子,自己走一遍 ndk 开发的流程,当然第一个工程肯定就是通用的 HelloWorld 了。

在小黄书(android 应用安全防护和逆向分析)里,也有对 helloworld 工程的举例,流程是:新建一个 java 类,在 java 类里申明一个 native 方法,再在 main 方法里载入动态链接库,新建一个实例调用 native 方法;然后使用 vc6.0 编译器编写好 native 方法,生成.dll 动态链接库文件;最后把该 dll 文件写入环境变量内,运行 java 类;具体代码和流程参见小黄书 p14-p15 页。

不过,其实不用如此麻烦,之所以用 vc 写 c 的内容,是因为当时 as 编译器对 c 的编写支持的还不够好,但 as3 以后,这块已经开始逐步完善了,因此,我们可以不用按照书里的流程,直接使用 as3 来写 c 的内容,不过,这里有一个坑,开始我是想仿造小黄书的示例代码,单独写一个具有 main 方法的 java 类来调用本地方法,大概是这样:

package com.example.test.testndk;

public class HelloWorld {

    public native String stringFromJNITest();
    static {

        System.loadLibrary("JNITest");
    }


    public static void main(String[] args){
        HelloWorld hello = new HelloWorld();
        String hi = hello.stringFromJNITest();
        System.out.println(hi);
    }
}

JNITest 是我事先写好的 cpp 文件编译成的.so 库,里面实现了 stringFromJNITest() 方法,.cpp 内容是这样的:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

JNICALL
Java_com_example_test_testndk_MainActivity_stringFromJNITest(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello world";
    return env->NewStringUTF(hello.c_str());
}

以上其实就是我们自己实现一个 ndk 开发的流程,即先写一个 java 类,在类中声明一个 native 方法,然后写.cpp 实现这个 native 方法,编译 cpp 成 so 库,在 java 类里用 loadlibrary 加载这个 so 库。

本例中,这是一个简单的 native 方法就是返回了一串字符串 “Hello World” 而已,原本我以为到此结束,点击运行这个 java 类我们的 Hello World 之旅就结束了,但是没想到报了个错:java.lang.UnsatisfiedLinkError: no JNITest in java.library.path。

开始我怀疑是不是 CMakelists.txt 的配置问题,但是我把这个 so 库在 android 工程的 mainactivity 里引用,居然没有问题,也就是说,应该不是配置的路径问题。经过在群里讨论以及百度,又开始怀疑是不是 java.library.path 的问题,因为据了解,其实你用 as3 运行 android 工程的 mainactivity,编译器是会自动替你生成 so 库用的(只要你 CMakelists 配置好了),所以不会有路径问题,但是当你新建一个 java 类,写上 main 方法,独立运行这个 java 类的时候,其实是按 java 工程的方式找你的动态链接库文件的,而这个方式,在 win 下,是在环境变量 path 里找;在 linux 下,是在系统变量 LD_LIBRARY_PATH 里找,为了证明这个结论,我在系统的环境变量 path 里,添加了 so 库所在的目录路径。

但是,依旧报错。。no JNITest in java.library.path;这时群友又提醒我,我是 win 的系统,而 win 的动态链接库的格式是.dll,因此光配置了路径,但因为我的格式是 .so,因此也找不到;现搭一个 linux 系统来做实验成本太高,我采用了一个取巧的方式,直接把生成的.so 文件后缀改为了 dll,因为如果是后缀的原因,那么会报错,但肯定不会再报找不到的错,算是侧面论证。

我把后缀改为 dll 之后,再次运行! 奇迹发生了,果然,还是报 on JNITest in java.library.path,为什么呢,我猜可能是 win 下和 android 下,寻找规则不同,在 android 工程里,只需要写库名,不需要写前缀 lib 和后缀 .so,在 win 下,则需要写上前缀 lib;最后,我把 System.loadLibrary("JNITest");改为 System.loadLibrary("libJNITest");再次点击运行!

这次报错不同了,不再是找不到,而是 Can't load this .dll (machine code=0x34) on a AMD 64-bit platform;终于,本次 Hello World 被坑事件的坑算是完美填上。

共收到 1 条回复 时间 点赞
匿名 #1 · 2018年04月26日

为什么第四步那里 mk 格式不生效?bug 吗?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册