在编写 Android 测试用例的时候,有时候我们需要涉及到屏幕分辨率相关测试用例。
比如不同分辨率得到不同的像素数值,可以参考如下:
比如不同语言得到不同的字符串,可以参考如下:
其他相关的测试参数,参考 Device Configuration。
注意需要在 build.gradle 中增加资源包含信息,否则在测试的时候会找不到指定的资源文件,默认只测试代码,被测试的资源文件不打包进入应用。
参考如下:
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。
在编写 Android 测试用例的时候,有时候我们需要涉及到屏幕分辨率相关测试用例。
比如不同分辨率得到不同的像素数值,可以参考如下:
比如不同语言得到不同的字符串,可以参考如下:
其他相关的测试参数,参考 Device Configuration。
注意需要在 build.gradle 中增加资源包含信息,否则在测试的时候会找不到指定的资源文件,默认只测试代码,被测试的资源文件不打包进入应用。
参考如下:
在编写Android测试用例的时候,有时候我们需要测试与Activity相关的功能,同时又没办法直接调用被测试代码中的Activity的时候,我们需要创建DummyActivity的方式来进行。
我们希望这个DummyActivity只在测试代码中存在,相关的资源也只存在于测试代码里面使用,不侵入主代码。
可以参考如下布局进行项目的处理。
上述代码同样适用于Android资源文件相关的测试逻辑。
只是需要注意的是,当引入资源的时候,我们需要使用 {module_package}.test.R 的方式进行引入,否则代码中会提示找不到资源文件。
最近遇到一个应用崩溃问题,这个问题是由于在 Activity 的 onSaveInstanceState 中进行了数据的保存,然后在 onRestoreInstanceState 进行解析的时候出现崩溃。
实际测试的时候,发现当内存充足的时候,非常难稳定的诱发 Activity 的 onSaveInstanceState 事件。
早期的版本,可以通过 ActivityManagerNative.getDefault().setAlwaysFinish 来强制系统在 Activity 切换到后台之后,立即触发 onSaveInstanceState 。
参考代码如下:
但是遗憾的是,新系统比如 Android 8 等系统上,在真机环境中已经没办法通过上述的方法进行诱发了。系统会直接抛出异常,或者设置无效。
真机环境,可以尝试在 开发人员选项 中设置开启 “不保留活动” 按钮,如下图所示:
最近 Android 11 系统兼容性测试的时候,发现界面适配异常(标题栏沉浸式部分),已经无法通过网上流行的,通过反射方法获取状态栏的高度了。
于是翻了一下以前公共库里的代码,发现是使用如下代码获取状态栏高度的:
其实从 Android 9 开始,就已经对通过反射调用非公开 API 的方式进行警告了。
Android 11以前的系统,只是给出警告, Android 11直接抛出了调用异常。
于是搜索一下,发现网上已经更改成如下方法了:
仔细观察两个方法,会发现,其实两者都是获取了系统里的状态栏使用的某个资源的高度信息,然后作为状态栏的高度信息。
另外,网上流传的另一段代码
不能解决小屏模式下的显示问题,在小屏模式下得到的偏移并不是正确的状态栏高度。
这样就引发一个问题,那就是如果非官方的系统UI,比如小米,华为等自定义的UI,不使用这个资源文件,或者根本就没有这个资源文件,那么获取到的高度信息不就是不正确的了吗?
JDK下使用javax.tools.JavaCompiler
进行动态代码的生成,编译,执行。
在本文中,我们将研究如何将Java代码动态加载到正在运行的jvm中。 该代码可能是全新的,或者我们可能想更改程序中某些现有代码的功能。
(在开始之前,您可能想知道为什么到底有人会这样做。显而易见的示例是规则引擎之类的东西。规则引擎希望为用户提供添加或更改规则的能力,而不必重新启动规则。您可以通过将DSL脚本作为规则注入,由规则引擎调用此方法,这种方法的真正问题是必须对DSL脚本进行解释,使其运行起来非常缓慢。然后可以像程序中的任何其他代码一样编译和运行该程序,效率将提高几个数量级。
我们将要使用的库是Chronicle开源库Java-Runtime-Compiler 。
从下面的代码中您将看到,该库的使用极其简单-实际上,它实际上只需要几行。 创建一个CachedCompiler,然后调用loadFromJava。 (有关实际最简单的用例,请参见此处的文档。)
下面列出的程序执行以下操作:
这是完整的代码清单:
这是输出:
请注意,在每次加载策略时,在代码中我们都创建了一个新的ClassLoader和一个CachedCompiler。 这样做的原因是,ClassLoader一次只能加载一个特定类的一个实例。
如果仅使用该库来加载新代码,则可以这样做,而无需创建ClassLoader(即使用默认的ClassLoader)和CachedCompiler。
Android下使用由于无法使用javax.tools.JavaCompiler
,因此使用 linkedin/dexmaker 进行动态代码的生成,编译,执行。
原文链接:
平台概述https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview
Android 基础安全测试https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05b-basic-security_testing
Android 反逆向防御https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05j-testing-resiliency-against-reverse-engineering
最近需要解决用户使用手机克隆进行手机备份,某些设备相关的数据,比如设备ID的的存储文件也被克隆,导致用户更换设备之后,从本地读取到的设备ID信息依旧是上个手机的(Android Q以及以上的设备,有时候没办法获取设备ID,只能给一个随机数,因此只能存储在应用本地),这样会导致安全问题。
利用 Android KeyStore System,您可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入密钥库后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。
密钥库系统由 KeyChain API 以及在 Android 4.3(API 级别 18)中引入的 Android 密钥库提供程序功能使用。本文说明了何时以及如何使用 Android 密钥库提供程序。
注意:Android Keystore的API非线程安全,对于API的调用需要进行互斥操作。线程不安全的原因很简单,那就是不管上层创建多少个对象,底层都只对应同一个加解密硬件,硬件没有完成操作之前给出另一个操作命令会导致硬件工作异常。这也是官方文档 EncryptedFile 类和 EncryptedSharedPreferences 类中的方法不是线程安全的根本原因。
我们从 Google Tink 项目的讨论 Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it #535中可以了解到,目前三星Samsung的部分机型的底层实现就是线程不安全的。
更多的机型信息参考 Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it 崩溃率排行中,三星,小米遥遥领先!
1、 存储密匙:Android提供的这个KeyStore最大的作用就是不需要开发者去维护这个密匙的存储问题,相比起存储在用户的数据空间或者是外部存储器都更加安全。需要注意:这个密匙随着用户清除数据或者卸载应用都会被清除掉。
2、得益于Android独立的一套密匙库系统,可以提高安全性
Android 密钥库系统可以保护密钥材料免遭未经授权的使用。首先,Android 密钥库可以防止从应用进程和 Android 设备中整体提取密钥材料,从而避免了在 Android 设备之外以未经授权的方式使用密钥材料。其次,Android 密钥库可以让应用指定密钥的授权使用方式,并在应用进程之外强制实施这些限制,从而避免了在 Android 设备上以未经授权的方式使用密钥材料。
Android 密钥库密钥使用两项安全措施来避免密钥材料被提取:
为了避免在 Android 设备上以未经授权的方式使用密钥材料,在生成或导入密钥时 Android 密钥库会让应用指定密钥的授权使用方式。一旦生成或导入密钥,其授权将无法更改。然后,每次使用密钥时,都会由 Android 密钥库强制执行授权。这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥以未经授权的方式使用。
支持的密钥使用授权可归为以下几个类别:
作为一项额外的安全措施,对于密钥材料位于安全硬件内部的密钥(请参阅 KeyInfo.isInsideSecurityHardware()),某些密钥使用授权可能由安全硬件实施,具体取决于 Android 设备。加密和用户身份验证授权可能由安全硬件实施。由于安全硬件一般不具备独立的安全实时时钟,时间有效性间隔授权不可能由其实施。
您可以使用 KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() 查询密钥的用户身份验证授权是否由安全硬件实施。
在需要系统级凭据时请使用 KeyChain API。在应用通过 KeyChain API 请求使用任何凭据时,用户需要通过系统提供的 UI 选择应用可以访问已安装的哪些凭据。因此,在用户同意的情况下多个应用可以使用同一套凭据。
使用 Android 密钥库提供程序让各个应用存储自己的凭据,并且只允许应用自身访问。这样,应用可以管理仅能由自己使用的凭据,同时又可以提供等同于 KeyChain API 为系统级凭据提供的安全优势。这一方法不需要用户选择凭据。
要使用此功能,请使用标准的 KeyStore 和 KeyPairGenerator 或 KeyGenerator 类,以及在 Android 4.3(API 级别 18)中引入的 AndroidKeyStore 提供程序。
AndroidKeyStore 注册为 KeyStore 类型以用于 KeyStore.getInstance(type) 方法,而在用于 KeyPairGenerator.getInstance(algorithm, provider) 和 KeyGenerator.getInstance(algorithm, provider) 方法时则注册为提供程序。
生成新的 PrivateKey 要求您同时指定自签署证书具备的初始 X.509 属性。之后,您可以使用 KeyStore.setKeyEntry 将证书替换为由证书颁发机构 (CA) 签署的证书。
要生成密钥,则使用 KeyPairGenerator 和 KeyPairGeneratorSpec:
要生成密钥,请使用 KeyGenerator 和 KeyGenParameterSpec。
AndroidKeyStore 提供程序的使用通过所有的标准 KeyStore API 加以实现。
通过调用 aliases() 方法列出密钥库中的条目:
通过从密钥库提取 KeyStore.Entry 并使用 Signature API(例如 sign())签署数据:
类似地,请使用 verify(byte[]) 方法验证数据:
生成密钥或将密钥导入到 AndroidKeyStore 时,您可以指定密钥仅授权给经过身份验证的用户使用。用户使用安全锁定屏幕凭据(模式/PIN/密码、指纹)的子集进行身份验证。
这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥被未经身份验证的用户使用。
如果密钥仅授权给经过身份验证的用户使用,可以将其配置为以下列两种模式之一运行:
Android 提供了 KeyStore 等可以长期存储和检索加密密钥的机制,Android KeyStore 系统特别适合于存储加密密钥。
“AndroidKeyStore” 是 KeyStore 的一个子集,存进 AndroidKeyStore 的 key 将受到签名保护,并且这些 key 是存在系统里的,而不是在 App 的 data 目录下,依托于硬件的 KeyChain 存储,可以做到 private key 一旦存入就无法取出,
每个 App 自己创建的 key,别的应用是访问不到的。
它提供了限制何时以何种方式使用密钥的方法,比如使用密钥时需要用户认证或限制密钥只能在加密模式下使用。
一个应用程式只能编辑、保存、取出自己的密钥。
App可以生成或者接收一个公私密钥对,并存储在Android的Keystore系统中。公钥可以用于在应用数据放置到特定文件夹前对数据进行加密,私钥可以在需要的时候解密相应的数据。
作用:
KeyStore 适用于生成和存储密钥,这些密钥可以用来加密运行时获取到的数据,比如运行时,用户输入的密码,或者服务端传下来的 token。
对称式加解密(AES)速度较快,但是对称式的Key若要存在 KeyStore 裡,Api Level一定要在23以上才支持,23以下是无法存入 KeyStore 的,非对称式的Key則不在此限。
考慮到加解密效能、版本兼容,下面會介紹用非对称式+对称式來加解密。
主流的加密方式有:(对称加密)AES、DES 、EC (非对称加密)RSA、DSA
工作模式:
DES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB);
AES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB)、计数器模式(CTR),伽罗瓦计数器模式(GCM)
PKCS5Padding是填充模式,还有其它的填充模式;
对于初始化向量 iv: 初始化向量参数,AES 为16bytes. DES 为 8bytes
(1)产生随机的 RSA Key
产生 RSA Key 会使用到 KeyPairGenerator:
其中 KeyPairGeneratorSpec 在Api 23以上已经 Deprecated 了;
Api level 23以上改使用 KeyGenParameterSpec
Api 23 以上使用 KeyGenParameterSpec
Api 23 以下使用 KeyPairGeneratorSpec
注意,已知,在某些低端设备上,RSA密钥对生成的时间可能超过1S以上。尝试过异步子线程初始化,但是由KeyStore内部的函数在实现的时候,线程不安全,导致子线程初始化的时候诱发了异常行为,因此只能在主线程中进行操作。
(2)产生AES Key后,并用RSA Public Key加密后存入SharedPrefs
1]加密存储:使用RSA Public Key 加密 AES Key,存入缓存中。
2] 解密使用:使用RSA Private Key 解密 得到 AES Key。
获取AES :
再使用AES 加解密内容:
对于:Cipher 初始化
具体使用:
iv 初始化向量
关于RSA:
使用RSA加解密时,在较低版本的手机上可能无法选择OAEP(最优非对称加密填充,RSA的加密解密是基于OAEP的)這個模式;
因此可以改使用RSA_PKCS1_PADDING模式,使用这个模式的話,输入必须比RSA的Key最大长度少11個字节,如果需要被加密的字串过长的话,可以在产生Key时指定Key Size长度,或是将字串分段加密。
以预设Key Size = 2048bit(256byte)來说,输入最长只能到256–11=245byte,我們可以透过setKeySize(int keySize)指定Key的长度,但是Key Size越大,加解密时速度就越慢。
需要注意,由于设备可能存储密钥到硬件设备(KeyInfo.isInsideSecurityHardware()),然而硬件设备不一定能保存我们手工指定的某些长度的密钥。导致如果我们设置了指定长度,可能由于硬件设备不支持,反而只能存储到系统中,造成密钥存储的安全性反而下降了。
我们希望厂家设置的默认值是硬件能支持的最大安全性,并且尽量存储到硬件中。尽管密钥安全性可能下降了,但是存储安全性反而上升了。
或者从高到低重试,选择当前设备支持的最高的硬件存储的长度。究竟是存储位置重要还是加密级别更重要,需要权衡。
判断生成的密钥是否由硬件存储,参考如下代码:
获取设备生成的密钥的长度,参考如下代码:
需要解决的一个疑惑就是,既然可以通过
的方式获得RSA私钥,那么,我们能不导出这个私钥呢?
答案显然是不能的,原因在于系统给出的私钥只是一个代理,并没有实际的私钥数据,私钥数据被存储在相关的硬件或者系统内核中,主要证据就是privateKey.getEncoded()
返回了null
,这样就实现了关键的私钥数据都无法获得。
参考如下测试代码:
需要注意的一个问题在于,由于RSA的大素数搜索机制,导致每次生成密钥的时间可能会超过预期,最长的可能会耗时1-2S以上。因此,如果设备支持,我们直接申请硬件AES密钥的方式来进行数据的加解密操作,达到更高的安全程度。
可用的参考代码如下:
参考 ubuntu 20.04编译Android 11源代码&模拟器 完成Android 11源代码的编译工作,保证能编译通过。
想自己手工编译Frida
源代码的话,请参照下面:
如果想直接下载对应版本的Frida
库并存放到已经编译过的库位置,由于64
位系统需要兼容32
位应用,因此需要安装两个版本的动态库:
创建Frida Gadget
库的配置文件
里面的配置内容如下:
观察Frida
源代码,发现在 frida-core/lib/gadget/gadget-glue.c
中配置了lib
库的入口函数
这就意味着只要使用dlopen
加载frida-gadget
,我们就能实现对于指定应用的Hook
。
我们只需要监听子进程,不需要在Zygote
中加载,因此只需要在源代码 frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
的com_android_internal_os_Zygote_nativeForkAndSpecialize
函数中增加加载代码:
具体添加位置如下:
编译并重新生成系统镜像:
选择system-qemu.img和vendor-qemu.img,这两个镜像是专门为qemu运行制作的,如果选择system.img 和vendor.img,则avd运行失败。
上面运行起来的镜像是从~/AndSrc/aosp/out/debug/target/product/generic/hardware-qemu.ini
即可读取配置信息的,但是这个文件直接修改无效,我们如果需要修改参数,只能从启动参数中设置。
比如我们如果需要增大内存,开启GPU
的支持,则执行如下命令:
最近在浏览 Android 11 源代码的时候,发现在ART虚拟机头文件 art_method.h 中,存在大量的类似REQUIRES_SHARED(Locks::mutator_lock_);
的代码。
我们认识到在移动端开发中安全性设置非常重要,尤其是目前非常流程H5混合式开发APP,在Android开发中,我们可以通过证书锁定的方式来增加客户端与服务端的安全保障《证书锁定SSL Pinning简介及用途》,本文主要介绍 SSL数字证书在Android开发中的证书锁定(SSL/TLS Pinning)
通常由CA权威机构签发的证书,其根证书都内置在最新的Android操作系统中,因此默认情况下可不进行SSL证书锁定,开发APP时也就变得非常简单,以infinisign.com为例,摘自App security best practices
本方案是官方提供,但需要依赖Android N(Android 7.0 API 24)及以后版本,可在APP开发阶段在APP中内置安全性设置,以达到防止中间人攻击(MITM)的目的,此方法只限制在Android 7.0 API 24以后版本,因此该版本之前的安全性设置仍然需要使用证书锁定方法,本文以infinisign.com为例。
创建文件res/xml/network_security_config.xml
,需要注意的是,使用证书锁定,需要配置一个备份密钥,假如证书到期或更换了CA品牌后,不至于重新发行APP,这个备份密码可以是中级证书或根证书。
通俗的说,如果系统检测到签发证书过期了,则自动使用其中级或才根级证书作为验证,因为通常中级机构、根机构的证书到期时间非常长。
但实际情况是,infinisign.com所售的CA签发证书有效期都是一年,而现在发行APP或更新APP通常在一年都会有更新重新上架操作。
在Androidmanifest.xml
引入配置文件android:networkSecurityConfig="@xml/network_security_config"
okHttp是一个用于Android处理网络请求的开源项目,是安卓端最流行的轻量级的网络框架,其主要用来替代HttpUrlConnection处理方式
不过需要注意的是,okHttp锁定证书方式不适用于公钥锁定方式,必须以证书锁定方式内置SSL数字证书。
TrustManager是一个比较老的证书锁定方法,主要用于早期的Android版本或者用于一些CA根机构在Android系统中缺失根证书的情形下,当然也适用于自签名证书的锁定,通常我们不建议这样做,因为TrustManager的缺点是中间人仍然可以使用成熟的绕过方案来实现截持。
在SSL普及的今天,let's encrypt的开源免费解决方案,和一些入门的便宜DV域名型SSL证书(见PositiveSSL ¥39/年)足以媲美自签名方案。
详细请参考javax.net.ssl.TrustManager接口实现,简单步骤如下
在APP源码中内置证书
使用KeyStore加载证书
TrustManagerFactory实例化证书
创建SSLContext实例,与TrustManager进行绑定。
在多数移动操作系统中有大大小小几十个CA机构内置的根证书,但也不排除已经不被信任的CA机构存在的旧根证书,还有一些例如国内的一些基于Android老版本的操作系统仍然面临着安全风险,所以使用SSL数字证书锁定(SSL/TLS Pinning)的目标是缩小可信CA的范围,让APP客户端传送数据更安全,更切实地保障用户数据。