如何分别使用xposed和frida来实现对APP采集mac地址的监控

一、背景

在近几年,随着数字信息时代的飞速发展,人们越来越关注个人隐私和数据安全。各国政府和监管机构为了保护消费者的隐私权益,对APP的合规要求提出了更高的标准,特别是关于敏感信息的采集和处理。在这个背景下,MAC地址作为设备的唯一标识符,其采集和使用受到了严格的审查。因此,对于APP开发者和安全测试人员来说,监控APP是否采集了MAC地址成为了一个重要的任务。

比如工信部对各应用市场中的APP进行抽查发现很多应用存在用户为同意隐私协议的情况下采集MAC地址等隐私信息的违规行为,相关应用会被通知整改,如果整改不力会被通知下架的严重后果。本文将介绍如何检测APP中使用的第三方库是否有采集MAC地址行为。 当检测到有第三方库有采集MAC地址行为时,可以采用延迟初始化的方式,即用户同意隐私协议后在对这些库进行初始化。

为了有效地监控和测试APP是否非法采集了MAC地址,`Xposed`框架和`Frida`成为了两个主流的技术手段。这两个工具都能够在不修改APP代码的情况下,对APP的运行时行为进行监控和修改,但它们在实现方式和使用场景上各有特点。

二、基本介绍

### Xposed框架

Xposed是一种基于Android操作系统的动态代码修改工具。它通过替换`/system/bin/app_process`来加载自己的`Xposed Bridge`,从而在运行时修改系统和应用程序的行为。Xposed能够让用户安装各种模块来实现系统级别的定制化功能,包括对APP行为的监控。

Xposed的优势在于其深度集成于Android系统,能够非常方便地拦截到系统级别的调用,例如API调用、系统服务调用等。这使得Xposed特别适合进行系统底层的行为修改和监控。然而,Xposed需要设备的root权限,这限制了其在一些场景下的使用。

### Frida

Frida则是一个跨平台的动态代码插桩工具,支持包括Android、iOS、Windows、macOS和Linux在内的多种操作系统。Frida工作原理是注入自己的代码到目标进程中,通过挂钩(Hooking)函数调用来监控和修改应用程序的行为。

与Xposed相比,Frida的一个显著优点是不需要设备的root权限,只要能够以某种方式运行Frida Server即可开始测试。这让Frida在非root设备上的应用程序测试中具有更广泛的适用性。Frida提供了丰富的API和脚本支持,使得用户可以灵活地编写自定义的测试脚本来满足各种测试需求。

### Xposed与Frida的区别

尽管Xposed和Frida都可以用于监控APP是否采集了MAC地址,它们之间存在一些关键的不同:

- **系统要求:** Xposed需要root权限和对Android系统的深度修改,而Frida则可以在不root的设备上使用,对系统的侵入性更小。

- **跨平台能力:** Frida支持包括Android、iOS在内的多个平台,更加灵活和广泛;Xposed主要针对Android平台。

- **使用复杂度:** Xposed通过安装模块的方式来实现功能,对于非开发者来说可能更易于使用;而Frida则侧重于通过编写和执行脚本来实现更复杂的监控逻辑,需要一定的编程知识。

三、最近几年frida用的更广泛

近几年,Frida的使用比Xposed更为广泛,主要由于以下几个原因:

1. 跨平台支持

Frida提供了广泛的跨平台支持,包括Android、iOS、Windows、macOS和Linux等。相比之下,Xposed主要针对Android系统,这在一定程度上限制了其应用范围。虽然frida在ios的使用,前提是设备已经越狱,在当前,越狱的ios设备大部分都是老旧版本。但是frida在支持平台上,仍然有明显的优势。

 2. frida无需Root权限

Frida可以在无需root权限的设备上运行,尤其对于iOS设备来说,这是一个重要优势。虽然在Android上使用Frida通常需要root权限或者某种形式的工作环境设置来启动Frida server,但Frida提供了更多灵活的部署选项,包括USB、网络和进程注入等。Xposed则需要对设备进行root,这增加了使用的复杂度。

3. 开发和社区支持

Frida拥有一个活跃的开发社区和持续的更新,提供了大量的文档、教程和示例代码。这些资源极大地降低了新用户的学习门槛,并为高级用户提供了强大的支持。与此同时,Xposed虽然也有一个支持社区,但近年来在更新和新功能开发方面相对较少。

4. 功能丰富和灵活

