乐于分享
好东西不私藏

iOS APP 运营商信息合规检测复盘

iOS APP 运营商信息合规检测复盘
在做 iOS 应用合规检测时,遇到 APP 获取运营商信息的风险问题,刚开始完全没有排查思路,不清楚该如何监测到底是哪段代码触发了运营商信息的读取。一开始想到利用运行时机制来监控相关接口调用,以此定位调用来源。
最开始采用静态代码检索的方式排查业务代码,把项目中所有读取运营商信息的业务代码全部注释处理,重新编译测试后,合规检测依旧报出获取运营商信息的风险。这说明违规调用并不来自我们自身业务代码,大概率是第三方 SDK 内部触发,静态代码扫描已经无法继续定位问题。
随后调整排查手段,采用动态监听的方式,捕获接口调用时的完整堆栈信息,顺着调用栈逐层溯源分析。通过堆栈还原调用链路,最终定位到是极光 SDK 内部发起的获取运营商信息行为。核实后确认项目所用极光 SDK 版本较低,旧版 SDK 会自动采集运营商信息,进而触发合规告警。后续通过升级极光 SDK 版本,解决了该合规隐患。
这次问题排查也积累了很实用的经验。面对隐私合规类问题,不能只依赖静态代码检索,很多风险来自第三方 SDK 的隐性调用,业务层看不到相关源码。借助运行时动态捕获调用堆栈,能够穿透上层业务,直接定位到底是哪个库、哪条链路产生的隐私接口调用,这套排查方式,对后续处理各类 SDK 隐性隐私调用、合规风险问题有很高参考价值。
iOS通过运行时检测调用运营商信息的核心代码如下:
import Foundationimport CoreTelephonyfinal class CarrierMonitor {    static let shared = CarrierMonitor()    private init() {}    func startTrace() {        // 监控 CTTelephonyNetworkInfo        guard let netInfoCls = objc_getClass("CTTelephonyNetworkInfo"as? AnyClass else { return }        swizzle(cls: netInfoCls,                originalSel: #selector(getter: CTTelephonyNetworkInfo.subscriberCellularProvider),                swizzledSel: #selector(CTTelephonyNetworkInfo.trace_subscriberCellularProvider))        swizzle(cls: netInfoCls,                originalSel: #selector(getter: CTTelephonyNetworkInfo.serviceSubscriberCellularProviders),                swizzledSel: #selector(CTTelephonyNetworkInfo.trace_serviceSubscriberCellularProviders))        // 监控 CTCarrier        guard let carrierCls = objc_getClass("CTCarrier"as? AnyClass else { return }        swizzle(cls: carrierCls,                originalSel: #selector(getter: CTCarrier.carrierName),                swizzledSel: #selector(CTCarrier.trace_carrierName))        swizzle(cls: carrierCls,                originalSel: #selector(getter: CTCarrier.mobileCountryCode),                swizzledSel: #selector(CTCarrier.trace_mobileCountryCode))        swizzle(cls: carrierCls,                originalSel: #selector(getter: CTCarrier.mobileNetworkCode),                swizzledSel: #selector(CTCarrier.trace_mobileNetworkCode))    }    private func swizzle(clsAnyClassoriginalSelSelectorswizzledSelSelector) {        guard let origMethod = class_getInstanceMethod(cls, originalSel),              let newMethod = class_getInstanceMethod(cls, swizzledSel) else {            return        }        let didAddMethod = class_addMethod(cls,                                           originalSel,                                           method_getImplementation(newMethod),                                           method_getTypeEncoding(newMethod))        if didAddMethod {            class_replaceMethod(cls,                                swizzledSel,                                method_getImplementation(origMethod),                                method_getTypeEncoding(origMethod))        } else {            method_exchangeImplementations(origMethod, newMethod)        }    }}// MARK: - CTTelephonyNetworkInfo 扩展extension CTTelephonyNetworkInfo {    @objc func trace_subscriberCellularProvider() -> CTCarrier? {        let carrier = self.trace_subscriberCellularProvider()        print("""        ⚠️【运营商API触发】subscriberCellularProvider(单卡运营商)        调用栈:        \(Thread.callStackSymbols.joined(separator: "\n"))        """)        return carrier    }    @objc func trace_serviceSubscriberCellularProviders() -> [StringCTCarrier]? {        let map = self.trace_serviceSubscriberCellularProviders()        print("""        ⚠️【运营商API触发】serviceSubscriberCellularProviders(双卡运营商)        调用栈:        \(Thread.callStackSymbols.joined(separator: "\n"))        """)        return map    }}// MARK: - CTCarrier 扩展extension CTCarrier {    @objc func trace_carrierName() -> String? {        let name = self.trace_carrierName()        print("""        ⚠️【运营商API触发】读取 carrierName        调用栈:        \(Thread.callStackSymbols.joined(separator: "\n"))        """)        return name    }    @objc func trace_mobileCountryCode() -> String? {        let mcc = self.trace_mobileCountryCode()        print("""        ⚠️【运营商API触发】读取 mobileCountryCode(MCC)        调用栈:        \(Thread.callStackSymbols.joined(separator: "\n"))        """)        return mcc    }    @objc func trace_mobileNetworkCode() -> String? {        let mnc = self.trace_mobileNetworkCode()        print("""        ⚠️【运营商API触发】读取 mobileNetworkCode(MNC)        调用栈:        \(Thread.callStackSymbols.joined(separator: "\n"))        """)        return mnc    }}
调用实例代码如下:
CarrierMonitor.shared.startTrace()