Android 唯一设备ID与Xposed Hook

简介: Android 唯一设备ID与Xposed Hook

APP开发中常需要获取设备的DeviceId,以应对刷单,目前常用的几个设备识别码主要有IMEI(国际移动设备身份码 International Mobile Equipment Identity)或者MEID(Mobile Equipment IDentifier),这两者也是常说的DeviceId,不过Android6.0之后需要权限才能获取,而且,在Java层这个ID很容易被Hook,可能并不靠谱,另外也可以通过MAC地址或者蓝牙地址,序列号等,暂列如下:


  • IMEI : (International Mobile Equipment Identity) 或者MEID :( Mobile Equipment IDentifier )
  • MAC 或者蓝牙地址
  • Serial Number(需要重新刷flash才能更新)
  • AndroidId ANDROID_ID是设备第一次启动时产生和存储的64bit的一个数,手机升级,或者被wipe后该数重置


以上四个是常用的Android识别码,系统也提供了详情的接口让开发者获取,但是由于都是Java层方法,很容易被Hook,尤其是有些专门刷单的,在手机Root之后,利用Xposed框架里的一些插件很容易将获取的数据给篡改。举个最简单的IMEI的获取,常用的获取方式如下:

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

假如Root用户利用Xposed Hook了TelephonyManager类的getDeviceId()方法,如下,在afterHookedMethod方法中,将DeviceId设置为随机数,这样每次获取的DeviceId都是不同的。

public class XposedModule implements IXposedHookLoadPackage {
        try {
            findAndHookMethod(TelephonyManager.class.getName(), lpparam.classLoader, "getDeviceId", new XC_MethodHook() {
                            @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            super.afterHookedMethod(param);
                                param.setResult("" + System.currentTimeMillis());
                        }
                    });
        } catch (Exception e1) {
        }catch (Error e) {
        } }

所以为了获取相对准确的设备信息我们需要采取相应的应对措施,比如:


  • 可以采用一些系统隐藏的接口来获取设备信息,隐藏的接口不太容易被篡改,因为可能或导致整个系统运行不正常
  • 可以自己通过Binder通信的方式向服务请求信息,比如IMEI号,就是想Phone服务发送请求获取的,当然如果Phone服务中的Java类被Hook,那么这种方式也是获取不到正确的信息的
  • 可以采用Native方式获取设备信息,这种方式可以有效的避免被Xposed Hook,不过仍然可以被adbi 在本地层Hook。


首先看一下看一下如何获取getDeviceId,源码如下

public String getDeviceId() {
    try {
        return getITelephony().getDeviceId();
    } catch (RemoteException ex) {
        return null;
    } catch (NullPointerException ex) {
        return null;
    }
}
private ITelephony getITelephony() {
    return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}

如果getDeviceId被Hook但是 getITelephony没被Hook,我们就可以直接通过反射获取TelephonyManager的getITelephony方法,进一步通过ITelephony的getDeviceId获取DeviceId,不过这个方法跟ROM版本有关系,比较早的版本压根没有getITelephony方法,早期可能通过IPhoneSubInfo的getDeviceId来获取,不过以上两种方式都很容被Hook,既然可以Hook getDeviceId方法,同理也可以Hook getITelephony方法,这个层次的反Hook并没有多大意义。因此,可以稍微深入一下。ITelephony.Stub.asInterface,这是一个很明显的Binder通信的方式,那么不如,我们自己获取Binder代理,进而利用Binder通信的方式向Phone服务发送请求,获取设备DeviceId,Phone服务是利用aidl文件生成的Proxy与Stub,可以基于这个来实现我们的代码,Binder通信比较重要的几点:InterfaceDescriptor+TransactionId+参数,获取DeviceId的几乎不需要什么参数(低版本可能需要)。具体做法是:


  • 直接通过ServiceManager的getService方法获取我们需要的Binder服务代理,这里其实就是phone服务
  • 利用com.android.internal.telephony.ITelephony$Stub的asInterface方法获取Proxy对象
  • 利用反射获取getDeviceId的Transaction id
  • 利用Proxy向Phone服务发送请求,获取DeviceId。


具体实现如下,这种做法可以应对代理方的Hook。

 public static int getTransactionId(Object proxy,
                                        String name) throws RemoteException, NoSuchFieldException, IllegalAccessException {
        int transactionId = 0;
        Class outclass = proxy.getClass().getEnclosingClass();
        Field idField = outclass.getDeclaredField(name);
        idField.setAccessible(true);
        transactionId = (int) idField.get(proxy);
        return transactionId;
    }
//根据方法名,反射获得方法transactionId
public static String getInterfaceDescriptor(Object proxy) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Method getInterfaceDescriptor = proxy.getClass().getDeclaredMethod("getInterfaceDescriptor");
    return (String) getInterfaceDescriptor.invoke(proxy);
}
 static String getDeviceIdLevel2(Context context) {
        String deviceId = "";
        try {
            Class ServiceManager = Class.forName("android.os.ServiceManager");
            Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
            getService.setAccessible(true);
            IBinder binder = (IBinder) getService.invoke(null, Context.TELEPHONY_SERVICE);
            Class Stub = Class.forName("com.android.internal.telephony.ITelephony$Stub");
            Method asInterface = Stub.getDeclaredMethod("asInterface", IBinder.class);
            asInterface.setAccessible(true);
            Object binderProxy = asInterface.invoke(null, binder);
            try {
                Method getDeviceId = binderProxy.getClass().getDeclaredMethod("getDeviceId", String.class);
                if (getDeviceId != null) {
                    deviceId = binderGetHardwareInfo(context.getPackageName(),
                            binder, getInterfaceDescriptor(binderProxy),
                            getTransactionId(binderProxy, "TRANSACTION_getDeviceId"));
                }
            } catch (Exception e) {
            }
            Method getDeviceId = binderProxy.getClass().getDeclaredMethod("getDeviceId");
            if (getDeviceId != null) {
                deviceId = binderGetHardwareInfo("",
                        binder, BinderUtil.getInterfaceDescriptor(binderProxy),
                        BinderUtil.getTransactionId(binderProxy, "TRANSACTION_getDeviceId"));
            }
        } catch (Exception e) {
        }
        return deviceId;
    }
    private static String binderGetHardwareInfo(String callingPackage,
                                                IBinder remote,
                                                String DESCRIPTOR,
                                                int tid) throws RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        String _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if (!TextUtils.isEmpty(callingPackage)) {
                _data.writeString(callingPackage);
            }
            remote.transact(tid, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readString();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }


