Android应用学习——AS3.0 NDK入门编程

android studio 下载 | 2018-12-05 12:48

Android的入坑真的是令人沉迷其中啊,之前学习了AS3.0下如何编写入门级Xposed模块和Frida入门系列课程,原本也想着多补充一些进阶系列的课程,无奈时间有限,只能先挖坑以后再填上,这段时间也在恶补android开发的相关知识辅助移动安全学习,随着学习的渐进,发现目前的apk稍微有点安全开发意识的,都会使用NDK进行开发,赵四大佬的《Android应用安全放回和逆向分析》更是在第二章花了大量篇幅介绍NDK开发的相关内容,也在章节末尾暗示了“ 学会NDK开发是学习后面章节内容的基础”,所以只能开始到处找资料尝试学习一下NDK开发啦。

不过赵四大佬的书中是以Eclipse为IDE编写相关NDK入门教程的,网上很多资料也是基于AS 2.2及以下版本进行的教程,然而AS 3.0以上版本NDK的相关项目开发由于IDE版本的进化简化了很多,以往的教程似乎已经有些过时了,所以今天专门写一篇适合AS3.0以上版本的NDK入门教程,也当是记录一下新版本NDK开发的过程

这里需要补充一些入门的基础知识,否则弄半天都不知道自己在干什么,这里直接参考CSDN上Carson_Ho童鞋整理的一篇教程,写的很全面。

定义:Native Development Kit,是 Android 的一个工具开发包。

NDK是属于 Android 的,与Java并无直接关系作用:快速开发C、C++的动态库,并自动将so和应用一起打包成 APK,即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互。

应用场景:在 Android 的场景下使用 JNI。

即 Android开发的功能需要本地代码(C/C++)实现

上面提到了一个概念叫 JNI,如果你入了移动安全的坑,对这个词一定也不会陌生,那么是什么意思呢?

定义:Java Native Interface,即 Java本地接口 。

作用: 使得 Java 与本地其他类型语言(如C、C++)交互。

即在 Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码特别注意:

JNI 是 Java 调用 Native 语言的一种特性

JNI 是属于 Java 的,与 Android 无直接关系

背景:实际使用中,Java 需要与本地代码进行交互

问题:因为 Java 具备跨平台的特点,所以 Java 与本地代码交互的能力非常弱

解决方案: 采用 JNI 特性增强 Java 与本地代码交互的能力

1. 在Java中声明Native方法(即需要调用的本地方法) 2. 编译上述 Java源文件javac(得到 .class文件) 3. 通过 javah 命令导出JNI的头文件(.h文件) 4. 使用 Java需要交互的本地代码 实现在 Java中声明的Native方法   如 Java 需要与 C++ 交互,那么就用C++实现 Java的Native方法 5. 编译.so库文件 6. 通过Java命令执行 Java程序,最终实现Java调用本地代码

1. 配置 Android NDK环境 2. 创建 Android 项目,并与 NDK进行关联 3. 在 Android 项目中声明所需要调用的 Native方法 4. 使用 Android需要交互的本地代码 实现在Android中声明的Native方法   比如 Android 需要与 C++ 交互,那么就用C++ 实现 Java的Native方法 5. 通过 ndk - bulid 命令编译产生.so库文件 6. 编译 Android Studio 工程,从而实现 Android 调用本地代码

JNI 是我们需要在 JAVA 中实现 Native 方法,最终实现Java调用本地代码,是目的。

NDK 是我们为了在 Android 中实现 JNI 而使用的工具包,帮助我们编译相关文件,是手段。

好了,简单总结一下:

Java 作为跨平台语言,虽然兼容性方面非常强但是与本地其它语言C、C++等交互能力较弱。

为了实现Java和本地其它语言交互的需求,JNI应运而生,实现了Java中使用C/C++代码,也实现了C/C++中使用Java。

为了在 Android 也能实现 JNI,Android 推出了 NDK,帮助开发者实现在 Android 应用中让 Java 源码能和其它本地语言进行交互。

那么为什么我们要去学习NDK呢?

因为很多 Android 应用为了防止部分核心代码被反编译,将一些核心方法通过 JNI 方式调用 C/C++ 实现,并不在 Java 源码中可见,所以作为一名安全测试人员,掌握 NDK 的相关知识能为我们反编译相关难度稍大的 apk 奠定良好基础。

说了这么多,也该开始实战环节了~~

操作系统:Windows 10 Android Studio版本:3.1.2 测试机:Google Nexus 5X(已root) 测试机版本:Android 4.4.4 Java版本:1.8.0_60初始状态下我这里并没有安装NDK相关内容,后文会介绍,目前的状态就是可以正常编译一个 HelloWorld 的 apk 并在手机上运行。

到这里基本准备工作就完成了,是时候动手了。

首先确认一下我们的目标

