首页
社区
课程
招聘
[原创]聊聊大厂设备指纹获取和对抗&设备指纹看着一篇就够了!
发表于: 2022-7-21 16:36 82542

[原创]聊聊大厂设备指纹获取和对抗&设备指纹看着一篇就够了!

2022-7-21 16:36
82542

现在大厂的设备指纹层出不穷,但是想要确保稳定性唯一性高精准其实也挺难的一件事,有的是通过设备信息比重进行的设备ID唯一值确认。比如A设备信息占比10%,B设备信息占比20%,当比重超过60%以上,设备指纹才会发生变化。这样的好处就是当你只修改某一个字段的时候,设备指纹不发生变化。还有的干脆找一个隐蔽的并且唯一的设备信息,作为缓存,每次读取缓存的方式去判断,设备信息是唯一,比如常见的有Native获取DRM,popen cat  /sys/devices/soc0/serial_number  ,svc读取bootid并且保存到文件,netlinker获取网卡。都是很常见并且隐蔽的的设备指纹。这篇文章主要介绍了各种指纹的获取情况,如何修改,站在上帝视角去俯看,攻击者和被攻击者遇到的问题 。


看完这篇文章主要你能学到如下:

1,常用的指纹检测有哪些?

2,如何修改设备指纹,难点在哪里,会有哪些坑需要踩?

3,现在的国内的指纹面临哪些问题?



Android本身是CS架构,客户端(client)服务端(server),我们常用的通过context上下文调用的API都是直接调用代理人的方式去调用的,而真正的服务端是ActivityManagerServer 简称,AMS, 他有很多代理,比如PackageManager,ActivityManager 等,这些都是AMS的代理人。而AMS就是被代理人代理模式是一种设计模式,代理人可以提供被代理人的部分或者全部功能,实现代码封装,做鉴权,代码安全的角度,代理模式很常用的设计模式。


AMS和代理们通过Binder进行通讯,Binder是什么,有什么好处这里就不详细展开了,安卓面试八股文,可以理解成进程间通讯的东西,底层实现是通过共享内存,数据传输,读取速度更快。当我们调用代理人的API得时候,本质上是通过Binder去发送一些数据包,和AMS通讯,当AMS收到消息以后把结果在传输给对应的代理人。然后返回给调用方。在每个Manager里面都有一个代理人 。


之前很久之前有一种动态代理的技术,原理就是替换了里面的代理人,因为代理是一个接口,然后我们自己通过Proxy这个类创建一个代理,然后反射set回去,就可以实现常用的API拦截和Hook。类似VA的沙盒,对多开的App提供一份自己实现的代理,然后控制这些代理的返回值,以此实现沙盒相关操作。还有一种比较好的过APK签名的方法就是直接Hook"水管" 也就是hook binder的通讯的方法,当接收到指定事件以后,直接修改具体的结果,以此对Java层进行全量Hook(binder的通讯方法被Hook以后,调用者和代理人只能拿到被修改以后的结果,以此实现Java层的全量Hook,后面再讲签名验证的时候我在详细说。)




因为在获取或者分析的时候,需要绕过反射9.0限制,这里采用的是Lsp作者的AndroidHiddenApiBypass进行隐藏API的调用 ,

在手机装了EDXP或者Magisk,以后一般隐藏API的限制都是默认去除的。因为在Edxp和Magisk里面也需要进行使用一些隐藏API。


设备指纹主要分为三部分,Java层设备指纹,Native设备指纹,popen执行一些命令获取设备信息,包括一些核心的设备指纹。

这篇文章也主要围绕这三部分进行展开讨论。比如一些内核文件等信息,我也会放在Native层进行讨论。

所有的每个设备指纹我都分为两部分

Get(如何获取,站在开发者角度) Mock (如何进行修改测试,站在攻击者角度)

所有的指纹我都分为三种类型,普通指纹,次要指纹,重要指纹。


在setting里面大家经常遇到的可能就是android id的获取的

API如下:


但是其实Setting里面还有很多别的功能东西,常见的就是Settings.Secure 和 Settings.Global

在Settings.Global 里面其实还有一些别的字段,具体API如下。这些都是一些比较隐蔽的设备指纹。


方法Hook


