转自:https://wetest.qq.com/lab/view/116.html
一、前言
对于移动端产品的常规统计分析和运营推广,渠道结算来说,能精准的识别区分并且跟踪一台终端设备(一个终端用户),是尤为重要和求之不得的事情。当然,例如手Q、微信、手游等强登录的产品,可以基于帐号体系来做大部分的事情,同时也存在依赖终端设备ID的诉求,如设备锁、跨应用数据互通/终端用户画像、游戏的外挂打击,安全防护等。
那么,在当前的Android和iOS两大阵营上,系统是否提供了现成的能够唯一且准确的标识一台终端设备的ID给到开发者采集获取呢?毫无疑问,答案是否定的。 Google官方的说法是,一切能跟踪用户的ID都涉及到用户隐私的保护,属于危险级权限,这个在Android 6.0上就做了权限级别划分,同时也赋予了用户更灵活的权限控制主动权。iOS就不用说,一向做的”非常棒“,限制各种ID的采集。OK,下面会一一给大家介绍终端各种ID的采集方式以及准确性。
在正式开始之前,还是插播一条广告:灯塔QIMEI v3.0 ,提供终端设备的唯一标识ID体系服务,能精准的区分识别每一台终端设备,拥有海量的跨应用用户ID关系积累,以及实时的ID找回能力,应用于常规运营,结算场景。
二、Android篇
1、IMEI/MEID
简介之*:
国际移动设备识别码(International Mobile Equipment Identity,IMEI),即通常所说的手机序列号、手机“串号”,用于在移动电话网络中识别每一部独立的手机等行动通讯装置,相当于移动电话的身份证。序列号共有15位数字,前6位(TAC)是型号核准号码,代表手机类型。接着2位(FAC)是最后装配号,代表产地。后6位(SNR)是串号,代表生产顺序号。最后1位(SP)一般为0,是检验码,备用。国际移动设备识别码一般贴于机身背面与外包装上,同时也存在于手机内存中,通过输入*#06#即可查询。
更详细的介绍也可参见百度百科:http://baike.baidu.com/item/IMEI。
IMEI码由GSM统一分配,那么CDMA制式的手机采用的识别码是MEID,MEID的介绍参见百度百科:http://baike.baidu.com/view/2823315.htm
终端标准采集方式:
TelephonyManager manager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
String imei = manager.getDeviceId();
好了,这里有人就会有疑问,对于市面上众多的双卡双待手机,是不是会有两个ID,如何把这两个ID都采集上来?
有两个IMEI,或者一个IMEI和一个MEID的设备都存在,Android原生系统并不支持双卡双待,均由厂商在硬件,系统层定制实现。由于没有统一的实现规范标准,那么对应的采集方式也需要进行厂商机型适配,例如三星的某些机型上,要拿到第二个TelephonyManager的实例,getSystemService(Context.TELEPHONY_SERVICE)中Context.TELEPHONY_SERVICE的值为"phone2",默认为"phone",见下图:
HTC的机型又为"htctelephony",基于MTK芯片解决方案或者华为,小米的又各不相同。
对双卡双待想继续深挖研究的同学可以细读下KM上的这篇文章,从源码层剖析的比较清楚了:双卡双待-工程师难言的痛 http://km.oa.com/group/3292/articles/show/152654
采集有效率:整体96%左右,其中接近一个点的为"","null"无效字符串,其他为
000000000000000", "111111111111111","123456789123456" 等各种奇葩无效串,以及大批量的山寨重复IMEI。
发现:在某些作弊比较严重的渠道上,IMEI采集为无效字符串的比例甚至高达了60%以上,数据来源于米格火眼。
对于系统级应用或者桌面应用,由于获取采集时机在设备模块初始化未完成之前,也会导致设备ID采集不到的情况发生,另外由于像手机管家,360手机助手,和一些厂商ROM自带的权限管家,有阻止应用采集手机IMEI的功能,设置之后也会导致正常采集返回0串或者其他固定串。还有山寨手机厂商为了节省成本,批量复制重复IMEI串烧进手机的EEPROM里面。
2、MAC
MAC地址的介绍,在这里不过多描述,还是贴一下基础科普文[百度百科]:http://baike.baidu.com/view/69334.htm。 这里说的MAC是指手机wifi无线网卡的MAC地址,与终端硬件关联,可用作设备的唯一标识。
终端标准采集方式:
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifi.getConnectionInfo();
String mac = info.getMacAddress();
好了,问题又来了,在Android 6.0及以上的系统上,通过上面的系统接口采集到的MAC地址返回的是一个固定串:02:00:00:00:00:00。这个问题会在第六小节Android 6.0特别篇里面统一介绍。
另外,我们在分析MAC地址的有效唯一性时发现,会出现同一个真实IMEI可对应多条MAC地址,也就是说MAC地址会发生变化(先排除作弊工具有意串改的场景,ID串改的在第八小节中统一介绍)。研究发现用户/刷机商在进行线刷时损坏nvram导致每次使用WIFI联网或者手机重启时MAC地址变化,nvram损坏的情况下,系统由于获取不到真实的MAC地址,Android内核会自动生成一个临时的虚拟MAC地址供用户联网,且每次重启或者断网重连时重新生成一个新的MAC地址(前3个字节不变,后3个字节随机生成),并且会在WIFI列表中显示一个“NVRAM WARNING Err=0x10”信号项。
Android内核随机生成MAC地址代码段:
if ((tuna_mac_addr[4] == 0) && (tuna_mac_addr[5] == 0)) {
srandom32((uint)jiffies);
rand_mac = random32();
tuna_mac_addr[3] = (unsigned char)rand_mac;
tuna_mac_addr[4] = (unsigned char)(rand_mac >> 8);
tuna_mac_addr[5] = (unsigned char)(rand_mac >> 16);
}
memcpy(buf, tuna_mac_addr, IFHWADDRLEN);
针对这个问题,可参考以下这篇帖子:
https://code.google.com/p/android/issues/detail?id=23330
采集有效率:93%左右
3、IMSI
简介之百度百科:国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。
科普链接:http://baike.baidu.com/view/715091.htm
那这里注意了,此ID是跟SIM卡绑定的,更换SIM卡就会发生变化,同样在仅支持wifi的pad设备上是没有的。
终端标准采集方式:
TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String imsi = manager.getSubscriberId();
IMSI跟IMEI一样,同样也会遇到双卡的问题,如果两个串号均想采集,那就需要根据不同的厂商机型的实现方式进行适配。
采集有效率:98%左右,存在无SIM卡的终端设备获取不到的问题,最大的问题是会发生变化,与终端设备非强绑定。
4、Android_ID
简介:Android_ID是设备首次启动时,系统生成的一个唯一串号,长16字节(例:70560687d711af97),由com.android.providers.settings 这个系统程序所管理,Android6.0 以下储存在/data/data/com.android.providers.settings/databases/settings.db中的secure 表。
系统生成此ID的部分源码如下:
final SecureRandom random = new SecureRandom();
final String newAndroidIdValue = Long.toHexString(random.nextLong());
final ContentValues values = new ContentValues();
values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
终端标准采集方式:
Settings.Secure.getString(context.getContentResolver(), "android_id")
了解了以上的信息,对此ID就比较清楚了,此ID与硬件无关,与Android系统有关,所以当系统还原出厂设置,刷机时这个ID就会重新生成了。另据官方资料显示,此ID在>=2.3的统上是可靠并且稳定的。But,由于国内的手机基本都是厂商定制的ROM,所以此ID的控制权均由厂商ROM层来控制,难免会发生一些bug,导致部分机型上的ID都相同。另外在OTA ROM升级的时候,有些厂商会保留原有db信息不变,有些厂商会清除重新生成。
采集有效率:99%左右,与系统强依赖,稳定性不足。
5、CID(for MMC,SDCard)
简介:Card Identification Register(CID),顾名思义,做为存储卡片的唯一识别信息,规范上要求每张卡片的ID都唯一。CID 寄存器有 16 字节长,如下表 所示,它包含了本卡的特别识别码(ID 号)。 这些信息是在卡的生产期间被编程(烧录),主控制器不 能修改它们的内容。 注意:SD卡的 CID 寄存器和 MMC 卡的 CID 寄存器在记录结构上是不同的。
参考资料:https://www.kernel.org/doc/Documentation/mmc/mmc-dev-attrs.txt
http://www.eetasia.com/ARTICLES/2004NOV/A/2004NOV26_MSD_AN.PDF?SOURCES=DOWNLOAD
终端标准采集方式:
adb shell cat /sys/class/mmc_host/mmc*/mmc*:*/cid
一般来讲MMC0 保存的是内置存储卡的信息,MMC1 保存的是扩展存储卡也就是SD卡的信息。因为SD卡跟终端设备又不是强关联的,会因为更换升级发生变化,那么我们主要看手机内置存储卡的CID。
采集有效率:95%~96% 与IMEI相当,在部分山寨低端机器上,会出现采集不到和采集的是SD卡的CID,初步分析是由于山寨低端机器上采用的存储设备为Flash卡或者并未遵循标准的设计规范,因此而采集不到。
6、Android 6.0特别篇
在15年发布的Android 6.0新系统上,新增了不少特性,例如应用权限管理,权限的分级收拢(分普通权限,运行时/敏感权限)并赋予用户更高的应用权限设置功能,省电模式等。与终端ID采集密切相关的就是权限控制这个特性了。经过我们团队的实际测试,部分结论如下:
1)、IMEI,IMSI需用户手动授权方可采集。
6.0里面READ_PHONE_STATE权限被归属成敏感权限,在运行时,如果应用程序获取TelephonyManager实例时,系统就会弹出是否允许应用拨打电话和管理通话的选择对话框,用户拒绝之后,就采集不到。
针对6.0及以上的系统,ID的采集需要进行兼容,使用 checkPermission 接口从内存中直接获取权限数据,判断用户是否已经授权。
2)、MAC地址通过非系统接口可采集(6.0以下通过系统接口采集),采集场景仅限于处于wifi连接状态。
非系统接口,其实指的就是直接读取文件的方式,如下:
cat /sys/class/net/wlan0/address
cat /sys/devices/virtual/net/wlan0/address
3)、AndroidID 采集不受影响,但此ID不与硬件绑定,刷机,系统还原,升级等场景会发生变化,不稳定。
说明:应用编译时选择的targetSdkVersion>=23,在6.0及以上的系统运行才会触发权限提示框,<23打出的apk包不受此影响。
7、Google官方推荐篇
在16年1月初,MIG商务合作中心的同事给予支持,我们团队与Google的官方技术人员有过两次针对6.0新特性(主要是权限管理方面)的交流咨询会议,这里也把官方人员推荐的做法也同步给大家了解。总结一下主要有三个建议:
1) Instance ID 文档地址:https://developers.google.com/instance-id/reference/?hl=zh-CN
The Instance ID API lets you integrate Instance ID with your Android or iOS app. Instance ID provides a unique identifier for each instance of your app and a mechanism to authenticate and authorize actions, like sending messages via Google Cloud Messaging. The InstanceID is long lived, but may expire for the following reasons:
Device factory reset.
User uninstalls the app.
User performs “Clear Data” in the app.
Device unused for an extended period (device and region determines the timespan).
Instance ID service detects abuse or errors and resets the InstanceID.
Server-side code if your client app requires that functionality.
The Instance ID service notifies your app of an InstanceID reset via callback to a InstanceIDListenerService. If your app receives this notification, it must call getToken() and retrieve the new InstanceID, and update its servers.
Use the getToken method to prove the ownership of the InstanceID and to allow servers to access data or services associated with the app. The method follows the patterns of OAuth2, and requires an authorizedEntity and scope. The authorizedEntity can be a project ID or another InstanceID, and it determines the services that are authorized to use the generated token. The scope determines the specific service or data to which the token allows access.
有几个问题,此ID主要是用于使用GCM服务,国内不可用,应用内唯一,可变因素太多。
2) Guid,Java Util库生成的全球唯一ID
应用内"唯一",需要终端自己实现本地/服务器存储。
3) 引导用户进行授权
官方:一切与硬件关联的ID,能跟踪到用户的都是属于危险级权限,涉及到用户隐私,不能随意采集。个人推测,以后Android在这方面会慢慢向苹果看齐。
8、ID串改方式了解(刷量篇)
对以上ID了解的差不多之后,那我们在看看黑产恶意作弊刷量的方式有哪些。
1)串改终端设备ID
a) 脚本修改系统配置文件
b) 工具框架hook串改系统接口函数
2) 固定真机脚本调起应用运行
3) 肉鸡云端下发指令后台调起运行
4) ROM+云端下发指令调起
5) 模拟器刷量,软件/硬件模拟器
ID 串改,有比较多的小工具/软件就可以完成,例如EasyIMEIChanger;高端一些的方式采用的是系统函数的hook方式进行调用拦截串改,例如Xposed框架有做这些函数的hook:
好吧,这里就不多说了,我们有专门的团队在做推广渠道刷量的检测分析,如有产品想详细了解和接入试用,也请联系咨询[灯塔小秘]或者项目负责人janexiong。
三、iOS篇
1、UDID
简介:Unique Device Identifier的缩写,iOS设备唯一标识符
采集方式:[UIDevice currentDevice] uniqueIdentifier]
准确性/唯一性:iOS设备唯一标识符,iOS2.0以上及iOS5.0以下的系统可用,iOS5以上已禁用
2、UUID
简介:Universally Unique Identifier的缩写,中文意思是通用唯一识别码
采集方式:[UIDevice currentDevice] uniqueIdentifier]
准确性/唯一性:当App升级或重装后,UUID的值会发生变化
3、MAC
简介:手机无线网卡物理地址
采集方式:
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if ((mib[5] = if_nametoindex("en0")) == 0) {
printf("Error: if_nametoindex error/n");
return NULL;
}
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 1/n");
return NULL;
}
if ((buf = (char*) malloc(len)) == NULL) {
printf("Could not allocate memory. error!/n");
return NULL;
}
if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 2");
if (buf) {
free(buf);
}
return NULL;
}
ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);
NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
if (buf) {
free(buf);
}
return [outstring uppercaseString];
准确性/唯一性:iOS7之前可用,iOS7之后,请求Mac地址都会返回一个固定值“02:00:00:00:00:00”,已被苹果禁用
4、IDFV
简介:Vendor标示符 (IDFV-identifierForVendor)
采集方式:[[[UIDevice currentDevice] identifierForVendor] UUIDString]
准确性/唯一性:同一个开发者账户下的app,在同一台终端上生成的IDFV一致,但是将此开发者账户下所有的app删除,下次再安装第一个app时,又会生成一个不一致的IDFV。例如:对于com.tencent.appone和com.tencent.apptwo这两个BundleID生成的IDFV是一样的,如果将这两个APP都删除,IDFV就会重新生成。(公司内有多个发布账号,每个账号下IDFV不同)
5、IDFA
简介:广告标示符(IDFA-identifierForIdentifier)
采集方式:[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]
准确性/唯一性:
卸载重装不会发生变化;
同一台终端不同的App获取到的都一样;
(设置 -> 通用 -> 还原 -> 还原位置与隐私) ,广告标示符会重新生成;
(设置-> 隐私 -> 广告 -> 还原广告标示符) ,广告标示符也会重新生成;
关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符;
四、业务自建Guid体系了解
了解到MIG内的三大产品,QQ浏览器,应用宝,手机管家,均有自建guid体系,其大体思路基本一致,服务器端根据终端采集上报的IMEI,IMSI,MAC,Android_ID等基础ID生成一个唯一ID,同时服务端也具备简单的找回能力。此方案是比较常用的一种方案,对于保证应用内ID唯一性足够,会有少量的一台终端对应多个guid的场景,常规产品运营分析影响不大。
手机QQ浏览器GUID唯一标识:http://km.oa.com/group/15197/articles/show/160738
这里也说明一下业务GUID和QIMEI体系的区别:
各业务GUID与灯塔Qimei方案思路是一致的, Qimei可补充GUID的找回能力,提升GUID稳定性。
GUID和Qimei由服务器生成下发,并建立和设备其他各种ID映射关系(imei,mac ,imsi ,android_id等), 用于丢失后的找回。
主要区别在于Qimei基于全业务范围的积累找回,GUID基本单业务范围的积累找回。
以及终端对于基础ID的采集,QIMEI/GUID的存储共享能力上的区别。