看雪学院 08月21日
APP设备风控分析及绕过
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细剖析了某App的层层风控策略,从注册拦截到实时环境检测,揭示了其背后复杂的安全机制。文章通过Hook OkHttp框架,发现并分析了用于风控的Header字段,如v-mode、fingerprint和X-DFPID。进一步,通过对libliteye.so库的逆向分析,追踪到用于加密和生成设备ID的Native函数,并解码了Zlib压缩后的数据,揭示了Frida注入和VPN接口的检测。文章还深入研究了Magisk检测,通过分析/proc/self/attr/prev文件和mountinfo信息,找到了检测原理,并提供了相应的绕过思路。最后,通过对detect函数的逆向,解析了详细的设备环境信息,为应对更复杂的风控提供了依据。

📱 **HTTP请求头中的风控标识**:文章通过Hook OkHttp框架,识别出App在请求中添加了如`v-mode`(用于检测VPN)、`fingerprint`(设备指纹)和`X-DFPID`(设备ID)等关键风控字段,这些字段是App进行身份验证和风险评估的重要依据。

🛡️ **Native层加密与设备ID生成**:通过逆向分析libliteye.so库,发现`siua`和`detect`等Native函数被用于生成设备ID和加密数据,这些操作在Java层难以直接观察,增加了逆向难度。分析结果显示,这些数据经过Zlib压缩和Base64编码,包含了Frida检测和VPN接口信息。

🚫 **Frida与VPN接口检测及绕过**:文章揭示了App会检测Frida Server和`tun0`(VPN接口)的存在,这是注册被拦截的关键原因。通过Frida脚本,修改检测到的数据为无效值,成功绕过了这一层风控。

🎩 **Magisk检测机制与绕过**:App通过检查`/proc/self/attr/prev`文件属性和`/proc/self/mountinfo`来检测Magisk。分析汇编代码发现,App通过打开和读取这些文件来判断是否存在Magisk的挂载信息。文章指出,若要绕过,需要解决Magisk Hide的兼容性问题或修改检测逻辑。

📊 **详细设备环境信息上报**:通过对`detect` Native函数的逆向,文章解析了App上报的详细设备信息,涵盖了系统配置、应用列表、安全设置(如SELinux、Magisk)、网络接口等,这些信息构成了App全面的设备画像,用于更精细化的风险评估。

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_6D98C

native层函数返回时,调用了 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), {    onEnterfunction (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);      }    },    onLeavefunction (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_E1268

lineage 是一个常量字符串。如果执行到代码块 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(30101);        long offsetSec = randomDays * 24 * 60 * 60;        // 生成纳秒随机数        long nsec = ThreadLocalRandom.current().nextLong(01_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] = {0xFF0x430x010xD10xFD0x7B0x010xA90xF70x130x000xF9};        return orig_fake_check_ins(result, (void*)buff);    }    else if(func_ptr == access) {        LOGI("[手动混淆插件名] check_ins access");        //E80300AA 600C8012 E203012A        uint8_t buff[12] = {0xE80x030x000xAA0x600x0C0x800x120xE20x030x010x2A};        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] = {0xE80x030x000xAA0x600x0C0x800x120xE20x030x010xAA};        return orig_fake_check_ins(result, (void*)buff);    }    else if(func_ptr == dlopen) {        LOGI("[手动混淆插件名] check_ins dlopen");        //FD7BBFA9 FD030091 E2031EAA        uint8_t buff[12] = {0xFD0x7B0xBF0xA90xFD0x030x000x910xE20x030x1E0xAA};        return orig_fake_check_ins(result, (void*)buff);    }    else if(func_ptr == readlinkat) {        LOGI("[手动混淆插件名] check_ins readlinkat");        //C80980D2 010000D4 1F0440B1        uint8_t buff[12] = {0xC80x090x800xD20x010x000x000xD40x1F0x040x400xB1};        return orig_fake_check_ins(result, (void*)buff);    }    else if(func_ptr == syscall) {        LOGI("[手动混淆插件名] check_ins syscall");        //E80300AA E00301AA E10302AA        uint8_t buff[12] = {0xE80x030x000xAA0xE00x030x010xAA0xE10x030x020xAA};        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] = {0xFF0xC30x040xD10xFD0x7B0x0E0xA90xFC0x7B0x000xF9};            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: 即使加了强混淆,对于专业的逆向分析而言,只是加了一些逆向分析的时间成本)

*本文为看雪论坛精华文章,由 CCTV果冻爽 原创,转载请注明来自看雪社区

议题征集中!看雪·第九届安全开发者峰会(SDC 2025)

球分享

球点赞

球在看

点击阅读原文查看更多

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

App风控 逆向工程 Frida Magisk Android安全 Hook技术
相关文章