聊聊Android签名检测总结与反思
背景:
这篇文章只讲Android端签名检测,安卓发展到现在,因为国内环境没有谷歌市场,所以很多官方推荐的Api没法使用 ,所以国内的签名检测方式也是“千奇百怪” 。发展至今每种方法都有一些绕过或者对抗手段,这些方法很难说就一定准 ,但是我们能做的就是取尽可能的提高攻击者的成本,提升Apk的签名检测能力,防止灰黑产进行攻击 。基础的什么Java获取签名信息这种基础方案,这里暂时跳过 ,不在过多叙述 。这篇文章主要分为Java和Native两部分 ,分别从不同的视角取检测签名,包括如何对抗等 , 其中包含一些大厂和企业壳的核心检测签名思路 。
Java层检测签名:
1、IPC协议获取签名信息 :
首先讲这块之前需要先简单讲一下IPC协议的实现 。
安卓基础架构就是CS架构。每个App都是客户端,服务端只有一个 ,客户端和服务端是不同的进程 。
这样做的,客户端一旦发生崩溃不影响服务端 ,服务端也可以根据不同的uid实现不同的鉴权操作 。当我们获取一些apk信息的时候都是客户端发送IPC协议 。
服务端接收以后进行处理,利用binder进行通讯,然后把数据写到客户端内部,客户端拿到 。这块设计很多技术点 ,比如动态代理,IPC协议 ,Binder通讯等
上面说起来可能很难懂 ,首先我们去Hook PackageInfo的构造方法 ,根据栈回溯看看如果想要生产一个PackageInfo到底需要哪些流程 。
然后讲一下,为什么推荐使用IPC协议去获取签名。由点到面中间可能会存在哪些问题 。大家在测试的时候最好在谷歌原生的系统上测试,如果是国产厂商可能会添加自己的一些拦截器 。测试版本Android 11 。从上往下看 ,一步一步讲一下 每一步到底做什么的
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 |
|
总结:
客户端IPC协议流程图简述:
发起人App调用获取PackageInfo-> PackageManager收到请求,判断是否存在cache,如果不存在 ->服务端代理人 ->
发起Binder协议->服务端接收 ,处理完毕写入->客户端接收。
这块我们发现,很多逻辑都是没用的,都是客户端去为了各种方便封装了很多方法,但是最终目的都是为了发送IPC协议和服务端通讯 ,
但是其中,中间每一层级都可能导致我们的程序被Hook导致我们拿到的数据是不安全的 ,比如攻击者是直接hook PackageManager 或者直接hook PackageManager代理人,然后修改对应的数据内容。导致我们的数据不安全 ,被替换 。
所以得出结论:
我们可以直接模拟IPC协议直接和服务端通讯,因为客户端这些都是不安全的 ,直接通讯绕过这些复杂的处理逻辑,但是弊端就是需要兼容不同的android版本 , 否则可能会导致解析失败。
实现代码如下:
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 |
|
反思:
这种方式一定是安全的么?
不是的,如果这块有个细节就是 我们本质上还是需要通过binder驱动进行通讯 ,和PackageInfo.CREATOR解析 。
如果攻击者 直接hook了binder驱动 ,或者hook解析方法还是可以绕过 。比如binder也是会有一些入口,还是可能被Hook劫持 。
- binder的入口android.os.BinderProxy->transact方法 。
或者直接hook签名解析的方法,因为在getPackageInfo也是需要去解析签名信息的,可以直接hook签名信息解析这块逻辑 。
- android.content.pm.PackageParser->generatePackageInfo 系统解析签名方法
- PackageInfo.CREATOR PackageInfo Parcel 解析签名方法都是可以的 。
还可以继续增强么?
可以根据安卓源码去自实现binder驱动和解析过程 ,然后连本地的binder也不信任,直接和服务端通讯也是可以的 。
Java层还有比较好的签名检测手段么?
目前推荐的只有这么一种,大部分都是SO层做的检测,Java层还可以做一些代理人的动态代理,包括一些代理人的classloader,或者
PackageInfo.CREATOR的classloader之类的去做对抗 。还有一个tee检测也很不错 ,用于检测各种证书,也是容易被hook,这块不多说了 ,重点放在So层 。
So层的基础svc拦截就可以bypass掉很多小白 。
Native层检测签名:
1、基础SVC读取签名:
这块什么是svc就不多说了,说了很多遍了 。感兴趣可以看 [原创]聊聊大厂设备指纹其二&Hunter环境检测思路详解!-Android安全-看雪-安全社区|安全招聘|kanxue.com
我之前再看雪发的文章 。
这种方式主要是直接在方法里面内敛svc的方式去openat打开已安装的apk ,然后去解析证书文件 。
如果svc层不做处理 ,很难绕过检测 。解析apk签名过程如下 ,代码来自Magisk ,调用 checkSign 对返回的字符串进行判断即可 。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
|
2、文件权限:
当我们打开fd的时候,文件很有可能会被IO重定向,重定向到一个新的文件,但是这个文件不可能是系统的,因为没权限。
所以当我们得到这个fd的时候可以,再次通过fstat去获取已经打开的这个fd 是不是一个系统文件,系统文件的gid和uid都是1000 。
确认我们打开的fd是系统文件 。因为/data/app/包名/base.apk是一个系统文件 。
对抗的话可以直接svc fstat 退出阶段,对返回结果进行bypass即可 。
3、Readlinkat反查路径:
当我们打开fd的时候,文件很有可能会被IO重定向,重定向到一个新的文件,我们根据fd通过readlinkat 反查fd路径,判断和传入的路径是否相等,
如果相等则认为正确 。也是在SVC Readlinkat推出阶段对查询的buff进行二次覆盖 。
4、Readlinkat返回值截断:
这个思路主要是根据 Readlinkat的返回返回值进行检测,在创建数组的时候 数组大小采用Readlinkat返回值的大小 。
Readlinkat返回值 返回值返回的是一个查询路径结果的长度,如果攻击者只改了Readlinkat的参数,改了路径,但是返回值忘记修改,这样他的返回值就会被阶段,也就是大小不匹配,也可以检测出来 路径和传入的原始路径是否相等 。
5、Inode:
inode是linux的文件或者目录的唯一标识符,每个文件都是独一无二的 ,如果文件被关闭,再次打开一个新的文件可能会被占用,不过一些系统apk 不会被删除和关闭 。
在maps里面 每个item类似如下 :
7041e02000-7041e4e000 r--p 00000000 fc:01 25131 /apex/com.android.conscrypt/lib64/libc++.so
这里面的倒数25131 第二项就是inode,我们当得到一个fd的时候可以获取文件的fd 信息 ,然后再去maps里面获取已经打开的文件fd信息 。
判断打开文件的inode和内存里面的inode是否相等 。
对抗的话可以直接svc fstat 退出阶段,对返回结果进行bypass即可 。
6、对已经打开的fd进行签名获取:
这个方法过于激进,很少部分人知道 ,很多手机在刚启动的时候会自动打开 /data/app/包名/base.apk
比如 我们可以直接遍历已经打开的fd ,然后对这个readlinkat反查路径 , 如果是base.apk的fd直接对fd进行签名解析 。相当于对已经打开的文件进行签名解析 。
因为这个apk是系统启动阶段就被打开了 ,所以不会走IO重定向的逻辑 ,相对安全 。
经过测试,有的手机App在开机启动阶段不会打开 ,只适合Apk再启动阶段打开了base.apk ,直接对已经打开的fd解析即可 。可以逃逸IO重定向 。
对抗的话 也很简单3种方案
-
如果有人通过已经打开的文件去遍历fd , 在readlinkat 结果处理 ,如果是base.apk直接隐藏掉 。
-
直接处理getdents64和getdents ,在遍历阶段直接找不到对应的fd即可
-
直接close,但是有的版本在close会报 fdsan异常,需要通过 android_fdsan_set_error_level 设置禁用
总结:
上述这些方案基本覆盖大多数手段 ,Hunter的核心检测思路也是上面这几种 。签名Native部分代码如下 。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
|
有没有一种方式可以轻轻松松绕过上面全部的检测呢?
经常有人问我,签名检测太难,不知道应该怎么绕过 ,有没有一种可以快速绕过全部检测的方案呢?并且我可以随便修改apk信息不被发现 。
其实很简单,在root手机上直接使用核心破解或者小米的三方lsp插件 Cemiuiler , 页面如下 。
直接禁用系统的签名验证,直接安装,也就是在破坏了原始签名的情况下 ,未签名,直接安装 。
**因为你没有修改apk原始的签名信息,他读取到的还是原始的签名信息,签名文件不会有任何变化 。**你二次签名本质的目的都是为了通过系统的签名检测 。