随着Android P预览版的发布,谷歌在改进系统稳定性的措施上又增加了新的限制,即应用程序引用非SDK接口,无论采用直接、反射、JNI获取等手段都将受到限制。在谷歌提供的预览版文档&&App Compatibility Changes一节中,我们可以知道如下信息:

在Frameworkdocumented中列出了能访问的sdk

Android P.DP1提供了测试非SDK接口调用日志输出

保留非SDK调用将会触发的异常类型

DP1限制效果预览

在官方提供的镜像环境下,当运行app时,其调用非SDKMethods/ Fields(函数/字段)则会触发保护机制而输出如下日志信息:

Log Format:

Log header + Method/Field + API signatures+(Greylist, Callers)

Log Header日志头部输出标识(Accessinghidden)

Method/Field表示当前是方法还是字段

API signatures与文件中内容格式等价

Methods

`class_descriptor>method_name(parameter_types)return_type`

Field编码

`class_descriptor->field_name:field_type`

Greylist3个限制等级,分别由3个文件列表提供。

a)Light greylist

b)Dark greylist

c)blacklist

CallersJNI、reflection

实现原理

Greylist所列出的三个限制等级,其数据来源于3个文本文件,包含了要被限制的函数或者字段。它们位于系统源码目录,在系统源码编译阶段完成从文本文件数据解析合并到dex格式文件的过程。(Dex文件中函数/字段被标记阶段,影响其access_flags_)

在art虚拟机内部存在一个转换过程。即将Dex格式中函数/字段被标记的值再次转换并存入art runtime访问标志(access_flags_)。

App运行时触发任意系统函数调用,进入art虚拟机内部时根据art的访问标志的值(access_flags_)识别出限制等级,从而达到限制非SDK调用的目的。

基于其上所描述,将分别从两个维度(编译阶段,类初始化阶段(art) )进行详细介绍。

编译阶段

由编译脚本控制3个文本文件

(hiddenapi-light-greylist.txt、hiddenapi-dark-greylist.txt、hiddenapi-blacklist.txt)

编译脚本路径:Framework/base/Android.mk

Android P 调用隐藏API限制原理_第1张图片

由编译脚本可知道:

hiddenapi-blacklist.txt,hiddenapi-dark-greylist.txt来源于Framework/base/config目录

hiddenapi-light-greylist.txt为private-dex.txt减去blacklist.txt与dark-greylist.txt的集合

(其中private-dex.txt位于目录Out/target/common/obj/PACKAGING/)

注:

APIsignatures(函数/字段)在3个文件中具备唯一性

位于config目录下的blacklist.txt、dark-greylist.txt内容为。(用官方提供的镜像测试发现存在dark-greylist数据,应该是DP1源码中末提供此数据)、

private-dex.txt为声明私有方式的集合数据(此处待验证)

2.编译阶段生成hiddenapi

源码位于: art/tools/hiddenapi

生成host可执行程序:out/host/linux-x86/bin/hiddenapi

Linux-x86:编译平台和生成目录对应

3.处理dex、jar

在满足1、2的前提下,在对应的目录下将会包含所需文件与程序,此时即可对dex进行处理。经过hiddenapi处理后,将完成对指定Dex文件中所有函数/字段重新标记,通过修改其access_flags_字段值实现。

hiddenapi运行参数:--light-greylist=out/target/common/obj/PACKAGING/hiddenapi-light-greylist.txt--dark-greylist=out/target/common/obj/PACKAGING/hiddenapi-dark-greylist.txt --blacklist=out/target/common/obj/PACKAGING/hiddenapi-blacklist.txt

编译时脚本

路径:build/core/definitions.mk

Android P 调用隐藏API限制原理_第2张图片

在源码编译后,请自行查阅编译日志build-aosp_walleye.ninja(设备pixel 2)。

4.Hiddenapi解析

1)遍历class.dex中的函数或者字段列表

Android P 调用隐藏API限制原理_第3张图片

2)IsOnApiList函数验证当前Method/Field是否存在文件中,且能得到以下值中一种

HiddenApiAccessFlags::kWhitelist => 0b00

hiddenapi-light-greylist.txt=> HiddenApiAccessFlags::kLightGreylist => 0b01

hiddenapi-dark-greylist.txt => HiddenApiAccessFlags::kDarkGreylist=> 0b10

hiddenapi-blacklist.txt => HiddenApiAccessFlags::kBlacklist => 0b11

3)SetHidden函数将2)中得到的二进制数据与ClassDataMethod/ClassDataField结构体中成员access_flags_原始值进行处理后重新写入(注意leb128格式保存)

ClassDataMethod/ClassDataField结构体成员access_flags_

Android P 调用隐藏API限制原理_第4张图片

b.其中access_flags_字段按bit存储表示不同含义

Bit(2:0)表示存储内容为public(0b001)、private(0b010)、protect(0b100)

Bit(8)表示当前method是native

算法原理:由2)步骤中得到值0bxx(低位)

如果值的低位为1,则原值与kAccVisibilityFlags进行异或(^)操作

注:

access_flags_原值低3位有且只有一位标记为1,其表示的意义为函数属于(private、protect、public)中一种,当经过异或运算后,新值低3位中有2位标记为1.则表示低位已经被写入。后续通过IsPowerOfTwo函数来校验access_flags_是否被修改。(校验思路:原值是否为2的幂次方,因为如果是2的幂次方只能存在一个1)

