首页
社区
课程
招聘
[原创]小米APP生成DeviceId算法分析
发表于: 2021-3-7 17:29 20835

[原创]小米APP生成DeviceId算法分析

2021-3-7 17:29
20835

前面发过2篇分析小米APP的文章,分别是 env、envKey、hash算法分析 以及 hashedEnvFactors属性溯源 这次又来拿它开刀啦。

这次是我们发现数据包里都会传一个cookie,里面带有一个属性是deviceId,按通常理解,cookie都是由服务器所产生作为临时保存的缓存机制。 但是经过一番搜索并未发现deviceId是从服务器返回的。但也可能是它返回的是一串其它字符串,然后在app内经过加密处理在发的呢? 这就需要我们去分析了。

这里没有选择通过直接搜索deviceId字符串进行搜索(其实我尝试过了~),因为搜索出来的结果过多,非常难精准定位到该地方,所以我们直接按照之前发布的文章,先重新回到登录触发的接口。

其中,我们发现有2行代码是插入cookie的,因为这个接口也正好是一个封装HTTP数据包的过程。

从字面意思我们可以看到,add(插入)DeviceId(设备ID)In(进)Cookies(小饼干)。。。
所以我们就跟进去看看,

由于它传进来的第二个参数就是deviceId,我们本来进来只是核实它是不是给cookies插入的deviceId,没想到这个方法里面竟然做了如果deviceId为空则重新获取的判断,这样就省了我们去往上溯源的操作了。

所以直接就跟进 getHashedDeviceId 里面

没啥好说的,继续跟进getHashedDeviceIdNoThrow里面。

继续跟进getHashedDeviceId里面。

这里我们看到有好几层判断,我们粗略看了一下,它就是用不同的方式获取deviceId,我们可以看到有获取androidId的,获取OAID的方式。如果其中一个可以获取则直接返回。那岂不是美哉?  随便跟一个就可以了。
我们就跟 String androidID = getAndroidID(this.context);的方式.

我们跟进去看到,就是一个很基础的获取手机android_id操作.获取后我们看到它刚才调用了
String str2 = ANDROID_ID_PREFIX + DeviceIDCloudCoder.getDataMd5Digest(androidID.getBytes());
字符串拼接,加上MD5的方式,我们看下这个字符串里面是啥常量.

我们看到它会在Androidid前面加上an 我估计是方便服务器判断这是一个什么样的deviceId.但是我们抓包看到并没有an也就是我这个deviceId并不是这个方式获取的. 但是理论上讲这样也没错,只是有可能服务器会判定为某个等级的风险级别把..
我们用同样的方式也可以看到OAID的方式获取,前面也会带oa
字符串

但是我们发现一处地方,它没有拼接常量的,估计就是货真价实的deviceId的获取方式.

所以我们就跟进 getRuntimeDeviceIdHashed 里面看看是如何获取的.

我们看到他获取到之后会传给一个hashDeviceInfo方法进行处理,看字面应该是hash某种算法,但我们先继续跟getUserEnvironmentPlainDeviceId方法内看看是如何获取的.

继续跟进

这里我直接给出了跟进去的这个类的所有代码,因为我们看到它的第一个方法就是刚刚跟进他,他的定义是interface(接口),但是正巧他就在底下调用了,所以它的实现就是最下面的那个PlainDeviceIdUtilImplDefault方法.最终得出它获取的是手机的deviceId在进行刚刚我们看到的hash某种算法

得知是这种方式后,我们返回去跟进刚刚看到的算法方法.

我们可以清楚的看到,算法就是进行了SHA1然后在进行BASE64编码,最后得出的deviceId

该算法也是非常简单的,重要的是能够掌握快速的代码定位技巧,也能够节省出不少时间.有时候找出一个参数的使用比找出参数的生成算法还要麻烦..

