CCTV果冻爽 2025-08-21 17:59 上海
看雪论坛作者ID:CCTV果冻爽
本文章内容仅用于逆向学习,请勿用于黑产行为,如有侵权,请联系本人删除,未经本人允许,不可转载。
使用google账号注册,直接被拦截:
搜索 google_login,查看到了疑似google登录的协议:
交叉引用,发送协议的参数貌似只有google登录用的token:
既然使用的是 OKHttp框架,那么一定会在 intercept 里加上额外的Header 。
通过hook 设置Header的函数,输出会额外设置哪些可疑的字段:
XposedHelpers.findAndHookMethod("okhttp3.Request$Builder", lpparam.classLoader, "a", java.lang.String.class, java.lang.String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); Log.d(getName(), "set header: " + param.args[0] + " -> " + param.args[1]); if(param.args[0].equals("v-mode")) { param.args[1] = "0"; } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); }});这些字段貌似与风控相关:
31938 32283 D 手动混淆名Injector: set header: v-mode -> 031938 32283 D 手动混淆名Injector: set header: fingerprint -> sbCHPR9lKEJl9Az05HrjMNrIN-tPyaI5RGM9OPkhcPA=31938 32283 D 手动混淆名Injector: set header: X-DFPID -> RfqWHagWCGf7tcuIeyI3Fzt1ZCztygjdsCAd+akQ871Thk5hW8tjRBMuRY5pEEv7eyicDKCqlpBUFgFoMwGTcw==31938 32283 D 手动混淆名Injector: set header: X-SIUA -> DDvtiVkMIB26EGpk9uBf9f6qeMnbZ9xCSnWLSdLzH2CyaTne1X8g5HvuDS11Bh+VQu60t6GLaZP2ycoEdG0xLOYyW9P0+gOpTPru738R3TU=v-mode 表示有没有使用VPN,如果有使用VPN ,注册时会被拦截
X-DFPID 像是一个设备id,X-SIUA 像是一个风控数据。
果然,这些字段都是在专门的intercept中被添加的:
继续跟踪发现了疑似风控SDK:
siua函数是一个native层函数。先找到native层地址,位于 0x8da04 :
[RegisterNatives] java_class: 手动混淆包名 name: siua sig: (Landroid/content/Context;)Ljava/lang/String; fnPtr: 0x71a56e3a04 module_name: libliteye.so module_base: 0x71a5656000 offset: 0x8da04
反编译的代码晦涩难懂,我们直接看汇编:
.text:000000000008DF34 MOV X0, X20.text:000000000008DF38 BL sub_4FDF0.text:000000000008DF3C ADRL X1, off_146D90.text:000000000008DF44 ADD X0, SP, #0x1C0+var_138.text:000000000008DF48 BL sub_51414.text:000000000008DF4C MOV X0, X19.text:000000000008DF50 BL sub_4FD30.text:000000000008DF54 LDR X0, [SP,#0x1C0+var_198].text:000000000008DF58 LDRB W9, [SP,#0x1C0+var_1B8].text:000000000008DF5C LDR X10, [SP,#0x1C0+var_1A8] 字符串地址.text:000000000008DF60 ADD X11, SP, #0x1C0+var_1B8.text:000000000008DF64 LDR X8, [X0].text:000000000008DF68 ORR X11, X11, #1.text:000000000008DF6C TST W9, #1.text:000000000008DF70 CSEL X1, X11, X10, EQ 待返回字符串.text:000000000008DF74 LDR X8, [X8,#0x538].text:000000000008DF78 BLR X8 env->NewStringUtf8.text:000000000008DF7C LDRB W8, [SP,#0x1C0+var_1B8].text:000000000008DF80 MOV X19, X0.text:000000000008DF84 TBZ W8, #0, loc_8DF90.text:000000000008DF88 LDR X0, [SP,#0x1C0+var_1A8].text:000000000008DF8C BL sub_6D98Cnative层函数返回时,调用了 env→NewStringUtf8,即地址 [SP,#0x1C0+var_1A8] 存放了最终加密后的字符串。
前面没有看到对 SP,#0x1C0+var_1A8 地址的直接赋值或者传递,但是看到了对 SP,#0x1C0+var_1B8 地址的传递:
.text:000000000008DECC ADD X0, SP, #0x1C0+var_168.text:000000000008DED0 MOV X1, X20.text:000000000008DED4 BL sub_8FEC0.text:000000000008DED8 ADD X0, SP, #0x1C0+tv.text:000000000008DEDC ADD X1, SP, #0x1C0+var_168.text:000000000008DEE0 BL sub_1190C8.text:000000000008DEE4 ADD X0, SP, #0x1C0+var_1B8 参数1 string 对象.text:000000000008DEE8 ADD X1, SP, #0x1C0+tv.text:000000000008DEEC BL do_compress_and_encrypt 因为分析过,所以改名了clang 或者 gcc 编译成的 c++代码中,string 对象一般占用 24个字节,字符串的实际地址,保存在string 对象的 +0x10处。
我们继续看 do_compress_and_encrypt 函数内部:
.text:000000000011D44C ADRL X2, unk_24F8E.text:000000000011D454 SUB X0, X29, #-var_C0.text:000000000011D458 BL sub_11C7F0.text:000000000011D45C SXTW X21, W21.text:000000000011D460 SUB X0, X29, #-var_C0.text:000000000011D464 MOV X1, X20.text:000000000011D468 MOV X2, X21.text:000000000011D46C BL encrypt.text:000000000011D470 SUB X0, X29, #-var_D8 参数1保存返回值.text:000000000011D474 MOV X1, X20.text:000000000011D478 MOV X2, X21.text:000000000011D47C BL base64_encode.text:000000000011D480 LDUR X8, [X29,#var_C8] c_str() 地址.text:000000000011D484 LDUR Q0, [X29,#var_D8].text:000000000011D488 STR X8, [X19,#0x10] X19是传入的string对象.text:000000000011D48C STR Q0, [X19].text:000000000011D490 MOV SP, X29.text:000000000011D494 LDP X20, X19, [SP,#var_s20].text:000000000011D498 LDP X28, X21, [SP,#var_s10].text:000000000011D49C LDP X29, X30, [SP+var_s0],#0x30.text:000000000011D4A0 RET同样的最终在进行 base64_encode 后,将字符串地址保存到了参数1传入的string对象中。通过分析发现在 base64_encode 前调用了加密函数,我们不关系用了什么加密,直接输出加密前的数据:
78da1dc74112c2200c40d11325104a911c2705ea38535b87a2abe4ee5537ffcd0f4866848c5ee936c729679a387150e19ac9c7006b5d66888b1460cf0225924c547d4eb998b92a43dc7614d9dc78be5c6fb8f647153c5bffb4eefe03726ffb8014f13c5475bc77ff457f31bb0080e829e2
二进制 0x78da,表示这是一个zlib压缩后的数据。继续解压得到了一串数据:
b'2.1}}1.9.0|1754388139692|a9d81042-fdb5-4bac-909a-c41a31d0868c}}/data/local/tmp/re.frida.server/frida-agent-64.so|||tun0||||0|||}}'
从这里可以明显看出,里面包含了 frida 注入, tun0 → vpn 接口。所以应该是这里被拦截了。
我们通过一个简单的frida 脚本绕发过这里的风控拦截:
Interceptor.attach(base.add(0x8fd44), { onEnter: function (args) { //console.log(Memory.readByteArray(args[1], args[2].toInt32())); var value = Memory.readUtf8String(args[1], args[2].toInt32()); if ( value.indexOf("frida") !== -1 || value.indexOf("tun0") !== -1 ) { console.log("fake"); // console.log( // "called from:\n" + // Thread.backtrace(this.context, Backtracer.ACCURATE) // .map(DebugSymbol.fromAddress) // .join("\n") + // "\n", // ); var fakeStr = "|||||||0|||"; args[1] = Memory.allocUtf8String(fakeStr); args[2] = ptr(fakeStr.length); } }, onLeave: function (retval) {}, });可以google注册了:
下一步:
再下一步:
又被封了!
实时的环境检测绕过了还是被封号,要么设备被标记了,或者还存在别的环境上报口径。
设备ID生成
风控SDK还提供了一个设备id接口:X-DFPID,如果设备被标记,或者设备存在环境问题,比如绕过设备ID。
这个设备id像是json串中的一个字段,应该是某个协议返回。
确实是协议返回,协议数据通过detect函数返回,也是安全SDK的一个native函数,地址位于 0x8d904。
[RegisterNatives] java_class: 手动混淆类名 name: detect sig: (Landroid/content/Context;)Ljava/lang/String; fnPtr: 0x71a56e3904 module_name: libliteye.so module_base: 0x71a5656000 offset: 0x8d904。
同样方式拦截加密前的数据,解压缩后:
{ "accessibility_services": "手动混淆包名/com.stardust.autojs.core.accessibility.AccessibilityService:", "ad_aaid": "", "android_id_cache": "error_values", "android_id_secure": "830eed0ce5d3c00f", "apk_digest": "6DC020DE115BBC92E8F26D2763519343", "app_path": "/data/app/~~TVfqUtbIXFMFDGGtd5Q5_g==/com.litatom.app-qBesS5m4xEB6L8wQPY-dzA==/base.apk", "application_name": "手动混淆类名", "arp_table": "", "attr_current": "u:r:untrusted_app:s0:c53,c257,c512,c768", "attr_prev": "u:r:zygote:s0", "available_block": " 7775991", "baseband": "AT20-0305_1024_3754711,AT20-0305_1024_3754711", "battery_path": "../../devices/soc/800f000.qcom,spmi/spmi-0/spmi0-02/800f000.qcom,spmi:qcom,pmi8998@2:qcom,qpnp-smb2/power_supply/battery", "battery_sn": "no_access", "block_size": "4096", "bluetooth_address": "", "board": "msm8998", "boot_id": "e3cc3714-1f59-4eda-be9a-004ada3a0a23", "boot_state": "green", "bootloader": "unknown", "brand": "samsung", "built_date": "2025-05-15 19:40:27.878", "cert_info": "", "cert_sig": "not_found", "cgroup": "4:schedtune:/top-app\n3:cpuset:/top-app\n2:cpuacct:/uid_10309/pid_28054\n1:cpu:/\n0::/", "classloader": "java.lang.BootClassLoader", "cores_num": 8, "country": "tr,tr", "cpu_abi": "arm64-v8a", "cpu_abi2": "", "cpu_name": "Hardware\t: Qualcomm Technologies, Inc MSM8998", "crc_liteye": "error_mem", "custom_rom": "lineage", "ddr": "", "debuggable": "0", "description": "gts7xlwifixx-user 11 RP1A.200720.012 T970XXU1BUAA release-keys", "devcie_id_generated": "", "device": "gts7xlwifi", "device_cid": "", "device_state": "locked", "drmId": "RW1nYmFhYWFocktFY0VPbXJuYWFrc0twc2x2ZFBxTAA=", "drmId_native": "RW1nYmFhYWFocktFY0VPbXJuYWFrc0twc2x2ZFBxTAA=", "dual_app": "0", "dynamic_library": "none", "emmc": "", "emu_files": "/dev/usb_accessory,", "emu_props": "{}", "eth_interface": "", "exist_battery_temp": "1", "exist_voltage_now": "1", "extm_uuid": "", "extract_native_libs": "false", "eye_file": "error_path", "eye_mem": "error_addr", "fd_app_path": "0|1000|1000|1", "fd_invalid": "{}", "fd_maps": "0|0|0|1", "fd_status": "0|0|0|1", "file_system_id": "8e126619b28c4351-1290144-327680", "fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "flash_locked": "1", "free_nodes": "3308819", "frida": "none", "frida_server": "", "frp": "/dev/block/platform/soc/1da4000.ufshc/by-name/frp", "gcbooster_uuid": "", "git_hash": "33dce53", "gnirehtet_ip": "", "gnirehtet_socket": "0", "gsm_serial": "", "gsm_state": "LOADED,LOADED", "gsm_state2": "", "hardware": "qcom", "has_battery_sym": "1", "hluda_server": "", "hook_class": "none", "host": "手动混淆", "huawei_global_uuid": "", "id": "RP1A.200720.012", "in_sand_box": "0", "init_svc_adbd": "running", "inst_access": "E80300AA600C8012E203012A", "inst_dlopen": "5000005800021FD600789409", "inst_fopen": "5000005800021FD600B32860", "inst_open": "5000005800021FD600759409", "inst_openat": "5000005800021FD600769409", "inst_readlinkat": "C80980D2010000D41F0440B1", "inst_stat": "E80300AA600C8012E20301AA", "inst_syscall": "5000005800021FD600779409", "installed_baseapk_time": "1754378457", "installed_dir_time": "1754378468", "installed_from_google": "-1", "isModApp": "0", "is_accessibility_enabled": "", "is_adb_enabled": "0", "is_cell_on": "", "is_emulator": "10", "is_inline_hooked": "0", "is_wifi_on": "1", "issuer_dn": "CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US", "key_mqs_uuid": "", "link_p2p0": "", "link_wlan0": "", "link_wlan1": "", "loaded_apk_list": "/data/app/~~TVfqUtbIXFMFDGGtd5Q5_g==/com.litatom.app-qBesS5m4xEB6L8wQPY-dzA==/base.apk:/data/app/~~TVfqUtbIXFMFDGGtd5Q5_g==/com.litatom.app-qBesS5m4xEB6L8wQPY-dzA==/split_config.arm64_v8a.apk:/data/app/~~X-jSy0h4R3kBfuf579rQtQ==/com.google.android.gms-wPFuZr_s5LDOXhJQE2k9_g==/base.apk:/data/app/~~X-jSy0h4R3kBfuf579rQtQ==/com.google.android.gms-wPFuZr_s5LDOXhJQE2k9_g==/split_MeasurementDynamite_installtime.apk:/data/app/~~X-jSy0h4R3kBfuf579rQtQ==/com.google.android.gms-wPFuZr_s5LDOXhJQE2k9_g==/split_config.en.apk:/data/app/~~X-jSy0h4R3kBfuf579rQtQ==/com.google.android.gms-wPFuZr_s5LDOXhJQE2k9_g==/split_config.xxhdpi.apk:", "mac_file": "no_permission", "magisk_mount": "6023 6022 259:28 / /dev/bqT/.magisk/mirror/vendor ro,relatime master:10 - ext4 /dev/bqT/.magisk/block/vendor ro,seclabel,discard", "manufacturer": "Samsung", "mi_health_id": "", "mod": "none", "model": "SM-T970", "network_config": "system:", "oem_unlock_allowed": "0", "oem_unlock_supported": "", "parent_id": "806", "persist_sys_oplus_opmuuid": "", "persist_sys_oppo_opmuuid": "", "pid": "28054", "pid_cgroup": "28054", "pipe_uname": "Linux localhost 4.4.276-perf #1 SMP PREEMPT Tue Apr 11 14:51:14 CST 2023 aarch64", "pmsHook": "none", "pps_oaid": "", "pref_mipub_random_device_id": "", "proc_version": "no_access", "processName": "com.litatom.app", "product": "gts7xlwifixx", "proxy": "none", "psuc": "adb", "radio": "", "release": "11", "ril_impl": "Qualcomm RIL 1.0", "riru": "", "riru_library": "", "ro_board_platform": "msm8998", "ro_boot_hardware_ddr": "", "ro_boot_selinux": "", "ro_bootimage_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_build_build_fingerprint": "", "ro_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_build_version_incremental": "T970XXU1BUAA", "ro_hardware_egl": "", "ro_odm_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_product_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_sf_lcd_density": "480", "ro_system_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_system_ext_build_fingerprint": "samsung/gts7xlwifixx/gts7xlwifi:11/RP1A.200720.012/T970XXU1BUAA:user/release-keys", "ro_vendor_build_date": "Tue Apr 18 12:10:25 CST 2023", "root": "0", "sdk_init": "30", "seccomp_prctl": "0", "seccomp_status": "-1", "secure": "1", "secure_device_serial": "", "security_patch": "2019-09-01", "serialno": "no_permission", "sig": "F2:57:EE:8E:87:40:D7:F1:D7:14:75:1C:E5:9C:62:B0:34:FC:82:4C", "sign_serial_id": "714994398057552869252302043653574864197501353708", "so_list": "", "soc0_sn_file": "3170573252", "soc0_sn_pipe": "3170573252", "suc": "adb", "sus": "adb", "tags": "release-keys", "time_android": "1751442009.388988870", "time_app_install": "1754378468.176192285", "time_boot_java": "1754380436924", "time_boot_native": "no_permission", "time_dcim": "1751442662.172387232", "time_first_install": "1754378484005", "time_local": "1754396479489", "time_pictures": "1681801186.896000929", "time_primary": "1681791038.806668042", "time_sdcard": "1681791038.806668042", "total_block": "13498992", "total_nodes": "3448832", "type": "user", "uid": "10309", "uid_cgroup": "10309", "uname_info": "Linux localhost 4.4.276-perf #1 SMP PREEMPT Tue Apr 11 14:51:14 CST 2023 aarch64", "uuid": "602f404b-cd94-4acb-b8cc-58040dcecee2", "v2s_actual_digest": "67c052a8d1727a5532c8db7f5eb8106ad4e673e239d135352ac399d97005031fa455f25112f6b0f6d542108ed65fb8ed1aee4141a1f5c303aa9691f91e93b0f7", "v2s_algorithm_id": "260", "v2s_expect_digest": "67c052a8d1727a5532c8db7f5eb8106ad4e673e239d135352ac399d97005031fa455f25112f6b0f6d542108ed65fb8ed1aee4141a1f5c303aa9691f91e93b0f7", "v2s_verify": "1", "vbmeta_digest": "", "vendor_gsm_serial": "", "verity_mode": "enforcing", "version": "1.9.0", "vivo_iroamingkey": "", "vivo_push_clientid": "", "vivo_q_zi_i": "", "vmos_prop": "", "vpn": "tun0", "wifi_interface": "wlan0"}上报内容有点多,包含了设备ID和 magisk 检测等设备风控数据,需要一一绕过。
1、magisk检测
"attr_prev": "u:r:zygote:s0",
"magisk_mount": "6023 6022 259:28 / /dev/bqT/.magisk/mirror/vendor ro,relatime master:10 - ext4 /dev/bqT/.magisk/block/vendor ro,seclabel,discard",
通过检测进程 /proc/self/attr/prev属性信息,正常app是通过 zygote直接fork而来,因此 prev标记为 init,而 magisk 中间接管了一层,因此 app的 prev 是 zygote。
由于magisk默认没有开启 magiskhide(开启hide后无法注入lsposed插件),因此app通过检测挂载信息可以检测到magisk :fopen: /proc/self/mountinfo。
定位到关键点:
.text:0000000000116660 loc_116660 ; CODE XREF: sub_1164CC+144↑j.text:0000000000116660 LDR X2, [X21,#0x10].text:0000000000116664.text:0000000000116664 loc_116664 ; CODE XREF: sub_1164CC+14C↑j.text:0000000000116664 MOV W0, #0x38 ; '8'.text:0000000000116668 MOV W1, #0xFFFFFF9C.text:000000000011666C MOV W3, WZR.text:0000000000116670 MOV W4, WZR.text:0000000000116674 BL sub_4A618.text:0000000000116678 MOV X21, X0 得到文件句柄.text:000000000011667C.text:000000000011667C loc_11667C ; CODE XREF: sub_1164CC+1D4↓j.text:000000000011667C MOV W2, #0x200 ; nbytes.text:0000000000116680 MOV W0, W21 ; fd.text:0000000000116684 MOV X1, X20 ; buf read函数读取得到.text:0000000000116688 BL .read.text:000000000011668C CMP X0, #1.text:0000000000116690 B.LT loc_1166A4.text:0000000000116694 SUB X0, X29, #-var_18 ; int.text:0000000000116698 MOV X1, X20 ; s value.text:000000000011669C BL sub_6EF14 设置json字段很明显,函数 sub_4A618 通过打开 /proc/self/attr/prev 获取到文件句柄,然后读取文件内容,来检测magisk。因此前面是对 /proc/self/attr/prev 这个字符串的解密还原。
.text:000000000004A618 sub_4A618 ; CODE XREF: sub_B5E88+11D50↓p.text:000000000004A618 ; sub_B5E88+2C2BC↓p ....text:000000000004A618 MOV X8, X0.text:000000000004A61C MOV X0, X1.text:000000000004A620 MOV X1, X2.text:000000000004A624 MOV X2, X3.text:000000000004A628 MOV X3, X4.text:000000000004A62C MOV X4, X5.text:000000000004A630 MOV X5, X6.text:000000000004A634 SVC 0.text:000000000004A638 RET函数 sub_4A618 通过 svc 指令来执行系统调用,系统调用号 0x38, 对应的是 openat .
2、frida检测
略。
3、ROM检测
"custom_rom": "lineage"
关键检测代码:
.text:00000000000E0B90 loc_E0B90 ; CODE XREF: sub_B5E88+2ACC4↑j.text:00000000000E0B90 STRB WZR, [X20,X19].text:00000000000E0B94 ADD X0, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000E0B98 ADD X1, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000E0B9C ADD X0, X0, #0xC80.text:00000000000E0BA0 ADD X1, X1, #0xB40.text:00000000000E0BA4 BL sub_116F38.text:00000000000E0BA8 ADD X8, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000E0BAC ADD X8, X8, #0xB40.text:00000000000E0BB0 LDRB W8, [X8].text:00000000000E0BB4 TBZ W8, #0, loc_E0BC0.text:00000000000E0BB8 LDR X0, [SP,#0x2CC0+var_1170].text:00000000000E0BBC BL sub_6D98C.text:00000000000E0BC0.text:00000000000E0BC0 loc_E0BC0 ; CODE XREF: sub_B5E88+2AD2C↑j.text:00000000000E0BC0 ADD X8, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000E0BC4 ADD X8, X8, #0xC80.text:00000000000E0BC8 LDRB W8, [X8].text:00000000000E0BCC LDR X9, [SP,#0x2CC0+var_1038].text:00000000000E0BD0 LSR X10, X8, #1.text:00000000000E0BD4 TST W8, #1.text:00000000000E0BD8 CSEL X8, X10, X9, EQ.text:00000000000E0BDC CBZ X8, loc_E0BF4.text:00000000000E0BE0 ADRL X1, aLineage ; "lineage".text:00000000000E0BE8 ADD X0, SP, #0x2CC0+var_2858 ; int.text:00000000000E0BEC BL sub_6ED38.text:00000000000E0BF0 B loc_E1268lineage 是一个常量字符串。如果执行到代码块 loc_E0BC0 , 说明是 lineageos系统。跳转条件来源于地址 0xE0BB4,根据 X8寄存器的取值是否为0来判断,即是否为空字符串。结果为 函数 sub_116F38 执行后的第二个参数。
跟进函数分析:
.text:0000000000116F38 ; __unwind {.text:0000000000116F38 SUB SP, SP, #0xB0.text:0000000000116F3C STP X29, X30, [SP,#0xA0+var_20].text:0000000000116F40 STR X21, [SP,#0xA0+var_10].text:0000000000116F44 STP X20, X19, [SP,#0xA0+var_s0].text:0000000000116F48 ADD X29, SP, #0x80.text:0000000000116F4C MOVI V0.2D, #0.text:0000000000116F50 STP Q0, Q0, [SP,#0xA0+var_40].text:0000000000116F54 STP Q0, Q0, [SP,#0xA0+var_60].text:0000000000116F58 STP Q0, Q0, [SP,#0xA0+var_80].text:0000000000116F5C STP Q0, Q0, [SP,#0xA0+var_A0].text:0000000000116F60 LDRB W8, [X1].text:0000000000116F64 MOV X19, X0.text:0000000000116F68 TBNZ W8, #0, loc_116F84.text:0000000000116F6C ADD X0, X1, #1.text:0000000000116F70 MOV X1, SP.text:0000000000116F74 BL .__system_property_get.text:0000000000116F78 CMP W0, #0.text:0000000000116F7C B.GT loc_116F9C.text:0000000000116F80 B loc_116F98.text:0000000000116F84 ; ---------------------------------------------------------------------------.text:0000000000116F84.text:0000000000116F84 loc_116F84 ; CODE XREF: sub_116F38+30↑j.text:0000000000116F84 LDR X0, [X1,#0x10].text:0000000000116F88 MOV X1, SP.text:0000000000116F8C BL .__system_property_get.text:0000000000116F90 CMP W0, #0.text:0000000000116F94 B.GT loc_116F9C.text:0000000000116F98.text:0000000000116F98 loc_116F98 ; CODE XREF: sub_116F38+48↑j.text:0000000000116F98 STRB WZR, [SP,#0xA0+var_A0].text:0000000000116F9C.text:0000000000116F9C loc_116F9C ; CODE XREF: sub_116F38+44↑j.text:0000000000116F9C ; sub_116F38+5C↑j.text:0000000000116F9C MOV X0, SP ; s.text:0000000000116FA0 BL .strlen.text:0000000000116FA4 CMN X0, #0x10.text:0000000000116FA8 B.CS loc_117010.text:0000000000116FAC MOV X20, X0.text:0000000000116FB0 CMP X0, #0x17.text:0000000000116FB4 B.CS loc_116FC8.text:0000000000116FB8 LSL W8, W20, #1.text:0000000000116FBC STRB W8, [X19],#1.text:0000000000116FC0 CBNZ X20, loc_116FE8.text:0000000000116FC4 B loc_116FF8.text:0000000000116FC8 ; ---------------------------------------------------------------------------.text:0000000000116FC8.text:0000000000116FC8 loc_116FC8 ; CODE XREF: sub_116F38+7C↑j.text:0000000000116FC8 ADD X8, X20, #0x10.text:0000000000116FCC AND X21, X8, #0xFFFFFFFFFFFFFFF0.text:0000000000116FD0 MOV X0, X21.text:0000000000116FD4 BL sub_6D8B8.text:0000000000116FD8 ORR X8, X21, #1.text:0000000000116FDC STP X20, X0, [X19,#8].text:0000000000116FE0 STR X8, [X19].text:0000000000116FE4 MOV X19, X0.text:0000000000116FE8.text:0000000000116FE8 loc_116FE8 ; CODE XREF: sub_116F38+88↑j.text:0000000000116FE8 MOV X1, SP ; src.text:0000000000116FEC MOV X0, X19 ; dest.text:0000000000116FF0 MOV X2, X20 ; n.text:0000000000116FF4 BL .memcpy.text:0000000000116FF8.text:0000000000116FF8 loc_116FF8 ; CODE XREF: sub_116F38+8C↑j.text:0000000000116FF8 STRB WZR, [X19,X20].text:0000000000116FFC LDP X20, X19, [SP,#0xA0+var_s0].text:0000000000117000 LDR X21, [SP,#0xA0+var_10].text:0000000000117004 LDP X29, X30, [SP,#0xA0+var_20].text:0000000000117008 ADD SP, SP, #0xB0.text:000000000011700C RET.text:0000000000117010 ; ---------------------------------------------------------------------------看起来主要是根据 system_property_get 来获取属性信息。
HOOK后打印参数:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 22 72 6f 2e 6c 69 6e 65 61 67 65 2e 64 65 76 69 "ro.lineage.devi00000010 63 65 00 69 63 65 73 00 e2 62 04 d3 72 00 00 00 ce.ices..b..r...
通过读取属性 ro.lineage.device 是否返回为空,来判断是否是lineageos
真实值:[ro.lineage.device]: [sagit]。
看来改机工具 MagiskHidePropsConf 默认是不会修改这个属性。
检测三方rom用到的属性:
ro.lineage.devicero.aospa.deviceorg.evolution.devicero.rising.maintainer
4、设备指纹
上报的数据中用到的设备指纹
1) android_id
2) boot_id
3) mediaDrimId
4) file_system_id
5) time_pictures 等时间戳
6)soc0_sn_file 序列号
重点看看 file_system_id 是怎么生成的:
关键代码:
.text:00000000000C2C94 loc_C2C94 ; CODE XREF: sub_B5E88+CDC0↑j.text:00000000000C2C94 ADD X8, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2C98 STRB WZR, [X20,X19].text:00000000000C2C9C ADD X8, X8, #0xA20.text:00000000000C2CA0 LDRB W8, [X8,#0x120].text:00000000000C2CA4 LDR X9, [SP,#0x2CC0+var_1170].text:00000000000C2CA8 ADRL X1, aR_0 ; modes.text:00000000000C2CB0 TST W8, #1.text:00000000000C2CB4 CSINC X0, X9, X21, NE ; command.text:00000000000C2CB8 STR XZR, [SP,#0x2CC0+var_1290].text:00000000000C2CBC STR XZR, [SP,#0x2CC0+var_12A0].text:00000000000C2CC0 STR XZR, [SP,#0x2CC0+var_1298].text:00000000000C2CC4 BL .popen.text:00000000000C2CC8 CBZ X0, loc_C2D2C.text:00000000000C2CCC ADD X21, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2CD0 ADRP X20, #asc_14E1B@PAGE ; "\n".text:00000000000C2CD4 MOV X19, X0.text:00000000000C2CD8 ADD X21, X21, #0xC80.text:00000000000C2CDC ADD X20, X20, #asc_14E1B@PAGEOFF ; "\n".text:00000000000C2CE0.text:00000000000C2CE0 loc_C2CE0 ; CODE XREF: sub_B5E88+CE98↓j.text:00000000000C2CE0 ADD X0, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2CE4 ADD X0, X0, #0xC80 ; s.text:00000000000C2CE8 MOV W1, #0x80 ; n.text:00000000000C2CEC MOV X2, X19 ; stream.text:00000000000C2CF0 BL .fgets.text:00000000000C2CF4 CBZ X0, loc_C2D24.text:00000000000C2CF8 ADD X0, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2CFC ADD X0, X0, #0xC80 ; s.text:00000000000C2D00 MOV X1, X20 ; reject.text:00000000000C2D04 BL .strcspn.text:00000000000C2D08 STRB WZR, [X21,X0].text:00000000000C2D0C ADD X0, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2D10 ADD X1, SP, #0x2CC0+var_2CBF,LSL#12.text:00000000000C2D14 ADD X0, X0, #0xA20 ; int.text:00000000000C2D18 ADD X1, X1, #0xC80 ; s.text:00000000000C2D1C BL sub_6EF14执行 popen命令: /system/bin/stat -f -c "%i-%b-%c" / 2>&1 获取根目录的挂载信息。
其它特征:
"inst_access": "E80300AA600C8012E203012A", "inst_dlopen": "5000005800021FD600789409", "inst_fopen": "5000005800021FD600B32860", "inst_open": "5000005800021FD600759409", "inst_openat": "5000005800021FD600769409", "inst_readlinkat": "C80980D2010000D41F0440B1", "inst_stat": "E80300AA600C8012E20301AA", "inst_syscall": "5000005800021FD600779409",
部分C函数的前3个指令,可以判断是否有inlinehook
当然,SDK 里还有 模拟器检测,root 检测,虚拟机检测,hook 检测等等,只是我测试的设备检测不到这些特征。
xposed插件绕过检测
1)Java 层插件
mediaDrm设备指纹绕过
package com.growth.client.injector.common;import android.media.MediaDrm;import com.growth.client.GrowthGlobal;import com.growth.client.injector.IInjector;import com.growth.util.Utils;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class MediaDrmInjector implements IInjector { @Override public String getName() { return MediaDrmInjector.class.getSimpleName(); } @Override public boolean match(XC_LoadPackage.LoadPackageParam lpparam) { return true; } @Override public boolean inject(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { fakeMediaMediaDrmId(); inject_native(); return true; } private void fakeMediaMediaDrmId() { String mediaDrmId = GrowthGlobal.getInstance().getString("mediaDrmId"); if(mediaDrmId == null) { mediaDrmId = Utils.base64(Utils.toByteArray(Utils.randomString(46))); GrowthGlobal.getInstance().putString("mediaDrmId", mediaDrmId); } GrowthGlobal.getInstance().getMachine().addDeviceKV("mediaDrmId", mediaDrmId); XposedHelpers.findAndHookMethod( MediaDrm.class, "getPropertyByteArray", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String key = (String) param.args[0]; if ("deviceUniqueId".equals(key)) { XposedBridge.log("MediaDrm.getPropertyByteArray called with key: " + key); } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String key = (String) param.args[0]; if ("deviceUniqueId".equals(key)) { String mediaDrmId = GrowthGlobal.getInstance().getString("mediaDrmId"); param.setResult(mediaDrmId.getBytes()); } } } ); } private static native void inject_native();}APP插件:
import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.util.Date;import java.util.List;import java.util.Random;import java.util.Timer;import java.util.TimerTask;import java.util.UUID;import java.util.concurrent.ThreadLocalRandom;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;public class 手动混淆名Injector extends AppBaseInjector { public final static String PACKAGE_NAME = "手动混淆包名"; @Override public boolean match(XC_LoadPackage.LoadPackageParam lpparam) { return lpparam.processName.equals(PACKAGE_NAME); } @Override public boolean inject(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { //设置Header XposedHelpers.findAndHookMethod("okhttp3.Request$Builder", lpparam.classLoader, "a", java.lang.String.class, java.lang.String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); //Log.d(getName(), "set header: " + param.args[0] + " -> " + param.args[1]); if(param.args[0].equals("v-mode")) { param.args[1] = "0"; }// else if(param.args[0].equals("X-DFPID")) {// param.args[1] = "";// } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); } }); fake_time_info(); fake_system_file_id(); fake_boot_id(); fake_property(); inject_native(); return true; } private void fake_property() { String build_date = GrowthGlobal.getInstance().getString("ro.vendor.build.date"); if(build_date == null) { // 当前时间毫秒 long now = System.currentTimeMillis(); long fourYearsAgo = now - (long)(365.25 * 3 * 24 * 60 * 60 * 1000); long twoYearsAgo = now - (long)(365.25 * 2 * 24 * 60 * 60 * 1000); long fakeTime = ThreadLocalRandom.current().nextLong(fourYearsAgo, twoYearsAgo); Date fakeDate = new Date(fakeTime); build_date = fakeDate.toString(); GrowthGlobal.getInstance().putString("ro.vendor.build.date", build_date); } GrowthGlobal.getInstance().getMachine().addDeviceKV("ro.vendor.build.date", build_date); } private void fake_system_file_id() { String system_file_id = GrowthGlobal.getInstance().getString("system_file_id"); if(system_file_id == null) { system_file_id = generateFakeStat(); GrowthGlobal.getInstance().putString("system_file_id", system_file_id); } GrowthGlobal.getInstance().getMachine().addDeviceKV("system_file_id", system_file_id); } private void fake_boot_id() { String boot_id_path = GrowthGlobal.getInstance().getString("boot_id_path"); if(boot_id_path == null) { String boot_id = UUID.randomUUID().toString(); File dir = GrowthGlobal.getInstance().getContext().getFilesDir(); // /data/data/your.package.name/files File file = new File(dir, "boot_id"); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(boot_id.getBytes()); } catch (IOException e) { e.printStackTrace(); } boot_id_path = file.getAbsolutePath(); GrowthGlobal.getInstance().putString("boot_id_path", boot_id_path); } GrowthGlobal.getInstance().getMachine().addDeviceKV("boot_id_path", boot_id_path); } private void fake_time_info() { Timespec time_key = getRandomCTime(); init_time_info("sdcard", time_key); init_time_info("primary", time_key); init_time_info("pictures", getRandomCTime()); init_time_info("dcim", getRandomCTime()); init_time_info("android", getRandomCTime()); } private void init_time_info(String key, Timespec time_key) { String key_sec = String.format("time_%s_sec", key); String key_nsec = String.format("time_%s_nsec", key); String time_key_sec = GrowthGlobal.getInstance().getString(key_sec); String time_key_nsec = GrowthGlobal.getInstance().getString(key_nsec); if(time_key_sec == null || time_key_nsec == null) { time_key_sec = String.valueOf(time_key.tv_sec); time_key_nsec = String.valueOf(time_key.tv_nsec); GrowthGlobal.getInstance().putString(key_sec, time_key_sec); GrowthGlobal.getInstance().putString(key_nsec, time_key_nsec); } GrowthGlobal.getInstance().getMachine().addDeviceKV(key_sec, time_key_sec); GrowthGlobal.getInstance().getMachine().addDeviceKV(key_nsec, time_key_nsec); } private String generateFakeStat() { Random rand = new Random(); // 模拟 inode 总数,生成一个16位十六进制字符串 long inode = Math.abs(rand.nextLong()); String inodeHex = Long.toHexString(inode); // 模拟块总数,生成一个大约百万级别的十进制数 int blocks = 100000 + rand.nextInt(900000); // 模拟创建时间,生成一个从1970年到现在的时间戳(秒) //long currentTimeSec = System.currentTimeMillis() / 1000; //long createTime = 100000 + rand.nextInt((int)(currentTimeSec - 100000)); long createTime = 327680; return inodeHex + "-" + blocks + "-" + createTime; } public static class Timespec { public long tv_sec; // 秒 public long tv_nsec; // 纳秒 } public static Timespec getRandomCTime() { long nowSec = System.currentTimeMillis() / 1000; // 随机生成 30 到 100 天之间的秒数 long randomDays = ThreadLocalRandom.current().nextLong(30, 101); long offsetSec = randomDays * 24 * 60 * 60; // 生成纳秒随机数 long nsec = ThreadLocalRandom.current().nextLong(0, 1_000_000_000); Timespec ts = new Timespec(); ts.tv_sec = nowSec - offsetSec; ts.tv_nsec = nsec; return ts; } private static native void inject_native();}2)native 层插件
mediaDrm设备指纹绕过
#include "MediaDrmPlugin.h"#include <dlfcn.h>#include <media/NdkMediaDrm.h>#include "log.h"#include "BridgeHelp.h"static int (*orig_AMediaDrm_getPropertyByteArray)(void* drm, char* key, AMediaDrmByteArray* result);staticint fake_AMediaDrm_getPropertyByteArray(void* drm, char* key, AMediaDrmByteArray* result);MediaDrmPlugin::~MediaDrmPlugin() = default;MediaDrmPlugin::MediaDrmPlugin() = default;std::string MediaDrmPlugin::getName() { return "MediaDrmPlugin";}std::string MediaDrmPlugin::getLibName() { return "libmediandk.so";}void MediaDrmPlugin::doInstall(char* name, void* handle) { //dlopen 会造成循环加载。// void* handle = dlopen("libmediandk.so", RTLD_NOW);// if (!handle) {// LOGI("[MediaDrmPlugin] dlopen(libmediandk.so) failed\n");// return;// } void* addr = dlsym(handle, "AMediaDrm_getPropertyByteArray");// void* addr = (void*)gum_module_find_export_by_name("libmediandk.so", "AMediaDrm_getPropertyByteArray"); if (!addr) { LOGI("dlsym(AMediaDrm_getPropertyByteArray) failed\n"); return; } hook_property(addr);}void MediaDrmPlugin::hook_property(void* address) { gum_init_embedded(); orig_AMediaDrm_getPropertyByteArray = (int (*)(void* drm, char* key, AMediaDrmByteArray * result))address; gum_interceptor_replace(this->m_interceptor, GSIZE_TO_POINTER(address), (gpointer) fake_AMediaDrm_getPropertyByteArray, nullptr); gum_interceptor_end_transaction(this->m_interceptor);}staticint fake_AMediaDrm_getPropertyByteArray(void* drm, char* key, AMediaDrmByteArray * result) { LOGI("[MediaDrmPlugin] AMediaDrm_getPropertyByteArray: %s\n", key); int status = orig_AMediaDrm_getPropertyByteArray(drm, key, result); if(!strcmp(key, "deviceUniqueId")) { LOGI("[MediaDrmPlugin] status= %d, orig mediaDrmId: %s\n",status, result->ptr); std::string val = BridgeHelp::redirectProp("mediaDrmId", ""); if(!val.empty()) { LOGI("[MediaDrmPlugin] fake mediaDrmId: %s\n", val.c_str()); result->ptr = (uint8_t*)val.c_str(); //memcpy((void*)result->ptr, val.c_str(), val.size()); result->length = val.size(); } } return status;}void MediaDrmPlugin::uninstall() {}void MediaDrmPlugin::hook_on_enter(GumInvocationContext *ic, PLUGIN_HOOK_ID id) {}void MediaDrmPlugin::hook_on_leave(GumInvocationContext *ic, PLUGIN_HOOK_ID id) {}APP 插件
#include "手动混淆插件名.h"#include "log.h"#include "BridgeHelp.h"#include "utils.h"#include <unistd.h>#include <fcntl.h>#include <sys/stat.h>#include <sys/types.h>#include <dlfcn.h>#include <net/if.h>#include <arpa/inet.h>#include <sys/syscall.h>staticint match_fake_system_property_get(constchar *name, char *value);static FILE *fake_fopen(constchar *path, constchar *mode);static FILE *fake_popen(constchar *command, constchar *mode);static void (*orig_fake_check_ins)(char* result, void* func_ptr);staticvoid fake_check_ins(char* result, void* func_ptr);int fake_stat(constchar* path, struct stat* buf);int fake_fstat(int fd , struct stat* buf);int fake_ioctl(int fd, unsignedint request, ...);手动混淆插件名::~手动混淆插件名() = default;手动混淆插件名::手动混淆插件名() = default;std::string 手动混淆插件名::getName() { return "手动混淆插件名";}std::string 手动混淆插件名::getLibName() { return "libliteye.so";}void 手动混淆插件名::doInstall(char* name, void* handle) { if(!strcmp(name, this->getLibName().c_str())) { GumAddress address = gum_module_find_base_address(this->getLibName().c_str()); hook_liteye((void*)address); }}void 手动混淆插件名::hook_liteye(void* address) { void* addValue = (void*)((uintptr_t)address + 手动混淆插件名::ADD_VALUE_OFFSET); void* proc_prev = (void*)((uintptr_t)address + 手动混淆插件名::READ_PROC_PREV_OFFSET); void* svc_syscall = (void*)((uintptr_t)address + 手动混淆插件名::SVC_SYSCALL_OFFSET); void* frida_len_check = (void*)((uintptr_t)address + 手动混淆插件名::FRIDA_SERVER_CHECK_OFFSET); void* check_ins = (void*)((uintptr_t)address + 手动混淆插件名::CHECK_LIBC_FUNC_INS_OFFSET); gum_init_embedded(); gum_interceptor_attach(this->m_interceptor, GSIZE_TO_POINTER(addValue), this->g_listener, GSIZE_TO_POINTER(IPlugin::PLUGIN_HOOK_ID::HOOK_JSON_ADD_VALUE)); //读取进程的 /proc/self/attr/prev 属性 检测 magisk gum_interceptor_attach(this->m_interceptor, GSIZE_TO_POINTER(proc_prev), this->g_listener, GSIZE_TO_POINTER(IPlugin::PLUGIN_HOOK_ID::HOOK_READ_PROC_ATTR_PRE)); //svc 系统调用 gum_interceptor_attach(this->m_interceptor, GSIZE_TO_POINTER(svc_syscall), this->g_listener, GSIZE_TO_POINTER(IPlugin::PLUGIN_HOOK_ID::HOOK_SVC_SYSCALL)); //读系统属性 gum_interceptor_replace(this->m_interceptor, (gpointer) __system_property_get, (gpointer) match_fake_system_property_get, nullptr); //fopen读取挂载信息检测 magisk gum_interceptor_replace(this->m_interceptor, (gpointer) fopen, (gpointer) fake_fopen, nullptr); //popen 读命令 gum_interceptor_replace(this->m_interceptor, (gpointer) popen, (gpointer) fake_popen, nullptr); //lstat 读时间 gum_interceptor_replace(this->m_interceptor, (gpointer) stat, (gpointer) fake_stat, nullptr); gum_interceptor_replace(this->m_interceptor, (gpointer) fstat, (gpointer) fake_fstat, nullptr); //ioctl 读取网卡接口 void* ioctl_ptr = gum_find_function("ioctl"); gum_interceptor_replace(this->m_interceptor, (gpointer) ioctl_ptr, (gpointer) fake_ioctl, nullptr); //检测部分函数前3个指令 orig_fake_check_ins = (void (*)(char *, void *))check_ins; gum_interceptor_replace(this->m_interceptor, (gpointer) check_ins, (gpointer) fake_check_ins, nullptr); //frida_server长度检测 gum_interceptor_attach(this->m_interceptor, GSIZE_TO_POINTER(frida_len_check), this->g_listener, GSIZE_TO_POINTER(IPlugin::PLUGIN_HOOK_ID::HOOK_FRIDA_SERVER)); gum_interceptor_end_transaction(this->m_interceptor);}int match_fake_system_property_get(constchar *name, char *value) { int ret = __system_property_get(name, value); //LOGI("[手动混淆插件名] system_property_get: %s - > %s\n", name, value); staticconstchar* hidePropList[] = { "ro.lineage.device", "init.svc.adb_root", "init.svc.adbd", "init.svc_debug_pid.adb_root", "init.svc_debug_pid.adbd", "persist.adb.wifi.guid", "persist.sys.usb.config", "persist.service.adb.enable", "ro.boottime.adb_root", "ro.boottime.adbd", "sys.usb.config", "sys.usb.state" }; for(constchar* prop: hidePropList) { if(strstr(name , prop)) { strcpy(value, ""); break; } } if(strstr(name, "ro.build.host")) { strcpy(value, "linux"); } else if(strstr(name, "ro.vendor.build.date")) { //ro.build.date ro.system.build.date ro.bootimage.build.date std::string val = BridgeHelp::redirectProp("ro.vendor.build.date", ""); if (!val.empty()) { LOGI("[手动混淆插件名] fake %s - > %s\n", name, val.c_str()); strcpy(value, val.c_str()); } } return ret;}FILE *fake_fopen(constchar *path, constchar *mode) { if (path != nullptr) { //LOGI("[手动混淆插件名]fopen("%s")", path); if(!strcmp(path, "/proc/self/mountinfo")) { LOGI("[手动混淆插件名]fopen("%s") bypass", path); return fopen("/proc/self/maps", mode); } } return fopen(path, mode);}FILE *fake_popen(constchar *command, constchar *mode) { if (command != nullptr) { LOGI("[手动混淆插件名]popen("%s")", command); if(strstr(command, "/system/bin/stat -f -c "%i-%b-%c" / 2>&1")) { LOGI("[手动混淆插件名]popen( %s ) bypass", command); std::string val = BridgeHelp::redirectProp("system_file_id", ""); if(!val.empty()) { char newCommand[64] = {0}; sprintf(newCommand, "echo "%s"", val.c_str()); LOGI("[手动混淆插件名] popen fake file_system_id: %s", val.c_str()); return popen(newCommand, mode); } } else if(strstr(command, "/system/bin/cat /sys/devices/soc0/serial_number")) { return popen("echo """, mode); } } return popen(command, mode);}int fake_stat(constchar* path, struct stat* buf) { int res = lstat(path, buf); //LOGI("[手动混淆插件名] stat %s", path); if(strstr(path, "/sdcard/Pictures")) { std::string pictures_sec = BridgeHelp::redirectProp("time_pictures_sec", ""); std::string pictures_nsec = BridgeHelp::redirectProp("time_pictures_nsec", ""); if(!pictures_nsec.empty()) { buf->st_ctim.tv_sec = utils::str_2_long(pictures_sec); } if(!pictures_nsec.empty()) { buf->st_ctim.tv_nsec = utils::str_2_long(pictures_nsec); } } else if(strstr(path, "/storage/self/primary")) { std::string primary_sec = BridgeHelp::redirectProp("time_primary_sec", ""); std::string primary_nsec = BridgeHelp::redirectProp("time_primary_nsec", ""); if(!primary_nsec.empty()) { buf->st_ctim.tv_sec = utils::str_2_long(primary_sec); } if(!primary_nsec.empty()) { buf->st_ctim.tv_nsec = utils::str_2_long(primary_nsec); } } else if(!strcmp(path, "/sdcard/")) { std::string sdcard_sec = BridgeHelp::redirectProp("time_sdcard_sec", ""); std::string sdcard_nsec = BridgeHelp::redirectProp("time_sdcard_nsec", ""); if(!sdcard_nsec.empty()) { buf->st_ctim.tv_sec = utils::str_2_long(sdcard_sec); } if(!sdcard_nsec.empty()) { buf->st_ctim.tv_nsec = utils::str_2_long(sdcard_nsec); } } else if(!strcmp(path, "/sdcard/DCIM")) { std::string dcim_sec = BridgeHelp::redirectProp("time_dcim_sec", ""); std::string dcim_nsec = BridgeHelp::redirectProp("time_dcim_nsec", ""); if(!dcim_nsec.empty()) { buf->st_ctim.tv_sec = utils::str_2_long(dcim_sec); } if(!dcim_nsec.empty()) { buf->st_ctim.tv_nsec = utils::str_2_long(dcim_nsec); } } else if(!strcmp(path, "/sdcard/Android")) { std::string android_sec = BridgeHelp::redirectProp("time_android_sec", ""); std::string android_nsec = BridgeHelp::redirectProp("time_android_nsec", ""); if(!android_nsec.empty()) { buf->st_ctim.tv_sec = utils::str_2_long(android_sec); } if(!android_nsec.empty()) { buf->st_ctim.tv_nsec = utils::str_2_long(android_nsec); } } return res;}int fake_fstat(int fd , struct stat* buf) { int res = fstat(fd, buf); //LOGI("[手动混淆插件名] fstat %d", fd); return res;}int fake_ioctl(int fd, unsignedint request, ...) { va_list args; void *argp = nullptr; // 取出第三个参数(如果有) va_start(args, request); argp = va_arg(args, void *); va_end(args); int res = ioctl(fd, request, argp); if (request == SIOCGIFCONF && res >= 0 && argp != nullptr) { struct ifconf* ifc = static_cast<struct ifconf *>(argp); struct ifreq* ifr = ifc->ifc_req; int nifs = ifc->ifc_len / sizeof(struct ifreq); //LOGI("SIOCGIFCONF: Found %d interfaces", nifs); for (int i = 0; i < nifs; i++) { char ifname[IFNAMSIZ]; strncpy(ifname, ifr[i].ifr_name, IFNAMSIZ); ifname[IFNAMSIZ - 1] = '\0'; //LOGI("Interface[%d]: %s", i, ifname); if(strstr(ifname, "tun")) { snprintf(ifr[i].ifr_name, IFNAMSIZ, "wlan1"); LOGI("[手动混淆插件名] ioctl vpn bypass"); } } } return res;}staticvoid fake_check_ins(char* result, void* func_ptr) { if(func_ptr == fopen) { LOGI("[手动混淆插件名] check_ins fopen"); //FF4301D1 FD7B01A9 F71300F9 uint8_t buff[12] = {0xFF, 0x43, 0x01, 0xD1, 0xFD, 0x7B, 0x01, 0xA9, 0xF7, 0x13, 0x00, 0xF9}; return orig_fake_check_ins(result, (void*)buff); } else if(func_ptr == access) { LOGI("[手动混淆插件名] check_ins access"); //E80300AA 600C8012 E203012A uint8_t buff[12] = {0xE8, 0x03, 0x00, 0xAA, 0x60, 0x0C, 0x80, 0x12, 0xE2, 0x03, 0x01, 0x2A}; return orig_fake_check_ins(result, (void*)buff); } else if(func_ptr == (void*)stat) { LOGI("[手动混淆插件名] check_ins stat"); //E80300AA 600C8012 E20301AA uint8_t buff[12] = {0xE8, 0x03, 0x00, 0xAA, 0x60, 0x0C, 0x80, 0x12, 0xE2, 0x03, 0x01, 0xAA}; return orig_fake_check_ins(result, (void*)buff); } else if(func_ptr == dlopen) { LOGI("[手动混淆插件名] check_ins dlopen"); //FD7BBFA9 FD030091 E2031EAA uint8_t buff[12] = {0xFD, 0x7B, 0xBF, 0xA9, 0xFD, 0x03, 0x00, 0x91, 0xE2, 0x03, 0x1E, 0xAA}; return orig_fake_check_ins(result, (void*)buff); } else if(func_ptr == readlinkat) { LOGI("[手动混淆插件名] check_ins readlinkat"); //C80980D2 010000D4 1F0440B1 uint8_t buff[12] = {0xC8, 0x09, 0x80, 0xD2, 0x01, 0x00, 0x00, 0xD4, 0x1F, 0x04, 0x40, 0xB1}; return orig_fake_check_ins(result, (void*)buff); } else if(func_ptr == syscall) { LOGI("[手动混淆插件名] check_ins syscall"); //E80300AA E00301AA E10302AA uint8_t buff[12] = {0xE8, 0x03, 0x00, 0xAA, 0xE0, 0x03, 0x01, 0xAA, 0xE1, 0x03, 0x02, 0xAA}; return orig_fake_check_ins(result, (void*)buff); } else { void* open = gum_find_function("open"); void* openat = gum_find_function("openat"); if(func_ptr == open || func_ptr == openat) { LOGI("[手动混淆插件名] check_ins open"); //FFC304D1 FD7B0EA9 FC7B00F9 uint8_t buff[12] = {0xFF, 0xC3, 0x04, 0xD1, 0xFD, 0x7B, 0x0E, 0xA9, 0xFC, 0x7B, 0x00, 0xF9}; orig_fake_check_ins(result, (void*)buff); } else { LOGI("[手动混淆插件名] check_ins unknown"); orig_fake_check_ins(result, func_ptr); } }// string res = utils::byte_2_hex_str(result, 8);// LOGI("[手动混淆插件名] check_ins result: %s", res.c_str());}void 手动混淆插件名::hook_on_enter(GumInvocationContext *ic, PLUGIN_HOOK_ID id) { switch (id) { case IPlugin::HOOK_JSON_ADD_VALUE: { char* value = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,1)); GumArm64CpuContext* context = (GumArm64CpuContext*)ic->cpu_context; //LOGI("[手动混淆插件名] addValue: %s\n", value); if(strstr(value, "frida") || strstr(value, "tun0")) { LOGI("[手动混淆插件名] frida or vpn bypass"); constchar* fakeStr = "|||||||0|||"; memcpy(value, fakeStr, strlen(fakeStr) + 1); context->x[2] = strlen(fakeStr); } } break; case IPlugin::HOOK_READ_PROC_ATTR_PRE: { GumArm64CpuContext* context = (GumArm64CpuContext*)ic->cpu_context; char* target = (char*)context->x[20]; LOGI("[手动混淆插件名] prev attr : %s\n", target); if(!strcmp(target, "u:r:zygote:s0")) { constchar* fake_attr = "u:r:init:s0"; LOGI("[手动混淆插件名] prev attr fake: %s\n", fake_attr); memcpy(target, fake_attr, strlen(fake_attr)+1); } } case IPlugin::HOOK_SVC_SYSCALL: { auto number = reinterpret_cast<uintptr_t>(gum_invocation_context_get_nth_argument(ic,0)); LOGI("[手动混淆插件名] svc syscall: %u\n", number); switch (number) { case __NR_openat: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); LOGI("[手动混淆插件名] svc syscall openat: %s\n", path); if(strstr(path, "/proc/sys/kernel/random/boot_id")) { std::string val = BridgeHelp::redirectProp("boot_id_path", ""); if(!val.empty()) { GumArm64CpuContext* context = (GumArm64CpuContext*)ic->cpu_context; context->x[2] = (guint64) val.c_str(); LOGI("[手动混淆插件名] boot_id redirect: %s\n", val.c_str()); } } break; } case __NR_listxattr: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,1)); LOGI("[手动混淆插件名] svc syscall listxattr: %s\n", path); break; } case __NR_faccessat: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); LOGI("[手动混淆插件名] svc syscall faccessat: %s\n", path); if(strstr(path, "/sys/devices/soc0/serial_number")) { GumArm64CpuContext* context = (GumArm64CpuContext*)ic->cpu_context; strcpy(reinterpret_cast<char *const>(context->x[2]), ""); } break; } case __NR_mount: { char* source = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,1)); char* target = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); //char* type = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,3)); LOGI("[手动混淆插件名] svc syscall mount"); break; } case __NR_removexattr: { //char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,1)); //char* name = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); LOGI("[手动混淆插件名] svc syscall removexattr\n"); break; } case __NR_linkat: { LOGI("[手动混淆插件名] svc syscall linkat\n"); break; } case __NR_lseek: { LOGI("[手动混淆插件名] svc syscall lseek"); break; } case __NR_read: LOGI("[手动混淆插件名] svc syscall read"); break; case __NR_readlinkat: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); LOGI("[手动混淆插件名] svc syscall readlinkat: %s\n", path); break; } case __NR_close: LOGI("[手动混淆插件名] svc syscall close"); break; case 79: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,2)); LOGI("[手动混淆插件名] svc syscall fstatat: %s\n", path); if(strstr(path, "/dev/usb_accessory")) { // 这里应该是误判 strcpy(path, ""); } break; } case 83: { char* path = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,1)); LOGI("[手动混淆插件名] svc syscall statfs: %s\n", path); break; } default: break; } } break; case IPlugin::HOOK_FRIDA_SERVER: { LOGI("[手动混淆插件名] frida_server check bypass"); char* value = reinterpret_cast<char*>(gum_invocation_context_get_nth_argument(ic,0)); strcpy(value, ""); } break; default: break; }}void 手动混淆插件名::hook_on_leave(GumInvocationContext *ic, PLUGIN_HOOK_ID id) {}插件注入后,设备指纹和风险特征消失了,然后注册成功:
总结
这款APP的风控组件检测的内容还算丰富,基本检测了所有的黑软特征。而且检测手段下层到了系统调用,使用了 svc 指令来做检测。
但是,native 层的代码没有做混淆保护,这样再全面的检测, 也可以轻松绕过。(PS: 即使加了强混淆,对于专业的逆向分析而言,只是加了一些逆向分析的时间成本)
看雪ID:CCTV果冻爽
*本文为看雪论坛精华文章,由 CCTV果冻爽 原创,转载请注明来自看雪社区
议题征集中!看雪·第九届安全开发者峰会(SDC 2025)
球分享
球点赞
球在看
点击阅读原文查看更多
