In APP development, DeviceId of the device is often needed to deal with brushing. Currently, several commonly used device identifiers are mainly IMEI (International Mobile Equipment Identity) or MEID (Mobile Equipment IDentifier). These two are also often said DeviceId, but after Android6.0 need permission to obtain, and, in Java layer this ID is easy to Hook, may not be reliable, in addition to MAC address or bluetooth address, serial number, etc., temporarily listed as follows:

  • IMEI: (International Mobile Equipment Identity) or MEID: (Mobile Equipment IDentifier)
  • MAC address or Bluetooth address
  • Serial Number (need to refresh flash to update)
  • AndroidId ANDROID_ID is the number of bits created and stored when the device is first booted, updated, or reset with a wipe

The above four are commonly used Android identification code, the system also provides the details of the interface for developers to obtain, but because are Java layer method, it is easy to Hook, especially some special brush single, after the mobile phone Root, using some plug-ins in the Xposed framework is easy to get the data to tamper. For example, the simplest method of obtaining IMEI is as follows:

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

If the Root user is using Xposed Hook TelephonyManager class getDeviceId() method, as follows, in afterHookedMethod, DeviceId is set to random number, so that each DeviceId is different.

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) { } }Copy the code

Therefore, in order to obtain relatively accurate equipment information, we need to take corresponding countermeasures, such as:

  • Some hidden interfaces can be used to obtain device information. Hidden interfaces are less likely to be tampered with because they may cause the entire system to run improperly
  • You can request information to the service through Binder communication, such as THE IMEI number, which is obtained by sending a request to Phone service. Of course, if the Java class in Phone service is hooked, the correct information cannot be obtained in this way
  • Can use Native way to obtain device information, this way can effectively avoid being Xposed Hook, but still can be adBI Hook in the local layer.

How to get 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));
}Copy the code

If getDeviceId is hooked but getITelephony is not, we can get TelephonyManager’s getITelephony method directly by reflection. Further, ITelephony’s getDeviceId can be used to obtain DeviceId. However, this method is related to ROM version. Earlier versions don’t have getITelephony method at all, so it can be obtained by IPhoneSubInfo’s getDeviceId. However, both of the above two methods are very suitable for Hook. Since Hook getDeviceId can be used, and Hook getITelephony can be used in the same way, anti-hook at this level does not have much significance. So, you can go a little deeper. ITelephony. Stub. AsInterface, this is an obvious Binder communication way, then, we obtain Binder agent, then using the Binder way communication sends a request to the Phone service DeviceId access equipment, Phone service is a Proxy and Stub generated by AIDL file, which can be used to implement our code. InterfaceDescriptor+TransactionId+ argument, very few arguments are required to get DeviceId (it may be required in earlier versions). The specific approach is:

  • The Binder service proxy we need is obtained directly through the ServiceManager getService method, which is actually the Phone service
  • Using com. Android. Internal. Telephony. ITelephony $Stub asInterface method to obtain the Proxy objects
  • Use reflection to get the Transaction ID of getDeviceId
  • Use Proxy to send a request to the Phone service to obtain DeviceId.

The implementation is as follows. This approach can deal with the Hook of the agent.

 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);
        returntransactionId; } // According to the method name, 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;
    }Copy the code

Using Native method against Xposed Hook

There are a lot of system parameters to obtain through Build, such as serial number, phone hardware information, etc., for example, to obtain serial number, in the Java layer directly using Build feild can be obtained

public static final String SERIAL = getString("ro.serialno");

private static String getString(String property) {
    return SystemProperties.get(property, UNKNOWN);
}Copy the code

The SystemProperties get method can be hooked, and the serial number can be changed at will. However, the SystemProperties class uses native methods to obtain hardware information, so we can write native code to obtain hardware parameters. This way you avoid getting hooked by Java,

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

Take a look at native source code

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;
}Copy the code

Reference this part of the source code, their own implementation. So library can, so can avoid being Java layer Hook.

Making connections CacheEmulatorChecker

Android device DeviceId with anti Xposed Hook