导语:AssetHook是一个工具,它可以让Android安全研究人员和普通用户能够在无需修改APK本身的情况下随时修改Android应用程序的部分Asset。这样的修改使研究人员可以改变嵌入式数据,以更好地评估和测试移动应用程序。目前来看AssetHook比现有方

AssetHook是一个工具,它可以让Android安全研究人员和普通用户能够在无需修改APK本身的情况下随时修改Android应用程序的部分Asset。这样的修改使研究人员可以改变嵌入式数据,以更好地评估和测试移动应用程序。目前来看AssetHook比现有方法更容易使用,且比传统方法更有效。

背景

去年年底,我开始关注Android启用React Native 后 Facebook的新框架,它将跨平台移动开发统一到JavaScript,显然JavaScript是一种不需要介绍的语言。在React Native之前,在JavaScript中构建跨平台移动应用程序的主要方法是使用PhoneGap(…或者现在是Cordova吗?甚至不让我从Ionic开始)。这些都是基于将JavaScript加载到Webview中(UIWebView在iOS上,WebView在Android上)的主要应用程序逻辑。然后,开发人员可以在平台的“本机”语言中编写一些平台特定的代码,并将它们与幽灵一般的webview魔术FFI连接在一起。

React Native将这种设计(也可能是理由)一脚踢开,然后颠覆了传统。它不是使用webview和HTML / CSS进行渲染,它将JavaScript声明的UI映射到平台的本地UI工具包,并嵌入WebKit的JavaScriptCore(JSC)库来运行该JavaScript,完全避免了平台的Webview实现。JSC支持在没有JIT的情况下解释JavaScript,这在iOS上是必需的,因为它的安全模型是基于不允许任何人生成可执行内存的(而且人们说OpenBSD的人是Luddites for W ^ X …); 最后我检查过,V8只是JIT(新的ignition interpreter似乎并没有改变,因为“ JIT代码生成仍然是IC和代码存根 ”)。

作为我最初在Android上进行React Native的一部分,我试图在应用程序中注入额外的JavaScript代码,以将开发控制台REPL加载(并执行janky函数hook)。所以,我在这种情况下做了一个通常的工作,并为Android的android.content.res.AssetManager类写了一个Xposed函数hook。..之后我再没有做什么。

Android使用AssetManager该类作为应用程序的界面来访问嵌入在其APK中的特定类型的文件资源(特别是APK中的assets/目录中)。鉴于我知道(从开放一个发布版本的React Native应用程序)主要的后处理JavaScript软件包作为资产存储,所以hook不起作用是非常奇怪的。然后,我通过React Native的Android代码库开始测试,很快就发现使用C API为Android的资产管理器加载了C ++代码,这解释了为什么Java函数挂起不起作用。

因为我想要修改这些软件包的内容,而不需要创建和签名修改的APK(然后需要更多的hook来欺骗他们的签名),我设置了构建一个工具来hook这些资源负载。我在C ++中写了最初的POC,然后在Rust中重写。

(A)AssetHook

AssetHook是一款LD_PRELOAD用于Android应用程序的函数hook库。它hook了Android的内部资产管理代码,并将资产文件加载从合法(即签名的)APK重定向到设备本地文件系统上的单独位置。(A)AssetHook的初始版本“AAssetHook”(两个“A”)拦截了Android 公共 AAssetManager C API的调用,其实际上是用extern "C"C ++编写的。然而,由于这个API只是一个直接由Java 类使用的Android 内部类的封装,所以我把它重写为“AssetHook”(一个'A')来代替底层的C ++ API。 AssetManagerandroid.content.res.AssetManager

如果没有像AssetHook这样的东西的话,替换Android资产将需要解压APK,替换资产文件,重新打包APK,可能会重新对齐,签署新的APK文件,然后安装它。这不但是一个繁琐和缓慢的过程(特别是APK复制到设备并为更大的应用程序安装过程),还必须处理新的签名打破跨应用程序签名检查的后果。对于使用自定义权限,共享用户ID或自定义IPC访问控制的应用程序组,此新签名将阻止修改的应用程序与其他应用程序进行交互或完全不起作用。然后需要额外的hook来欺骗修改后的APK的签名证书以匹配原来的APK。此外,如果没有更多根深蒂固和灵活的功能hook基础架构(例如修改系统分区二进制文件的Xposed),这种hook在Android上可能是不可能的。使用这种框架的一个关键问题是由于需要移植工作,因此必然会延迟支持新的Android版本。

C API Hooking 使用了 LD_PRELOAD

C API hooking变体(“AAssetHook”)是由vanilla的 LD_PRELOADhook来实现的,它声明了一些重复的函数符号,尽管动态链接的魔力 – 在从其二进制文件外部访问时重写对这些函数的调用。然后在dlopen(3)/ dlsym(3)(在Unix上)使用一个Rust包装库来根据需要代理对原始函数的调用。之后,它会检查一个APK中的给定文件路径是否与设备文件系统上的现有路径匹配,并欺骗C API返回磁盘文件的文件内容,而不是in-

C++ API Hooking 使用了 LD_PRELOAD

