Android客户端安全测试系列

前言

最近整理Android测试工具,顺便做一个复习,部分测试环境需要现搭,可能会影响更新速度。

一、签名安全

测试方法

1
jarsigner.exe -verify demo.apk -verbose -certs
  • jarsigner.exe来自JAVE_PATH/bin
  • 如MobSF此类工具也可直接解析签名信息,后续系列详细补充

测试结果

命令执行输出摘要如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s       7500 Thu Mar 12 12:20:46 CST 2015 META-INF/MANIFEST.MF

>>> 签名者
X.509, CN=Android Debug, O=Android, C=US
[证书的有效期为7/28/13 4:56 PM至7/21/43 4:56 PM]
[无效的证书链: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]

7553 Thu Mar 12 12:20:46 CST 2015 META-INF/CERT.SF
776 Thu Mar 12 12:20:46 CST 2015 META-INF/CERT.RSA

s = 已验证签名
m = 在清单中列出条目
k = 在密钥库中至少找到了一个证书
i = 在身份作用域内至少找到了一个证书

- 由 "CN=Android Debug, O=Android, C=US" 签名
摘要算法: SHA1
签名算法: SHA1withRSA, 1024 位密钥

jar 已验证。

警告:
此 jar 包含其证书链无效的条目。原因: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
此 jar 包含其签名者证书为自签名证书的条目。
此 jar 包含的签名没有时间戳。如果没有时间戳, 则在其中任一签名者证书到期 (最早为 2043-07-21) 之后, 用户可能无法验证此 jar。
签名者证书将于 2043-07-21 到期。

关注结果:

  • 确认是否输出jar 已验证
  • 证书签名者身份,如CN=Android Debug, O=Android, C=US

其他说明

从我以前做过的一些Android客户端安测案例来看,遇到的通用签名情况还是挺多,一般有外包/第三方开发团队的签名。
举个例子来说APK的签名,就好发公文流程,你的公文(APP)上盖的路人甲的公章(路人甲签名),然而群众(手机)看到路人甲的公章(路人甲签名)就认为(信任)是你发的公文,从这个例子就不难理解通用签名/第三方签名的风险在哪个环节了。
作为APP所有者,需要考虑以后的版本更新问题、私钥泄露问题等。

二、进程注入

测试方法

使用工具已经放到github,方便各位乙方同学
项目地址:https://github.com/x51/droidsoject

测试结果

avatar

注入成功样例日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
D/INJECT  ( 3738): [+] Calling dlclose in target process.
D/INJECT ( 3738): [+] Target process returned from dlclose, return value=0, pc=b6f590bc
E/WakeLock( 1893): GCM_HB_ALARM release without a matched acquire!
D/INJECT ( 3933): [+] Injecting process: 3719
D/INJECT ( 3933): [+] get_remote_addr: local[b6f2a000], remote[b6f58000]
D/INJECT ( 3933): [+] Remote mmap address: b6f6ab25
D/INJECT ( 3933): [+] Calling mmap in target process.
D/INJECT ( 3933): [+] Target process returned from mmap, return value=8cd4c000, pc=0
D/INJECT ( 3933): [+] get_remote_addr: local[b6f8d000], remote[b6fc6000]
D/INJECT ( 3933): [+] get_remote_addr: local[b6f8d000], remote[b6fc6000]
D/INJECT ( 3933): [+] get_remote_addr: local[b6f8d000], remote[b6fc6000]
D/INJECT ( 3933): [+] get_remote_addr: local[b6f8d000], remote[b6fc6000]
D/INJECT ( 3933): [+] Get imports: dlopen: b6fc6f11, dlsym: b6fc6e61, dlclose: b6fc6ddd, dlerror: b6fc6d8d
D/INJECT ( 3933): [+] Calling dlopen in target process.
D/INJECT ( 3933): [+] Target process returned from dlopen, return value=90c805b8, pc=0
D/INJECT ( 3933): [+] Calling dlsym in target process.
D/INJECT ( 3933): [+] Target process returned from dlsym, return value=8d665c5d, pc=0
D/INJECT ( 3933): hook_entry_addr = 0x8d665c5d
D/INJECT ( 3933): [+] Calling hook_entry in target process.
D/DEBUG ( 3719): Hook Success, getpid = 3719
D/DEBUG ( 3719): Hello x51
D/DEBUG ( 3719): You can inject some evil code here.
D/INJECT ( 3933): [+] Target process returned from hook_entry, return value=0, pc=0

查看目标进程mmap分布
avatar

其他说明

1、libhello.so的路径在inject中写死了/data/local/tmp/libhello.so,可以改但没必要.

三、组件安全

测试方法

本节测试需要借助drozer来完成,关于如何安装该工具及详细介绍请移步https://labs.f-secure.com/tools/drozer]

查看攻击面:

1
2
3
4
5
6
7
dz> run app.package.attacksurface com.mwr.example.sieve
Attack Surface:
3 activities exported
0 broadcast receivers exported
2 content providers exported
2 services exported
is debuggable

或者直接查看AndroidManifest.xml文件中标记为exported="true"的组件(注意到这儿的exported数量和上面差异,因为入口Activity默认是导出的)

upload successful

以activity为例,查看列表:

1
2
3
4
5
6
7
8
9
10
dz> run app.activity.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.FileSelectActivity
Permission: null
com.mwr.example.sieve.MainLoginActivity
Permission: null
com.mwr.example.sieve.PWList
Permission: null

dz>

