freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

安卓逆向-NDK与JNI详述与实战
FreeBuf_362808 2021-08-24 17:34:40 80957

前言

在Android OS上开发应用程序,有提供了两种开发包:SDK和NDK。
我们知道Android的SDK主要是基于Java的,所以在用Android SDK进行开发必须使用Java语言。不过,Android 平台一开始就支持C/C++了,Google说明Android也支持JNI编程,使用第三方应用(比如:NDK)通过JNI调用自己的C动态库。

一、什么是NDK,什么是JNI

1、什么是NDK

NDK全名:Native Develop Kit。

Android NDK 是一个工具集,可让您使用 C 和 C++ 等语言以原生代码实现应用的各个部分。对于特定类型的应用,这可以帮助您重复使用以这些语言编写的代码库。

简单来说,Android NDK 是一个工具集,可以让你通过 C 和 C++ 来实现Android中部分应用程序的功能,不用java开发也可以。

Android 开发语言是Java,Android是基于Linux的,其核心库很多都是C/C++,NDK的作用,就是一个在Java中调用C/C++的方式。NDK本身其实就是一个交叉工作链,包含了Android上的一些库文件,然后,NDK为了方便使用,提供了一些脚本,使得更容易的编译C/C++代码。一般情况,是用NDK工具把C/C++编译为.so文件,然后在Java中调用。

官网
https://developer.android.google.cn/ndk/index.html

image-20210816010314803

2、什么是JNI

JNI全称为Java Native Interface。

JNI是Java的本地接口。Java本支持调用C/C++,即JNI。JNI就是Java调用C++的规范。通过JNI可以使得Java与C/C++进行交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。

二、环境安装

在使用ndk之前需要先安装好环境

1、jdk下载地址(安装过程略过)

此处建议安装jdk1.8

https://www.androiddevtools.cn/

image-20210812184812321

2、NDK安装

下载地址,并配置好JAVA_HOME,PATH的环境变量

https://www.androiddevtools.cn/

image-20210816012551623

我们这里是安装的r10 STL debug info版本,下载后解压到C盘

image-20210816012708970

配置环境变量,将ndk的绝对目录配置的path中

image-20210816012841470

cmd窗口,输入

ndk-build

image-20210816013400859

注意:这里有个报错,是因为少配置文件、所在的目录等问题

3、adb安装

下载

https://www.androiddevtools.cn/

image-20210812185426578

解压,配置环境变量

image-20210812185545188

验证adb安装,cmd直接执行adb

image-20210812185627813

三、JNI接口及用法详解

image-20210816104925257

从上图中可知,JNI接口是处在java层和C/C++层之间,承担桥梁作用

1、JNI详解

前面我们也说了JNI的全称是Java Native Interface: Java本地开发接口,它提供了若干的API实现了Java和其他语言的通信(主要是C和C++),目的就是Java可以调用C或C++开发的函数,C或C++也能调用Java的方法。

特点:

其一就是效率,C/C++是本地语言,比java更高效;

其二就是可以复用已经存在的C/C++代码;

其三是Java反编译比C语言容易,一般加密算法都是用C语言编写,不容易被反编译。

如图JNI接口源码:

image-20210816105116287

JNI接口就是一大堆函数的API,纽带、桥梁的作用

2、JNI接口文件分析

刚开始,#include <**>,典型的C语言中代码

image-20210816162455380

之后我们看到很对typedef,typedef是C语言代码,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE

typedef unsigned char BYTE;

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char的缩写,例如:

BYTE  b1, b2;

详细请参考

https://www.runoob.com/cprogramming/c-typedef.html

image-20210816105539879

typedef uint8_t         jboolean;

就是java的boolean数据类型进行重命名为uint8_t。(前面价格j代表为java语言)

3、NI接口讲解

接口中的类型

1.调用java层普通方法-Call0bjectMethod
2.获取Java层实例字段的值-GetMethodID
3.获取java层实列字段的值-GetObjectField
4.设置java层实例字段的值-SetObjectField
......

1、调用java层普通方法-CallObjectMethod

image-20210816113111680

jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);

**jobject:**返回值是object,若是void是表示返回值为空。

**CallObjectMethod:**方法名,并且后面跟着三个参数,要想使用方法需要构建这三个参数。

**JNIEnv:**动态调试经常用到,本地调用的一个接口,提供了大量的jni接口函数去调用,就理解JNI默认传入的即可!默认参数型。
**jobject:**也是默认参数,那么CallObjectMethod最少有两个默认参数。
**jmethodID:**CallObjectMethod运行操作,还需要一个方法ID,这里是java层方法的ID,这个如何获取呢? 可以使用GetMethodID方法。