public static AccountInfo loginByPassword(PasswordLoginParams passwordLoginParams) throws InvalidResponseException, InvalidCredentialException, InvalidUserNameException, NeedVerificationException, NeedCaptchaException, IOException, AccessDeniedException, AuthenticationFailureException, NeedNotificationException {
     if (passwordLoginParams == null || passwordLoginParams.password == null) {
         throw new IllegalArgumentException("password params should not be null");
     }
     String str = passwordLoginParams.userId;
     String str2 = passwordLoginParams.password;
     String str3 = passwordLoginParams.deviceId;
     String str4 = TextUtils.isEmpty(passwordLoginParams.serviceId) ? PASSPORT_SID : passwordLoginParams.serviceId;
     String str5 = passwordLoginParams.captIck;
     String str6 = passwordLoginParams.captCode;
     String[] strArr = passwordLoginParams.hashedEnvFactors;
     boolean z = passwordLoginParams.returnStsUrl;
     boolean z2 = passwordLoginParams.needProcessNotification;
     MetaLoginData metaLoginData = passwordLoginParams.metaLoginData;
     ActivatorPhoneInfo activatorPhoneInfo = passwordLoginParams.activatorPhoneInfo;
     EasyMap easyPut = new EasyMap().easyPutOpt(ProfileRecordUtils.Area.f6514a, str).easyPut(a.e, CloudCoder.getMd5DigestUpperCase(str2)).easyPutOpt("sid", str4).easyPutOpt("captCode", str6).easyPutOpt(d.p, passwordLoginParams.countryCode).easyPut("_json", Constants.SdkSettings.VALUE_TRUE);
     addEnvToParams(easyPut, strArr);
     EasyMap easyPutOpt = new EasyMap().easyPutOpt("ick", str5).easyPutOpt("ticketToken", passwordLoginParams.ticketToken);
     addDeviceIdInCookies(easyPutOpt, str3);
     addAntiSpamIpAddressInCookies(easyPutOpt);
     if (activatorPhoneInfo != null) {
         easyPut.easyPutOpt("userHash", activatorPhoneInfo.phoneHash);
         easyPutOpt.easyPutOpt("activatorToken", activatorPhoneInfo.activatorToken);
     }
     String str7 = URLs.URL_LOGIN_AUTH2_HTTPS;
     PassportRequestArguments passportRequestArguments = new PassportRequestArguments();
     passportRequestArguments.putAllParams(easyPut);
     passportRequestArguments.putAllCookies(easyPutOpt);
     passportRequestArguments.setUrl(str7);
     passportRequestArguments.setReadBody(true);
     PassportLoginRequest.ByPassword byPassword = new PassportLoginRequest.ByPassword(passportRequestArguments, str, str4, metaLoginData);
     try {
         ProtocolLogHelper.newRequestLog(str7, HttpMethod.POST, new String[]{a.e, "ticketToken", "userHash", "activatorToken"}).paramWithMaskOrNull(easyPut).cookieWithMaskOrNull(easyPutOpt).log();
         SimpleRequest.StringContent executeEx = byPassword.executeEx();
         logLoginResponseAllowNull(str7, executeEx);
         if (executeEx == null) {
             throw new IOException("failed to get response from server");
         }
         try {
             return processLoginContent(executeEx, str4, z2, z);
         } catch (PackageNameDeniedException unused) {
             throw new InvalidResponseException("It's not loginByPassToken(), PackageNameDeniedException is unexpected");
         }
     } catch (PassportCAException unused2) {
         throw new IllegalStateException("this should never happen in product environment.Have you set sDisableLoginFallbackForTest to be true? ");
     }
 }
