栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 逆向本地库

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 逆向本地库

从Android App Reverse Engineering 101 | Learn to reverse engineer Android applications!复制的信息(您可以在那里找到解决方案) Android 应用程序可以包含已编译的本地库。本机库是开发人员编写然后针对特定计算机体系结构编译的代码。大多数情况下,这意味着用 C 或 C++ 编写的代码。开发人员可能会这样做的良性或合法原因是数学密集型或时间敏感的操作,例如图形库。恶意软件开发人员已开始转向本机代码,因为与分析 DEX 字节码相比,逆向工程编译的二进制文件往往是一种不太常见的技能。这主要是因为 DEX 字节码可以反编译为 Java,而本机的编译代码通常必须作为程序集进行分析。

目标 本节的目标不是教您汇编 (ASM) 或如何更一般地对编译代码进行逆向工程,而是如何将更一般的二进制逆向工程技能应用到 Android 中。因为本次研讨会的目标不是教你 ASM 架构,所有练习都将包括一个 ARM一个 x86 版本的库进行分析,以便每个人都可以选择他们更熟悉的架构。

学习ARM汇编 如果您之前没有二进制逆向工程/组装经验,这里有一些建议的资源。大多数 Android 设备都在 ARM 上运行,但本次研讨会中的所有练习还包括一个 x86 版本的库。 要了解和/或审核ARM汇编,我强烈建议ARM汇编基础从Azeria实验室。

Java 本机接口 (JNI) 简介 Java 本机接口 (JNI) 允许开发人员声明在本机代码(通常是编译的 C/C++)中实现的 Java 方法。JNI 接口不是 Android 特定的,但更普遍地适用于在不同平台上运行的 Java 应用程序。 Android 原生开发工具包 (NDK) 是基于 JNI 的 Android 特定工具集。根据文档: 在 Android 中,本机开发工具包 (NDK) 是一个工具集,允许开发人员为其 Android 应用程序编写 C 和 C++ 代码。 JNI 和 NDK 一起允许 Android 开发人员在本机代码中实现他们的一些应用程序功能。Java(或 Kotlin)代码将调用 Java 声明的本机方法,该方法在已编译的本机库中实现。

参考 Oracle JNI 文档 JNI规范 JNI功能< -我一直有这样一个开放的,是指它,而倒车Android原生库 Android JNI 和 NDK 参考 Android的JNI技巧< -强烈建议阅读“本地库”一节开始 入门NDK < -这是一个开发人员如何开发本机库和理解事物是如何构建的指导,使得它更容易扭转。

分析目标 - Android 原生库 在本节中,我们将重点介绍如何对已在 Android 原生库中实现的应用功能进行逆向工程。当我们说 Android 原生库时,我们指的是什么? Android 原生库作为.so包含在 APK 中,共享对象库采用 ELF 文件格式。如果您之前分析过 Linux 二进制文件,它的格式是相同的。 默认情况下,这些库包含在 APK 文件路径/lib//lib.so 中。这是默认路径,但如果他们愿意,开发人员也可以选择将本机库包含在/assets/ 中。更常见的是,我们看到恶意软件开发人员选择在/lib以外的路径中包含本机库,并使用不同的文件扩展名来试图“隐藏”本机库的存在。 由于本机代码是为特定 CPU 编译的,如果开发人员希望他们的应用程序在超过 1 种类型的硬件上运行,他们必须在应用程序中包含这些版本的已编译本机库。上面提到的默认路径,包括Android官方支持的每种cpu类型的目录。 中央处理器 本地库路径 “通用” 32 位 ARM 库/armeabi/libcalc.so x86 lib/x86/libcalc.so x64 lib/x86_64/libcalc.so ARMv7 lib/armeabi-v7a/libcalc.so ARM64 lib/arm64-v8a/libcalc.so