image-20210816114326548

想要使用jmethodID参数,先要使用GetMethodID方法,然后把这个方法ID作为一个返回值给jmethodID参数。

...:调用方法参数列表细信息。

2、获取Java层实列字段的值-GetMethodID

image-20210816120057305

jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

**jmethodID:**方法的返回值

GetMethodID:方法名称,后面有四个参数

*JNIEnv:** 默认参数

jclass:jclass参数需要调用findclass()方法,获得findclass()方法的返回值,将返回值传入jclass参数使用。

image-20210816142527200

*const char:**java层方法的名称

*const char:** java层方法的一个签名,就是返回值+参数!

3、获取java层实列字段的值-GetObjectField

image-20210816142848042

jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);

**jobject:**返回值是object。

**GetObjectField:****方法名,后面跟着三个参数。

**JNIEnv:**动态调试经常用到,本地调用的一个接口,提供了大量的jni接口函数去调用,就理解JNI默认传入的即可!默认参数型。
**jobject:**也是默认参数,那么CallObjectMethod最少有两个默认参数。

**jfieldID:**需要GetFieldID()方法返回值获取jfieldID参数

jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);

调用GetFieldID方法需要四个参数

*JNIEnv:**动态调试经常用到,本地调用的一个接口,提供了大量的jni接口函数去调用,就理解JNI默认传入的即可!默认参数型。

**jclass:**jclass参数需要调用findclass()方法,获得findclass()方法的返回值,将返回值传入jclass参数使用

*const char:**是实列字段的名称

*const char:**是实例字段的签名信息

4、设置java层实例字段的值-SetObjectField

image-20210816143538377

void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);

**void:**返回值为void,代表设置好参数就可以,不需要返回

**JNIEnv:**动态调试经常用到,本地调用的一个接口,提供了大量的jni接口函数去调用,就理解JNI默认传入的即可!默认参数型。
**jobject:**也是默认参数,那么CallObjectMethod最少有两个默认参数。

**jfieldID:**都是GetFieldID的返回值

**jobject:**是一个java层实列字段设置的值

根据上面对接口方法的分析,方法大概分为三类

get:获取
set:配置
call:调用

四、通过编译so文件

1、编写C代码

使用记事本编写C语言实现输出Hello word

#include<stdio.h>
int main(){
printf("Hello word!")
return 0;
}

image-20210815200303889

将文件后缀名修改为.c

2、编写Android.mk文件

1)什么是Android.mk文件

Android.mk 文件位于项目 jni/ 目录的子目录中,用于向构建系统描述源文件和共享库。 它实际上是构建系统解析一次或多次的微小 GNU makefile 片段。 Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目范围设置。 它还可替换特定模块的项目范围设置。
Android.mk 的语法用于将源文件分组为模块。 模块是静态库、共享库或独立可执行文件。 可在每个 Android.mk 文件中定义一个或多个模块,也可在多个模块中使用同一个源文件。 构建系统只会将共享库放入应用软件包。 此外,静态库可生成共享库。

除了封装库之外,构建系统还可为您处理各种其他详细信息。例如,您无需在 Android.mk 文件中列出标头文件或生成的文件之间的显式依赖关系。 NDK 构建系统会自动为您计算这些关系。

2)Android.mk内容详述

LOCAL_PATH := $(call my-dir)	#获取jni文件路径
include $(CLEAR_VARS)
LOCAL_MODULE := test	#模块名称
LOCAL_SRC_FILES := test.c	#源文件 .c或者.cpp
LOCAL_ARM_MODE := arm	#编译后的指令集ARM指令
LOCAL_LDLIBS += -llog	#依赖库
LOCAL_CFLAGS += -pie -fPIE	#引入PIE
LOCAL_LDFLAGS += -pie -fPIE	#引入PIE
include $(BUILD_SHARED_LIBRARY)	#指定编译文件的类型.so
1)LOCAL_PATH :=(call my-dir):获取jni文件路径,每个mk必须以local开始,my-dir是由Build System提供的,他会返回一个包含mk文件的路径,就是要获取相应的文件目录路径去调用jni属性

2)include $(CLEAR_VARS):CLEAR_VARS变量也是由Build System提供,他会指定清理LOCAL_开头的文件,但不会清理LOCAL_PATH开头的!

3) LOCAL_ARM_MODE :=arm:编译后的指令集,arm每个指令由有四个字节。

4)LOCAL_MODULE :=test : 定义的模块名称,如果这里编译出so文件前面+lib后面+.so