Frida不仅支持挂钩和修改运行中的应用程序代码,还提供了内存访问、动态分析和脚本编写等高级功能。这些功能使得Frida在应用程序分析、逆向工程和安全测试领域非常强大。而Xposed则主要通过模块化的方式提供特定的修改和定制功能,虽然强大但在某些高级场景下可能不如Frida灵活。

5. 安全测试和逆向工程需求的变化

随着移动应用和安全测试领域的发展,对工具的需求也在变化。Frida的动态分析能力非常适合现代的安全审计和快速迭代开发模式,能够及时发现和修复潜在的安全问题。相比之下,Xposed在定制Android系统行为和开发特定功能方面仍然有其独特的优势,但在安全测试和逆向工程方面可能不如Frida全面。

6,frida的易用性更好

相比起xposed框架需要进行java代码编写框架,frida提供了更灵活的javascript的方式来进行函数hook,在使用门槛和易用性上,要超过xposed。

四,如何使用xposed来进行mac获取函数的hook

一,在手机中安装VirtualXposed

首先需要确定需要检测的APP是32位还是64位架构,并下载相应版本的VirtualXposed源码进行编译或者直接下载APK

VirtualXposed<=0.18.2版本仅支持32位架构

VirtualXposed>0.18.2版本不在支持32位架构,仅支持64位架构

将VirtualXposed APK文件安装到测试手机(这里需要注意测试手机的CPU架构也需要跟VirtualXposed支持的架构保持一致),安装后需手动进入VirtualXposed的应用权限设置界面,将所有的权限都开启(VirtualXposed并不会自动请求权限,所以需要手动开启),然后运行VirtualXposed

二,编写Hook程序

编写Hook程序前需要先知道通过哪些接口可以获取到MAC地址

获取MAC地址方法一:调用NetworkInterface类的getHardwareAddress()方法

获取MAC地址方法二:调用WifiInfo类的getMacAddress()方法

获取MAC地址方法三:通过Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address ")方法获取

明确了需要hook的方法,开始编写Hook程序:

1.新建一个APP项目

2.项目中新建一个Main类,实现IXposedHookLoadPackage接口

public class Mainimplements IXposedHookLoadPackage {

private static final StringTAG ="Main";

    private StringHOOK_PREFIX ="my_hook -> ";

    @Override

    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam)throws Throwable {

hookMacAddress1(lpparam);

        hookMacAddress2(lpparam);

        hookMacAddress3(lpparam);

    }

private StringgetStackTrace(Throwable ex) {

Writer writer =new StringWriter();

        PrintWriter printWriter =new PrintWriter(writer);

        try {

ex.printStackTrace(printWriter);

            Throwable cause = ex.getCause();

            while (cause !=null) {

cause.printStackTrace(printWriter);

                cause = cause.getCause();

            }

return writer.toString();

        }catch (Throwable e) {

Log.e(TAG, "getStackTrace", e);

        }finally {

printWriter.close();

        }

return ex.toString();

    }

/**

* 检测App有没有获取mac地址,适用于7.0以上的系统

*/

    private void hookMacAddress1(XC_LoadPackage.LoadPackageParam lpparam) {

try {

Class clazz = lpparam.classLoader.loadClass("java.net.NetworkInterface");

            String methodName ="getHardwareAddress";

            XposedHelpers.findAndHookMethod(clazz, methodName, new XC_MethodHook() {

@Override

                protected void beforeHookedMethod(MethodHookParam param)throws Throwable {

super.beforeHookedMethod(param);

                    Log.i(TAG, HOOK_PREFIX +"hookMacAddress1 getHardwareAddress");

                    Log.e(TAG, HOOK_PREFIX + getStackTrace(new Exception()));

                }

});

        }catch (Throwable e) {

Log.e(TAG, "hookMacAddress1", e);

        }

}

/**

* 检测App有没有获取mac地址,适用于7.0以下系统

*/

    private void hookMacAddress2(XC_LoadPackage.LoadPackageParam lpparam) {

try {

Class clazz = lpparam.classLoader.loadClass("android.net.wifi.WifiInfo");

            String methodName ="getMacAddress";

            XposedHelpers.findAndHookMethod(clazz, methodName, new XC_MethodHook() {

@Override

                protected void beforeHookedMethod(MethodHookParam param)throws Throwable {

super.beforeHookedMethod(param);

                    Log.i(TAG, HOOK_PREFIX +"hookMacAddress2 getMacAddress");

                    Log.e(TAG, HOOK_PREFIX + getStackTrace(new Exception()));

                }

});

        }catch (Throwable e) {

Log.e(TAG, "hookMacAddress2", e);

        }

}

