APP开发中常须要获取设备的DeviceId,以应对刷单,目前经常使用的几个设备识别码主要有IMEI(国际移动设备身份码 International Mobile Equipment Identity)或者MEID(Mobile Equipment IDentifier),这二者也是常说的DeviceId,不过Android6.0以后须要权限才能获取,并且,在Java层这个ID很容易被Hook,可能并不靠谱,另外也能够经过MAC地址或者蓝牙地址,序列号等,暂列以下:android
以上四个是经常使用的Android识别码,系统也提供了详情的接口让开发者获取,可是因为都是Java层方法,很容易被Hook,尤为是有些专门刷单的,在手机Root以后,利用Xposed框架里的一些插件很容易将获取的数据给篡改。举个最简单的IMEI的获取,经常使用的获取方式以下:git
TelephonyManager telephonyManager = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); return telephonyManager.getDeviceId()
假如Root用户利用Xposed Hook了TelephonyManager类的getDeviceId()方法,以下,在afterHookedMethod方法中,将DeviceId设置为随机数,这样每次获取的DeviceId都是不一样的。github
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) { } }
因此为了获取相对准确的设备信息咱们须要采起相应的应对措施,好比:web
首先看一下看一下如何获取getDeviceId,源码以下app
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的几乎不须要什么参数(低版本可能须要)。具体作法是:框架
具体实现以下,这种作法能够应对代理方的Hook。ide
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; }
有不少系统参数咱们是经过Build来获取的,好比序列号、手机硬件信息等,例如获取序列号,在Java层直接利用Build的feild获取便可svg
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,ui
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。
做者:看书的小蜗牛
原文连接获取Android设备DeviceId与反Xposed Hook