人生倒计时
- 今日已经过去小时
- 这周已经过去天
- 本月已经过去天
- 今年已经过去个月
常春藤实验室|文字
入坑真是让人上瘾。我之前在 AS3.0 下学习过如何编写入门模块和 Frida 入门课程。我原本想添加更多高级课程。可惜时间有限,只能先挖坑,再补。这段时间,我也在补充开发的相关知识,辅助移动安全学习。随着学习的进展,发现现在的apk有点安全开发意识,会用NDK。开发方面,赵大四的《应用安全替换与逆向分析》甚至在第二章用了很大篇幅介绍了NDK开发的相关内容,并在章末暗示“学NDK开发就是为了学”后面几章的内容基本”,所以只能开始到处找资料尝试学习NDK开发了。
不过赵的书是写IDE相关的NDK入门教程。网上很多资料也是基于AS2.2及以下的教程,但AS3.0及以上版本由于IDE版本的演进,NDK相关项目的开发简化了很多。之前的教程好像已经过时了,所以今天写一篇适合AS3.0及以上的NDK入门教程,应该也是记录新版本NDK开发的过程
基础知识
这里需要补充一些入门的基础知识,不然你做的时间长了都不知道自己在做什么。这里可以直接参考CSDN上童鞋整理的一篇教程,非常全面。
什么是 NDK?
定义:Kit,是一个工具开发包。
NDK是属于 Android 的,与Java并无直接关系
功能:快速开发C和C++的动态库,并自动将so和应用程序一起打包成APK,让您可以在NDK中使用JNI与代码(如C、C++)进行交互。
应用场景:在.的场景中使用JNI。
即 Android开发的功能需要本地代码(C/C++)实现
上面提到了一个叫做JNI的概念。如果你入过移动安全的坑,肯定对这个词很熟悉,那么它是什么意思呢?
什么是 JNI?
定义:Java,Java 原生接口。
作用:允许Java与其他原生语言(如C、C++)交互。
即在 Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码
特别注意:
JNI 是 Java 调用语言的一个特性。 JNI属于Java,与Java没有直接关系
JNI的含义
背景:在实践中,Java 需要与原生代码交互
问题:由于Java是跨平台的,Java与原生代码交互的能力很弱
解决方案:使用 JNI 功能增强 Java 与本机代码交互的能力
JNI 实现步骤
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调用本地代码
NDK 实施步骤
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 调用本地代码
NDK和JNI的关系
JNI就是我们需要在JAVA中实现方法,最终实现Java调用原生代码的目的。
NDK是我们用来实现JNI的工具包,帮助我们编译相关文件,是手段。
好的,简单总结一下:
Java作为跨平台语言,兼容性强,但与C、C++等其他本地语言的交互性较弱。
为了实现Java与其他本地语言的交互,JNI应运而生,实现了在Java中使用C/C++代码,在C/C++中使用Java。
为了实现JNI,引入了NDK,帮助开发者在自己的应用中实现Java源代码与其他原生语言的交互。
那么我们为什么要学习 NDK?
因为很多应用程序通过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相关内容。我稍后会介绍它。目前的状态是一个apk可以正常编译并在手机上运行。
基本的准备工作到此完成,是时候开始了。
使用 NDK 编写您的第一个应用程序
首先确认我们的目标
目标:在AS 3.0版本中使用NDK编写一个在屏幕上输出指定字符串的apk
嗯,一点也不……
不过没关系。大家可以参考一些教程互相交流。看了很多教程,终于弄明白了相关的套路,所以我们正式开始吧~~~
第一步:新建项目
记得上下勾选“C++”,然后一路Next,但是第二步卡住了。原因是AS需要去外网下载一些libs来支持我们的C++,所以AS需要翻越长城,天梯自成一体。准备好了。
如果你的梯子不错,应该很快就好了。
继续下一步直到下一步,这是加载 C++ 后的附加内容之一。
选择C++版本,默认是“”,下面什么都不需要勾选,然后是“”,如果你对这些配置项感兴趣,我这里也简单补充一下。
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++”能帮我们搞定NDK吗?
第二步:安装 NDK 及其插件
当然你不能没有 NDK,你在创建一个新项目的那一刻就知道了。
左下角直接报错“NDK not”。好吧,它不起作用。安装NDK的方式其实并不难。可以通过AS自带的SDK安装。
在右上角或“文件”->“”->“&”->“”->“SDK”打开。
在 SDK Tools 中勾选三项:
CMake LLDB NDK
其中需要NDK。前两个在一些辅助编译调试的教程中提到过,这里也补充一下。
当然你还是需要一个梯子,不然你打开SDK Tools会发现没有这个东西可以选择,而且左下角还在加载信息:
如果网速足够快,通常需要几分钟。如果是第一次安装,多试几次就OK了。前提是你的梯子很结实。安装完成后,SDK Tools自然会显示“”会显示出来。
好的,回到项目,在右上角同步,就完全OK了。
好,我们来简单的看一下目前的文件情况:
视图可能看起来有点粗略,让我们切换回视图:
可以看出,主文件夹中除了java文件夹外,还多了一个cpp文件夹。里面的文件是-lib.cpp。让我们来看看里面有什么。先来看看我们熟悉的.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(); }
与常规项目的.java相比,静态方法块加载和()调用更多。
再看-lib.cpp文件jni 调用java方法,后缀名表示这是一个典型的C++类:
#include#include 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和两个头文件。
这里和都是JNI关键字,表示这个函数要被JNI调用。
然后定义一个方法返回字符串“Hello from C++”,这个方法是上面.java调用的最后一个方法!
说实话,如果你只是想看jni的java实现,可以编译运行项目,使用NDK编译apk。此时屏幕上显示“Hello from C++”。
当然,我们是想自己实现JNI的人。自然,我们不能止步于此。项目中有.java和-lib.cpp来实现JNI。我们也可以编写一对java和c/c++文件来实现类似的实现。效果。
Step3:自己实现JNI
首先我们右键点击cpp文件夹,新建一个“C/C++文件”。
命名为“”,记得检查后缀名是.c而不是.cpp,否则后面会报错
这个c文件主要实现返回自定义字符串,比如返回“Hello NDK From C”。
#includeJNIEXPORT jstring JNICALL Java_com_example_xfn_myndktest_HelloNDK_sayhello(JNIEnv *env, jclass jclass) { return (*env)->NewStringUTF(env,"Hello NDK From C"); }
我们自定义方法的名字是(),全名以包名为前缀。
源码和-lib.cpp很相似,但是如果保存为.cpp格式,会在*env处报错,这自然是C和C++的区别造成的。
好了,有了C文件,我们就可以写对应的java文件来调用了。我们在java文件夹中新建一个java类,命名为“”,内容如下:
package com.example.xfn.myndktest; public class HelloNDK { static { System.loadLibrary("HelloNDK"); } public native String sayhello(); }
静态块声明加载,然后调用方法(),自然就是我们上面c中定义的()方法。
当然我们的目标是在主屏幕上输入我们的字符串“Hello NDK From C”,所以我们还需要修改.java和.xml。
首先在.xml中注册一个来显示我们的字符串。
然后回到.java,调用方法中的方法,通过新类的()方法,使用方法显示其返回值作为方法的参数。
TextView tv2 = (TextView)findViewById(R.id.tv_hellondk); tv2.setText(new HelloNDK().sayhello());
当然,这并没有结束。如果你看过相关的教程,编译头文件和修改配置的操作还是很多的。当然,上面的AS3.0不需要这么麻烦,我们离成功还有很长的路要走。
Step4:修改.txt
文件在app目录下
.txt 实际上源自 CMake 工具。 CMake 是一个跨平台的编译工具,比 make 更先进,使用也更方便。 CMake主要编写.txt文件,然后使用cmake命令将.txt文件转换成make需要的文件,最后使用make命令编译源码生成可执行程序或共享库(so())。
在此处添加以下内容:
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 )
主要目的是在上面加上.c的信息,让编译器知道我们自定义了一个叫 的库,这样就可以通过JNI调用java源代码了。
好的,如果你看到这个,我很高兴的告诉你,你马上就能看到结果了jni 调用java方法,哈哈~
Step5:编译并运行第一个 NDK 应用程序
我们下一步其实是直接编译运行,但是如果你一路跟着我,你会发现,不,源代码有错误
Can't parse() 方法...这个其实不影响,AS作为一系列编译器,java中的源代码在apk没有运行的时候不能和C文件中的方法动态交互,我们编译直接运行就可以了。
很不错,系统的Hello from C++输出在屏幕中央,我们定义的“Hello NDK From c”在屏幕左上角。这实际上是由 .xml 控制的。别担心,我们可以说我们的目标已经实现了!恭喜~
NDK编译区别
如果你只是想编译一个NDK应用程序,那么上面其实已经完成了。下面我简单介绍一下NDK编译出来的apk和普通apk的区别。
我们在AS build中生成我们项目的apk,然后进入相关目录。我们知道apk本质上是一个zip,我们把后缀改成zip,然后解压到文件夹中。
和普通的apk没什么区别,我们进入lib目录。
里面有4个文件夹,分别对应不同的CPU系统架构。我们的手机是arm-v7架构,进入-v7a文件夹即可。
如您所见,此时我们cpp文件夹中的两个文件就是这个文件夹中的so文件的形式,那么我们的.c文件也是在这里编译生成的吗?
我们用IDA打开直接拖进去,注意选择类型为arm。
本次不关注其他内容,请自行调整:
您可以清楚地看到我们在.c中定义的字符串“Hello NDK From C”。
看来so文件是编译时生成的c文件。
总结
AS3.0:
中NDK编程的一般步骤小结
1. 创建C++ support项目; 2. 配置NDK环境; 3. 创建Java文件,在该类中调用native方法; 4. 创建c/cpp文件并实现头文件里面的方法; 5. Java文件里面加入静态方法块; 6. 配置CmakeLists.txt文件;
当然,我的一些步骤可能会被简化,一些不必要的步骤在我的教程中没有提到,也有一些其他教程中提到的步骤,比如修改构建。我这里就不介绍了,有兴趣的可以自行了解。
本文只是介绍NDK入门相关知识。如果你学过 NDK 编程,如果你想练手,还是可以找 OWASP 的 -.apk 和 -.apk 来配合 Frida 工作,或者找几个基于 NDK 开发的 apk 进行实战。为了加深对相关知识的理解,本教程就先到这里吧~~
参考链接