Android Runtime Restrictions Bypass

0x00背景

在这里插入图片描述
Twitter上quark实验室分享了一篇绕过Android高版本运行时限制绕过的方案,我们来分析Google对于Android运行时的限制
和quark实验室给出的bypass方案。

0x01 Android Runtime限制

1. dlopen() restrictions

从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。

当我们使用dlopen打开私有的/system/bin/linker时,会出现一个报错:

在这里插入图片描述

1. bybass dlopen()

All native libraries are associated with a soinfo struct.
Anyone can read and write fields of soinfo.
Native libraries are tied to namespace(s) that is implemented using soinfo.
We can update this structure with new namespaces.

我们通过jni调用dlopen时,出现error

adb logcat -s "linker:E"
4729  4729 E linker :
  library "/system/lib64/libart.so" ("/system/lib64/libart.so") needed or dlopened by
  /data/app/re.android.restrictions-yALrH==/lib/arm64/librestrictions-bypass.so
  is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="",
  default_library_paths="/data/app/re.android.restrictions/...,
  permitted_paths="/data:/mnt/expand:/data/data/re.android.restrictions"]

从这个错误中,我们可以看到我们的JNI库与命名空间classloader-namespace相关联, 并且仅限于从以下目录加载库:

/data
/mnt/expand
/data/data/re.android.restrictions
As libart.so does not belong to the allowed directories, the library is not loaded and dlopen() returns a null pointer.

由于libart.so不属于允许的目录,因此未加载库,dlopen()返回空指针。

soinfo结构

所有加载的库都绑定到一个结构,该结构包含有关加载库的上下文的元数据。这个名为soinfo的结构注册了各种信息,如:

The name of the library (e.g. libc.so)
The path to the library (e.g. /system/lib64/libc.so)
The base address choose by the ASLR (e.g. 0x7f13e09a5000)
The Namespace(s) associated with the library.

soinfo结构在linker / linker_soinfo.h中定义为Android Bionic模块的一部分。从这个结构中,我们可以注意到与Android命名空间实现相关的两个字段:

struct  soinfo  { 
  ... 
  android_namespace_t *  primary_namespace_ ; 
  android_namespace_list_t  secondary_namespaces_ ; 
  ...... 
};

如果以某种方式,应用程序设法访问和修改这些字段,它可以添加自己的命名空间 和从不允许的目录加载库。

soinfo and library mapping

正如上一节中所解释的,所有加载的库绑到soinfo结构。事实证明,这种在内存中映射是使用std :: unordered_map执行的,其密钥是唯一的id ,其值是soinfo结构上的指针。

此映射存储在名为g_soinfo_handles_map的导出静态变量中(请参阅linker / linker_globals.cpp),该变量位于 链接器模块(/ system / bin / linker)中。

从应用程序的角度来看,这意味着可以使用ELF动态符号表来解析此变量的相对虚拟地址。然后可以使用dl_iterate_phdr()计算绝对地址, 并查找/ system / bin / linker的基址。

一旦应用程序解析了g_soinfo_handles_map的绝对虚拟地址,它就可以遍历std :: unordered_map并查找与其JNI库关联的soinfo。

static const std::string JNI_LIBRARY_NAME = "...";

for (auto&& [hdl, info] : *g_soinfo_handles_map) {
  const char* name = get_soname(info);
  if (name == JNI_LIBRARY_NAME) {
    return info;
  }
}
return nullptr; // Not found

我们拿到soinfo结构上的指针后,它可以使用get_primary_namespace()访问库名称空间 :

android_namespace_t *  ns  =  get_primary_namespace (soinfo_ptr );

在此指针上,应用程序可以调用set_ld_library_paths()和set_isolated()函数来扩展允许的目录列表:

