本文介绍一种适合线上使用的跨进程敏感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));
总结
方案优势
- 可线上使用,可动态拦截。
- 相对xposed,frida的单设备检测方案,该方案不依赖设备环境检测,能检测到更底层的调用,大幅度提高对抗难度。
- 相对java层的动态代理检测,该方案支持动态下发检测api,并且实现在libbinder.so层依然能检测到上层自己构建parcel调用binder的通信的情况。
不足
- 只能支持检测跨进程敏感api的调用 不通过binder的api调用无法检测。