加载库 在 Android 应用程序可以调用和执行在本机库中实现的任何代码之前,应用程序(Java 代码)必须将库加载到内存中。有两种不同的 API 调用可以执行此操作: 1 System.loadLibrary("calc") 已复制! 或者 1 System.load("lib/armeabi/libcalc.so") 已复制! 两个api调用的区别在于loadLibrary只以库的短名称作为参数(即libcalc.so = “calc” & libinit.so = “init”),系统会正确判断当前运行的架构从而使用正确的文件。另一方面,加载需要库的完整路径。这意味着应用程序开发人员必须确定架构,从而确定加载自己的正确库文件。 当Java 代码调用这两个(loadLibrary或load)API 中的任何一个时,作为参数传递的本机库将执行其JNI_OnLoad(如果它是在本机库中实现的)。 重申一下,在执行任何本机方法之前,必须通过在 Java 代码中调用System.loadLibrary或System.load来加载本机库。当执行这两个 API 中的任何一个时,也会执行本机库中的JNI_OnLoad函数。

Java 到本机代码的连接 为了从本机库执行函数,必须有一个 Java 声明的本机方法,Java 代码可以调用该方法。当这个 Java 声明的本地方法被调用时,本地库 (ELF/.so) 中的“配对”本地函数将被执行。 Java 声明的本地方法出现在 Java 代码中,如下所示。它看起来像任何其他 Java 方法,除了它包含native关键字并且在其实现中没有代码,因为它的代码实际上在已编译的本地库中。 1 public native String doThingsInNativeLibrary(int var0); 已复制! 要调用此本机方法,Java 代码将像调用任何其他 Java 方法一样调用它。但是,在后端,JNI 和 NDK 将改为执行本机库中的相应功能。为此,它必须知道 Java 声明的本机方法与本机库中的函数之间的配对。 有两种不同的方法可以进行这种配对或链接: 1 . 使用 JNI 本地方法名称解析的动态链接,或 2 . 使用RegisterNatives API 调用的静态链接

动态链接 为了动态链接或配对Java 声明的本地方法和本地库中的函数,开发人员根据规范命名方法和函数,以便JNI 系统可以动态地进行链接。 根据规范,开发人员将函数命名如下,以便系统能够动态链接本地方法和函数。本机方法名称由以下组件连接而成: 1 . 前缀 Java_ 2 . 错误的完全限定类名 3 . 下划线(“_”)分隔符 4 . 错误的方法名称 5 . 对于重载的本机方法,两个下划线(“__”)后跟重整参数签名 为了对下面的 Java 声明的本机方法进行动态链接,假设它在类com.android.interesting.Stuff 中 1 public native String doThingsInNativeLibrary(int var0); 已复制! 本机库中的函数需要命名为: 1 Java_com_android_interesting_Stuff_doThingsInNativeLibrary 已复制! 如果本机库中没有具有该名称的函数,则意味着应用程序必须进行静态链接。