private void hookMacAddress3(XC_LoadPackage.LoadPackageParam lpparam) {

try {

Class clazz = lpparam.classLoader.loadClass("java.lang.Runtime");

            String methodName ="exec";

            XposedHelpers.findAndHookMethod(clazz, methodName, String.class, new XC_MethodHook() {

@Override

                protected void beforeHookedMethod(MethodHookParam param)throws Throwable {

super.beforeHookedMethod(param);

                    Log.i(TAG, HOOK_PREFIX +"hookMacAddress3 getMacAddress param = " + param.args[0]);

                    Log.e(TAG, HOOK_PREFIX + getStackTrace(new Exception()));

                }

});

        }catch (Throwable e) {

Log.e(TAG, "hookMacAddress3", e);

        }

}

public static StringgetMacAddress1() {

String macAddress =null;

        StringBuffer buf =new StringBuffer();

        NetworkInterface networkInterface =null;

        try {

networkInterface = NetworkInterface.getByName("eth1");

            if (networkInterface ==null) {

networkInterface = NetworkInterface.getByName("wlan0");

            }

if (networkInterface ==null) {

return "02:00:00:00:00:02";

            }

byte[] addr = networkInterface.getHardwareAddress();

            for (byte b : addr) {

buf.append(String.format("%02X:", b));

            }

if (buf.length() >0) {

buf.deleteCharAt(buf.length() -1);

            }

macAddress = buf.toString();

        }catch (SocketException e) {

e.printStackTrace();

            return "02:00:00:00:00:02";

        }

return macAddress;

    }

public static StringgetMacAddress2(Context context) {

WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

        WifiInfo info = wifi.getConnectionInfo();

        return info.getMacAddress();

    }

private StringgetMacAddress3() {

String macSerial =null;

        String str ="";

        try {

Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address ");

            InputStreamReader ir =new InputStreamReader(pp.getInputStream());

            LineNumberReader input =new LineNumberReader(ir);

            for (; null != str; ) {

str = input.readLine();

                if (str !=null) {

macSerial = str.trim();// 去空格

                    break;

                }

}

}catch (IOException ex) {

// 赋予默认值

            ex.printStackTrace();

        }

return macSerial;

    }

}

3.在src/main/目录下新建一个Assets Folder,并在Assets Folder中创建xposed_init文件,xposed_init文件中写入Main类的完成名称,例如:com.w.myhook.Main

4.在AndroidManifest.xml中声明xposed meta-data

<meta-data android:name="xposedmodule"

    android:value="true"/>

<meta-data android:name="xposedminversion"

    android:value="53"/>

<meta-data android:name="xposeddescription"

    android:value="myAppHook"/>

5.配置xposed库依赖

compileOnly'de.robv.android.xposed:api:82'

compileOnly'de.robv.android.xposed:api:82:sources'

6.编译运行

三,安装MyHook模块到VirtualXposed

1. 启动VirtualXposed后,点击底部的按钮 - 添加应用 - 选择MyHook - 点击安装 - 选择VirtualXposed

2.返回VirtualXposed首页,向上滑动打开抽屉,启动Xposed Installer,开启MyHook模块

3.重启VirtualXposed

不需要重启手机,只需要将VirtualXposed APP的进程重启即可

四,安装需要检测的APP

可以直接从手机SD卡中安装,也可以像第三步的第1步安装MyHook那样把需要检测的APP安装到VirtualXposed,这里就不再赘述了,注:安装APP后不需要再添加模块或重启VirtualXposed了

五,运行被检测的APP

返回VirtualXposed首页,向上滑动打开抽屉,点击运行需要检测的APP

过滤日志关键字my_hook,就可以看到mac地址的调用情况和调用堆栈:


五,如何使用frida来进行mac获取函数的hook

一、环境配置

frida分为服务端和客户端,其中服务端运行在设备上,在本例中,就是运行在已经root的android手机,或者模拟器上。

我平时使用的模拟器居多,当然模拟器也有问题。现在很多app在启动的时候就会检测环境,如果是root手机,只会检测到已经root。而如果是模拟器,可能会被检测到已经root还有模拟器环境,在这种情况下,可能还需要进行模拟器检测的bypass。

如果使用的是PC上安装Frida可以直接使用pip进,确保你的PC上已安装Python和pip。