由于资产管理器的C ++实现不是公共API,C ++ APIhook变体(“AssetHook”)稍微复杂一点,这在很大程度上依赖于多态性,并且在较小的Android版本中可能会发生变化。当在C ++中使用多态时,通常将通过动态调度处理虚拟方法调用。在大多数“平稳”平台上,通过将虚拟表(vtable)指针作为类的内存结构的第一个元素之一来实现。该指针在构造期间设置,并指向一个填充有该类使用的特定虚拟方法的函数指针的表。LD_PRELOAD在这种情况下,函数hook非常有限,因为它只能挂接导出符号的二进制函数调用,而动态调度调用则使用直接函数指针。这样可以避免基于vtable的呼叫被LD_PRELOAD符号覆盖直接挂起。此外,这些函数指针的顺序很可能会在次级类定义更改时发生更改。在不同的编译器之间也是不一致的,通常可能难以推断出多个版本的二进制文件。

注意:这完全取决于编译器,但是这一点在Unix上的Clang和GCC(至少对于x86 / amd64和ARM / AArch64来说都是一致的)。

例如,下面的代码片段的对象可以被布置,如下图所示:

#include <stdio.h>struct Base {
  virtual void foo() {
    puts("base!");
  }
  size_t a = (size_t)-};struct Derived : public Base {
  virtual void foo() {
    puts("derived!");
  }
  size_t b = (size_t)-};struct DDerived : public Derived {
  void foo() final override {
    puts("dderived!");
  }
  size_t c = (size_t)-};void call_foo(Base& br.foo();}int main() {
  Base b;
  Derived d;
  DDerived dd;
  call_foo(b);
  call_foo(d);
  call_foo(dd);
  return 0;}

 1.png

功能搜索和vtable插槽映射

处理vtable排序的波动的一种方法是将给定的类“vtable”与其二进制文件的符号表进行交叉引用(用binutils的GPL bfd.h或libelfin解析)。使用手中的函数地址,可以对vtable进行交叉引用以找到其偏移量。然而,这可能不起作用,因为一些虚拟方法函数可能没有符号,就像Android资产管理器的C ++实现一样。另一种方法是扫描这种不符号的函数的原始字节,但这不太可能跨越多个二进制版本。 

事实上,这是完全可能的(使用类似Capstone的东西)去解析调用目标类的虚拟方法并提取这些方法的vtable偏移量的符号符号函数的汇编。

vtable Slot Knocking

AssetHook的C ++ APIhook实现使用了最后一种方法的一个变体,依靠这样一个事实,即将虚拟方法调用的C API本身就是具有ABI稳定性保证的公共API。AssetHook不是分析C API函数的指令来剔除vtable偏移量,而是创建一个假的C ++对象,并使用它来配置vtable。该对象vtable指针指向一个虚构的vtable,其中包含一系列报告调用顺序的函数指针。AssetHook将该对象作为嵌入到void*掩码包装结构体中的指针传递给C API,然后C API通过编译器提供的偏移量调用关联的虚拟方法。执行此调用时,将触发嵌入式函数指针,暴露给定操作的vtable偏移量。这允许通过调用这个假的对象上已知的C API函数来逐个获取实际的vtable顺序,以获得它们包装的C ++方法的vtable偏移量。

注意:对于特殊的涉及 thunk的 “复杂”类,需要更加动态地分析vtable,因为类别的vtable段中的“正常”成员函数指针实际上是由编译器生成的包装函数。该函数相应地移动this指针,并从实际类的更大的vtable中的其他地方调用“真实”成员函数指针。

hook虚拟方法

之后,在返回对象指针LD_PRELOAD的AssetManager类的“公共”非虚拟方法的变形符号名称上创建一个类型的hookAsset。这个hook确定文件是否应该被hooking,如果是这样,返回一个指向一个修改后的Asset对象的指针,其中一个vtable包含了函数hook。

例子:

在这个例子中,我们将使用AssetHook将我们自己的JavaScript文件交换到Tic-tac-toe示例应用程序中(参考React Native文档,我必须传递–dev false给react-native build它来使其大部分应用程序成为最小化的)。我们假设您按照文件中的描述安装了它。

1、安装一个“release”版本的应用程序(为了尽可能的缩小,AssetHook可以同时发布和调试Android APK版本)在一个根深蒂固的测试设备上(AssetHook已知可以与Android 5.x,6.x和7.x一起用32位和64位ARM,以及32位x86上的Android O Developer Preview)。

2、从APK文件中提取assets/index.android.bundle(从嵌入的Android来源app/build目录或使用该设置adb shell pm list packages -f | grep <pkg>和adb pull)。

3、修改文件并注入JavaScript alert(…)。

4、运行以下命令:

$ adb push path/to/index.android.bundle /data/local/tmp/assethook/<pkg>/assets/
$ adb shell su -c 'setprop wrap.<pkg> LD_PRELOAD=/data/local/tmp/lib/libassethook_cppapi.so'

注意: React Native使用32位二进制文件,甚至在64位Android上。因此,32位模式下进程加载,我们需要使用32位版本的AssetHook。

5、启动应用程序(如果已经打开,请先关闭它)。

2.png

未来的工作

主要优先事项是在执行模式下完全启用SEAndroid时支持Android。现在,AssetHook要求将SELinux置于允许模式,因为替换文件存在于Google试图通过SELinux限制访问现代Android版本的共享临时目录中。我正在寻求支持从应用程序自己的内部目录加载替换资产文件,但这可能需要额外的工具来将文件上传到设备上。

源链接

Hacking more

...