一种跨进程敏感api调用检测方法

本文介绍一种适合线上使用的跨进程敏感api检测方法,支持动态下发检测拦截策略的方案

敏感api调用分析

观察隐私api列表,简单去看一下获取隐私数据的方式。 发现大部分代码都是aidl生成的,我们通过/system/framework/framework.jar 拉取代码反编译分析。 最终都是调用到IBinder.transact 方法进行跨进程通信,这就催生了一个想法。能不能在binder通信的时候去进行检测是否敏感api调用。

android.content.pm.PackageManager.getInstalledPackages()

android.app.ActivityManager.getRunningAppProcesses()

android.telephony.TelephonyManager.getDeviceId()

紧接着去梳理一下自身客户端进程通信的一个原理,对端进程就先不管了,我们只有监测自己进程的能力。

得到了如下的一个流程图。

获取代码调用的一个堆栈

寻找调用 hook

通过代码分析我们能知道,不同方法的调用可以用token+code 区分 比如这边的 getInstalledPackages方法调用的token 为android.content.pm.IPackageManager code为36,那我们就需要在某个地方拿到这两个参数进行确定是某个api的调用

hook 方法和方式选择

Plt hook很稳定可以在线上使用,我们首先在各个so中plt表中查找是否有可以hook的方法。

下面方法在libbinder.so的plt表中 该方法可以获取到code

arduino 复制代码
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)

下面方法在libandroid_runtime.so的plt表中,该方法可以获取到token

arduino 复制代码
android::Parcel::writeInterfaceToken(char16_t const*, unsigned long)

Android 12之前

rust 复制代码
android::Parcel::writeInterfaceToken(android::String16 const&)

token和code在两个不同方法中 如何去对应起来?

看堆栈调用都会先调用writeInterfaceToken 再调用transact方法 同一个线程中这个顺序肯定是固定的,那么我们就可以在writeInterfaceToken方法中用thread_local存一下token和对应的Parcel, 在transact方法中判断是否是相应的Parcel 然后在拿到对应的token 再去判断token和code是否在我们的敏感api列表中。

hook代码实现