目标:在AS 3.0版本中使用NDK编写一个在屏幕上输出指定字符串的apk嗯,完全不会啊......

但是没有关系,我们可以参考几篇教程,互通有无,在 Google 了很多教程以后,总算摸清了相关的套路,那就正式开始吧~~~

Step1: 新建项目

记得勾选上下方的"Include C++ support",然后一路Next,结果第二步就卡住了,原因就是AS需要去外网下载一些 lib 去支援咱们的 C++ support,所以 AS 需要翻越长城,梯子自备。

如果你的梯子不错,应该很快就会好了。

继续Next,直到下面一步,这里就是你的C++ support加载后多出来的内容之一。

选择 C++ 的版本,默认的"Toolchain Default"即可,下面的啥也不用勾选,然后 "Finish",如果你对这些配置项感兴趣我这里也简单补充一下。

C++ Standard : 希望使用哪种C++标准,一般情况下,选择默认即可。 Exceptions Support(-fexceptions): 是否启用对 C++ 异常处理的支持,如果启用,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。 Runtime Type Infomation Support (-frtti): 如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。好了,就慢慢等待项目创建吧。

如果你是一个细心的小伙子,你可能会发现有点不太对...

我们的教程是开发NDK应用,我们也没有安装NDK,这样创建项目可以嘛,难道"C++ support"能帮我们搞定NDK?

Step2: 安装NDK及其插件

没有NDK当然是不行的,当你新建完项目的那一刻你就会知道。

左下角直接报错"NDK not configured",好吧,果然不行,安装NDK的方式其实不难,通过AS自带的SDK Manager安装即可。

右上角打开或者"File" -> "Setting" -> "Appearance&Behavior" -> "System Settings" -> "Android SDK"打开都可以。

在SDK Tools里勾选三项:

CMake LLDB NDK其中 NDK 是必须的,前两个在一些教程中提到了辅助编译调试用的,这里也加上。

当然还是要有梯子,否则你打开SDK Tools时会发现,根本没有上面这些供你选择,左下角还在不断加载信息:

网速够快的情况,一般几分钟搞定,如果第一次安装有没有安装上的,多试几次就OK了,前提是你的梯子给力,安装完成以后,SDK Tools里自然就会显示"Installed"。

好了回到项目中,右上sync一下,完全OK。

好了我们简单看一下现在的文件情况:

Android视图看着可能有点简略,我们还是切换回Project视图:

可以看出 main 文件夹下除了 java 的文件夹还多出了 cpp 文件夹,里面的文件为 native-lib.cpp,我们看一下里面大致是什么,先看一下我们熟悉的 MainActivity.java 的源码:

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(); }和常规项目的 MainActivity.java 相比多出了静态方法块的加载和 native 方法 stringFromJNI() 的调用。

再看下 native-lib.cpp 文件,后缀名表明了这是一个典型的C++类:

#include <jni.h> #include <string.h> extern "C" JNIEXPORT jstring JNICALL Java_com_example_xfn_myndktest_MainActivity_stringFromJNI(        JNIEnv *env,        jobject /* this */) {    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str()); }C++的知识已经尘封多年,不过大致含义也能看懂,开头引入了 jni 和 string 两个头文件。

这里 JNIEXPORT 和 JNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的。

然后定义了 stringFromJNI 方法,返回字符串"Hello from C++",这个方法就是上面 MainActivity.java 最后调用的方法啦!

说实话,如果你只是单纯想看下 java 实现 jni,此时就可以编译运行项目使用 NDK 编译得到 apk 了,这个时候就是屏幕上显示"Hello from C++"。

当然,我们是要自己实现JNI的人,自然不能在这里止步了,项目里有 MainActivity.java 和 native-lib.cpp 配合实现 JNI,我们也可以自己写一对 java 和 c/c++ 文件实现类似效果。

Step3: 自己实现JNI

首先我们在 cpp 文件夹上右键,新建"C/C++ Source File"。

起名叫"HelloNDK",切记勾选后缀名为 .c 而非 .cpp,否则后面会报错

这个 c 文件主要也是实现返回一个自定义的字符串,比如返回"Hello NDK From C"。

#include <jni.h> JNIEXPORT jstring JNICALL Java_com_example_xfn_myndktest_HelloNDK_sayhello(JNIEnv *env, jclass jclass) {    return (*env)->NewStringUTF(env,"Hello NDK From C"); }我们自定义的方法名称为 sayhello(),完整名称前面加上包名。

源码和 native-lib.cpp 很像,但是如果你保存成 .cpp 格式就会在 *env 处报错,原因自然是 C 和 C++ 的差异导致的。

好了,有了 C 文件,我们就可以写出对应的 java 文件来调用它了,我们在 java 文件夹新建一个 java 类,命名也为“HelloNDK”,内容如下:

