首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
iOS安全
发新帖
3
3
[原创] iOS App的商业级环境检测手段 - 基于某讯A<E Framework 详解
发表于: 2026-7-1 18:34
3218
[原创] iOS App的商业级环境检测手段 - 基于某讯A<E Framework 详解
mako233
2026-7-1 18:34
3218
# iOS App的商业级环境检测手段 - 基于某讯Å<E Framework 详解 ## 0. 引言 移动App与游戏安全领域常常聚焦在Android客户端。对于同时拥有Android和iOS双端互通的游戏、各大常用App,通常可以解iOS安装包`.ipa`来降低分析成本,原因是iOS对JIT的严格限制,以及各大厂商对AppStore加密的信任,致使iOS应用中的二进制`Framework`(也可以理解为apk中的`lib/arch/*.so`)往往更加规范、易于分析。iOS App运行在沙箱内,权限也相当低。那么在这种情况下,都有哪些手段抵抗用户对环境的修改、越狱、甚至调试注入? ## 1. 解包 本次研究的App是一个使用Unity引擎开发的游戏,在AppStore上免费发布。要分析一个iOS应用程序,要进行以下步骤: ### 1.1 获取解密后的`ipa` 任何在AppStore上免费发布的App都可以通过[Decrypt IPA Store](decrypt.day)来获取解包后的程序,如果App是付费的,则需要你自行购买并使用Root后的设备进行解包,这里不再赘述。 ### 1.2 文件结构 `ipa`本质上就是一个`zip`文件,直接使用`Bandizip`解压缩即可。解压缩后可见`ipa`结构如图所示:  `Payload`下默认的目录名称应当是`*.app`,但是由于OSX会自动将`*.app`的目录识别为软件包,因此我这里将其改成了`*-app`便于查看文件。对于Unity游戏来说,App根目录下的`ProductName`只是一个`entry`,所有代码都在`UnityFramework`中(也就是Android Unity游戏包中常见的`libil2cpp.so`)。但我们今天分析的重点是另外两个库`anort`与`anogs`,分别是某讯反作弊套件中的包装层与核心检测层。 ## 2. 分析 ### 2.1 调用拓扑 先查看`UnityFramework`的依赖 ``` mako@makos-laptop UnityFramework.framework % llvm-otool -L UnityFramework UnityFramework: ... @rpath/anort.framework/anort (compatibility version 1.0.0, current version 1.0.0) ... ``` 发现其只依赖`anort`包装层。再看`anort`的依赖 ``` mako@makos-laptop anort.framework % llvm-otool -L anort anort: ... @rpath/anogs.framework/anogs (compatibility version 1.0.0, current version 1.0.0) ... ``` 所以简单得出`anogs`为检测核心: ``` UnityFramework -> anort -> anogs ``` ### 2.2 `anogs`核心分析 #### 2.2.1 字符串加密模式 直接用IDA Pro打开`anogs`二进制,左侧可以看到其导出表没有被混淆,先看`_AnoSDKGetReportData`这个可疑函数:  可见`sub_3CC48`是关键函数,跟进去看一下:  发现一些函数仅通过一个整数arg获取返回值,随便点进去几个看看: ``` char *__fastcall sub_105278(int a1) { _BYTE *v2; // x19 _BYTE *v3; // x0 char v4; // w17 __int64 v5; // x9 unsigned __int8 v6; // w11 unsigned __int8 *v7; // x12 char *v8; // x8 char *v9; // x8 char v10; // w16 char v11; // t1 __int64 v12; // x14 char i; // w1 char v14; // w15 int v15; // w16 __int64 j; // x17 int v17; // w1 char v18; // w17 char *v19; // x2 char v20; // t1 v2 = sub_56F58(); v3 = sub_56F6C(); v4 = 0; v5 = a1 + 1LL; v6 = v3[v5]; v7 = &v2[a1]; v8 = &v3[a1]; v11 = *v8; v9 = v8 + 2; v10 = v11; v12 = a1 + 2LL; for ( i = 1; ; i = 0 ) { v14 = i; if ( (v4 & 1) != 0 || !v10 ) { v15 = *v7; v6 = v2[v5] ^ v15; if ( v6 ) { for ( j = 0; j != v6; v15 = ((v15 + j++) ^ 6) + 1 ) v9[j] = v7[j + 2] ^ v15; } v3[v12 + v6] = 0; v3[v5] = v6; v10 = 1; v3[a1] = 1; } if ( v6 ) { v17 = 0; v18 = -1; v19 = v9; do { v20 = *v19++; v18 ^= v20; ++v17; } while ( (unsigned __int8)v17 < (unsigned int)v6 ); } else { v18 = -1; } if ( (*v7 ^ (unsigned __int8)v2[(int)(v12 + v6)]) == (unsigned __int8)~v18 ) break; v4 = 1; if ( (v14 & 1) == 0 ) { byte_189268 = 1; return v9; } } return v9; } ``` 另一个: ``` char *__fastcall sub_106130(int a1) { ... for ( j = 0; j != v6; v15 = ((v15 + j++) ^ 0x13) + 2 ) ... ``` 每个函数除了此处常数不一样,其他位置都一样。表存储位置: ``` void *sub_56F58() { return &unk_119A68; } ``` 所以该解密模式大致可以总结成: ``` 表地址: 0x119A68 每条字符串: 第 0 字节: key 第 1 字节: len ^ key 后续 len 字节: cipher[i] ^ key 每解一个字节后: key = (((key + i) ^ xor_const) + add_const) & 0xff 尾部有一个以 0xff 开头的校验字节 ``` 所有字符串解码函数都有 `sub_56F58()` 的调用,并且该函数只有获取表的功能,所以可以xref `sub_56F58()` 定位到所有字符串解码函数,再根据函数的 xref 获取常量,解出全部字符串。 #### 2.2.2 聚合检测函数`sub_89fc` 查看字符串列表:  发现函数`sub_89FC`包含大量越狱管理器`*.app`与`/bin/su`等路径字符串,转到该函数分析: ##### 2.2.2.1 检测常见越狱路径存在 一开始就能看到一大坨: ``` *(_QWORD *)task_info_out = sub_10B7B4(9295); *(_QWORD *)&task_info_out[2] = sub_104DF4(9502); *(_QWORD *)&v43 = sub_106FF4(9532); *((_QWORD *)&v43 + 1) = sub_10945C(9564); *(_QWORD *)&v44 = sub_10AFB8(9588); *((_QWORD *)&v44 + 1) = sub_10649C(9622); *(_QWORD *)&v45 = sub_108350(9649); *((_QWORD *)&v45 + 1) = sub_10A32C(9677); *(_QWORD *)&v46 = sub_1055D8(9709); *((_QWORD *)&v46 + 1) = sub_106C90(10829); *(_QWORD *)&v47 = sub_10810C(10847); *((_QWORD *)&v47 + 1) = sub_10945C(10864); *(_QWORD *)&v48 = sub_10B324(10891); *((_QWORD *)&v48 + 1) = sub_105398(11007); *(_QWORD *)&v49 = sub_107114(11033); *((_QWORD *)&v49 + 1) = sub_107DA0(11044); v50 = sub_108A28(11055); v51 = sub_10AC4C(11085); v52 = sub_105A64(11113); v53 = sub_107C7C(11143); v54 = sub_109FC0(11174); if ( (sub_94FC(*(__int64 *)task_info_out) & 1) != 0 ) { v4 = 0; LABEL_6: if ( !a1 ) return &dword_0 + 1; v7 = *(const char **)&task_info_out[2 * v4]; goto LABEL_8; } v5 = 0; while ( v5 != 20 ) { v4 = v5 + 1; v6 = sub_94FC(*(_QWORD *)&task_info_out[2 * v5 + 2]); v5 = v4; if ( v6 ) goto LABEL_6; } ``` `sub_94FC`内进行操作: ``` bool __fastcall sub_94FC(const char *a1) { _BOOL8 v2; // x20 stat v4; // [xsp+0h] [xbp-1C0h] BYREF char v5; // [xsp+90h] [xbp-130h] BYREF char v6[263]; // [xsp+91h] [xbp-12Fh] BYREF if ( !(unsigned int)sub_9FD04(a1, 0) || !(unsigned int)sub_9FCB0(a1, &v4) ) return 1; sub_95EC(&v5, a1); if ( (unsigned int)sub_9FD04(v6, 0) ) v2 = (unsigned int)sub_9FCB0(v6, &v4) == 0; else v2 = 1; if ( v5 ) unlink(v6); return v2; } ``` 两个条件分别是: ``` __int64 __fastcall sub_9FD04(const char *a1, int a2) { __int64 (__fastcall *v3)(__int64); // x8 if ( dword_175530 ) return access(a1, a2); v3 = (__int64 (__fastcall *)(__int64))off_178948; if ( !off_178948 ) { v3 = sub_AC4E8; off_178948 = sub_AC4E8; } return v3(33); } __int64 __fastcall sub_9FCB0(const char *a1, stat *a2) { __int64 (__fastcall *v3)(__int64); // x8 if ( dword_175530 ) return stat(a1, a2); v3 = (__int64 (__fastcall *)(__int64))off_178948; if ( !off_178948 ) { v3 = sub_AC4E8; off_178948 = sub_AC4E8; } return v3(338); } ``` 可以看到使用的是`access`与`stat`二者,任意一者检测到即为风险。检测名单如下: - `/Applications/Cydia.app` - `/Applications/Blackra1n.app` - `/Applications/FakeCarrier.app` - `/Applications/Icy.app` - `/Applications/IntelliScreen.app` - `/Applications/MxTube.app` - `/Applications/RockApp.app` - `/Applications/SBSetttings.app` - `/Applications/WinterBoard.app` - `/Applications/Sileo.app` - `/bin/su` - `/usr/sbin/sshd` - `/Library/MobileSubstrate` - `/usr/lib/tweaks` - `/electra` - `/chimera` - `/usr/lib/libsubstrate.dylib` - `/usr/lib/jelbrekLib.dylib` - `/usr/lib/libjailbreak.dylib` - `/usr/lib/libsubstitute.dylib` - `/private/etc/apt/sileo.sources` ##### 2.2.2.2 符号链接检测 再往下看: ``` v9 = (const char *)sub_107A34(9741); *(_QWORD *)task_info_outCnt = v9; *(_QWORD *)&task_info_outCnt[2] = sub_108C6C(9757); *(_QWORD *)&v57 = sub_10AFB8(9788); *((_QWORD *)&v57 + 1) = sub_106130(9819); *(_QWORD *)&v58 = sub_107DA0(9844); *((_QWORD *)&v58 + 1) = sub_109A0C(9869); *(_QWORD *)&v59 = sub_107A34(10341); v48 = 0u; v49 = 0u; v46 = 0u; v47 = 0u; v44 = 0u; v45 = 0u; *(_OWORD *)task_info_out = 0u; v43 = 0u; if ( sub_104590(v9, (char *)task_info_out, 0x7Fu) > 0 ) { LABEL_13: if ( !a1 ) return &dword_0 + 1; v8 = a1; v7 = v9; LABEL_15: sub_1C36C(v8, (unsigned __int64)v7, a2); return &dword_0 + 1; } v10 = 2; while ( v10 != 14 ) { v9 = *(const char **)&task_info_outCnt[v10]; v48 = 0u; v49 = 0u; v46 = 0u; v47 = 0u; v44 = 0u; v45 = 0u; *(_OWORD *)task_info_out = 0u; v43 = 0u; v10 += 2; if ( sub_104590(v9, (char *)task_info_out, 0x7Fu) >= 1 ) goto LABEL_13; } ``` 核心检测函数`sub_0104590` ``` ssize_t __fastcall sub_104590(const char *a1, char *a2, size_t a3) { if ( (dword_189240 & 0x40000) != 0 ) return sub_AC4E8(58); else return readlink(a1, a2, a3); } ``` 可以看到使用`readlink`函数检测固定符号链接。检测列表为: - `/Applications` - `/var/stash/Library/Ringtones` - `/var/stash/Library/Wallpaper` - `/var/stash/usr/include` - `/var/stash/usr/libexec` - `/var/stash/usr/share` - `/User` ##### 2.2.2.3 fstab可写挂载检测 继续往下看: ``` v12 = sub_106378(9321); v13 = sub_107238(9334); v55[0] = 0; v55[1] = 0; if ( (sub_94FC((__int64)v12) & 1) != 0 || (v12 = (char *)v13, (unsigned int)sub_94FC(v13)) ) { if ( v12 ) { v14 = sub_9ECD4(v12, "r"); if ( v14 ) { v15 = v14; v58 = 0u; v59 = 0u; *(_OWORD *)task_info_outCnt = 0u; v57 = 0u; *(_QWORD *)&v46 = 0; v44 = 0u; v45 = 0u; *(_OWORD *)task_info_out = 0u; v43 = 0u; do { if ( !sub_9FB8C((char *)task_info_outCnt) ) { sub_9F018(v15); goto LABEL_33; } } while ( sscanf( (const char *)task_info_outCnt, "%s %s %s %s %d %d", task_info_out, &v43, &v44, &v45, &v46, (char *)&v46 + 4) != 6 || DWORD1(v46) != 1 ); sub_1C36C(v55, (unsigned __int64)&v45, 16); sub_9F018(v15); v17 = sub_1C238(v55, "rw", 2); v7 = v12; if ( v17 ) goto LABEL_33; if ( !a1 ) return &dword_0 + 1; goto LABEL_8; } } } LABEL_33: ``` 可以看到其打开`/etc/fstab`和`/private/etc/fstab`两个文件,然后逐行`sscanf("%s %s %s %s %d %d")`,当一行能解析出6列,并且第4列是 `rw`,最后一个整数是 `1`,就认为系统存在可写挂载配置。 ##### 2.2.2.4 检测隐藏的su ``` v27 = sub_106C90(10829); if ( (sub_94FC(v27) & 1) == 0 ) { v28 = sub_1077EC(10839); v29 = sub_9FEA0(v28); if ( v29 ) { v30 = v29; while ( 1 ) { v31 = sub_9FF74(v30); if ( !v31 ) break; if ( !(unsigned int)sub_1C0F0(v31->d_name, "su") ) { sub_A0044(v30); v7 = "shadowed"; goto LABEL_8; } } sub_A0044(v30); } } ``` 这里直接枚举`/bin/`,只要目录项里出现了`su`就检测到,防止路径访问被直接hook。 ##### 2.2.2.5 SSH检测 ``` if ( (sub_92B4(a1, a2) & 1) != 0 ) return &dword_0 + 1; ``` 此处跳进`SSH`检测: ``` __int64 __fastcall sub_92B4(_BYTE *a1, __int64 a2) { __int64 v4; // x0 __int64 v5; // x21 __int64 v6; // x0 __int64 result; // x0 char *v8; // x21 unsigned __int64 v9; // x0 sub_AD0CC(); v5 = v4; v6 = sub_108904(9254); result = sub_AD3F8(v5, v6, 1, 1); if ( (_DWORD)result ) { v8 = sub_10B56C(11393); if ( (sub_9350(0x16u, v8) & 1) != 0 || (result = sub_9350(0x2Cu, v8), (_DWORD)result) ) { if ( a1 ) { v9 = sub_1066E4(10424); sub_1C36C(a1, v9, a2); } return 1; } } return result; } ``` 此处可以看到使用`sub_10B56C`字符串,查询一下:  定位到`SSH-`,因此确定其检测的是`SSH Banner` 依旧是两个条件,分别往这个函数传入`0x16u`与`0x2Cu`,代表端口`22`和`44`: ``` _BYTE *__fastcall sub_9350(unsigned __int16 a1, _BYTE *a2) { _BYTE *v2; // x19 unsigned int v4; // w0 int v5; // w20 int v6; // w21 __int64 v7; // x22 __int128 v9; // [xsp+0h] [xbp-180h] BYREF fd_set v10; // [xsp+10h] [xbp-170h] BYREF _BYTE v11[104]; // [xsp+98h] [xbp-E8h] BYREF _OWORD v12[4]; // [xsp+100h] [xbp-80h] BYREF v2 = a2; if ( a2 ) { if ( *a2 ) { sub_9D94(v11, 0, 0); if ( (unsigned int)sub_9E68(v11, "127.0.0.1", a1, 0) ) goto LABEL_10; v4 = sub_A0BC(v11); v5 = v4; if ( v4 - 1 > 0x3FE || (memset(&v10, 0, sizeof(v10)), v6 = 1 << v4, v7 = v4 >> 5, v10.fds_bits[(unsigned int)v7] |= 1 << v4, v9 = xmmword_118C30, select(v4 + 1, &v10, 0, 0, (timeval *)&v9) < 1) || (v10.fds_bits[v7] & v6) == 0 || (memset(v12, 0, sizeof(v12)), (unsigned int)sub_104748(v5, v12) == -1) || (sub_10B56C(11393), (sub_DEB44(v12, v2) & 1) == 0) ) { LABEL_10: v2 = 0; } else { v2 = (_BYTE *)(&dword_0 + 1); } sub_9E5C(v11); } else { return 0; } } return v2; } ``` ##### 2.2.2.6 Substrate / Substitute 模块检测 ``` *(_QWORD *)task_info_out = sub_105398(11207); *(_QWORD *)&task_info_out[2] = sub_106ED4(11231); *(_QWORD *)&v43 = sub_108C6C(11257); *((_QWORD *)&v43 + 1) = sub_10AB28(11284); *(_QWORD *)&v44 = sub_1054B8(11308); *((_QWORD *)&v44 + 1) = sub_107238(11334); v18 = sub_D85D8(); if ( v18 ) { v19 = v18; v20 = 0; while ( 2 ) { v21 = sub_D8620((__int64)v19); if ( v21 ) { v22 = v21; for ( i = 0; i != 12; i += 2 ) { if ( (unsigned int)sub_DEBB4(v22, *(_QWORD *)&task_info_out[i]) ) { if ( a1 ) { v26 = basename(v22); sub_1C118(a1, v26, a2); } sub_D875C(v19); return &dword_0 + 1; } } if ( ++v20 != 100000 ) continue; } break; } sub_D875C(v19); } ``` 其中`sub_D85D8`和`sub_D8620`分支比较长,这里不再贴代码了,遍历`100000`个image路径,然后检测匹配: - `MobileSubstrate.dylib` - `substitute-loader.dylib` - `SubstrateBootstrap.dylib` - `SubstrateLoader.dylib` - `SubstrateInserter.dylib` - `libsubstrate.dylib` ##### 2.2.2.7 检测环境变量`DYLD_INSERT_LIBRARIES` ``` v24 = sub_108A28(11355); v25 = getenv(v24); if ( v25 ) { if ( a1 ) sub_1CCE8(a1, a2, "%s,%s", v24, v25); return &dword_0 + 1; } ``` 这里的`sub_108A28(11355)`就是环境变量名`DYLD_INSERT_LIBRARIES` ##### 2.2.2.8 检测`task_for_pid` ``` sub_AD0CC(); v33 = v32; v34 = sub_106928(12226); if ( (unsigned int)sub_AD3F8(v33, v34, 1, 1) ) { v44 = 0u; v45 = 0u; *(_OWORD *)task_info_out = 0u; v43 = 0u; task_info_outCnt[0] = 16; if ( !task_info(mach_task_self_, 0x13u, task_info_out, task_info_outCnt) && (__int64)v43 >= 1 ) { if ( !a1 ) return &dword_0 + 1; v41 = (char *)sub_106928(12226); goto LABEL_66; } } ``` 这里检测的是: ``` task_info(mach_task_self_, TASK_EXTMOD_INFO=0x13, out, outCnt=16) ``` 该函数可以查当前进程扩展信息里是否留下过 `task_for_pid` 相关痕迹。 ##### 2.2.2.9 `posix_spawn`检测 ``` sub_AD0CC(); v36 = v35; v37 = sub_1077EC(12239); result = (_DWORD *)sub_AD3F8(v36, (__int64)v37, 1, 1); if ( (_DWORD)result ) { v38 = (const char *)sub_108904(12254); result = dlopen(v38, 1); if ( result ) { v39 = result; v40 = (const char *)sub_10AA04(12283); result = dlsym(v39, v40); if ( result ) { if ( *result != 1476395088 ) return 0; if ( !a1 ) return &dword_0 + 1; v41 = sub_1077EC(12239); LABEL_66: v7 = v41; LABEL_8: v8 = a1; goto LABEL_15; } } } return result; } ``` 这里的流程等价于 ``` dlopen("/usr/lib/libSystem.B.dylib", 1) dlsym(handle, "posix_spawn") 读取 posix_spawn 首 4 字节 比较 0x58000050 ``` 检测首条指令是否等于 `0x58000050`,不太清楚该绝对地址代表什么特征,如有大佬清楚可以补充解答一下。 #### 2.2.3 Frida检测函数 `sub_12B30` 从字符串列表里可以看见一个显眼的`frida-server`,定位到该函数。 ``` __int64 sub_12B30() { __int64 i; // x19 __int64 v2; // x20 unsigned __int8 *v4; // x20 void *v5; // x0 __int64 v6; // x19 int v7; // w22 __int64 v8; // x0 unsigned __int64 v9; // x21 char *v10; // x0 _OWORD v11[8]; // [xsp+0h] [xbp-D0h] BYREF const char *v12[4]; // [xsp+88h] [xbp-48h] v12[0] = (const char *)sub_10921C(27862); v12[1] = (const char *)sub_1055D8(27909); v12[2] = (const char *)sub_107238(27934); v12[3] = (const char *)sub_1096A0(27966); if ( sub_36E74(v12[0]) ) return 1; for ( i = 1; i != 4; ++i ) { if ( sub_36E74(v12[i]) ) return 1; } v4 = (unsigned __int8 *)sub_106254(28020); v5 = sub_A83B0(); if ( !v5 ) return 0; v6 = (__int64)v5; v7 = 1000; while ( 1 ) { v8 = sub_A8400(v6); if ( !v8 ) { LABEL_12: v2 = 0; goto LABEL_15; } v9 = v8 + 4; if ( !(unsigned int)sub_1C238((_BYTE *)(v8 + 4), v4, 512) ) break; if ( !--v7 ) goto LABEL_12; } memset(v11, 0, sizeof(v11)); sub_1C36C(v11, v9, 128); v10 = sub_106378(27621); sub_98EF0(706, v10); v2 = 1; LABEL_15: sub_A8464(v6); return v2; } ``` 开局先解四个字符串,分别是: - `/Library/LaunchDaemons/re.frida.server.plist` - `/usr/sbin/frida-server` - `/var/jb/usr/sbin/frida-server` - `/var/jb/Library/LaunchDaemons/re.frida.server.plist` 路径检测的方式依旧是`access`与`stat`,这里不再展开。如果路径检测没中,就进入`sub_A83B0`函数读取进程表: ``` void *sub_A83B0() { void *v0; // x0 void *v1; // x19 v0 = malloc(0x218u); v1 = v0; if ( v0 ) { bzero(v0, 0x218u); if ( (unsigned int)sub_A8280((__int64)v1) ) { free(v1); return 0; } } return v1; } __int64 __fastcall sub_A8280(__int64 a1) { __int64 result; // x0 size_t v3; // x0 void *v4; // x0 size_t v5; // [xsp+8h] [xbp-38h] BYREF int v6[6]; // [xsp+10h] [xbp-30h] BYREF *(_OWORD *)v6 = xmmword_129040; v5 = 0; if ( sysctl(v6, 4u, 0, &v5, 0, 0) ) return 0xFFFFFFFFLL; v3 = v5; if ( __ROR8__(0x2C3F35BA781948B1LL * v5, 3) >= 0x6522C3F35BA782uLL ) { v3 = v5 + v5 / 0xA; v5 = v3; } *(_QWORD *)a1 = v3 / 0x288; *(_DWORD *)(a1 + 16) = 0; v4 = malloc(v3); *(_QWORD *)(a1 + 8) = v4; if ( !v4 ) return 0xFFFFFFFFLL; result = sysctl(v6, 4u, v4, &v5, 0, 0); if ( (_DWORD)result ) { free(*(void **)(a1 + 8)); *(_QWORD *)(a1 + 8) = 0; return 0xFFFFFFFFLL; } return result; } ``` 这里用的是`sysctl(KERN_PROC_ALL)`读取进程表。随手进入`sub_A8400`: ``` __int64 __fastcall sub_A8400(__int64 a1) { unsigned __int64 v1; // x8 __int64 v2; // x9 __int64 v3; // x8 __int64 v4; // x19 v1 = *(int *)(a1 + 16); if ( *(_QWORD *)a1 <= v1 ) return 0; v2 = *(_QWORD *)(a1 + 8); *(_DWORD *)(a1 + 16) = v1 + 1; v3 = v2 + 648 * v1; *(_DWORD *)(a1 + 20) = *(_DWORD *)(v3 + 40); v4 = a1 + 20; sub_1C3E4(a1 + 24, v3 + 243, 512); return v4; } ``` 返回`{pid, name}`临时结构,进程名来自`kinfo_proc`,偏移值`+243`。最多1000条,只检测`frida-server`命中。 #### 2.2.4 Flex修改器检测 `sub_1F600` 依旧公式,字符串里找到了`gp3_mod_flex`定位到函数: ``` __int64 __fastcall sub_1F600(__int64 a1, unsigned __int8 *a2) { unsigned __int8 *v4; // x0 __int64 v6; // x0 if ( !a2 ) return 0; if ( !*a2 ) return 0; v4 = (unsigned __int8 *)sub_10A7BC(25081); if ( (unsigned int)sub_1C0F0(a2, v4) ) return 0; v6 = sub_79088(); sub_7CE64(v6, a1 + 16); sub_1F660(); return 1; } ``` 只检测传入运行时对象名是不是`gp3_mod_flex` #### 2.2.5 反调试聚合 `sub_3BF44` ``` __int64 __fastcall sub_3BF44(__int64 a1) { __int64 v3; // x0 __int64 v4; // x20 char *v5; // x0 __int64 v6; // x2 __int64 v7; // x3 __int64 v8; // x4 __int64 v9; // x5 __int64 v10; // x6 __int64 v11; // x7 __int64 v12; // x0 const char *v13; // x0 int v14; // [xsp+8h] [xbp-18h] BYREF v14 = 32; if ( !*(_BYTE *)(a1 + 12) ) { sub_AD0CC(); v4 = v3; v5 = sub_10B56C(36693); if ( (sub_AD3F8(v4, (__int64)v5, 0, 1) & 1) != 0 || (v12 = sub_6E524(), sub_F8B98(v12)) ) { if ( !sub_3A044(0, 0, v6, v7, v8, v9, v10, v11) ) { if ( (unsigned int)sub_3C010() ) { v13 = (const char *)sub_108D90(35158); sub_99048(v13); } else if ( !sub_3C0BC() ) { *(_BYTE *)(a1 + 12) = 1; } } } } return sub_130E8(&v14); } ``` 这里聚合了一些反调试功能,通过xref ptrace检测函数找到该调度点。 ##### 2.2.5.1 ptrace检查 `sub_3C010` 常规反调试特征检查 ``` __int64 sub_3C010() { bool v0; // w8 size_t v2; // [xsp+8h] [xbp-2B8h] BYREF int v3[2]; // [xsp+10h] [xbp-2B0h] BYREF int v4; // [xsp+18h] [xbp-2A8h] pid_t v5; // [xsp+1Ch] [xbp-2A4h] _BYTE v6[32]; // [xsp+20h] [xbp-2A0h] BYREF int v7; // [xsp+40h] [xbp-280h] bzero(v6, 0x288u); v2 = 648; *(_QWORD *)v3 = 0xE00000001LL; v4 = 1; v5 = getpid(); v0 = sysctl(v3, 4u, v6, &v2, 0, 0) != -1; return *(_DWORD *)&v0 & ((unsigned __int16)(v7 & 0x800) >> 11); } ``` 输出大小 `0x288`,也就是 648 字节的 `kinfo_proc`。读取 `extern_proc.p_flag` 后判断`p_flag & 0x800`,`0x800` 就是 `P_TRACED`。 ##### 2.2.5.2 设置`PT_DENY_ATTACH`:`sub_3C0BC` 如果上面ptrace没命中,则进入: ``` int sub_3C0BC() { return mac_syscall(SYS_ptrace, 31, 0, 0, 0); } ``` 这里的反汇编是: ``` __text:000000000003C0BC MOV X0, #0x1F __text:000000000003C0C0 MOV X1, #0 __text:000000000003C0C4 MOV X2, #0 __text:000000000003C0C8 MOV X3, #0 __text:000000000003C0CC MOV X16, #0x1A __text:000000000003C0D0 SVC 0x80 __text:000000000003C0D4 BR X30 ``` `x0=31` 是 `PT_DENY_ATTACH`,`x16=26` 是 macOS/iOS syscall 表里的 ptrace syscall。调用成功后,`sub_3BF44` 会把对象字段 `a1+0x0c` 置 1,避免重复设置。 ##### 2.2.6 Receipt检查 `sub_6BD44` ``` void sub_6BD44() { unsigned __int8 *v0; // x0 _BYTE v1[1024]; // [xsp+8h] [xbp-418h] BYREF bzero(v1, 0x400u); if ( !(unsigned int)sub_3A158(v1, 1024) ) { v0 = (unsigned __int8 *)sub_107EC4(6045); if ( sub_DEBB4((__int64)v1, v0) ) sub_970FC((__int64)v1); } } ``` 先收集Receipt信息: ``` __int64 __fastcall sub_3A158(_BYTE *a1, __int64 a2) { void *v4; // x19 NSBundle *v5; // x20 NSURL *v6; // x24 NSString *v7; // x23 NSString *v8; // x20 const char *v9; // x1 __int64 v10; // x21 v4 = objc_autoreleasePoolPush(); v5 = objc_retainAutoreleasedReturnValue(+[NSBundle mainBundle](&OBJC_CLASS___NSBundle, "mainBundle")); v6 = objc_retainAutoreleasedReturnValue(-[NSBundle appStoreReceiptURL](v5, "appStoreReceiptURL")); v7 = objc_retainAutoreleasedReturnValue(-[NSURL absoluteString](v6, "absoluteString")); objc_release(v6); objc_release(v5); if ( v7 && (v8 = objc_retainAutorelease(v7), (v9 = -[NSString UTF8String](v8, "UTF8String")) != 0) ) { sub_1C36C(a1, (unsigned __int64)v9, a2); v10 = 0; v7 = v8; } else { v10 = 0xFFFFFFFFLL; } objc_release(v7); objc_autoreleasePoolPop(v4); return v10; } ``` 收集到的信息匹配`sandboxReceipt`,沙盒receipt一般说明App被测试签名、测试安装,说明不是正版,大概率是Sideload。 #### 2.2.7 CodeResources 与自定义防篡改 `sub_34E94` 先获取`bundle path`,然后拼接字符串`/_CodeSignature/CodeResources`: ``` if ( !(unsigned int)sub_822F0((__int64)&v67, 0x100u) ) { v5 = sub_108230(14348); sub_1C2E8(&v67, v5, 256); ``` 然后打开,解析`plist`,找到`files2`与`hash2`: ``` sub_FADD0(v123); v4 = 65539; if ( (sub_FAE4C(v123, &v67) & 1) != 0 ) { v6 = sub_FB228(v123); v7 = (_QWORD *)sub_ED200(v6); v8 = v7; if ( v7 ) { v9 = sub_E7208(*v7, "plist"); if ( v9 ) { v10 = sub_E7208(v9, "dict"); if ( v10 ) { for ( i = sub_E7208(v10, "key"); i; i = sub_E7294(i, "key") ) { v12 = (unsigned __int8 *)sub_E7A6C(i); if ( v12 && !(unsigned int)sub_1C238("files2", v12, 7) ) { v19 = (unsigned __int8 *)sub_E7A6C(j); if ( v19 && !(unsigned int)sub_1C238("hash2", v19, 6) ) { ``` 遍历bundle内资源: ``` v13 = sub_E7294(i, "dict"); if ( v13 ) { v14 = sub_E7208(v13, "key"); if ( v14 ) { while ( v14 ) { v15 = sub_E7A6C(v14); v16 = v15; if ( v15 && (sub_35D04(v15) & 1) == 0 ) { v17 = sub_E7294(v14, "dict"); if ( !v17 ) goto LABEL_35; for ( j = sub_E7208(v17, "key"); j; j = sub_E7294(j, "key") ) { ``` 计算哈希,置入表内: ``` v20 = sub_E7294(j, "data"); j = v20; if ( !v20 ) goto LABEL_35; v21 = sub_E7A6C(v20); if ( v21 && (unsigned int)sub_D7174(v21, &v124, 33) == 32 ) { v126 = 0; v83 = 0; v84 = 0; sub_D890(&v83, v16); v90 = 0u; if ( v83 ) v22 = v83; else v22 = ""; sub_D890(&v90, v22); LOBYTE(v93) = v126; v91 = v124; v92 = v125; sub_35EEC(v65, &v90); sub_7704(&v90); sub_7704(&v83); } } } } v14 = sub_E7294(v14, "key"); } v4 = 0; } } break; } } } } LABEL_35: sub_ED2B8(v8); } else { v4 = 65538; } } else { v4 = 65537; } sub_FAE14(v123); ``` 统计缺失、冗余、hash 不匹配(v1 = hash 不匹配,v2 = 冗余,v3 = 缺失): ``` v43 = (_QWORD *)v64[0]; v1 = 0; v2 = 0; if ( v64[0] ) { do { v44 = (const char **)(v43 + 2); v45 = sub_363D4(v65, v43 + 2); if ( v45 ) { *(_BYTE *)(v45 + 72) = 1; if ( (unsigned int)sub_1C0B4(v45 + 40, v43 + 5, 32) ) { v46 = *v44 ? (char *)*v44 : ""; if ( (sub_35AC8((__int64)v46) & 1) == 0 ) { v1 = (unsigned int)(v1 + 1); if ( *v44 ) v47 = *v44; else v47 = ""; sub_35C70(v1, v47, 256); } } } else { if ( *v44 ) v48 = *v44; else v48 = ""; if ( (sub_35AC8((__int64)v48) & 1) == 0 ) { v2 = (unsigned int)(v2 + 1); if ( *v44 ) v49 = *v44; else v49 = ""; sub_35C70(v2, v49, 512); } } v43 = (_QWORD *)*v43; } while ( v43 ); } v50 = v65[0]; while ( 1 ) { v4 = v50; if ( !v50 ) break; v50 = *(_QWORD *)(v50 + 8); if ( !v50 ) { v3 = 0; LABEL_88: if ( *(_BYTE *)(v4 + 72) != 1 ) { if ( *(_QWORD *)(v4 + 24) ) v51 = *(const char **)(v4 + 24); else v51 = ""; v3 = (unsigned int)v3 + ((unsigned int)sub_35AC8((__int64)v51) ^ 1); } v52 = *(_QWORD *)(v4 + 16); while ( v52 ) { v53 = v52; v52 = *(_QWORD *)(v52 + 8); if ( !v52 ) { v4 = v53; goto LABEL_88; } } v54 = (__int64 *)v4; while ( 1 ) { v4 = *v54; if ( !*v54 ) goto LABEL_57; v55 = *(_QWORD *)(v4 + 8) == (_QWORD)v54; v54 = (__int64 *)*v54; if ( v55 ) goto LABEL_88; } } } ``` 上述检测完成后,如果没有问题,则进入自定义清单检测,分别有两个文件: 1. `/__a**info.dat` ``` v23 = sub_10A698(14380); sub_1C2E8(v123, v23, 512); sub_FADD0(v66); if ( (sub_FAE4C(v66, v123) & 1) != 0 ) { v24 = (char *)sub_FB228(v66); v25 = sub_FB230(v66); __dst = 0u; sub_D7630(&v124); sub_D7650((int)&v124, v24, v25); sub_D8170(&v124, &__dst); ``` 2. `/__a**cfinfo.dat` ``` if ( !(unsigned int)sub_822F0((__int64)&v90, 0x200u) ) { v26 = sub_10B9F8(14397); sub_1C2E8(&v90, v26, 512); sub_FADD0(&v83); if ( (sub_FAE4C(&v83, &v90) & 1) == 0 ) goto LABEL_47; ``` 获取记录后首先校验Magic `v30 == -1417418754`: ``` v122 = xmmword_119200; if ( v25 >= 0x10 ) { sub_FB3C0(&v67, v24, v25, 0, 0); v30 = sub_FB61C(&v67); v31 = sub_FB61C(&v67); v32 = sub_FB61C(&v67); if ( v30 == -1417418754 && (v33 = v25 - 12, (((_DWORD)v25 - 12) & 0xF) == 0) ) { ``` 然后对资源进行解密: ``` sub_D8948(&v90); if ( (unsigned int)sub_D8964((int)&v90, (int)&v122, off_177EF0, 16, 0x10u) ) { v4 = 32769; } else { sub_FB3C0(&v124, 0, 0, 1, 0); if ( (sub_FB910(&v124, v57) & 1) != 0 ) { v58 = v124; bzero((void *)v124, v125); if ( (unsigned int)sub_D9BB4(&v90, v56, v58, v57, 2) ) { v4 = 32771; } ``` 这两个文件是运行时产生的,解密是标准AES-128-CFB: ``` key = 71 9A 62 0E 24 EA 10 EA E0 EA DA 6A DD 3F 41 ED iv = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ``` 解密函数比较大,使用AI辅助分析一下即可: ``` sub_D8948(&v90) 初始化 Rijndael/AES ctx: ctx[0] = off_168F00 ctx+8 = 0,表示未初始化 sub_D8964(&v90, &v122, off_177EF0, 16, 0x10) Rijndael/AES key schedule key_len = 16 block_len = 16 rounds = 10 key = v122,也就是栈上的 16 字节 key iv = *off_177EF0 指向的前 16 字节 sub_FB3C0 / sub_FB910 只是在申请输出缓冲区,不参与密码算法 sub_D9BB4(&v90, v56, v58, v57, 2) mode = 2 AES-CFB 解密 ``` 解密后再与实际资源进行匹配: ``` v59 = sub_FB61C(&v124); if ( v59 ) { while ( (_QWORD)v125 - *((_QWORD *)&v124 + 1) >= 0x2Du ) { __dst = 0u; v89 = 0u; v60 = sub_FB660(&v124); sub_FB8B4((int)&v124, &__dst); sub_FB61C(&v124); v61 = sub_FB6A8(&v124); if ( (sub_35D04(v61) & 1) == 0 ) { v83 = 0; v84 = 0; v86 = __dst; v87 = v89; sub_D890(&v83, v61); v85 = v60; sub_362E4(v64, &v83); sub_7704(&v83); } v4 = 0; if ( !--v59 ) goto LABEL_110; } v4 = 16388; } ``` #### 2.2.8 系统状态检测 ##### 2.2.8.1 VPN检测 ``` __int64 __fastcall sub_388CC(_BYTE *a1, int a2) { __int64 v4; // x0 __int64 v5; // x19 __int64 v6; // x0 __int64 v7; // x20 __int64 v8; // x0 void *v9; // x27 UIDevice *v10; // x21 NSString *v11; // x23 double v12; // d0 __int64 v13; // x20 CFDictionaryRef v15; // x26 NSString *v16; // x21 id v17; // x24 id v18; // x19 id v19; // x0 __int64 v20; // x23 __int64 v21; // x8 void *v22; // x20 NSString *v23; // x27 NSString *v24; // x21 NSString *v25; // x26 NSString *v26; // x19 id v27; // x20 __int64 v28; // x20 const char *v29; // x0 __int64 v30; // x0 _BYTE *v31; // [xsp+10h] [xbp-160h] void *v32; // [xsp+18h] [xbp-158h] const __CFDictionary *v33; // [xsp+20h] [xbp-150h] NSString *v34; // [xsp+28h] [xbp-148h] int v35; // [xsp+30h] [xbp-140h] __int64 v36; // [xsp+38h] [xbp-138h] id obj; // [xsp+40h] [xbp-130h] __int64 v38; // [xsp+48h] [xbp-128h] __int128 v39; // [xsp+50h] [xbp-120h] BYREF __int128 v40; // [xsp+60h] [xbp-110h] __int128 v41; // [xsp+70h] [xbp-100h] __int128 v42; // [xsp+80h] [xbp-F0h] _BYTE v43[128]; // [xsp+90h] [xbp-E0h] BYREF if ( *(_BYTE *)(sub_6E524() + 1245) ) return 0; v4 = sub_6E524(); v5 = sub_F8898(v4); sub_AD0CC(); v7 = v6; v8 = sub_10735C(17235); if ( !(unsigned int)sub_AD3F8(v7, v8, v5, 1) ) return 0; v9 = objc_autoreleasePoolPush(); v10 = objc_retainAutoreleasedReturnValue(+[UIDevice currentDevice](&OBJC_CLASS___UIDevice, "currentDevice")); v11 = objc_retainAutoreleasedReturnValue(-[UIDevice systemVersion](v10, "systemVersion")); objc_release(v10); -[NSString doubleValue](v11, "doubleValue"); if ( v12 >= 9.0 ) { v15 = CFNetworkCopySystemProxySettings(); v35 = a2; v16 = objc_retainAutoreleasedReturnValue(+[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", sub_10AD70(11986))); v17 = objc_retainAutoreleasedReturnValue(-[__CFDictionary objectForKeyedSubscript:](v15, "objectForKeyedSubscript:", v16)); v18 = objc_retainAutoreleasedReturnValue(objc_msgSend(v17, "allKeys")); objc_release(v17); objc_release(v16); v41 = 0u; v42 = 0u; v39 = 0u; v40 = 0u; obj = objc_retain(v18); v19 = objc_msgSend(obj, "countByEnumeratingWithState:objects:count:", &v39, v43, 16); if ( v19 ) { v38 = *(_QWORD *)v40; v33 = v15; v34 = v11; v31 = a1; v32 = v9; while ( 2 ) { v20 = 0; if ( (unsigned __int64)v19 <= 1 ) v21 = 1; else v21 = (__int64)v19; v36 = v21; do { if ( *(_QWORD *)v40 != v38 ) objc_enumerationMutation(obj); v22 = *(void **)(*((_QWORD *)&v39 + 1) + 8 * v20); v23 = objc_retainAutoreleasedReturnValue(+[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", sub_10BC40(11999))); if ( objc_msgSend(v22, "rangeOfString:", v23) != (id)0x7FFFFFFFFFFFFFFFLL ) goto LABEL_25; v24 = objc_retainAutoreleasedReturnValue(+[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", sub_105154(12005))); if ( objc_msgSend(v22, "rangeOfString:", v24) != (id)0x7FFFFFFFFFFFFFFFLL ) goto LABEL_24; v25 = objc_retainAutoreleasedReturnValue(+[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", sub_105820(12011))); if ( objc_msgSend(v22, "rangeOfString:", v25) != (id)0x7FFFFFFFFFFFFFFFLL ) { objc_release(v25); LABEL_24: objc_release(v24); LABEL_25: objc_release(v23); LABEL_26: v28 = 1; v15 = v33; v11 = v34; a1 = v31; v9 = v32; goto LABEL_27; } v26 = objc_retainAutoreleasedReturnValue(+[NSString stringWithUTF8String:](&OBJC_CLASS___NSString, "stringWithUTF8String:", sub_106130(12019))); v27 = objc_msgSend(v22, "rangeOfString:", v26); objc_release(v26); objc_release(v25); objc_release(v24); objc_release(v23); if ( v27 != (id)0x7FFFFFFFFFFFFFFFLL ) goto LABEL_26; ++v20; } while ( v36 != v20 ); v19 = objc_msgSend(obj, "countByEnumeratingWithState:objects:count:", &v39, v43, 16); v28 = 0; v15 = v33; v11 = v34; a1 = v31; v9 = v32; if ( v19 ) continue; break; } } else { v28 = 0; } LABEL_27: objc_release(obj); v29 = (const char *)sub_10A450(39978); sub_1CCE8(a1, v35, v29, v28); v30 = sub_6E524(); sub_F98D8(v30, 1, v28); objc_release(obj); objc_release(v15); v13 = 1; } else { v13 = 0; } objc_release(v11); objc_autoreleasePoolPop(v9); return v13; } ``` 先判断: `UIDevice.systemVersion.doubleValue >= 9.0`,如果满足,则调用 `CFNetworkCopySystemProxySettings`,读取字典返回的`__SCOPED__`字段,遍历所有interface key,检测是否包含: - tap - tun - ipsec - ppp 若包含,说明开启了VPN。 #### 2.2.8.2 屏幕录制检测 ``` __int64 __fastcall sub_386C4(_BYTE *a1, int a2) { __int64 result; // x0 void *v5; // x20 UIScreen *v6; // x0 UIScreen *v7; // x19 void *v8; // x21 NSNotificationCenter *v9; // x22 id v10; // x22 NSOperationQueue *v11; // x23 id location; // [xsp+8h] [xbp-38h] BYREF if ( *(_BYTE *)(sub_6E524() + 1244) ) return 0; result = sub_1162E0(11, 0, 0); if ( (_DWORD)result ) { v5 = objc_autoreleasePoolPush(); v6 = objc_retainAutoreleasedReturnValue(+[UIScreen mainScreen](&OBJC_CLASS___UIScreen, "mainScreen")); v7 = v6; if ( !v6 ) { objc_release(0); objc_autoreleasePoolPop(v5); return 0; } sub_1CCE8(a1, a2, "iScreenCaptured:%d", -[UIScreen isCaptured](v6, "isCaptured")); if ( (unsigned int)sub_1162E0(11, 0, 0) ) { if ( (byte_1797F8 & 1) == 0 ) { byte_1797F8 = 1; v8 = objc_autoreleasePoolPush(); v9 = objc_retainAutoreleasedReturnValue(+[NSNotificationCenter defaultCenter](&OBJC_CLASS___NSNotificationCenter, "defaultCenter")); objc_initWeak(&location, v9); objc_release(v9); v10 = objc_loadWeakRetained(&location); v11 = objc_retainAutoreleasedReturnValue(+[NSOperationQueue mainQueue](&OBJC_CLASS___NSOperationQueue, "mainQueue")); objc_release( objc_retainAutoreleasedReturnValue( objc_msgSend( v10, "addObserverForName:object:queue:usingBlock:", UIScreenCapturedDidChangeNotification, 0, v11, &stru_165360))); objc_release(v11); objc_release(v10); objc_destroyWeak(&location); objc_autoreleasePoolPop(v8); } } objc_release(v7); objc_autoreleasePoolPop(v5); return 1; } return result; } ``` 调用`-[UIScreen isCaptured]`获取屏幕当前被录制状态。 ## 3. 附件 1. Framework样本 2. 字符串抽取工具 ## 结语 个人能力有限,本文未涉及绕过检测的具体手段与Patch方案。后续可能会(如果不咕咕咕)制作CrackMe与Patch教程,同时欢迎大佬指正文章中不严谨的内容。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
Frameworks.zip
(984.64kb,14次下载)
extract_strings.py
(14.18kb,15次下载)
收藏
・
3
点赞
・
3
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
梧桐生
感谢你的积极参与,期待更多精彩内容!
2026-9-5 19:04
wx_晨梦
期待更多优质内容的分享,论坛有你更精彩!
2026-7-16 09:43
Yangser
谢谢你的细致分析,受益匪浅!
2026-7-2 11:23
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
mb_cfjwplfo
雪 币:
284
能力值:
( LV1,RANK:0 )
在线值:
发帖
6
回帖
92
粉丝
20
关注
私信
mb_cfjwplfo
2
楼
欢迎加入我的团队
2026-7-1 20:44
0
mb_hcqhhwln
雪 币:
200
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
15
粉丝
0
关注
私信
mb_hcqhhwln
3
楼
0x58000050是inlinehook吧
2026-7-6 12:21
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mako233
3
发帖
1
回帖
30
RANK
关注
私信
他的文章
[原创]午时·永数囚笼 WriteUp
727
[原创] iOS App的商业级环境检测手段 - 基于某讯A<E Framework 详解
3218
[原创] AIDA64 8.25.8200 Business 逆向工程与Keygen实战
21709
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
顽劣
byhk
saykao
ONewTach
mb_rjdrqvpa
天流
mb_stzbjchs
mb_pmfjyznj
veluboii
mb_cebxltxx
mb_lthgjpwj
谁下载
×
ylp1332
顽劣
byhk
saykao
codingman
ONewTach
mb_rjdrqvpa
天流
mb_stzbjchs
mb_cfjwplfo
mb_pmfjyznj
mb_cebxltxx
mb_lthgjpwj
mb_lwjnazem
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部