利用Native方法反Xposed Hook


有很多系统参数我们是通过Build来获取的,比如序列号、手机硬件信息等,例如获取序列号,在Java层直接利用Build的feild获取即可

public static final String SERIAL = getString("ro.serialno");
private static String getString(String property) {
    return SystemProperties.get(property, UNKNOWN);
}

不过SystemProperties的get方法很容被Hook,被Hook之后序列号就可以随便更改,不过好在SystemProperties类是通过native方法来获取硬件信息的,我们可以自己编写native代码来获取硬件参数,这样就避免被Java Hook,

public static String get(String key) {
    if (key.length() > PROP_NAME_MAX) {
        throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
    }
    return native_get(key);
}

来看一下native源码

static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz,
                                      jstring keyJ, jstring defJ)
{
    int len;
    const char* key;
    char buf[PROPERTY_VALUE_MAX];
    jstring rvJ = NULL;
    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        goto error;
    }
    key = env->GetStringUTFChars(keyJ, NULL);
    len = property_get(key, buf, "");
    if ((len <= 0) && (defJ != NULL)) {
        rvJ = defJ;
    } else if (len >= 0) {
        rvJ = env->NewStringUTF(buf);
    } else {
        rvJ = env->NewStringUTF("");
    }
    env->ReleaseStringUTFChars(keyJ, key);
error:
    return rvJ;
}

参考这部分源码,自己实现.so库即可,这样既可以避免被Java层Hook。

目录
相关文章
|
4月前
|
Android开发 iOS开发 UED
探索未来:Android与iOS在智能穿戴设备上的较量
随着科技的飞速进步,智能穿戴设备已经成为我们日常生活中不可或缺的一部分。本文将深入探讨两大操作系统——Android和iOS——在智能穿戴领域的竞争与发展,分析它们各自的优势与挑战,并预测未来的发展趋势。通过比较两者在设计哲学、生态系统、用户体验及创新技术的应用等方面的差异,揭示这场较量对消费者选择和市场格局的影响。 【7月更文挑战第31天】
49 0
|
3月前
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
81 0
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
本文介绍了如何在基于Amlogic T972的Android 9.0系统上使用Platform平台驱动框架和设备树(DTS),实现设备与驱动的分离,并通过静态枚举在设备树中描述设备,自动触发驱动程序的加载和设备创建。
45 0
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
|
3月前
|
Android开发 C语言
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
这篇文章是关于如何在基于Amlogic T972的Android 9.0系统上,通过自动分配设备号和自动创建设备节点文件的方式,开发字符设备驱动程序的教程。
53 0
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
|
3月前
|
自然语言处理 Shell Linux
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
本文是关于在Amlogic安卓9.0平台上创建字符设备驱动的教程,详细介绍了驱动程序的编写、编译、部署和测试过程,并提供了完整的源码和应用层调用示例。
68 0
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
|
3月前
|
传感器 Android开发 芯片
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
本文是系列文章的第三篇,展示了如何在Android系统中利用现有的i2c bus驱动,通过编写设备树节点和应用层的控制代码,实现对基于i2c bus的Slaver设备(如六轴陀螺仪模块QMI8658C)的控制,而无需编写设备驱动代码。
43 0
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
|
3月前
|
Android开发
不写一行代码(二):实现安卓基于PWM的LED设备驱动
本文介绍了在Android系统中不编写任何代码,通过设备树配置和内核支持的通用PWM LED驱动来实现基于PWM的LED设备驱动,并通过测试命令调整LED亮度级别。
42 0
不写一行代码(二):实现安卓基于PWM的LED设备驱动
|
3月前
|
Linux Android开发 C语言
不写一行代码(一):实现安卓基于GPIO的LED设备驱动
本文通过实践操作,展示了在Android系统中不编写任何代码,利用设备树(DTS)配置和内核支持的通用GPIO LED驱动来控制LED设备,并进一步通过C语言编写NDK测试APP来实现LED的闪烁效果。
121 0
不写一行代码(一):实现安卓基于GPIO的LED设备驱动
|
3月前
|
Java 开发工具 Android开发
Android Studio利用Build.gradle导入Git commit ID、Git Branch、User等版本信息
本文介绍了在Android Studio项目中通过修改`build.gradle`脚本来自动获取并添加Git的commit ID、branch名称和用户信息到BuildConfig类中,从而实现在编译时将这些版本信息加入到APK中的方法。
62 0
|
3月前
|
存储 Ubuntu API
如何使用Python创建服务器向Android设备发送GCM推送通知
如何使用Python创建服务器向Android设备发送GCM推送通知
24 0