public static AccountInfo loginByPassword(PasswordLoginParams passwordLoginParams) throws InvalidResponseException, InvalidCredentialException, InvalidUserNameException, NeedVerificationException, NeedCaptchaException, IOException, AccessDeniedException, AuthenticationFailureException, NeedNotificationException {
     if (passwordLoginParams == null || passwordLoginParams.password == null) {
         throw new IllegalArgumentException("password params should not be null");
     }
     String str = passwordLoginParams.userId;
     String str2 = passwordLoginParams.password;
     String str3 = passwordLoginParams.deviceId;
     String str4 = TextUtils.isEmpty(passwordLoginParams.serviceId) ? PASSPORT_SID : passwordLoginParams.serviceId;
     String str5 = passwordLoginParams.captIck;
     String str6 = passwordLoginParams.captCode;
     String[] strArr = passwordLoginParams.hashedEnvFactors;
     boolean z = passwordLoginParams.returnStsUrl;
     boolean z2 = passwordLoginParams.needProcessNotification;
     MetaLoginData metaLoginData = passwordLoginParams.metaLoginData;
     ActivatorPhoneInfo activatorPhoneInfo = passwordLoginParams.activatorPhoneInfo;
     EasyMap easyPut = new EasyMap().easyPutOpt(ProfileRecordUtils.Area.f6514a, str).easyPut(a.e, CloudCoder.getMd5DigestUpperCase(str2)).easyPutOpt("sid", str4).easyPutOpt("captCode", str6).easyPutOpt(d.p, passwordLoginParams.countryCode).easyPut("_json", Constants.SdkSettings.VALUE_TRUE);
     addEnvToParams(easyPut, strArr);
     EasyMap easyPutOpt = new EasyMap().easyPutOpt("ick", str5).easyPutOpt("ticketToken", passwordLoginParams.ticketToken);
     addDeviceIdInCookies(easyPutOpt, str3);
     addAntiSpamIpAddressInCookies(easyPutOpt);
     if (activatorPhoneInfo != null) {
         easyPut.easyPutOpt("userHash", activatorPhoneInfo.phoneHash);
         easyPutOpt.easyPutOpt("activatorToken", activatorPhoneInfo.activatorToken);
     }
     String str7 = URLs.URL_LOGIN_AUTH2_HTTPS;
     PassportRequestArguments passportRequestArguments = new PassportRequestArguments();
     passportRequestArguments.putAllParams(easyPut);
     passportRequestArguments.putAllCookies(easyPutOpt);
     passportRequestArguments.setUrl(str7);
     passportRequestArguments.setReadBody(true);
     PassportLoginRequest.ByPassword byPassword = new PassportLoginRequest.ByPassword(passportRequestArguments, str, str4, metaLoginData);
     try {
         ProtocolLogHelper.newRequestLog(str7, HttpMethod.POST, new String[]{a.e, "ticketToken", "userHash", "activatorToken"}).paramWithMaskOrNull(easyPut).cookieWithMaskOrNull(easyPutOpt).log();
         SimpleRequest.StringContent executeEx = byPassword.executeEx();
         logLoginResponseAllowNull(str7, executeEx);
         if (executeEx == null) {
             throw new IOException("failed to get response from server");
         }
         try {
             return processLoginContent(executeEx, str4, z2, z);
         } catch (PackageNameDeniedException unused) {
             throw new InvalidResponseException("It's not loginByPassToken(), PackageNameDeniedException is unexpected");
         }
     } catch (PassportCAException unused2) {
         throw new IllegalStateException("this should never happen in product environment.Have you set sDisableLoginFallbackForTest to be true? ");
     }
 }
addDeviceIdInCookies(easyPutOpt, str3);
addAntiSpamIpAddressInCookies(easyPutOpt);
addDeviceIdInCookies(easyPutOpt, str3);
addAntiSpamIpAddressInCookies(easyPutOpt);
private static void addDeviceIdInCookies(EasyMap<String, String> easyMap, String str) {
      if (easyMap == null) {
          throw new IllegalArgumentException("cookie params should not be null");
      }
      Application applicationContext = XMPassportSettings.getApplicationContext();
      String oaid = OAIDUtil.getOAID(applicationContext);
      if (TextUtils.isEmpty(str)) {
          str = getHashedDeviceId();
      }
      if (applicationContext != null) {
          AssertionUtils.checkCondition(applicationContext, !TextUtils.isEmpty(str), "deviceId cannot be empty", true);
      }
      easyMap.easyPutOpt("deviceId", str).easyPutOpt("pass_o", oaid).easyPutOpt(SimpleRequestForAccount.COOKIE_NAME_USER_SPACE_ID, UserSpaceIdUtil.getNullableUserSpaceIdCookie());
  }
private static void addDeviceIdInCookies(EasyMap<String, String> easyMap, String str) {
      if (easyMap == null) {
          throw new IllegalArgumentException("cookie params should not be null");
      }
      Application applicationContext = XMPassportSettings.getApplicationContext();
      String oaid = OAIDUtil.getOAID(applicationContext);
      if (TextUtils.isEmpty(str)) {
          str = getHashedDeviceId();
      }
      if (applicationContext != null) {
          AssertionUtils.checkCondition(applicationContext, !TextUtils.isEmpty(str), "deviceId cannot be empty", true);
      }
      easyMap.easyPutOpt("deviceId", str).easyPutOpt("pass_o", oaid).easyPutOpt(SimpleRequestForAccount.COOKIE_NAME_USER_SPACE_ID, UserSpaceIdUtil.getNullableUserSpaceIdCookie());
  }
if (TextUtils.isEmpty(str)) {
        str = getHashedDeviceId();
    }
if (TextUtils.isEmpty(str)) {
        str = getHashedDeviceId();
    }
private static String getHashedDeviceId() {
    return new HashedDeviceIdUtil(XMPassportSettings.getApplicationContext()).getHashedDeviceIdNoThrow();
}
private static String getHashedDeviceId() {
    return new HashedDeviceIdUtil(XMPassportSettings.getApplicationContext()).getHashedDeviceIdNoThrow();
}
public synchronized String getHashedDeviceIdNoThrow() {
      return getHashedDeviceId(true);
  }