Global和Secure 都是实现的NameValueTable接口。

底层调用的是getStringForUser(resolver, name, resolver.getUserId()) 三个参数,如果Hook的话可以对这个方法进行入手。

Settings.Secure->getStringForUser & Settings.Global ->getStringForUser


内存反射:

很多开发者会采用内存反射的方式去获取变量,所以仅仅是通过mock方法的方式不够,如果进行Mock需要将Settings.Secure 和 Settings.Global 里面的内存变量进行修复,Settings.Global是放了一些全局变量,Settings.Secure放一些安全相关,


Settings.Secure->getStringForUser Settings.Global ->getStringForUser 和 具体方法如下。

可以看到,整体的cache都是放在sNameValueCache变量和MOVED_TO_GLOBAL变量内部进行存储 。

我们可以直接反射MOVED_TO_GLOBAL这个HashSet或者去sNameValueCache 这个变量然后去获取这个值的话,也是很容易可以拿到最真实的值的。所以光mock是不够的。

比如很多大厂就是Android高版本绕过了反射限制以后,或者判断当前手机没有API反射限制以后直接通过反射变量的方式去获取。

sNameValueCache在高版本是一个对象,低版本安卓他是一个ArrayMap 这块需要注意。

sNameValueCache修改的话可以调用API putStringForUser  往里面强制赋值 。这么一来下次对方在通过API去调用的时候就会拿到你已经进行过Mock的值。所以你修改的时候需要进行判断,当前获取的值是否是你已经Mock过的。


蓝牙的网卡不是普通的网卡,后面会介绍netlinker获取真实的网卡。

主要方法就是通过BluetoothAdapter->getAddress

可以看到这个方法主要是通过IPC的代理类方式去获取的。所以Hook的话尽可能先Hook代理的IPC类。先尝试反射 android.bluetooth.IBluetooth$Stub$Proxy然后Hook IPC里面的getAddress 而不是直接HookBluetoothAdapter->getAddress

因为很多大厂获取设备的指纹的时候会检测这个方法是否被Hook,检测也很简单,只需要获取这个artmethod结构体以后

判断这个方法入口是否被替换,比如Sandhook之类的常用的Hook框架,低版本采用的是inlinehook形式,在高版本里面采用的是入口替换,可以直接获取到方法的入口的函数地址,判断一下函数所在的so即可。所以尽可能HookIPC的方法。如果用XPosed去修改的话,还需要注意魔改,否则大厂会通过XposedHelpers->sHookedMethodCallbacks变量把你Hook的方法进行上报。


小技巧:这个变量是一个静态变量,所以我们只需要拿到XposedHelpers这个class即可。想要拿到class必须先拿到这个类的classloader,正常的Xposed是通过系统的classloader作为父类classloader,但是edxp这种,是一个方法内部的成员变量,没有任何地方引用这个classloader,所以想拿到这个classloader需要用到内存漫游。把内存全部的classloader都从内存抠出来,然后挨个去反射获取XposedHelpers 即可。代码可参考如下:

https://bbs.pediy.com/thread-269094.htm


sHookedMethodCallbacks里面保存了XPosed全部的Hook方法信息,用于石锤当前方法是否被Hook。获取被Hook方法具体如下:

XposedHelpers->methodCache 不建议使用,如果攻击者使用了XposedBridge->HookAllmethod 的话,可能会导致Hook方法上报的遗漏。


这个变量在高版本里面基本已经拿不到,及时拿到了也是一个unknow,但是也需要兼容低版本的Android 。

如果返回的不是空,并且不是 unknow 或者UNKNOWN,随机一份原始长度的字符串即可。另外该字段同上,也可以直接对IPC类进行处理,直接Hook IPC对象getSerialForPackage 方法即可 ,的实现方法具体如下 。


这些基础的Java设备指纹字段没啥好说的,百度一下就能找到具体的获取方法,但是修改的时候需要注意,不要直接Hook,尝试优先Hook ipc即可 。


Build里面还是有很多有用的东西,比如手机是否开启adb ,usb接口的状态之类的。我们主要将Build里面分为两部分 。指纹相关又分为两部分,单一字段 /复合字段。


1,配置相关

2,指纹相关

单一字段(只有一个设备信息)

