前面发过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);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!