热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【技术分享】如何从命令行调用AndroidJNI函数并传递Java对象参数

翻译:興趣使然的小胃预估稿费:200RMB投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿一、前言当我们对某个使用原生库(native library)的恶意软件或者应用进行分析或

http://p9.qhimg.com/t015ec9331e64246128.jpg

翻译:興趣使然的小胃

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


一、前言

当我们对某个使用原生库(native library)的恶意软件或者应用进行分析或渗透测试时,如果能够对库函数进行隔离和执行是再好不过的事情,这样做我们就可以使用其自身的代码来调试对抗恶意软件。举个例子,如果恶意软件包含加密字符串,并使用原生函数完成解密过程,你可以选择花大量时间逆向分析算法来编写自己的解密函数,也可以选择直接利用这个函数来处理任意输入数据。如果使用后一种方法,即使恶意软件作者完全改变了软件的加密算法,你也可能不需要做任何修改即可完成任务。在这篇文章中,我将向读者介绍如何利用并执行原生库函数,即使调用这些函数时需要传入JVM实例作为参数也没问题。

在之前的一篇文章中,我介绍了如何从Android原生代码中创建一个Java虚拟机,但我没有给出一个具体的例子。因此,我会在本文中给出一个具体的例子来说明这一点。

我们至少可以使用两种方法来调用原生函数。第一种方法是对应用进行修改,使应用接受你的输入数据并传递给原生函数。例如,你可以写一个intent filter,将其转化为Smali语言,将代码添加到目标应用中,修改manifest文件,运行应用,使用adb命令将带有参数的intent发送给目标应用即可。另一种方法更好,你可以添加一个小型socket或web服务器,使用curl向其发送请求,这种方法不需要修改manifest文件。

第二种方法的目标是创建一个通过命令行运行的小型原生可执行工具,用来加载库文件、调用目标函数、传递我们输入的任意参数。这样我们就可以单独运行一个可执行文件,而不需要运行整个应用程序,因此调试起来也就更为方便。


二、目标应用

我创建了一个示例应用,方便读者按照教程学习,应用名为“native-harness-target”。你可以使用以下命令将工程文件复制到本地并完成编译(记得修改其中的“$ANDROID_*”变量)。

git clone https://github.com/CalebFenton/native-harness-target.git
cd native-harness-target
echo 'ndk.dir=$ANDROID_NDK' > local.properties
echo 'sdk.dir=$ANDROID_SDK' >> local.properties
./gradlew build

APK文件最终生成在“app/build/outputs/apk/”目录。这篇文章中,我使用的是一个x86模拟器镜像以及一个名为“app-universal-debug.apk”的应用。

该应用程序包含一个加密字符串,并会在运行时使用原生库对字符串进行解密。以下是在Smail中字符串的解密过程:

const/16 v3, 0x57
new-array v1, v3, [B
fill-array-data v1, :array_2a
.local v1, "encryptedStringBytes":[B
invoke-static {}, Lorg/cf/nativeharness/Cryptor;->getInstance()Lorg/cf/nativeharness/Cryptor;
move-result-object v0
.line 21
.local v0, "c":Lorg/cf/nativeharness/Cryptor;
# v3 contains a String made from encrypted bytes
new-instance v3, Ljava/lang/String;
invoke-direct {v3, v1}, Ljava/lang/String;->([B)V
# Call the decryption method, move result back to v3
invoke-virtual {v0, v3}, Lorg/cf/nativeharness/Cryptor;->decryptString(Ljava/lang/String;)Ljava/lang/String;
move-result-object v3

三、构建Harness工具

我使用的是Tim 'diff' Strazzere开发的一款名为“native-shim”的工具(Tim是RedNaga的一名成员)作为整套利用工具的基础,我将这个工具命名为“Harness”。在Android中,shim就像一个中间垫片,作用是加载一个库,并调用其“JNI_OnLoad”方法。它可以使调试工作更加简单,我们只需要使用调试器启动shim,并将具体路径以参数形式传递给目标库即可。我们可以设置调试器的断点,在库加载时触发断点,这样就能进入“JNI_OnLoad”函数的处理流程。此外,native-shim还可以加载库文件(.so文件)、获取函数的引用并调用函数,这一切对我们来说都非常实用。

首先,我添加了部分代码以初始化一个Java虚拟机实例,并将该实例传递给JNI_OnLoad函数,这样处理可以使JNI的初始化过程更为准确。如果没有真实的虚拟机实例,JNI库的内部状态看起来可能会有些奇怪。不同库文件的JNI_OnLoad的实现可能不尽相同,但这并不重要,重要的是这些实现都会检查JNI版本,如这段代码所示。因此我们需要创建一个虚拟机实例。

printf(" [+] Initializing JavaVM Instancen");
JavaVM *vm = NULL;
JNIEnv *env = NULL;
int status = init_jvm(&vm, &env);
if (status == 0) {
  printf(" [+] Initialization success (vm=%p, env=%p)n", vm, env);
} else {
  printf(" [!] Initialization failure (%i)n", status);
  return -1;
}
printf(" [+] Calling JNI_OnLoadn");
onLoadFunc(vm, NULL);

我们的最终目标是通过harness工具,开启一个socket服务器,读取socket上传输的参数,使用这些参数来调用函数。这样一来,解密函数就会变成一个服务,我们可以简单使用一个Python脚本与其通信。


四、理解目标函数

在调用函数前,我们需要了解函数的签名(即参数个数和参数类型)及函数的返回类型。我们可以先看一下org.cf.nativeharness.Cryptor类的反编译代码,类中包含decryptString原生方法的声明,如下所示:

public class Cryptor {
    private static Cryptor instance = null;
    public static Cryptor getInstance() {
        if (instance == null) {
            instance = new Cryptor();
        }
        return instance;
    }
    public native String decryptString(String encryptedString);
}

从这段代码中,我们可知该方法使用了一个String对象作为参数,返回了一个String对象,看上去比较简单。现在我们将其转化为原生函数形式,如下所示:

Java_org_cf_nativeharness_Cryptor_decryptString(JNIEnv *env, jstring encryptedString)

每个JNI原生方法都需要将JNIEnv对象作为第一个参数。这意味着定义我们函数的typedef语句应该如下所示:

typedef jstring(*decryptString_t)(JNIEnv *, jstring);

不幸的是,如果你试图使用上述typedef语句执行这个函数,你会得到一个错误信息,如下所示:

E/dalvikvm: JNI ERROR (app bug): attempt to use stale local reference 0x1
E/dalvikvm: VM aborting
A/libc: Fatal signal 6 (SIGABRT) at 0x00000a9a (code=-6), thread 2714 (harness)

这让我困惑了好一阵子。我原先以为我可能在某个地方使用了空指针引用,因此我花了很多功夫,添加了许多printf语句,将内存中所有相关指针的位置全部打印出来。这个错误信息貌似在提示我某个参数出现了问题,但我排查后发现所有指针都是正常的,没有空引用情况。

我敢肯定我传递的参数没有问题,问题可能出在JNI上。为了证实这一点,我使用了javah命令,它可以生成实现原生方法所需要的C语言头文件以及源代码文件。

为了完成这个工作,你需要安装dex2jar,找到正确的类路径,将“platforms/android-19”改为你已经安装的具体平台,如下所示:

$ d2j-dex2jar.sh app-universal-debug.apk
dex2jar app-universal-debug.apk -> ./app-universal-debug-dex2jar.jar
$ javah -cp app-universal-debug-dex2jar.jar:$ANDROID_SDK/platforms/android-19/android.jar org.cf.nativeharness.Cryptor

上述命令可以生成“_org_cf_nativeharness_Cryptor.h_”文件,其中包含如下信息:

JNIEXPORT jstring JNICALL Java_org_cf_nativeharness_Cryptor_decryptString(JNIEnv *, jobject, jstring);

我们可以看到多了一个jobject作为第二个参数,这究竟是为什么?如果你已经知道了这个问题的答案,我敢打赌你已经花了很多时间深入学习了Smali,特别是其中的invoke-virtual方法。无论你在何时调用虚拟方法(通常都是些非静态方法),第一个参数总是某个对象的实例,这个实例负责方法的具体实现。对于这个例子来说,此时第一个参数应该是“org.cf.nativeharness.Cryptor”类的一个实例。

当然,你可以投机取巧,比如可以查看str-crypt.c代码,找到函数的具体调用形式。但你要知道你是个逆向分析师(或渗透测试员),你不可能拿到源代码。

因此正确的typedef语句中应该包含Cryptor实例的一个jobject对象,如下所示:

typedef jstring(*decryptString_t)(JNIEnv *, jobject, jstring);

你可能会感到好奇,为什么我们不以静态方法开始介绍?没有特别的理由,主要是因为我在写这篇博客时,所分析的原始应用中目标方法不是静态方法,仅此而已。

这一部分内容最大的收获就是,如果你不确定函数的具体调用形式,你可以试一下javah命令,时刻牢记虚拟方法与Java中的Method#invoke()类似,使用某个实例对象作为第一个参数。


五、构建Socket服务器

这是整个工作中最无趣的一个环节,如果你不介意的话,我会跳过这一部分。你可以自行查看具体的实现源码,如果愿意的话也可以提出修改意见。


六、Harness工具的使用方法

你可以通过如下几个步骤来使用Harness工具。

1、启动模拟器

2、将harness push到设备中

3、将目标原生库及其他依赖库push到设备中(本文示例中不涉及到依赖库)

4、将目标应用push到设备中

5、运行harness工具

6、将模拟器的端口转发到宿主机上

7、运行“decrypt_string.py”,祈祷一切顺利

你可以使用以下命令将应用及原生库push到设备中。

$ adb push app/build/output/apk/app-universal-debug.apk /data/local/tmp/target-app.apk
$ unzip app/build/outputs/apk/app-universal-debug.apk lib/x86/libstr-crypt.so
Archive:  app/build/outputs/apk/app-universal-debug.apk
  inflating: lib/x86/libstr-crypt.so
$ adb push lib/x86/libstr-crypt.so /data/local/tmp
lib/x86/libstr-crypt.so: 1 file pushed. 1.5 MB/s (5476 bytes in 0.004s)

使用如下命令将harness工具push到设备中。

cd harness
make && make install

注意:以上命令会将x86库push到设备中,如果你确实想要使用其他的模拟器镜像,你可以使用“adb push libs//harness /data/local/tmp”命令替换“make install”命令。

现在,你可以运行harness,将目标库路径作为第一个参数传入,如下所示:

$ adb shell /data/local/tmp/harness /data/local/tmp/libstr-crypt.so
[*] Native Harness
 [+] Loading target: [ /data/local/tmp/libstr-crypt.so ]
 [+] Library Loaded!
 [+] Found JNI_OnLoad, good
 [+] Initializing JavaVM Instance
WARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix.
 [+] Initialization success (vm=0xb8e420a0, env=0xb8e420e0)
 [+] Calling JNI_OnLoad
 [+] Found decryptString function, good (0xb761f4f0)
 [+] Finding Cryptor class
 [+] Found Cryptor class: 0x1d2001d9
 [+] Found Cryptor.getInstance(): 0xb27bc270
 [+] Instantiated Cryptor class: 0x1d2001dd
 [+] Starting socket server on port 5001

为了测试工具是否正常工作,你可以在另一个终端上运行如下命令:

$ ./decrypt_string.py
Sending encrypted string
Decrypted string: "Seek freedom and become captive of your desires. Seek discipline and find your liberty."

如果你在输出结果中看到解密后的字符串,表明一切顺利,非常完美。


七、总结

你可以根据实际情况,修改harness工具源码中的目标函数。另外,实际场景中,目标程序错综复杂,我并不能保证这种方法100%有效。


推荐阅读
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了GregorianCalendar类的基本信息,包括它是Calendar的子类,提供了世界上大多数国家使用的标准日历系统。默认情况下,它对应格里高利日历创立时的日期,但可以通过调用setGregorianChange()方法来更改起始日期。同时,文中还提到了GregorianCalendar类为每个日历字段使用的默认值。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
author-avatar
周郎某某某
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有