scss 复制代码
void hook_binder() {
    xh_core_register(
            "libandroid_runtime.so",
            "_ZN7android6Parcel19writeInterfaceTokenERKNS_8String16E",
            (void *) Parcel_writeInterfaceToken_proxy,
            (void **) &writeInterfaceToken_orig);
    xh_core_register(
            "libandroid_runtime.so",
            "_ZN7android6Parcel19writeInterfaceTokenEPKDsm",
            (void *) Parcel_writeInterfaceToken_proxy_12,
            (void **) &writeInterfaceToken_orig2);
    xh_core_register(
        "/system/lib64/libbinder.so",
        "_ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j",
        (void *) IPCThreadState_transact_proxy,
        (void **) &IPCThreadState_transact_orig);
xh_core_refresh(0);
xh_core_clear();
    LOGE("hook success");
}
c 复制代码
status_t IPCThreadState_transact_proxy(void *IPCThreadState,
                                       int32_t handle,
                                       uint32_t code,
                                       const void *data,
                                       void *reply,
                                       uint32_t flags) {
    std::string name = "unknown";
    if (parcelToken.parcel == (uintptr_t) data) {
        name = parcelToken.token;
        std::string formart = name + std::to_string(code);
        if (std::find(hook_binder_list.begin(), hook_binder_list.end(), formart) != hook_binder_list.end()) {
            LOGE("binder name %s  %d", name.c_str(), code);
            JNIEnv *env = NULL;
            kJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
            if (env != NULL) {
                jstring jname = env->NewStringUTF(formart.c_str());
                env->CallStaticVoidMethod(kJavaBridgeClass, kBinderTransactPublic, jname, (jint) code);
                env->DeleteLocalRef(jname);
            }
        }

    }
    return ((type_t4) IPCThreadState_transact_orig)(
            IPCThreadState, handle, code, data, reply, flags);

如何 拦截

数据拦截,在上述几个例子方法中我们可以看到数据都是从_reply的parcel对象中读取的。 需要拦截时我们只要不去调用实际的transact方法 直接返回NO_ERROR 。使用小米手机测试拦截前后获取应用安装列表,拦截前应用行为中显示读取了安装列表,拦截后不显示。这种拦截方式或许可以用在特别敏感api调用的紧急拦截。

如何建立调用 api token 以及code的对应关系

GenerateInterfaceDescriptors token 默认情况就是包名+aidl文件名 比如android.content.IClipboard.aidl 的token就是android.content.IClipboard,code也可以在TRANSACTION开头的字段中找到对应关系。 那我们就可以通过反射Stub类去获取相应信息。

获取相应api的token和code,代码如下

ini 复制代码
e static List<String> getBinderCheckList(List<BinderHookEntity> binderHookEntities) {
    ArrayList<String> binderNames = new ArrayList<>();
    for (BinderHookEntity binderHookEntity : binderHookEntities) {
        String aidlName = binderHookEntity.aidlName;
        String aidlMethod = binderHookEntity.aidlMethod;
        try {
            Method metaGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredFields");
            Class<?> aClass = Class.forName(aidlName + "$Stub");
            Field[] fields = (Field[]) metaGetDeclaredField.invoke(aClass);
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers()) && field.getType() == int.class) {
                    field.setAccessible(true);
                    Object value = field.get(null);
                    if (value instanceof Integer) {
                        int v = ((Integer) value).intValue();
                        if (TextUtils.equals("TRANSACTION_" + aidlMethod, field.getName())) {
                            binderNames.add(aidlName + v);
                        }
                    }

                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return binderNames;

}

public static class BinderHookEntity {
    //token 
    String aidlName;
    //method name 
    String aidlMethod;
    boolean reject = false;
}

配置下发检测封装,我们只需下发BinderHookEntity对应的数据。下面是调用例子

csharp 复制代码
ArrayList<PrivacyBinderHookHelper.BinderHookEntity> binderHookEntities = new ArrayList<>();
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.content.pm.IPackageManager", "getInstalledPackages", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.content.pm.IPackageManager", "getInstalledApplications", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.content.pm.IPackageManager", "queryIntentActivities", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.app.IActivityManager", "getRunningAppProcesses", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("com.android.internal.telephony.ITelephony", "getDeviceIdWithFeature", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("com.android.internal.telephony.ITelephony", "getImeiForSlot", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("com.android.internal.telephony.ITelephony", "getMeidForSlot", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("com.android.internal.telephony.IPhoneSubInfo", "getSubscriberIdForSubscriber", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("com.android.internal.telephony.IPhoneSubInfo", "getIccSerialNumberForSubscriber", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.content.IClipboard", "getPrimaryClip", false));
binderHookEntities.add(new PrivacyBinderHookHelper.BinderHookEntity("android.location.ILocationManager", "getLastLocation", false));
PrivacyBinderHookHelper.binderHook(binderHookEntities,
        (name, code) -> Timber.tag("mobcaa_binder").e(new Throwable(), "%s %d", name, code));

总结

方案优势

  1. 可线上使用,可动态拦截。
  2. 相对xposed,frida的单设备检测方案,该方案不依赖设备环境检测,能检测到更底层的调用,大幅度提高对抗难度。
  3. 相对java层的动态代理检测,该方案支持动态下发检测api,并且实现在libbinder.so层依然能检测到上层自己构建parcel调用binder的通信的情况。

不足

  1. 只能支持检测跨进程敏感api的调用 不通过binder的api调用无法检测。
相关推荐
B.-1 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克2 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
明月与玄武2 小时前
关于性能测试:数据库的 SQL 性能优化实战
数据库·sql·性能优化
大耳猫7 小时前
主动测量View的宽高
android·ui
帅次9 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
_乐无10 小时前
Unity 性能优化方案
unity·性能优化·游戏引擎
枯骨成佛10 小时前
Android中Crash Debug技巧
android
kim565915 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼16 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ16 小时前
Android Studio使用c++编写
android·c++