复合字段(多个单一字段复合而成)

这个单独通过Java层去修改是完全不够的,底层走的是system_property_get 这个方法(在native指纹部分会详细介绍)。

还有要防止popen getprop 这种方法去扫描全部的Build相关参数(popen getprop 在popen相关会详细介绍,这里只介绍Java应该如何处理),这个Build相关需要重点关注,他在Android底层实现类似树状结构。也就是说很多树枝都会有相同的内容。目前所有的作用域一共有七种。


举个例子,比如常见的fingerprint复合字段系列 ,就分为如下七种作用域。

ro.build.fingerprint/

ro.build.build.fingerprint/

ro.bootimage.build.fingerprint/

ro.odm.build.fingerprint/

ro.product.build.fingerprint/

ro.system_ext.build.fingerprint/

ro.system.build.fingerprint/

ro.vendor.build.fingerprint/


作用域分别如下


这里面的值构成顺序也都是一样,所以Hook的话也需要全部进行hook,只处理单一是没用的。因为很多大厂做采集,不会只收集一项。

会七个作用域都进行收集。


常见的配置如下,这些字段其实修改不修改不重要,因为很多大厂如果手机开了开发者选项或者debug模式之类的。

会增加当前手机的风险值。所以尝试进行Mock 和修改 。

在不修改机型的前提下,下面这些应该都是需要处理的 。大厂扫描频率很高的Build参数 ,随机的话,在原有的基础上开头或者结尾,随机几位数即可。


复合字段是多个单一字段拼成的字段,常用的有ro.build.description 还有之前说的7个fingerprint 相关。 这些Mock以后的值要和之前单一字段Mock的值对等。比如某个单一字段值被mock成A 以后,复合字段里面的内也应该是A 。

android.os.SystemProperties->get 底层调用的是native_get ,一个native方法,所以Hook的时候优先处理 native_get

Java hook完毕以后 还需要反射将Build里面的成员变量进行set。防止采集通过反射的方式去获取


很多大厂会把这个字段也作为指纹的一部分,所以这个方法也需要处理。

优先Hook ipc


优先Hook ipc


这个函数不需要太多处理,每个手机类型基本都差不多,每次打乱一下返回结果排序顺序即可。

优先Hook ipc


这个DRM是水印相关,主要为了处理不同手机加水印的唯一ID 核心的是一个叫deviceUniqueId 的东西,这玩意是一个随机的32位字节数组。很多大厂用这个作为核心的设备指纹,不仅在Java层进行获取,还有在Native层进行获取,在后面Native设备指纹会再次介绍到。


Hook的话很简单,这个方法没有IPC底层有自己的实现,直接Hook get的方法即可 。java层Hook是远远不够的,还需要处理native层。

每次随机32位字节数组即可。


大厂应该不会信任Java层的mac,底层都是通过netlinker直接获取网卡,或者直接popen执行 ip a 进行网卡信息的全量获取(详细参考后面popen相关介绍)。我直接在底层处理的netlinker  socket通讯的时候,所以Java层不进行处理。任何获取网卡的方法,底层最终走的都是netlinker去获取的网卡

直接通过netlinker获取网卡,这种方式在安卓10上面貌似已经失效了,但是手机Root以后是没有限制的(亲测android 13 开发板获取成功),这种方式还可以用来检测当前手机是否Root。

但是当执行ip a这种命令的时候,或者调用Java层原始API的时候,底层还是走的netlinker,直接在底层通过ptrace在函数调用执行完毕以后,对寄存器进行Mock 和 Set即可 。详细获取方式可以参考我之前的帖子 。Android netlink&svc 获取 Mac方法深入分析


很多大厂会收集/sdcard/ 或者相册目录的一些创建时间,作为设备指纹,但是很多文件都是默认的1970时间戳,有的少数文件夹创建时间也是很重要的设备标识 。Java里面File对象有文件的创建时间。


聊了挺多Java相关的设备指纹,其实Java层采集的指纹,并不是关键因素,核心的指纹基本都在native层进行处理的。Native部分会详细介绍包括内核文件,还有一些获取指纹的骚操作 。

之前在Java层介绍了,Java获取最终总的是native_get,而native_get底层走的就是这个system_property_get 。