算法原理:由2)步骤中得到值0bxx(高位)

如果值的高位为1,则根据access_flags_中第8位的原始值来决定与kAccDexHiddenBit/kAccDexHiddenBitNative进行或(|)操作。

access_flags_第8位(bit8)表示native。

:非native:则取值为kAccDexHiddenBit

1: native则取值为kAccDexHiddenBitNative

Android P 调用隐藏API限制原理_第5张图片

4) 完成access_flags_值的读取和写入,主要涉及以下函数

Android P 调用隐藏API限制原理_第6张图片

5) 最后一步,重新校验dex头部签名

5.Hiddenapi处理后,完成从3个文本文件数据与原始dex格式文件的合并,即生成新的dex。

类初始化阶段(art)

前面已经分析过在编译阶段hiddenapi程序是如何将3个配置文件中每个函数/字段重新写入dex文件,在这个阶段我们从ClassLinker中Loadfield/LoadMethod来分析如何将Dex结构体中的access_flags_值转换为Art Runtime时所需的值(art结构体中access_flags_)。

以LoadField为例

文件位于art/runtime/class_liner.cc

Android P 调用隐藏API限制原理_第7张图片

1.此处校验是否是bootclassloader后,直接调用DecodeHiddenAccessFlags读取Dex缓存中access_flag_的值。

2.DecodeHiddenAccessFlags调用的是HiddenApiAccessFlags中的DecodeFromDex函数

DecodeFromDex功能和EncodeFromDex是一个相反的过程。

EncodeFromDex将二进制数据(0bXX)按格式存入dex结构体中的access_flags_

DecodeFromDex读取Dex结构体中的access_flags_中的值。并将2位bit转换为0~3的整数。等价于前面提到的

0b01 HiddenApiAccessFlags::kLightGreylist

0b10 HiddenApiAccessFlags::kDarkGreylist

0b11 HiddenApiAccessFlags::kBlacklist

那么对应关系如下:

hiddenapi-light-greylist.txt (0b01)、

hiddenapi-dark-greylist.txt (0b10)、

hiddenapi-blacklist.txt (0b11)

3.最后进入到EncodeForRuntime函数中,此函数的功能是将重新获取的2位二进制数据写入artmethod结构体中access_flags_中。以方便在art运行时进行最后校验。

不同于dex文件中access_flags_的存储方式,由上述代码可知,此处通过左移的方式将这2位二进制数据存储到art结构体中成员access_flags_的最高位。

4.在app运行时,会校验artmethod结构体中access_flags_最高2位的值

5.校验的手段包括直接调用、反射、JNI获取

1)关键调用过程

运行阶段请自行查阅GetMethodID/GetFieldID调用流程,最终会进入 到ShouldBlockAccessToMember这个函数进行校验

2)校验过程

其校验函数:ShouldBlockAccessToMember

源码位于:Art/runtime/hidden_api.h

Android P 调用隐藏API限制原理_第8张图片

Android P 调用隐藏API限制原理_第9张图片

其中,GetMemberAction调用HiddenApiAccessFlags中DecodeFromRuntime获取其返回值

Android P 调用隐藏API限制原理_第10张图片

通过返回值,就可以清楚的看到当前调用的函数或者字段是属于哪个属性。

Android P 调用隐藏API限制原理_第11张图片

kAllow = 0;此处对应关系:

0(0b00):=>HiddenApiAccessFlags::kWhitelist

1(0b01):hiddenapi-light-greylist.txt =>HiddenApiAccessFlags::kLightGreylist

2(0b10): hiddenapi-dark-greylist.txt =>HiddenApiAccessFlags::kDarkGreylist

3(0b11): hiddenapi-blacklist.txt =>HiddenApiAccessFlags::kBlacklist

AndroidP. DP1中通过Action的行为来判断:

0(0b00) kAllow直接放过

1(0b01) kAllowButWarn放过,但日志警告

2(0b10) kAllowButWarnAndToast放过,且日志警告和弹窗

3(0b11) kDeny拒绝

日志输出函数

Android P 调用隐藏API限制原理_第12张图片

总结

本文基于Android P.DP1研究非sdk限制机制的核心思路,其中忽略了一些细节。限于篇幅后续根据稳定版的改动再补充。如果有问题,欢迎大家在留言区指出与探讨!


http://kuaibao.qq.com/s/20180327G17Y3L00?refer=spider

更多相关文章

  1. Android多点触控开发原理
  2. 浅谈Android文件管理器的几种实现方式(原理篇)--对我有帮助
  3. Android WebView、js交互方式原理总结
  4. Android 操作系统 获取Root权限 原理解析
  5. Android运行原理及运行机制知识汇总
  6. Android Activity的onCreate()函数
  7. Softap热点原理分析

随机推荐

  1. android音频处理
  2. Android中AutoCompleteTextView的特殊使
  3. Android(安卓)修改U盘名称
  4. Android与蓝牙耳机建立连接的分析
  5. Android(安卓)点击back键两次退出程序
  6. 一、 Android完全退出应用程序
  7. Android(安卓)NDK编程实现终端功能(调用sy
  8. 50个常用sql语句 网上流行的学生选课表的
  9. SQL Server 数据库索引其索引的小技巧
  10. 设置SQLServer数据库中某些表为只读的多