隱私合規(guī)神器:從此告別隱私違規(guī)下架

概述

你是否像我一樣,有過這樣的經(jīng)歷?因?yàn)槿絊DK過度索取用戶信息而造成APP被應(yīng)用市場強(qiáng)制下架?當(dāng)遇到這種問題的時(shí)候,你會(huì)怎么辦呢?我想大部分用戶可能會(huì)去找對(duì)應(yīng)SDK的技術(shù)客服尋求解決辦法,這可能是目前我們所能想到的最好的解決辦法,或者是去掉那些不重要的SDK。


image.png

然而,三方SDK的維護(hù)速度可能會(huì)讓你崩潰,他們可能會(huì)排期處理,但是往往得好幾天,甚至幾十天,那么有沒有一種方便、有效的辦法去解決這個(gè)問題呢?


image.png

當(dāng)然有了,下面就是我今天要介紹的主角:PrivacySentry

他號(hào)稱可規(guī)避應(yīng)用市場上架合規(guī)檢測的大部分問題,我也在項(xiàng)目中采用了,目前來說,沒有發(fā)現(xiàn)什么太大的問題,需要注意的一點(diǎn)是,你的項(xiàng)目AGP需要保持在8.0以下,如果不能的話,可能無法使用這個(gè)工具

一、如何使用

  1. 在根目錄的build.gralde下添加
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

    buildscript {
         dependencies {
             // 添加插件依賴
             classpath 'com.github.allenymt.PrivacySentry:plugin-sentry:1.3.4.2'
         }
    }

    allprojects {
        repositories {
            maven { url 'https://jitpack.io' }
        }
    }

  1. 在項(xiàng)目中的build.gralde下添加

        // 在主項(xiàng)目里添加插件依賴
        apply plugin: 'privacy-sentry-plugin'

        dependencies {
            // aar依賴
            def privacyVersion = "1.3.4.2"
            implementation "com.github.allenymt.PrivacySentry:hook-sentry:$privacyVersion"
            implementation "com.github.allenymt.PrivacySentry:privacy-annotation:$privacyVersion"

             // 代理類的庫,如果自己沒有代理類,那么必須引用這個(gè)aar??!
             // 如果不想使用庫中本身的代理方法,可以不引入這個(gè)aar,但是自己必須實(shí)現(xiàn)代理類?。?             // 引入privacy-proxy,也可以自定義類代理方法,優(yōu)先以業(yè)務(wù)方定義的為準(zhǔn)
            implementation "com.github.allenymt.PrivacySentry:privacy-proxy:$privacyVersion"
            // 1.2.3 新增類替換,主要是為了hook構(gòu)造函數(shù)的參數(shù),按業(yè)務(wù)方需求自己決定
            implementation "com.github.allenymt.PrivacySentry:privacy-replace:$privacyVersion"
        }

        // 黑名單配置,可以設(shè)置這部分包名不會(huì)被修改字節(jié)碼
        // 項(xiàng)目里如果有引入高德地圖,先加黑 blackList = ["com.loc","com.amap.api"], asm的版本有沖突
        // 如果需要生成靜態(tài)掃描文件, 默認(rèn)名是replace.json
       privacy {
               // 設(shè)置免hook的名單
                blackList = []
                // 開關(guān)PrivacySentry插件功能,核心功能開關(guān),默認(rèn)為true
                enablePrivacy = true

                // 開啟hook反射的方法,默認(rèn)為false,按需打開
                hookReflex = false
                //  配置反射攔截 反射獲取小米系統(tǒng)的oaid、aaid、vaid,例如極光、個(gè)推、穿山甲等SDK都有獲取
                reflexMap = ["com.android.id.impl.IdProviderImpl":["getOAID","getAAID","getVAID"]]

                // 默認(rèn)為false,按需打開
                hookConstructor = false
                // 默認(rèn)為false,按需打開
                hookField = false


                //*************以下是分割線,主要是對(duì)Service的自啟動(dòng)優(yōu)化處理,默認(rèn)為false,按需打開****************
                // 處理Manifest文件,主要是處理Service的Priority , 關(guān)閉Service的Export
                enableProcessManifest = false
                // hook Service的部分代碼,修復(fù)在MIUI上的自啟動(dòng)問題
                // 部分Service把自己的Priority設(shè)置為1000,這里開啟代理功能,可以代理成0
                enableReplacePriority = false
                replacePriority = 1

                // 支持關(guān)閉Service的Export功能,默認(rèn)為false,注意部分廠商通道之類的push(xiaomi、vivo、huawei等廠商的pushService),不能關(guān)閉
                enableCloseServiceExport = false
                // Export白名單Service
                serviceExportPkgWhiteList = ["white"]
                enableHookServiceStartCommand = false
        }

 初始化方法最好在attachBaseContext中第一個(gè)調(diào)用?。?!(1.3.1開始不需要了,可以晚點(diǎn)初始化,不影響檢測結(jié)果)
 完成功能的初始化
    PrivacySentryBuilder builder = new PrivacySentryBuilder()
                        // 自定義文件結(jié)果的輸出名
                        .configResultFileName("buyer_privacy")
    `       //  debug打開,可以看到logcat的堆棧日志
            .syncDebug(true)
                        // 配置寫入文件日志 , 線上包這個(gè)開關(guān)不要打開!?。?!,true打開文件輸入,false關(guān)閉文件輸入
                        .enableFileResult(true)
                        // 持續(xù)寫入文件30分鐘
                        .configWatchTime(30 * 60 * 1000)
                        // 文件輸出后的回調(diào)
                        .configResultCallBack(new PrivacyResultCallBack() {

                            @Override
                            public void onResultCallBack(@NonNull String s) {

                            }
                        });
    // 添加默認(rèn)結(jié)果輸出,包含log輸出和文件輸出
    PrivacySentry.Privacy.INSTANCE.init(application, builder);
 如果在日志中發(fā)現(xiàn)check!!! 還未展示隱私協(xié)議,Illegal print,說明此時(shí)還未同意隱私協(xié)議,調(diào)用了敏感或者違規(guī)的api
    所以在隱私協(xié)議確認(rèn)的時(shí)候調(diào)用,這一步非常重要!,一定要加,這一步是告知SDK,APP已經(jīng)同意隱私協(xié)議了
    kotlin:PrivacySentry.Privacy.updatePrivacyShow()
    java:PrivacySentry.Privacy.INSTANCE.updatePrivacyShow();
  支持自定義配置hook函數(shù)
    /**
 * @author yulun
 * @since 2022-01-13 17:57
 * 主要是兩個(gè)注解PrivacyClassProxy和PrivacyMethodProxy,PrivacyClassProxy代表要解析的類,PrivacyMethodProxy代表要hook的方法配置
 */
@Keep
open class PrivacyProxyResolver {

    // kotlin里實(shí)際解析的是這個(gè)PrivacyProxyCall$Proxy 內(nèi)部類
    @PrivacyClassProxy
    @Keep
    object Proxy {

        // 查詢
        @SuppressLint("MissingPermission")
        @PrivacyMethodProxy(
            originalClass = ContentResolver::class,   // hook的方法所在的類名
            originalMethod = "query",   // hook的方法名
            originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL //hook的方法調(diào)用,一般是靜態(tài)調(diào)用和實(shí)例調(diào)用
        )
        @JvmStatic
        fun query(
            contentResolver: ContentResolver?, //實(shí)例調(diào)用的方法需要把聲明調(diào)用對(duì)象,我們默認(rèn)把對(duì)象參數(shù)放在第一位
            uri: Uri,
            projection: Array<String?>?, selection: String?,
            selectionArgs: Array<String?>?, sortOrder: String?
        ): Cursor? {
            doFilePrinter("query", "查詢服務(wù): ${uriToLog(uri)}") // 輸入日志到文件
            return contentResolver?.query(uri, projection, selection, selectionArgs, sortOrder)
        }

        @RequiresApi(Build.VERSION_CODES.O)
        @PrivacyMethodProxy(
            originalClass = android.os.Build::class,
            originalMethod = "getSerial",
            originalOpcode = MethodInvokeOpcode.INVOKESTATIC //靜態(tài)調(diào)用
        )
        @JvmStatic
        fun getSerial(): String? {
            var result = ""
            try {
                doFilePrinter("getSerial", "讀取Serial")
                if (PrivacySentry.Privacy.getBuilder()?.isVisitorModel() == true) {
                return ""
                }
            result = Build.getSerial()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        return result
        }
    }
}


    支持多進(jìn)程,多進(jìn)程產(chǎn)出的文件名前綴默認(rèn)增加進(jìn)程名

    如何配置替換一個(gè)類
    可以參考源碼中PrivacyFile的配置,使用PrivacyClassReplace注解,originClass代表你要替換的類,注意要繼承originClass的所有構(gòu)造函數(shù)
    可以配置 hookConstructor = false關(guān)閉這個(gè)功能
/**
 * @author yulun
 * @since 2022-11-18 15:01
 * 代理File的構(gòu)造方法,如果是自定義的file類,需要業(yè)務(wù)方單獨(dú)配置自行處理
 */
@PrivacyClassReplace(originClass = File.class)
public class PrivacyFile extends File {

    public PrivacyFile(@NonNull String pathname) {
        super(pathname);
        record(pathname);
    }

    public PrivacyFile(@Nullable String parent, @NonNull String child) {
        super(parent, child);
        record(parent + child);
    }

    public PrivacyFile(@Nullable File parent, @NonNull String child) {
        super(parent, child);
        record(parent.getPath() + child);
    }

    public PrivacyFile(@NonNull URI uri) {
        super(uri);
        record(uri.toString());
    }

    private void record(String path) {
        PrivacyProxyUtil.Util.INSTANCE.doFilePrinter("PrivacyFile", "訪問文件", "path is " + path, PrivacySentry.Privacy.INSTANCE.getBuilder().isVisitorModel(), false);
    }
}

二、基本原理

  1. 編譯期注解+hook方案,第一個(gè)transform收集需要攔截的敏感函數(shù),第二個(gè)transform替換敏感函數(shù),運(yùn)行期收集日志

  2. 為什么不用xposed等框架? 因?yàn)橄胱霰镜刈詣?dòng)化定期排查,第三方hook框架外部依賴性太大

  3. 為什么不搞基于lint的排查方式? 工信部對(duì)于運(yùn)行期 敏感函數(shù)的調(diào)用時(shí)機(jī)和次數(shù)都有限制,代碼掃描解決不了這些問題

三、支持的hook函數(shù)列表

支持hook以下功能函數(shù):

支持敏感字段緩存(磁盤緩存、帶有時(shí)間限制的磁盤緩存、內(nèi)存緩存)

hook替換類 (構(gòu)造函數(shù))

當(dāng)前運(yùn)行進(jìn)程和任務(wù)

系統(tǒng)剪貼板服務(wù)

讀取設(shè)備應(yīng)用列表

讀取 Android SN(Serial,包括方法和變量),系統(tǒng)設(shè)備號(hào)

讀寫聯(lián)系人、日歷、本機(jī)號(hào)碼

獲取定位、基站信息、wifi信息

Mac 地址、IP 地址

讀取 IMEI(DeviceId)、MEID、IMSI、ADID(AndroidID)

手機(jī)可用傳感器,傳感器注冊(cè),傳感器列表

權(quán)限請(qǐng)求

四、常見的合規(guī)字段整理

IMEI、MAC地址、MEID、IMSI、SN、ICCID等設(shè)備唯一標(biāo)識(shí)符,Android ID、WiFi(WiFi名稱、WiFi MAC地址以及設(shè)備掃描到的所有WiFi信息),SIM卡信息(IMSI、SIM卡序列號(hào)ICCID、手機(jī)號(hào)、運(yùn)營商信息),應(yīng)用安裝列表(設(shè)備所有已安裝應(yīng)用的包名和應(yīng)用名),傳感器(傳感器列表、加速度傳感器、溫度傳感器等),藍(lán)牙信息(設(shè)備藍(lán)牙地址和設(shè)備掃描到的藍(lán)牙設(shè)備信息),基站定位、GPS(用戶地理位置信息),賬戶(各類應(yīng)用注冊(cè)的不同賬號(hào)信息)、剪切板、IP地址、硬件序列號(hào)、SDCard信息(公有目錄)

五、總結(jié)

這么簡單?沒錯(cuò),就是這么簡單,要不然怎么說他是神器呢?項(xiàng)目的地址在下面,感興趣的可以在項(xiàng)目中嘗試一下希望大家能多多點(diǎn)贊,關(guān)注一下,不定期分享優(yōu)秀的開源工具項(xiàng)目地址:https://github.com/allenymt/PrivacySentry

往期內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容