通过pip安装Frida:pip install frida-tools

在Android设备上安装Frida Server

根据你的设备架构下载对应版本的Frida Server(例如arm, arm64, x86等),可从Frida的GitHub发布页面找到。

https://github.com/frida/frida

将下载的Frida Server二进制文件推送到Android设备上,并确保其具有执行权限:

adb push frida-server /data/local/tmp/

adb shell "chmod 755 /data/local/tmp/frida-server"

推送完成后,就可以在android脚本上启动frida-server

adb shell "/data/local/tmp/frida-server &"

二、确定android中需要hook的具体函数

这里用没有代码权限,需要反编译apk举个例子。

比如我们拿到了一个apk,这里用一个没有进行加固的apk举例,加固过的apk还涉及到脱壳,这里就不过于复杂了。

首先,你需要反编译APK文件来查看其代码。虽然你不能直接获取到源代码,但是通过反编译工具可以得到相对易读的smali代码或者Java代码(通过工具如jadx)。

jadx是一个非常强大的工具,它可以将DEX文件转换成Java源代码。直接用GUI版本的jadx打开APK文件,会更方便查看和搜索代码。这里我们通过jadx直接反编译apk。

如下图


反编译的目录结构:

源代码目录(源代码)

android:这通常包含了对Android SDK中的类的引用或者继承。

androidx:代表使用了AndroidX库,这是支持库的一个新版本,提供了向后兼容性。

comcnorgnet:这些目录包含了按照包名组织的应用代码和依赖库的代码。

kotlinkotlinx:包含了Kotlin语言编写的代码和扩展库。

demojcifsokhttp3okiorxp000360updatetopv0:可能是应用本身的代码或者第三方库的代码。

资源文件夹(资源文件夹)

assets:通常包含应用需要的资源文件,如配置文件、文本文件、数据库文件等。

res:包含了应用的资源文件,例如布局(layout)、字符串(values)、图片(drawable)等。

其他重要文件和目录

AndroidManifest.xml:这是APK文件的清单文件,包含了应用的包名、权限请求、活动(Activities)、服务(Services)、广播接收器(Broadcast Receivers)等。

classes.dexclasses2.dexclasses3.dexclasses4.dex:这些DEX文件包含了应用的编译后的代码。多个DEX文件通常表示应用使用了多DEX文件来绕过单个DEX文件大小的限制。

resources.arsc:这是编译后的资源文件,包含了所有的非代码资源,如字符串、样式等。

META-INF:包含了APK签名和其他元数据的文件夹。

lib:存放应用使用的原生库(Native libraries),可能按照不同的CPU架构有不同的子目录。

我们把jadx反编译后的文件保存到目录下, 然后使用idea打开,全局搜索关键函数


例如在com/xxxx/commonlib/utils/y.java这个y类下面有函数h,调用了gethardwareaddress(),那我们就可以针对这个函数进行frida脚本的hook.


三.编写Frida脚本

既然已经确定了需要hook的函数h位于com.xxxx.commonlib.utils.y类中,你可以编写一个Frida脚本来hook这个方法。下面是一个基本的Frida脚本示例,它将在调用h方法时输出当前类名和一些可能有用的调用

Java.perform(function () {

    // 定位到类

    var TargetClass = Java.use('com.xxxx.commonlib.utils.y');

    // hook该类的h方法

    TargetClass.h.overloads.forEach(function(overload) {

        overload.implementation = function () {

            // 输出日志

            console.log('Called h from com.xxxx.commonlib.utils.y');

            // 如果需要,这里可以输出方法的参数

            // console.log('Arguments:', JSON.stringify(arguments));

            // 调用原始方法

            return this.h.apply(this, arguments);

        };

    });

});

这段脚本使用了Java.use函数来hook指定的Java类。overloads属性和方法用于hook所有的h方法重载。如果h方法没有重载,那么可以去掉overloads.forEach循环并直接替换implementation。

四。使用Frida监控Android应用

确定目标应用的包名,例如com.example.app。

使用Frida运行之前编写的脚本并附加到目标应用上:

frida -U -f com.example.app-l path/to/your/script.js--no-pause

这里的-U表示通过USB连接设备,-f用于指定目标应用的包名,-l指定要加载的Frida脚本路径,--no-pause意味着在应用启动时不暂停。

操作目标应用,尝试触发获取MAC地址的行为。如果应用尝试获取MAC地址,你将在控制台中看到相应的日志输出。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容