安卓追踪方法调用堆栈

接到组长下发的书城隐私协议问题,记录了一下排查过程,以后可以模仿此步骤,进行隐私协议排查工作

问题如下图所示:

很明显,我们在未被授权获取用户设备信息的情况下,频繁的请求了用户的设备信息。这是不允许的。
查找资料,如下代码,可以获取用户信息

TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
imei = telephonyManager.getDeviceId();

那我们可以按照如下逻辑,进行排查工作。
1.分析我们代码里面的获取逻辑,做权限检查+imei缓存处理
2.分析三方SDK里面的获取情况

那么问题来了,检查1,自己的代码我们可以进行检索,排查。三方的SDK我们如何知晓调用情况呢,断点打到系统上?其实方案很简单,就是Hook。
一方面我们可以使用java的动态代理,Hook本应用获取的系统服务,另一方面我们可以使用Xposed,注入系统进程,Hook所有的系统服务。
市面上经常有模拟器刷量,他们Hook系统api,返回虚假的IMEI,我们可以如法炮制。条条道路通罗马,我们选择最简单的那条路(Xposed Hook)。

准备工具如下:
1.雷电模拟器
2.Xposed jar
3.Xposed开发环境部署
4.免重启Xposed模块改进

首先安装雷电模拟器,自带Root权限。启动后,安装Xposed installer(需梯子)。
然后按照工具3,部署Xposed开发环境。我已在文章末尾附上git地址,大家clone下开箱可用。

回到我们的主题,我们需要Hook系统的getSystemService,咨询了技术部的大佬@杨滨(yangbin),写出了如下的代码。

首先,我们Hook loadClass.看看是否加载了 android.content.ContextWrapper

XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {
                // 在类方法loadClass执行之后执行的代码
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                 // 参数的检查
                    if (param.hasThrowable()) {
                        return;
                    }
                    // 获取指定名称的类加载之后的Class<?>
                    Class<?> clazz = (Class<?>) param.getResult();
                    // 获取加载的指定类的名称
                    String strClazz = clazz.getName();
                    if (strClazz.startsWith("android.content.ContextWrapper")) {
                        XposedBridge.log("--------------------LoadClass-------------------------- : " + strClazz);
                        .......
                     }
                }
          }
);

然后我们Hook getSystemService方法

for (int i = 0; i < m.length; i++) {
    XposedBridge.log("--------------HOOKED CLASS-METHOD-------------------: " + strClazz + "-" + m[i].toString());
    if (m[i].toString().indexOf("getSystemService") == -1) {
        continue;
    }
    if (!Modifier.isAbstract(m[i].getModifiers())           // 过滤掉指定名称类中声明的抽象方法
            // && !Modifier.isNative(m[i].getModifiers())     // 过滤掉指定名称类中声明的Native方法
            && !Modifier.isInterface(m[i].getModifiers())  // 过滤掉指定名称类中声明的接口方法
    ) {
            // 对指定名称类中声明的非抽象方法进行java Hook处理
            XposedBridge.hookMethod(m[i], new XC_MethodHook() {

                // 被java Hook的类方法执行完毕之后,打印log日志
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                   .....
                }
             }
        }
}       

最后我们Hook 获取Context.TELEPHONY_SERVICE服务的调用,打印出调用栈。

// 打印被java Hook的类方法的名称和参数类型等信息
if ((param.args != null) && (param.args.length > 0)) {
    for (int i = 0; i < param.args.length; i++) {
        if (param.args[i] != null) {
            String c = param.args[i].toString();
            if (c.indexOf("phone") != -1) {
                Thread.sleep(100);
                new Exception("-------phone---printStackTrace").printStackTrace();
            }
            XposedBridge.log("---------------xxxxxx-----HOOKED METHOD: -------param-------------" + "-" + "-" + param.method + "-" + i + ":" + param.args[i]);
        }
    }

}

// 打印被java Hook的类方法的名称和参数类型等信息
if (param.getResult() != null) {
    try {
        XposedBridge.log("--------------------HOOKED METHOD: --------------result------" + param.getClass().getName() + "-" + param.method.toString() + ":" + param.getResult().toString());
    } catch (Throwable e) {

    }
}

这样,我们就可以在所有第三方SDK,调用系统phone服务的时候,打印出当前的调用栈。

安装好Xposed模块并勾选,因为是免重启模块,杀死宿主和模块,点开我们的书城即可查看。

在日志中我们可以查看到,广点通服务在频繁的调用,

QM统计在频繁调用

哈哈,是不是原形毕露呢,这样我们不仅可以找到是谁在调用IMEI,还可以有证据证明他们在调用,可以愉快的把图发给他们(三方SDK),让他们整改了。

一通操作,可以发现,有很多处地方在频繁获取系统服务,比如phone,网络等。可以针对这种问题,给SDK提出整改意见,进行cache处理,优化执行效率。

举一反三,我们可以Hook系统的函数,修改系统参数,Hook三方App,修改程序逻辑。仅限学习研究!

备注:
Xposed学习链接
Xposed编写
Xposed编写
免重启Xposed原理解析
java动态代理学习链接
维术大佬系列文章
开箱即用的Xposed开发环境
http://gitlab.inner.yuewen.local/zhanghao.c/XposedHook.git