调用外部activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
dz> run app.activity.start -h
usage: run app.activity.start [-h] [--action ACTION] [--category CATEGORY [CATEGORY ...]]
[--component PACKAGE COMPONENT] [--data-uri DATA_URI]
[--extra TYPE KEY VALUE] [--flags FLAGS [FLAGS ...]]
[--mimetype MIMETYPE]

Starts an Activity using the formulated intent.

Examples:
Start the Browser with an explicit intent:

dz> run app.activity.start
--component com.android.browser
com.android.browser.BrowserActivity
--flags ACTIVITY_NEW_TASK

If no flags are specified, drozer will add the ACTIVITY_NEW_TASK flag. To launch an activity with no flags:

dz> run app.activity.start
--component com.android.browser
com.android.browser.BrowserActivity
--flags 0x0

Starting the Browser with an implicit intent:

dz> run app.activity.start
--action android.intent.action.VIEW
--data-uri http://www.google.com
--flags ACTIVITY_NEW_TASK

For more information on how to formulate an Intent, type 'help intents'.

Last Modified: 2012-11-06
Credit: MWR InfoSecurity (@mwrlabs)
License: BSD (3 clause)

optional arguments:
-h, --help
--action ACTION specify the action to include in the Intent
--category CATEGORY [CATEGORY ...]
specify the category to include in the Intent
--component PACKAGE COMPONENT
specify the component name to include in the Intent
--data-uri DATA_URI specify a Uri to attach as data in the Intent
--extra TYPE KEY VALUE
add an field to the Intent's extras bundle
--flags FLAGS [FLAGS ...]
specify one-or-more flags to include in the Intent
--mimetype MIMETYPE specify the MIME type to send in the Intent
dz>

测试结果

开始测试,Activity调用时需要先完全退出APP,避免影响结果。
直接调用:

1
run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.FileSelectActivity

调用成功效果:
upload successful

在这儿一般出现的安全问题有本地拒绝服务、越权等问题,比如经典的手势密码绕过。

调用Service发生本地拒绝服务实例:

upload successful

其他说明

除了Activity组件,还有Service、Broadcast Reciever、Content Provider,测试方式大同小异,一般来说APP的主入口Activity是必须导出的,根据功能还需导出部分Service等,所以还是要以实际危害结果为主。
BTW:MobSF的动态检测功能包含了组件导出安全测试,会自动调用暴露组件并生成截图,效果不错。

四、客户端完整性校验

测试方法

这儿只考虑静态校验文件完整性,所以操作简易,不考虑其他绕过或修复等情况,简单来说流程:反编译——增/删/改文件——回编——签名——安装,结果以最终成功运行且改动生效为准。

apktool反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
I: Using Apktool 2.4.0 on xxx.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
S: WARNING: Could not write to (/home/john/.local/share/apktool/framework), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Loading resource table from file: /tmp/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

建议修改客户端名称,位于./res/values/strings.xml

upload successful
回编译过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
I: Using Apktool 2.4.0
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether sources has changed...
I: Smaling smali_classes2 folder into classes2.dex...
I: Checking whether resources has changed...
I: Building resources...
S: WARNING: Could not write to (/home/john/.local/share/apktool/framework), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Copying libs... (/lib)
I: Copying libs... (/kotlin)
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...

成功后还需签名,不然提示Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]

使用测试签名对回编译后的apk文件签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  正在签名: org/apaches/commons/codec/language/bm/sep_exact_french.txt
正在签名: org/apaches/commons/codec/language/bm/sep_exact_hebrew.txt
正在签名: org/apaches/commons/codec/language/bm/sep_exact_italian.txt
正在签名: org/apaches/commons/codec/language/bm/sep_exact_portuguese.txt
正在签名: org/apaches/commons/codec/language/bm/sep_exact_spanish.txt
正在签名: org/apaches/commons/codec/language/bm/sep_hebrew_common.txt
正在签名: org/apaches/commons/codec/language/bm/sep_lang.txt
正在签名: org/apaches/commons/codec/language/bm/sep_languages.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_any.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_french.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_hebrew.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_italian.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_portuguese.txt
正在签名: org/apaches/commons/codec/language/bm/sep_rules_spanish.txt
>>> 签名者
X.509, CN=x51, OU=www.utf32.com, O=www.utf32.com, L=BeiJing, ST=Beijing, C=CN
[可信证书]

jar 已签名。

警告:
签名者证书为自签名证书。

测试结果

修改成功截图:

upload successful

其他说明

五、代码保护

测试方法

此项一般测试客户端是否有代码混淆、客户端加壳等措施,使用jad直接打开apk文件查看:

upload successful
是否混淆/加壳也比较容易判断,免赘述。

测试结果

其他说明

  • 如果目标APP已加壳/混淆, 不再继续脱壳/反混淆操作。

六、界面劫持保护

测试方法

大概原理:恶意软件监控栈顶程序,如果发现当前栈顶是目标页面,则替换之,达到劫持目的。具体关于栈、Activity等概念此处省略。Android系统更新到6.0以后,对权限的管理机制发生变化,通过以下方法获取栈顶程序不再有效。
实现了一个简单的测试工具,输入目标包名即可在APP启动时进行劫持,劫持效果为一个透明页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="该页面存在被劫持风险! \n tested by www.utf32.com." />
</RelativeLayout>

upload successful

测试结果

启动劫持服务,默认目标:com.android.settings,打开系统设置,劫持成功:

upload successful
当前栈内Activity情况:

upload successful

其他说明