package com.example.xfn.myndktest; public class HelloNDK {    static {        System.loadLibrary("HelloNDK");    }    public native String sayhello(); }静态块声明对 HelloNDK 进行加载,然后调用 native 方法 sayhello(),该方法自然就是我们在上面的c中定义的 Java_com_example_xfn_myndktest_HelloNDK_sayhello() 方法。

当然我们的目标是在主屏幕输入我们的字符串"Hello NDK From C",所以还需要修改一下 MainActivity.java 和 activity_main.xml。

首先在 activity_main.xml 中注册一个 TextView,用于显示我们的字符串。

<TextView        android:id="@+id/tv_hellondk"        android:layout_width="match_parent"        android:layout_height="wrap_content" />

然后回到 MainActivity.java 中在 onCreate 方法中调用该 TextView 并使用 setText 方法,通过 new HelloNDK 类的 sayhello() 方法,将其返回值作为 setText 方法的参数进行展示。

TextView tv2 = (TextView)findViewById(R.id.tv_hellondk); tv2.setText(new HelloNDK().sayhello());

当然,并不是到这里就结束了,如果你看过 eclipse 的相关教程,后面还有一大堆编译头文件、修改配置的操作,当然 AS 3.0 以上不用这么麻烦,我们离成功还差一点点距离。

Step4: 修改CMakeLists.txt

该文件就在app目录下

CMakeLists.txt 其实源于 CMake 工具,CMake 是一种跨平台编译工具,比 make 更为高级,使用起来要方便得多。CMake 主要是编写 CMakeLists.txt 文件,然后用 cmake 命令将 CMakeLists.txt 文件转化为 make 所需要的 makefile 文件,最后用 make 命令编译源码生成可执行程序或共享库(so(shared object))。

这里添加如下内容:

add_library( # Sets the name of the library.             HelloNDK             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).             src/main/cpp/HelloNDK.c )主要是添加一下我们上面的 HelloNDK.c 的信息,让编译器知道我们自定义了一个库叫 HelloNDK,这样java源码中才能通过JNI来调用。

好了,如果你看到这里,我很开心告诉你,马上就能看到成果了,哈哈~

Step5: 编译运行第一个NDK应用

我们下一步其实就是直接编译运行,但是如果你是跟着我一路做下来的你就会发现,不对啊,源码有报错

不能解析 sayhello() 方法啊...这个其实不影响的,AS 作为 Android 系列编译器在 apk 未运行情况下 java 里的源码是不能动态和 C 文件中的方法进行交互的,我们直接编译运行即可。

非常nice,系统的 Hello from C++ 输出在屏幕中央,我们定义的"Hello NDK From c"在屏幕左上角,这个其实受 activity_main.xml 控制,不用在意这个,可以说我们的目标已经完成了!恭喜啦~

如果你只是想编译一个NDK的应用,那么到上面其实就完成了,下面我将简单介绍一下:NDK编译的apk和普通apk的区别。

我们在 AS 的 build 中生成一下我们项目的 apk,然后进入相关目录,我们知道 apk 本质上是个 zip,我们把后缀名改成 zip,然后解压到文件夹中。

和常规 apk 并无区别嘛,此时我们进一下 lib 目录。

里面有 4 个文件夹,分别对应不同 CPU 系统架构,我们的手机是 arm-v7 的架构,就进一下 armeabi-v7a 这个文件夹好了。

可以看到,我们 CPP 文件夹下的两个文件此时在该文件夹里是 so 文件的形式,那么这里面是我们的 HelloNDK.c 文件编译生成的吗?

我们使用 IDA 打开看下,直接把 so 拖进去即可,注意选择一下 processor type 为 arm。

别的内容我们这次不关注,调一下Strings Window:

可以清晰看到我们在 HelloNDK.c 中定义的字符串"Hello NDK From C"。

看来该 so 文件是 c 文件在编译时生成无疑了。

简单总结一下 AS 3.0 中 NDK 编程的大致步骤:

1. 创建C++ support项目; 2. 配置NDK环境; 3. 创建Java文件,在该类中调用native方法; 4. 创建c/cpp文件并实现头文件里面的方法; 5. Java文件里面加入静态方法块; 6. 配置CmakeLists.txt文件;当然我的步骤可能有些环节进行了简化,有些非必要步骤我的教程里面没有提及,也有一些步骤比如修改 build.gradle 等其它教程中提及的步骤在这里我没有介绍,如有兴趣可以自行了解。

本篇只是 NDK 入门相关知识的介绍,学会了 NDK 编程如果你想练手的话还是可以找 OWASP 的 UnCrackable-Level2.apk 和 UnCrackable-Level3.apk 配合 Frida 一起,或者找几款基于 NDK 开发的 apk 进行实战,加深相关知识的理解,本篇教程就先到这里了~~