一、基本情况介绍
数盟IOS端可信ID介绍页: 数字联盟
数盟号称是还原出原生的IDFA, 但是苹果官网这么介绍:
用户开启跟踪允许跟踪后,APP才可以请求获取IDFA,且用户交互界面允许后,APP才能获取到IDFA.
官网给出的基本架构:
APP集成SDK后,SDK上报一些信息到后台,后台进行一些数据分析并与数据库比对,返回可信设备ID和IDFA到客户端。
二、SDK及上报数据分析
官网给出了这么多合作客户:
以知乎IOS客户端分析数盟SDK,提取知乎APP后发现数盟SDK以动态库的形式集成:
du.framework即为SDK库
反编译主程序du文件,SDK使用了代码平坦化混淆、类名方法名混淆、字符串混淆。
直接定位到请求后台的关键代码处:
输出请求后台后返回的JSON数据并解密后格式如下:
{
"err" :0 ,
"rid" :"2TU2CA:0KM1:R7E6:3R0E8" , // 数盟APP层面设备ID
"cdd" :"D2mvLaZZIwmd8SJa6RXIHkOA1sdwcmsG5gw6YPSo3o6a=X0d" , // 后台生成的APP层面可信设备ID
"cck" :"36" ,
"rdm" :"176" ,
"qid" :"FC44AE3D-B6F1-A01E-9C03-24EA241CE1B4" //全局的IDFA
}
cdd是可信设备ID, qid 是IDFA
同时接口 http://idfa2.shuzilm.cn/q?did=&pkg= 可以返回IDFA,参数为设备ID和包名,这里的did即返回的cdd。
SDK请求后台有两个部分:携带的数据类似:
1)只上报数据,后台无返回数据,每次启动上报:http://idaa.shuzilm.cn/report?v=1.0&t=iaa
2)上报数据后,后台返回设备ID,首次运行(APP缓存内无相关字段时)获取:++https://ipi.shuzilm.cn/report?v=1.0++
拦截请求前和加密前的上报字符串,如下:
javascript
{
AAA = "v1.0";
AI3 = (
"zh-Hans-CN"
);
BBB = "v1.0";
BP1 = 1;
Be8 = "12603.479420792";
En9 = {
bO4 = "";
sb4O = "";
sl2 = "";
};
FU2 = "u9v3o@Wlp";
Fx4 = {
AI7 = "7@jlA7h8:A?;k7AlAm7A?mlmm";
};
GI7 = {
AI7 = "hkj@A7k<hA?hmlA?Ak7A?@l9m";
};
Gd9 = 1;
Gl1 = iOS;
HJ2 = 0;
Hg2 = {
kCFProxyTypeKey = kCFProxyTypeNone;
};
Hq2 = "0.119999997317791";
Ih2 = "2.3.4";
Ke6 = "sg.bigo.alpha";
LW5 = "";
LZ3 = {
fW2 = 77777777477777777747777477777777747;
gy8 = "";
js2 = ":<;:M<;@4:LM?;=9KJ4K9<J47<H7<;@?94M";
qH4 = "M89??I>;?4L=9HK;9H<4:KIL48I<M889<:4K";
se1 = "48B8B8:D2119D:F25820:fc00";
sf2W = 66be31e;
sg8y = "";
sj2s = 06964dc;
zT2 = "l?k@78<lm<7li:k<him7?h8i7:i;>>88;8ll=87l";
};
NA8 = 1;
NP1 = osee2unifiedRelease;
OW5 = "7.3.1";
PJ2 = 3;
Pm4 = "48B8B8:D2119D:F25820:fc00";
Pq2 = {
alt = 0;
lat = 0;
lng = 0;
};
RP5 = 858;
RQ5 = "8;;";
TV5 = 1615277876;
VR1 = 1615290767;
Wm7 = 4;
Wt9 = 1;
XE3 = (
"77??5577?958"
);
ZY9 = 4G;
at9 = {
IW2 = 67895296;
bB8 = 3146072064;
vB9 = 234242048;
};
aw2 = {
AI7 = "7A@k?j<iA:?Ak;m<9jAA>=l<@;m?AAh?97k;j9";
Oa6 = "5>=;:8855:7>8";
Ox7 = "5<<<<9955<<<<99";
tK5 = "5>=;:8855:7>8";
};
bj9 = 3866;
bk6 = {
qA4 = 47185707008;
uu2 = 127968497664;
};
eK5 = "wIwKh65ujvhpt{nhpj\"p6sKw9wLHI67lHs?kHuH|KI86Lz4yLl7uJpHh4{IuMvHj;64y<h:};6>l4{9h=}<p>y9wH6";
fC8 = 6332d8dbf4b37a95e1ea4b773c35c109;
go4 = 2;
hL4 = ":=>";
Identifier prefix = 1;
jN2 = {
};
jj5 = {
fW2 = 77777777477777777747777477777777747;
js2 = ":<;:M<;@4:LM?;=9KJ4K9<J47<H7<;@?94M";
qH4 = "M89??I>;?4L=9HK;9H<4:KIL48I<M889<:4K";
se1 = "48B8B8:D2119D:F25820:fc00";
sf2W = 66be31e;
sj2s = 06964dc;
zT2 = "l?k@78<lm<7li:k<him7?h8i7:i;>>88;8ll=87l";
};
kU4 = "zhihu_shuzilm_cn";
kn7 = "dj/jzHEK";
kp6 = 1615291006;
lH8 = {
AI7 = "ijA@m8l8lAAm:m<>jjAA=7<<<;?lAA?;7@;89";
};
lo7 = "2020-10-03 04:14:37 +0000,2020-10-03 04:14:37 +0000";
mM3 = 1;
mT5 = 1;
mi1 = {
Uc4 = "+9cmRoTSYyKLhgs5";
mE1 = uj;
nO8 = 87;
xX9 = "=7;";
};
ms1 = {
RH9 = 2;
bt3 = ARM64;
};
nC9 = "Jv@p8{;h8jMpIs=w8w@H=6:h4{JhLK@6Iz4y>l7u7p;h4{:uMv8JK64l>s;p;iJvIt96Ly;h6}u6";
oF8 = "0.7466846704483032";
ou5 = 1615290786;
tJ3 = 1615277876983893;
tP9 = "14.1";
tf8 = {
fW2 = 77777777477777777747777477777777747;
js2 = ":<;:M<;@4:LM?;=9KJ4K9<J47<H7<;@?94M";
qH4 = "M89??I>;?4L=9HK;9H<4:KIL48I<M889<:4K";
se1 = "48B8B8:D2119D:F25820:fc00";
sf2W = 66be31e;
sj2s = 06964dc;
zT2 = "l?k@78<lm<7li:k<him7?h8i7:i;>>88;8ll=87l";
};
th1 = 1615291006;
xs6 = 0;
zk4 = (
"A??A?A????A???7A;79",
"A?@A@A?@?@A???7A;79"
);
zv7 = 1;
}
JSON字符串的Key和Value都有加密处理,上报内容丰富
利用iPhone 7P 越狱手机, 测试 贝壳找房APP和 知乎APP 测试得到不同的可信设备ID,但是请求接口 http://idfa2.shuzilm.cn/q?did=&pkg= 时,返回了相同的IDFA!
三、解密关键数据
上报数据的接口都做了加密处理,定位到关键解密的类,HOOK输出参数和加密结果(加密方式有多种):
还原出字符串的原始值:
javascript
{
"model":"iPhone9,2",
"fC8":"11dc97a54f95d15dcb3ea403223a5f69",
"RQ5":"414",
"PJ2":"3",
"mi1":{ // SIM卡信息
"locale":"CN",
"xX9":"460", //中国 移动网络标志
"Uc4":"+9cmRoTSYyKLhgs5", // 应该是 中国联通
"nO8":"01" // 01 是联通
},
"tJ3":1615133047280091,
"proxy_info":{ // 代理
"port":8080,
"type":"http",
"ip":"172.24.67.27"
},
"net_type":"WIFI",
"lo7":"2020-10-03 04:14:37 +0000,2020-10-03 04:14:37 +0000", // 系统文件的改写时间,可以认为是系统更新的时间
"ips":[
"172.24.8.15",
"172.24.8.16"
],
"ou5":1615185614,
"En9":{
"sl2":"",
"bO4":"",
"sb4O":""
},
"ms1":{
"bt3":"ARM64",
"RH9":2
},
"nC9":"/var/mobile/Containers/Data/Application/57E3F5DB-B9C6-448B-AA7A-B24010B266C4", // 文件保存路径
"Hq2":0.43000000715255737,
"mM3":0,
"kn7":"9uwmRarGbDmbBtvLcAHu",
"Be8":"15086.353942000",
"NA8":false,
"Wm7":4,
"time":1615185614,
"BP1":"0.",
"at9":{
"bB8":3146072064,
"vB9":218480640,
"IW2":46333952
},
"AI3":[
"zh-Hans-CN"
],
"Pq2":{
"lat":22.934495944497922,
"lng":113.38160809348251,
"alt":5.5634598731994629
},
"Gd9":true, //应该是越狱状态
"TV5":1615133047,
"tf8":{
"IDFV":"0ECDDDCC-2CCA-4720-A173-90F92AAB971F",
"OpenUDID":"b61de9b10bdd52286dc9b198b2d249ea2ae5c7e2",
"IDFA":"00000000-0000-0000-0000-000000000000",
"simulateIDFA":"D32151ED-A4A6-4B81-6617-5438DACAA604",
"deviceid":"9C487F:ACC4DC:CB91C9:5200"
},
"oF8":0.73140573501586914,
"app_versionn":"7.3.1",
"utun0 ":"fe80::dcd3:f490:692c:4fd0", // 应该是ipv6地址
"aw2":{ // 移动网络IP信息
"cip":"10.91.113.211",
"mask":"255.255.255.255",
"pdp_ip0 ":"2408:8456:c03:46ed:4d54:6579:af3b:7103" //应该是ipv6地址
},
"os_type":"iOS",
"LW5":"",
"jN2":{
},
"VR1":1614826186,
"zv7":true,
"BBB":"v1.0",
"xs6":true,
"lip":"172.24.106.152", // 内网ip地址
"deviceid":"9C487F:ACC4DC:CB91C9:5200",
"AAA":"v1.0",
"jj5":{
"did":"9C487F:ACC4DC:CB91C9:5200",
"idfa":"00000000-0000-0000-0000-000000000000",
"idfv":"0ECDDDCC-2CCA-4720-A173-90F92AAB971F",
"IDFA1":"",
"simulateIDFA":"D32151ED-A4A6-4B81-6617-5438DACAA604",
"OpenUDID":"b61de9b10bdd52286dc9b198b2d249ea2ae5c7e2"
},
"go4":1,
"kU4":"zhihu_shuzilm_cn", //应该是集成的key
"tP9":"14.1",
"RP5":1170,
"bj9":"3866", // APP版本信息
"bk6":{
"qA4":48616042496,
"uu2":127968497664
},
"identifierPrefix":4,
"eK5":"/var/mobile/Containers/Data/Application/57E3F5DB-B9C6-448B-AA7A-B24010B266C4",
"mT5":4,
"identifier":"com.zhihu.ios",
"ip0":"2408:8556:c07:bda6:241c:150c:f9ae:8f91", // ipv6
"hL4":"736",
"Wt9":true,
"HJ2":false,
"sdk_ver":"2.3.4",
"LZ3":{
"IDFV":"0ECDDDCC-2CCA-4720-A173-90F92AAB971F ",
"openUDID":"b61de9b10bdd52286dc9b198b2d249ea2ae5c7e2",
"IDFA":"00000000-0000-0000-00000000",
"simulateIDFA ":"D32151ED-A4A6-4B81-6617-5438DACAA604",
"IDFA1":"", //这个字段如果不为空,权重较大。
"did":"9C487F:ACC4DC:CB91C9:5200"
},
"filename":"osee2unifiedRelease",
"zk4":[
]
}
几个确定设备的重要参数:
1、did
形式如:9C487F:ACC4DC:CB91C9:5200 , 如果设备是越狱状态,APP有文件系统的读权限时,从下面四个路径获取:
- /Library/Managed Preferences/mobile/com.apple.mobileserver.plist
2)/private/var/MobileDevice/ProvisioningProfiles/b87aa91c-bdb2-1b91-baaf-d73ec4bfb86c
- /private/var/logs/mediaserverd/com.apple.mediaserverd.plist
4)/var/mobile/Library/Preferences/com.mobicom.net2.plist
也就是说对于越狱设备,直接读取到的就是与APP无关的全局设备ID,当系统不存在这些文件以及设备未越狱时,由SDK生成并保存,未越狱设备测试发现每次清空数据后启动did值改变。
2)IDFA
系统提供的获取IDFA的接口,但是IOS14以后默认关闭,因为正常情况下基本是全0
2)openUDID
一个开源的ID方案,测试更改包名变化较大。
3)simulateIDFA
一个开源的ID方案,有变化,有一定稳定性。
组合方案包括:系统版本、硬件型号、SIM卡信息、系统容量等,
以及开机时间、国家码、语言、设备名称两部分组成
碰撞率小,但是明显存在不稳定性。
4)IDFA1
暂且叫做IDFA1,应为它是后台返回的IDFA缓存在本地,每次都会带上,可能APP中缓存和剪切板中缓存(跨APP,新版本系统限定在同一开发者)
5)IDFV
跟APP的开发者有关,一个开发者的所有APP都卸载后,再次运行会改变。
6)lo7
某个系统文件上次的改写时间,目前APP沙盒外的文件,具备读权限的只发现一个:
/System/Library/CoreServices/SystemVersion.plist
可以认为是上次系统更新的时间。该文件记录了系统版本,系统类型(IOS、MacOS)以及系统版本的ID和系统镜像的ID(对应机型和系统版本)
7)文件路径
文件路径中存在随机字符串,可能也会成为归因的一个因素
8)设备其它信息:机型、系统版本、设备名、IP、经纬度等
测试几组数据:
1、改变包名,但是是同一个开发者账号,IDFA1不为空,上报返回同一个qid和cdd。
2、改变包名,不是同一个开发者,IDFA1此时为空,simulateIDFA相同,其余ID不同,但是返回了不同的qid和cdd
基本结论: 所谓的可信设备ID,并不是还原原生的IDFA,同样只是上报一些数据,后台归因然后返回后台保存的IDFA.后台归因时,不是简单的根据某个ID一样判断出是一个设备。而是结合了多个ID,可能每个ID有一定的权重。
用户安装一个全新的APP时下列情况IDFA肯定会变:
1、全新安装,并且没有安装该开发者其它的APP,且设备有重启或系统更新等。这种情况下各个ID都出现不同的值。
2、SDK内部的did出现碰撞等。SDK以did作为高权重。did一样,基本认为是同一个设备,即did出现碰撞,则IDFA会对应错误。
3、系统更新后再安装一个未安装过的应用。
只考虑正常用户,设备ID保存在缓存中或者keychain中能够提高稳定性,基本不会发散。