ns - > set_ld_library_paths ({/ system / lib64” , “/ sytem / lib” }; 
ns - > set_isolated (false );

此后/ system / lib64和/ sytem / lib就不再是属于不允许的目录了。

我们来分析demo里bybass的完整代码的实现:

__attribute__((constructor))
void __ctor(void) {
  ALOG("PID: %d", getpid());
  linker     = get_module("linker64");
  if (not linker) {
    ALOG("Unable to find linker!");
  } else {
    g_soinfo_handles_map  = reinterpret_cast<decltype(g_soinfo_handles_map)>(linker.get_address("__dl_g_soinfo_handles_map"));
    get_soname            = reinterpret_cast<decltype(get_soname)>(linker.get_address("__dl__ZNK6soinfo10get_sonameEv"));
    get_primary_namespace = reinterpret_cast<decltype(get_primary_namespace)>(linker.get_address("__dl__ZN6soinfo21get_primary_namespaceEv"));
  }
}

constructor方法率先加载,读取linker的结构体。然后拿到g_soinfo_handles_map,get_soname和get_primary_namespace这三个结构体。

void disable_namespace(void) {
  for (auto&& [hdl, info] : *g_soinfo_handles_map) {
    const char* name        = get_soname(info);
    android_namespace_t* ns = get_primary_namespace(info);//访问库名称空间 
    void* start = reinterpret_cast<void*>(page_start(reinterpret_cast<uintptr_t>(ns), 4096));
    size_t size = page_align(reinterpret_cast<uintptr_t>(ns) + sizeof(android_namespace_t), 4096);
    mprotect(start, size, PROT_READ | PROT_WRITE);//修改内存页为可读写
    ns->set_ld_library_paths({"/system/lib64", "/sytem/lib"});//扩展允许的目录列
    ns->set_isolated(false);
  }
}

2. SDK Restriction: hidden API

从Android 9(API级别28)开始,该平台限制您的应用可以使用哪些非SDK接口。只要应用程序引用非SDK接口或尝试使用反射或JNI获取其句柄,这些限制就适用。

在这里插入图片描述
比如在访问geVmFeatureList等在blacklist的接口时会出现异常返回
在这里插入图片描述
此限制在称为hiddenapi的Android运行时(ART)中实现。通过查看位于jni_internal.cc中的GetStaticMethodID()的源代码- 我们可以看到函数调用FindMethodID(),它执行检查以允许或拒绝对方法的访问:

static jmethodID FindMethodID(...) {
  ...
  if (... && ShouldBlockAccessToMember(method, soa.Self())) {
    ...
  }
}

该函数的调用链如下:
在这里插入图片描述
此函数可以返回四个值,具体取决于配置限制的方式:

无检查:无限制地启用访问。
警告:显示警告但允许访问。
深灰色和黑色列表:拒绝访问深灰色列表和黑名单。
黑名单:仅拒绝访问黑名单。

默认情况下,运行时配置为深灰色和黑色列表参数。库可以通过尝试解析Java方法android.os.Debug.getVmFeatureList()来触发限制:

jboolean Java_re_android_hiddenapi_MainActivity_isHiddenApiEnabled(...) {
  jclass Debug  = env->FindClass("android/os/Debug");
  jmethodID mid = env->GetStaticMethodID(Debug, "getVmFeatureList", "()[Ljava/lang/String;");

  if (mid == nullptr) {
    __android_log_write(ANDROID_LOG_VERBOSE, "hiddenapi", "Can't load method!");
    return true;
  }
  return false;
}
2. bybass hidden API

Restriction is implemented in libart.so.
This library is always loaded in the process memory space of applications and we can get a pointer on the art::Runtime class.
SetHiddenApiEnforcementPolicy() can be called to disable the restriction.
Inline functions enable to avoid dependencies against libart.so.

在上一部分中,我们解释了主检查最终会调用GetHiddenApiEnforcementPolicy() ,该调用在art :: Runtime类中定义。

bybass限制的一种方法是使用许可配置强制运行时,例如EnforcementPolicy :: kNoChecks

要更改运行时配置,应用程序需要获取Android Runtime的当前实例的句柄。它可以通过调用静态方法art :: Runtime :: Current()来完成:

#include <runtime/runtime.h>
art::Runtime* current = art::Runtime::Current();

但是,此函数调用的缺点是它需要从libart.so 导入符号art :: Runtime :: instance_(实际上是_ZN3art7Runtime9instance_E)。因此,它使JNI库与libart.so库链接。正如我们在上一部分中所解释的那样,除非我们更新名称空间bybass其dlopen,否则我们将收到错误。

获取Runtime句柄的另一种方法是使用给予JNI_OnLoad(…)的第一个参数 是一个不透明指针–JavaVM * - 这实际上是内部类art :: JavaVMExt上的指针。

此类在art / runtime / java_vm_ext.h中定义,并在运行时提供访问器:

namespace art {
class JavaVMExt : public JavaVM {
  ...
  Runtime* GetRuntime() const {
    return runtime_;
  }
}

两个函数art :: JavaVMExt :: GetRuntime()和art :: Runtime :: Current()都是内联函数,但第一个函数返回一个class属性,而前者使用静态变量。

通过使用类属性,编译器不必导入符号,因为它只需要计算类实例中属性的偏移量:

static art::Runtime* my_runtime = nullptr;

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
  my_runtime = reinterpret_cast<art::JavaVMExt*>(vm)->GetRuntime();
  return JNI_VERSION_1_4;
}

一旦应用程序获得art :: Runtime对象上的指针,它就可以通过调用SetHiddenApiEnforcementPolicy()来更改当前的强制策略:

#include <runtime/hidden_api.h> // Expose EnforcementPolicy enum class
...
my_runtime->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kNoChecks);

此外,函数SetHiddenApiEnforcementPolicy()是内联的,因此此调用不会触发对libart.so的导入和依赖。

我们现在可以无限制地访问整个Android framework。

Hooking: 可能想要将函数GetHiddenApiEnforcementPolicy()与Frida 挂钩以返回所需的值。事实证明,此函数也是内联的,因此没有与此函数关联的符号。特别是,Module.findExportByName(…)不适用于此功能。

分析demo代码的实现:

void disable_hidden_api(void) {
  ALOG("Version: %s",           runtime->GetVersion());
  ALOG("EnforcementPolicy: %x", runtime->GetHiddenApiEnforcementPolicy());
  // Disable
  runtime->SetHiddenApiEnforcementPolicy(art::hiddenapi::EnforcementPolicy::kNoChecks);
}

and so much on

https://github.com/evilzhou/linkerpatch
android N linker patch

0x02 参考

Android Runtime Restrictions Bypass
代码实现
Android 7.0 dlopen的不同

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值