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。

Github连接 CacheEmulatorChecker

作者:看书的小蜗牛
原文链接获取Android设备DeviceId与反Xposed Hook

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容