5)LOCAL_SRC_FILES :=test.c : 表示同文件目录下.c文件

6) LOCAL_CFLAGS += -pie -fPIE;LOCAL_LDFLAGS += -pie -fPIE :PIE这个安全机制从4.1引入的,但是Android L之前的系统版本并不会去检验可执行文件是否基于PIE编译出的。因此不会报错,但是android L开始开启验证机制,如果调用可执行文件不是基于PIE方式编译的,则无法运行

6)include $(BUILD_SHARED_LIBRARY):把文件构造建成可执行程序,如果是动态链接库就用:include (shared library),如果是静态链接库:include $(static library)

3、编写Application.mk文件代码

1)什么是Application.mk文件

Application.mk文件,这是android NDK构建系统使用的一个可选构建文件。它的目的是描述应用程序需要哪些模块,也定义了所有模块的一些通用变量。

2)Application.mk内容详述

APP_ABI :=X86 armeabi-v7a

这里只有一行代码,这里是关于程序是否可以在不同框架的CPU上运行,目前主流的Android设备是armeabi-v7a架构的,然后就是x86和armeabi了。如果同时包含了 armeabi,armeabi-v7a和x86,armeabi-v7a是可以兼容armeabi的,所有设备都可以运行。

4、ndk编译c文件为so文件

1)编译方式

**第一种方式:**将三个文件放在$PROJECT/jni/目录下, 其中$PROJECT表示你的工程目录,这样就可以被ndk-build脚本文件找到.(注:在这种方式下,进入jni目录,即$PROJECT/jni/,然后执行ndk-build,就可以直接编译jni生成.so文件了)。

第二种方式:另将三个文件放在$NDK/app//目录,其中$NDK为NDK的安装目录,为你的应用程序名.在这种方式下,进入$NDK安装目录,然后输入make APP=,即可编译你的JNI代码.此种方法是ndk-r4之前的方法,虽然出于兼容的原因目录还支持,但是不建议使用此种方法,因为第一种方法简单,且方便。

2)编译测试

首先创建一个名字为jni的文件夹,分别编辑test.c、Application.mk、Android.mk

image-20210816094749675

在jni执行ndk-build,直接编译jni生成.so文件了

ndk-build

image-20210815213444258

生成两个文件夹:libs、obj

image-20210815213659565

libs文件加里面都是ELF文件,是可以直接在安卓执行的

3)libs库中的armeabi-v7a,armeabi和x86

armeabi-v7a,armeabi和x86都表示的是CPU类型,早期的Android系统几乎只支持ARMv5的CPU架构,但是现在已经很多种了。ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起)等,每一种都关联着一个相应的ABI(应用程序二进制接口(ApplicationBinary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库)。Android现在的主流CPU是armeabi-v7a。armeabi-v7a是针对有浮点运算或高级扩展功能的ARMv7CPU。

image-20210815213855355

image-20210815213902660

5、运行so文件

使用adb将armeabi-v7a目录下面的test文件上传到安卓模拟器或者真机(root过后的)

adb devices	#检查设备列表
adb push C:\test\libs\armeabi-v7a\test /data/local/tmp/	#将test文件上传安卓的tmp目录下
adb shell	#获得安卓的shell
su	#切换为root权限
cd /data/local/tmp/	#进入/data/local/tmp/	目录
ls -al	#查看目录下的详细

image-20210816003720055

赋777权限,并执行,测试是否输出hello word

chmod 777 test
./test

image-20210816004931797

五、总结

此章节使用的工具如下:

JDK:需使用jDK1.8B版本的,JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

adb:全称为Android Debug Bridge,就是起到调试桥的作用。通过adb我们可以在Eclipse中方便通过DDMS来调试Android程序,说白了就是debug工具。

ndk:是Android的一种开发工具包,快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互。

我们通过此章节熟悉了,ndk工具功能及使用方法,了解了jni的概念,通过实践将jni通过ndk编译为so文件,详细讲解了编译的方法及编译过程中使用的到配置文件详述,为日后进一步分析和调试安卓apk建立概念。如有不足之处请多见谅,谢谢。

# android安全 # c语言 # java # SDK # NDK
本文为 FreeBuf_362808 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
移动攻防之路
FreeBuf_362808 LV.4
这家伙太懒了,还未填写个人描述!
  • 35 文章数
  • 63 关注者
web安全基础篇-点击劫持(Click Jacking)
2022-05-27
WEB安全基础篇-跨站脚本攻击(XSS)
2022-05-25
web安全基础篇-跨站点请求伪造(CSRF)
2022-05-25
文章目录