隐私合规-设备应用列表获取

本文探讨了为何应用需要获取设备安装列表,包括应用间相互拉起、大数据统计、精准推送、应用市场功能及风控SDK等场景。接着介绍了四种获取设备安装列表的常见方法:命令获取、API获取、UID爆破和反向包名获取,并提供了相应的Java代码示例。最后,提出了通过Frida进行hook检测这些行为的方案,以实现对应用隐私合规性的监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

最近研究隐私合规检测工作的时候有个需求是需要对应用收集用户设备的安装列表这个行为进行检测,现在简单记录下常用的获取设备安装列表的方法。
先搞清楚一个问题,为什么有些应用要获取设备安装的应用列表,目的是什么?

应用场景

1.应用间相互拉起

这种可能是应用联盟玩应用矩阵保活,比如你安装了应用A,它会主动读取手机中是否安装了兄弟应用B,如果有安装,那么它就可以主动唤醒应用B,还有一种正常点的就是应用A如果需要使用支付功能或地图导航功能,但它自己没有实现,那么就可唤起设备中有支付功能或地图导航功能的应用来完成。

2.大数据统计、精准推送

这种主要是对设备中安装的应用列表进行获取并上传到云端,一是用来做应用安装量的数据统计,二是基于这些应用来判断用户兴趣爱好,对用户进行画像。有了用户喜好后期就便于广告的精准推送。

3.应用市场、清理、杀毒类应用

这类目的主要是一些工具类产品,比如应用市场通过获取设备中安装应用的信息来检查应用是否需要升级更新,清理杀毒类应用通过获取安装列表并通过云端的匹配来判断用户设备是否安全,是否安装了病毒、盗版软件等。

4.风控SDK

还有一种目的主要是用于应用做风控,应用通过获取设备中安装的应用来判断设备是否为模拟器、用户是否为正常用户。如果应用发现自己在模拟器中运行那么就启动主动防御功能,比如运行错误的逻辑又或者直接退出等。

获取方案

知道了获取应用安装列表的目的后来看看一些常见的获取应用列表的方案。

1.通过命令获取安装列表

private List<String> getPkgListFromShell_PM() {
    List<String> packages = new ArrayList<String>();
    try {
        Process p = Runtime.getRuntime().exec("pm list packages");
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), "utf-8"));
        String line = br.readLine();
        while (line != null) {
            line = line.trim();
            if (line.length() > 8) {
                String prefix = line.substring(0, 8);
                if (prefix.equalsIgnoreCase("package:")) {
                    line = line.substring(8).trim();
                    if (!TextUtils.isEmpty(line)) {
                        Log.d("Demo", "install: " + line);
                        packages.add(line);
                    }
                }
            }
            line = br.readLine();
        }
        br.close();
        p.destroy();
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return packages;
}

2.通过API获取安装列表