在介绍之前我们需要先看看这个函数的源码,android 9以上和9以下实现的方式是不同的。

android 9:

android 9以下 :

安卓9以下是直接实现的这个方法,所以这块又有个细节,android 9 以上 hook __system_property_get 不仅仅需要Hook

入口方法,还需要Hook system_properties.Get 这个方法。

很多大厂在android 9以上会直接调用system_properties.Get ,先解析So获取到system_properties.Get 非导出函数的函数指针,强转成函数指针以后,直接去调用system_properties.Get ,而非直接调用system_property_get ,如果只Hook system_property_get的话可能就会导致指纹泄漏。所以在android 9以上需要额外处理 system_properties.Get(name, value); 这个方法。


如果直接Hook __system_property_get 可能会存在短指令问题。因为这个方法就一个BL指令,普通的inlinehook 可能会失效。

这块需要用到异常Hook 。当然也可以直接判断安卓版本号在9.0以上直接Hook system_properties.Get 即可。这个system_properties.Get 是一个非导出函数,需要解析So获取到非导出函数的地址。可以参考sandhook的ELFUtils.cpp 。

同理read方法也是如此,也需要这么处理 ,在9.0以上需要特殊处理


Hook的时候需要注意一件事就是Mock的值长度不能大于原始长度。当 system_property_get 执行完毕以后memcpy 将Mock的value拷贝进去即可 。处理的过程函数如下 。实现也很简单。

这块有个细节问题:

因为get和read底层走的都是find函数,为什么不直接在find函数处理呢,find函数返回的是prop_info*

这个指针指向的是系统内存的变量,直接写入会直接sign11 如果使用mprotect如果直接对内存变量强制写入可能会导致系统的不稳定,导致出现问题。之前踩过这个坑。所以就只处理了get和find这两个函数 。

使用的话很简单,直接导入头文件就好。


这个指纹也是很多大厂用作唯一ID的核心指纹。处理的话也需要注意,很核心的一个设备指纹ID。

使用的话很简单,直接导入头文件就好。代码不超过10行 。

导入的头文件实现这个So在mediandk.so里面 ,所以cmake->target_link_libraries引入的时候别忘记添加mediandk 引入依赖。

这个值不同App 读取的内容都不一样,这块需要注意。

Hook的话也很简单,直接Hook这个函数地址就行,但是这个方法也是一个短指令,需要用到异常Hook。

处理逻辑如下,因为我们只需要关注description 即可。其他内容不处理。这块有时候直接写入可能会导致问题,需要先mprotect,不能直接用mprotect需要计算一下扇叶大小,是否内存对齐。


因为之前说过,Linux底层不管什么样的获取网卡,最终底层直接会走Netlinker去获取网卡。在android 10以下可以绕过系统权限从而获取网卡信息,高版本已经失效了 。底层都是svc直接调用recvfrom或者recvmsg去接受socket的消息 。所以不处理svc的话,无法做到全量修改的。

我用的是ptrace 在recvfrom 执行完毕以后,读取参数寄存器,将数据修改以后在重新覆盖寄存器即可 。处理过程如下。

细节点:

socket主要接受消息的函数主要就三个,recvfrom,recvmsg,recv   ,netlinker通讯就是通过这三个函数处理的,recv底层调用的是recvfrom ,所以我们只需要处理recvfrom,和 recvmsg 即可。recvfrom执行完毕以后参数是个数组,我们只需要把这个数组buff的值进行覆盖即可,但是recvmsg的话不能这么处理,他的参数是iovec 指针,这个东西大家可以理解成一个箱子。里面装了具体的内容,长度和开始位置 。所以修改的时候需要读取这个开始位置的指针才可以进行set。



netlinker获取的Mac方式详细看我之前的这篇帖子,这里不多重复介绍了 。

Android netlink&svc 获取 Mac方法深入分析

如何使用使用ptrace修改svc可以参考我的另一篇文章。

SVC的TraceHook沙箱的实现&无痕Hook实现思路

上述方法处理完毕以后,哪怕就是执行系统的ip -a命令,拿到的也是被修改以后的值。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-7-21 19:06 被珍惜Any编辑 ,原因: 格式修改
收藏
免费 59
支持
分享
打赏 + 61.00雪花
打赏次数 7 雪花 + 61.00
 