静态链接 如果开发人员不想或不能根据规范命名本机函数(例如,想要剥离调试符号),那么他们必须使用RegisterNatives ( doc ) API 的静态链接,以便在Java 声明的本地方法和本地库中的函数。该RegisterNatives功能从本机代码调用,而不是Java代码,并经常在被称为JNI_OnLoad函数,因为RegisterNatives之前必须先调用Java声明的本地方法来执行。 1 jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); 2 ​ 3 类型定义结构{ 4 字符 *名称; 5 字符 * 签名; 6 无效 *fnPtr; 7 } JNINativeMethod; 已复制! 在逆向工程中,如果应用程序使用静态链接方法,我们作为分析人员可以找到传递给RegisterNatives的JNINativeMethod结构,以确定在调用 Java 声明的本地方法时执行本地库中的哪个子例程。 该JNINativeMethod结构需要Java声明的本地方法名的字符串和方法签名的字符串,所以我们应该可以在我们的本地库找到这些。 方法签名 该JNINativeMethod结构要求的方法签名。方法签名说明了该方法采用的参数类型以及它返回的内容类型。此链接在“类型签名”部分记录了JNI 类型签名。 Z:布尔值 B:字节 C: 字符 S:短 我:整数 J:长 F:浮动 D:双 l 完全合格级;:完全合格的类 [类型:类型[] ( arg-types ) ret-type: 方法类型 V:无效 对于本地方法 1 public native String doThingsInNativeLibrary(int var0); 类型签名是 1 (一)Ljava/lang/String; 这是本机方法及其签名的另一个示例。对于以下是方法声明 1 public native long f (int n, String s, int[] arr); 它具有类型签名: 1 (ILjava/lang/String;[I)J

练习 #5 - 找到本机函数的地址 在练习 #5 中,我们将学习在反汇编程序中加载本机库并识别调用本机方法时执行的本机函数。对于这个特定的练习,目标不是对本地方法进行逆向工程,只是找到 Java 中对本地方法的调用与在本地库中执行的函数之间的链接。在本练习中,我们将使用示例 Mediacode.apk。此示例位于 VM 中的~/samples/Mediacode.apk。它的 SHA256 哈希是 a496b36cda66aaf24340941da8034bd53940d1b08d83a97f17a65ae144ebf91a。 目标 本练习的目标是: 1 . 识别 DEX 字节码中声明的本地方法 2 . 确定加载了哪些本机库(以及可以在何处实现本机方法) 3 . 从 APK 中提取本机库 4 . 将本机库加载到反汇编器中 5 . 识别调用本机方法时执行的本机库中函数的地址(或名称) 指示 1 . 在 jadx 中打开 Mediacode.apk。请参考前面的练习#1 2 . 这一次,如果您展开 Resources 选项卡,您将看到此 APK 有一个lib/目录。此 APK 的本机库位于默认 CPU 路径中。 3 . 现在我们需要识别任何声明的本地方法。在 jadx 中,搜索并列出所有声明的本地方法。应该有两个。 4 . 在声明的本地方法周围,查看是否有任何地方加载了本地库。这将为查找要实现的功能的本机库提供指导。 5 . 通过创建新目录并将 APK 复制到该文件夹​​中,从 APK 中提取本机库。然后运行命令unzip Mediacode.APK。您将看到从 APK 中提取的所有文件,其中包括lib/目录。 6 . 选择您要分析的本机库的架构。 7 . 通过运行ghidraRun启动ghidra。这将打开 Ghidra。 8 . 要打开本地库进行分析,请选择“新建项目”、“非共享项目”,选择保存项目的路径并为其命名。这将创建一个项目,然后您可以将二进制文件加载到其中。 9 . 创建项目后,选择龙图标以打开代码浏览器。转到“文件”>“导入文件”以将本机库加载到工具中。您可以保留所有默认值。 10 . 您将看到以下屏幕。选择“分析”。 11 . 使用上面的链接信息,确定在调用 Java 声明的本机方法时执行的本机库中的函数。

将文件加载到 Ghidra 代码浏览器

 

在 jadx 中打开 Mediacode 的屏幕截图 解决方案

反转 Android 本机库代码 - JNIEnv 当开始对 Android 原生库进行逆向工程时,我不知道我需要知道的一件事是关于JNIEnv。JNIEnv是一个指向JNI Functions的函数指针结构。Android 本机库中的每个 JNI 函数都将JNIEnv*作为第一个参数。 来自 Android JNI Tips文档: JNIEnv 和 JavaVM 的 C 声明与 C++ 声明不同。“jni.h”包含文件根据它是包含在 C 还是 C++ 中提供不同的 typedef。出于这个原因,在两种语言都包含的头文件中包含 JNIEnv 参数是一个坏主意。(换句话说:如果你的头文件需要 #ifdef __cplusplus,你可能需要做一些额外的工作,如果该头文件中的任何内容都引用了 JNIEnv。) 以下是一些常用的函数(以及它们在 JNIEnv 中的偏移量): JNIEnv + 0x18: jclass (*FindClass)(JNIEnv , const char ); JNIEnv + 0x34: jint (*Throw)(JNIEnv*, jthrowable); JNIEnv + 0x70: jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); JNIEnv + 0x84: jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); JNIEnv + 0x28C: jstring (*NewString)(JNIEnv , const jchar , jsize); JNIEnv + 0x35C: jint (*RegisterNatives)(JNIEnv , jclass, const JNINativeMethod , jint); 在分析 Android 原生库时,JNIEnv 的存在意味着: 1 . 对于 JNI 本机函数,参数将移动 2。第一个参数始终是 JNIEnv*。第二个参数是函数应该运行的对象。对于静态本地方法(它们在 Java 声明中有 static 关键字),这将为 NULL。 2 . 您经常会在反汇编中看到间接分支,因为代码将偏移量添加到 JNIEnv* 指针,取消引用以获取该位置的函数指针,然后分支到函数。 这是JNIEnv 结构的 C 实现的电子表格,用于了解不同偏移量处的函数指针。 实际上,在反汇编中,这显示了许多不同的间接地址分支,而不是直接函数调用。下图显示了这些间接函数调用之一。反汇编中突出显示的行显示blx r3。作为逆向者,我们需要弄清楚 r3 是什么。它没有显示在屏幕截图中,但在这个函数的开头,r0被移到了r5 中。因此,r5是JNIEnv*。在 0x12498 行,我们看到r3 = [r5]。现在r3是JNIEnv(没有 *)。 在 0x1249e 行,我们将 0x18 添加到r3并取消引用它。这意味着r3现在等于 JNIEnv 中偏移量 0x18 处的任何函数指针。我们可以通过查看电子表格来找出答案。[JNIEnv + 0x18] = FindClass 方法的指针 因此,第 0x124a4 行上的blx r3正在调用FindClass。我们可以在此处的 JNIFunctions 文档中查找有关FindClass(以及 JNIEnv 中的所有其他函数)的信息。

 

从 JNIEnv 调用函数的反汇编屏幕截图 值得庆幸的是,有一种方法可以在不手动完成所有这些操作的情况下获取 JNI 函数!在 Ghidra 和 IDA Pro 反编译器中,您可以将 JNI 函数中的第一个参数重新键入为JNIEnv *类型,它会自动识别被调用的 JNI 函数。在 IDA Pro 中,这项工作开箱即用。在 Ghidra 中,您必须首先加载 JNI 类型(jni.h 文件或 jni.h 文件的 Ghidra 数据类型存档)。为方便起见,我们将从 Ayrx 生成的 Ghidra 数据类型存档 (gdt) 中加载 JNI 类型,可在此处获取。为方便起见,此文件在 VM 中的~/jni_all.gdt 中可用。 要加载它以在 Ghidra 中使用,请在数据类型管理器窗口中,单击右上角的向下箭头并选择“打开文件存档”。

 

打开文件存档菜单的屏幕截图 然后选择jni_all.gdt文件加载。加载后,您应该会在数据类型管理器列表中看到 jni_all,如下所示。

在数据类型管理器中加载的 jni_all 的屏幕截图 一旦将其加载到 Ghidra 中,您就可以在反编译器中选择任何参数类型并选择“Retype Variable”。将新类型设置为 JNIEnv *。这将导致反编译器现在显示调用的 JNIFunction 的名称,而不是指针的偏移量。

将参数重新输入为 JNIEnv* 后 JNI 函数名称的屏幕截图

练习 #6 - 查找和反转本机函数 我们将把我们之前的所有技能放在一起:确定 RE 的起点、反转 DEX 和反转本机代码,以分析可能在本机代码中移动其有害行为的应用程序。示例是~/samples/HDWallpaper.apk。 目标 本练习的目标是将我们所有的 Android 逆向技能放在一起来分析整个应用程序:它的 DEX 和本机代码。 练习上下文 您是 Android 应用程序的恶意软件分析师。您担心此样本可能会进行高级 SMS 欺诈,这意味着它会在未经披露和用户同意的情况下向高级电话号码发送 SMS。为了标记为恶意软件,您需要确定 Android 应用程序是否: 1 . 发送短信,以及 2 . 该 SMS 消息将发送到一个付费号码,并且 3 . 如果有明显的披露,并且 4 . 如果短信仅在用户同意后发送到付费号码。 指示 继续前进! 解决方案

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/314506.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号