private List<String> getPkgListFromAPI_getInstalledPackages(Context context) {
    List<String> packages = new ArrayList<String>();
    try {
        List<PackageInfo> packageInfos = context.getPackageManager().getInstalledPackages(PackageManager.GET_ACTIVITIES |
                PackageManager.GET_SERVICES);
        for (PackageInfo info : packageInfos) {
            String pkg = info.packageName;
            Log.d("Demo", "install: " + pkg);
            packages.add(pkg);
        }
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return packages;
}
private List<String> getPkgListFromAPI_queryIntentActivities(Context context) {
    List<String> packages = new ArrayList<String>();
    try {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        List<ResolveInfo> mResolveInfos = context.getPackageManager().queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo info : mResolveInfos) {
            String appName = info.activityInfo.applicationInfo.loadLabel(context.getPackageManager()).toString();
            String appPkg = info.activityInfo.packageName;
            Log.d("Demo", "install: " + appName + " " + appPkg);
            packages.add(appPkg);
        }
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return packages;
}
private List<String> getPkgListFromAPI_getInstalledApplications(Context context) {
    List<String> packages = new ArrayList<String>();
    try {
        List<ApplicationInfo> mApplicationInfo = context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);
        for (ApplicationInfo info : mApplicationInfo) {
            String appName = info.name;
            String appPkg = info.packageName;
            Log.d("Demo", "install: " + appPkg);
            packages.add(appPkg);
        }
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return packages;
}

3.通过UID爆破应用列表

private boolean getPkgListFromAPI_getPackagesForUid(Context context){
    for (int uid=10001;uid<30000;uid++){
        String[] uidToPackage = context.getPackageManager().getPackagesForUid(uid);
        if(uidToPackage != null){
            Log.d("Hook",uid + "    " + uidToPackage[0]);
        }
    }
    return true;
}

4.根据包名反向获取应用列表

private boolean getPkgListFromAPI_getLaunchIntentForPackage(Context context){
    List<String> appPkgList = new ArrayList<>();
    appPkgList.add("com.google.android.apps.docs");
    appPkgList.add("com.google.android.apps.docs2");
    appPkgList.add("com.google.android.calendar");
    for (String pkg:appPkgList){
        Intent intent = context.getPackageManager().getLaunchIntentForPackage(pkg);
        String status = intent == null?" 不存在":" 存在";
        Log.d("Demo",pkg + status);
    }
    return true;
}
private boolean getPkgListFromAPI_getPackageInfo(Context context){
    List<String> appPkgList = new ArrayList<>();
    appPkgList.add("com.google.android.apps.docs");
    appPkgList.add("com.google.android.apps.docs2");
    appPkgList.add("com.google.android.calendar");
    for (String pkg:appPkgList){
        boolean flag = false;
        try {
            context.getPackageManager().getPackageInfo(pkg, PackageManager.GET_GIDS);
            flag = true;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        String status = flag ?" 存在":" 不存在";
        Log.d("Demo",pkg + status);
    }
    return true;
}

检测方案

知道了常见获取应用安装列表的方法后通过编写相应的hook代码来对这些关键API进行监控,以此来完成对应用获取安装列表的行为进行监控。
hook的实现可以使用Xposed、Frida或修改系统源码都可以,这里直接使用Frida来实现该功能,不熟悉Frida的小伙伴可以看看之前的文章Android逆向-Frida入门学习笔记

代码

Java.perform(function() {
    var cn = "android.app.ApplicationPackageManager";	//因为PackageManager类是个抽象类,ApplicationPackageManager实现了该抽象类,所以Hook上ApplicationPackageManager即可
    var appPackageManager = Java.use(cn);
    if (appPackageManager) {
        //Hook getInstalledPackages
        var overloadcount = appPackageManager["getInstalledPackages"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["getInstalledPackages"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->getInstalledPackages");
                return this["getInstalledPackages"].apply(this, arguments);
            };
        }

        //Hook queryIntentActivities
        var overloadcount = appPackageManager["queryIntentActivities"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["queryIntentActivities"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->queryIntentActivities");
                return this["queryIntentActivities"].apply(this, arguments);
            };
        }

        //Hook getInstalledApplications
        var overloadcount = appPackageManager["getInstalledApplications"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["getInstalledApplications"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->getInstalledApplications");
                return this["getInstalledApplications"].apply(this, arguments);
            };
        }

        //Hook getPackagesForUid
        var overloadcount = appPackageManager["getPackagesForUid"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["getPackagesForUid"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->getPackagesForUid");
                return this["getPackagesForUid"].apply(this, arguments);
            };
        }

        //Hook getLaunchIntentForPackage
        var overloadcount = appPackageManager["getLaunchIntentForPackage"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["getLaunchIntentForPackage"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->getLaunchIntentForPackage");
                return this["getLaunchIntentForPackage"].apply(this, arguments);
            };
        }

        //hook getPackageInfo
        var overloadcount = appPackageManager["getPackageInfo"].overloads.length;
        for (var i=0; i<overloadcount; i++){
            appPackageManager["getPackageInfo"].overloads[i].implementation = function () {
                if(arguments.length){
                    for(var j=0; j<arguments.length; j++){
                        console.log("arg["+ j +"]: " + arguments[j]);
                    }
                }
                console.log("call " + cn + "->getPackageInfo");
                //return this["getPackageInfo"].apply(this, arguments);
            };
        }
    }
});
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值