赞赏  寒夜看xue   +20.00 2022/09/24 好文,厉害nb
赞赏  wx_花卷   +10.00 2022/08/09
赞赏  orz1ruo   +5.00 2022/07/22 助人为乐~
赞赏  zpsemo   +10.00 2022/07/22 我珍惜牛逼
赞赏  Forgo7ten   +1.00 2022/07/21 珍惜大佬nb
赞赏  wx_白熊   +10.00 2022/07/21
赞赏  菱志漪   +5.00 2022/07/21
最新回复 (65)
雪    币: 514
活跃值: (1836)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
第一!!
2022-7-21 16:37
1
雪    币: 4116
活跃值: (1034)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
tql
2022-7-21 16:37
0
雪    币: 8
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
第一第一
2022-7-21 16:37
0
雪    币: 202
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
惜佬牛皮,先三连,晚上再享用
2022-7-21 16:37
0
雪    币: 838
活跃值: (3995)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
2022-7-21 16:37
0
雪    币: 1490
活跃值: (9913)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
7
第三
2022-7-21 16:37
0
雪    币: 265
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
第八
2022-7-21 16:38
0
雪    币: 638
活跃值: (1772)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
9
万字长文、先留言再看、不然没位子啦~
2022-7-21 16:38
1
雪    币: 2300
活跃值: (1870)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
惜佬牛皮
2022-7-21 16:38
0
雪    币: 337
活跃值: (1257)
能力值: ( LV4,RANK:48 )
在线值:
发帖
回帖
粉丝
11
惜少爷牛逼
2022-7-21 16:38
0
雪    币: 2291
活跃值: (2185)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
12
先顶后看,珍惜大佬牛逼!
2022-7-21 16:39
0
雪    币: 47
活跃值: (422)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
学习学习
2022-7-21 16:43
0
雪    币: 3071
活跃值: (4152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
惜佬nb
2022-7-21 16:43
0
雪    币: 820
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15

20220816:

学习途中,发现 hiddenapi-greylist 隐藏的api灰名单列表,给大家贴一个安卓10源码里面的:

http://aospxref.com/android-10.0.0_r47/xref/frameworks/base/config/hiddenapi-greylist-max-o.txt

需要改指纹的,再结合珍惜佬的 【hook IPC + 上面隐藏灰名单api】 食用盛佳!!!!!



最后于 2022-8-16 10:27 被qqizai编辑 ,原因: 这链接是宝藏啊
2022-7-21 16:53
0
雪    币: 222
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
顶!!!
2022-7-21 17:05
0
雪    币: 7201
活跃值: (21965)
能力值: ( LV12,RANK:550 )
在线值:
发帖
回帖
粉丝
17
2022-7-21 17:11
0
雪    币: 4985
活跃值: (6568)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
18
顶一个,学习了,大厂负责设备指纹的睡不安稳了
2022-7-21 17:47
1
雪    币: 416
活跃值: (951)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
惜佬牛皮
2022-7-21 17:51
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
其实我觉得合法性比唯一性更重要,比如不同原厂rom的特殊字段,原厂rom的特殊模块。
2022-7-21 20:28
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
原厂rom分区大小,原厂rom /proc/self/maps的特殊布局。
2022-7-21 20:30
0
雪    币: 0
活跃值: (633)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
讲课是个好的副业啊,做逆向的感觉可选的太少了
2022-7-22 09:08
0
雪    币: 18
活跃值: (430)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
23
大哥的帖子让我学习到了很多
2022-7-22 10:46
0
雪    币: 35
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
占个位置; 方便以后学习~
2022-7-22 11:44
0
雪    币: 18
活跃值: (430)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
25
Settings.Global.getString(context.getContentResolver(),"mi_health_id")
Settings.Global.getString(context.getContentResolver(),"gcbooster_uuid")
Settings.Global.getString(context.getContentResolver(),"key_mqs_uuid")
Settings.Global.getString(context.getContentResolver(),"ad_aaid")

这几个字段是针对小米手机吧?华为好的手机没有
2022-7-22 17:39
1
游客
登录 | 注册 方可回帖
返回
//