public synchronized String getHashedDeviceIdNoThrow() {
      return getHashedDeviceId(true);
  }
public synchronized String getHashedDeviceId(boolean z) {
     IUnifiedDeviceIdFetcher unifiedDeviceIdFetcher;
     DeviceIdPolicy policy = policy();
     if (policy == DeviceIdPolicy.RUNTIME_DEVICE_ID_ONLY) {
         return getRuntimeDeviceIdHashed();
     } else if (policy != DeviceIdPolicy.CACHED_THEN_RUNTIME_THEN_PSEUDO) {
         throw new IllegalStateException("unknown policy " + policy);
     } else {
         String loadHistoricalHashedDeviceId = loadHistoricalHashedDeviceId();
         if (!TextUtils.isEmpty(loadHistoricalHashedDeviceId)) {
             return loadHistoricalHashedDeviceId;
         }
         String runtimeDeviceIdHashed = getRuntimeDeviceIdHashed();
         if (runtimeDeviceIdHashed != null) {
             saveHistoricalHashedDeviceId(runtimeDeviceIdHashed);
             return runtimeDeviceIdHashed;
         }
         if (z) {
             if (!isMainThread() && (unifiedDeviceIdFetcher = GlobalConfig.getInstance().getUnifiedDeviceIdFetcher()) != null) {
                 String hashedDeviceId = unifiedDeviceIdFetcher.getHashedDeviceId(this.context);
                 if (!TextUtils.isEmpty(hashedDeviceId)) {
                     saveHistoricalHashedDeviceId(hashedDeviceId);
                     return hashedDeviceId;
                 }
             }
         }
         String oaid = OAIDUtil.getOAID(this.context);
         if (!TextUtils.isEmpty(oaid)) {
             String str = OAID_PREFIX + DeviceIDCloudCoder.getDataMd5Digest(oaid.getBytes());
             saveHistoricalHashedDeviceId(str);
             return str;
         }
         String androidID = getAndroidID(this.context);
         if (!TextUtils.isEmpty(androidID)) {
             String str2 = ANDROID_ID_PREFIX + DeviceIDCloudCoder.getDataMd5Digest(androidID.getBytes());
             saveHistoricalHashedDeviceId(str2);
             return str2;
         }
         String createPseudoDeviceId = createPseudoDeviceId();
         saveHistoricalHashedDeviceId(createPseudoDeviceId);
         return createPseudoDeviceId;
     }
 }
public synchronized String getHashedDeviceId(boolean z) {
     IUnifiedDeviceIdFetcher unifiedDeviceIdFetcher;
     DeviceIdPolicy policy = policy();
     if (policy == DeviceIdPolicy.RUNTIME_DEVICE_ID_ONLY) {
         return getRuntimeDeviceIdHashed();
     } else if (policy != DeviceIdPolicy.CACHED_THEN_RUNTIME_THEN_PSEUDO) {
         throw new IllegalStateException("unknown policy " + policy);
     } else {
         String loadHistoricalHashedDeviceId = loadHistoricalHashedDeviceId();
         if (!TextUtils.isEmpty(loadHistoricalHashedDeviceId)) {
             return loadHistoricalHashedDeviceId;
         }
         String runtimeDeviceIdHashed = getRuntimeDeviceIdHashed();
         if (runtimeDeviceIdHashed != null) {
             saveHistoricalHashedDeviceId(runtimeDeviceIdHashed);
             return runtimeDeviceIdHashed;
         }
         if (z) {
             if (!isMainThread() && (unifiedDeviceIdFetcher = GlobalConfig.getInstance().getUnifiedDeviceIdFetcher()) != null) {
                 String hashedDeviceId = unifiedDeviceIdFetcher.getHashedDeviceId(this.context);
                 if (!TextUtils.isEmpty(hashedDeviceId)) {
                     saveHistoricalHashedDeviceId(hashedDeviceId);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (4)
雪    币: 3862
活跃值: (1504)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
不错,通俗易懂,适合新手
2021-3-8 20:59
0
雪    币: 323
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
大佬牛逼!
啥时候发一个小米有品茅台预约的yp-ss参数的解析呀?
期待大佬更新!!!
2021-5-14 08:46
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
楼主那个能私信一下么 cookie 找了半天调用了很多方法不知道怎么生成的
2021-10-27 00:19
0
游客
登录 | 注册 方可回帖
返回
//