文章结构: 按照分析时的可疑类为单位划分
DeviceToken
调用链
-
PushAgentManager.initialize()
: 创建了一个线程, 运行了PushAgentManager.initialize.1(this)
-
PushAgentManager.initialize.1(this)
运行run()
, 其中PushAgentManager本身被传入到线程中, 又获取了manager
的mPushAgent
数据域
com.tanma.unirun.network.settings.MyInterceptor
包拦截器
大致逻辑:
-
APPKEY
: 是固定的 -
sign
: 根据APPKEY(常量), APPSECRET(常量), item(未分析), paramValue(未分析), 经过MD5加密后生成sign
值.
sign获取
抓包
- 同一设备的sign相同
- 不同的设备Pixel和小米的sign不同
分析
主要是sign的生成流程, 但是中间流程反编译出来的结果并不理想, 需要慢慢分析
第一直觉
感觉可以Hook上StringBuilder
的append()
方法, 然后搜索APPKEY("389885588s0648fa"
)在那附近的数据就是我们要找的数据
com.tanma.unirun.ui.activity.login.LoginPresenterImpl (重点类)
思路:
- 一开始的思路是抓包或者是找导入了net包的文件, 但是这种方式效率实在太低
- 所以直接找了activity.login包
- 找到了LoginActivity
- 在里面很多都看不懂...于是又是找导入的文件, 发现了导入Intent, 因为LoginActivity里面的内容实在太少, 所以可以猜到应该使用了Service, Intent等等方式将逻辑放到其他地方了.
- 最后找到了LoginPresenterImpl类(这个presenter提供者最好记住, 后面可以根据名字一眼就看出来了)
LoginActivity调用
在LoginActivity中直接调用了login()静态方法, 所以优先查看login()方法
((LoginPresenter)this.getMPresenter()).login();
方法
login()
分析:
- 看了前面的东西基本都是初始化
- 从
LoginBody body = new LoginBody();
开始, 就是构造登录报文实体体的部分了 - 其中需要注意的是
MD5Digest
这个类是程序自定义的, 怀疑在里面修改了算法, 不一定是标准的md5 (下一阶有分析复现) - 跟进MD5Digest
public void login() {
CheckBox checkBox0 = (CheckBox)((FragmentActivity)this.getContext()).findViewById(id.checkbox);
Intrinsics.checkExpressionValueIsNotNull(checkBox0, "context.checkbox");
if(!checkBox0.isChecked()) {
Toast.makeText(((Context)this.getContext()), "请同意“用户协议”和“隐私政策”", 0).show();
return;
}
EditText AAAccount = (EditText)((FragmentActivity)this.getContext()).findViewById(id.et_loginAccount);
Intrinsics.checkExpressionValueIsNotNull(AAAccount, "context.et_loginAccount");
if(TextUtils.isEmpty(((CharSequence)AAAccount.getText()))) {
TextView textView0 = (TextView)((FragmentActivity)this.getContext()).findViewById(id.tv_alert);
Intrinsics.checkExpressionValueIsNotNull(textView0, "context.tv_alert");
String s = this.accountEmpty;
if(s == null) {
Intrinsics.throwUninitializedPropertyAccessException("accountEmpty");
}
textView0.setText(((CharSequence)s));
((EditText)((FragmentActivity)this.getContext()).findViewById(id.et_loginAccount)).requestFocus();
return;
}
EditText editText1 = (EditText)((FragmentActivity)this.getContext()).findViewById(id.et_password);
Intrinsics.checkExpressionValueIsNotNull(editText1, "context.et_password");
if(TextUtils.isEmpty(((CharSequence)editText1.getText()))) {
TextView textView1 = (TextView)((FragmentActivity)this.getContext()).findViewById(id.tv_alert);
Intrinsics.checkExpressionValueIsNotNull(textView1, "context.tv_alert");
String s1 = this.pwdEmpty;
if(s1 == null) {
Intrinsics.throwUninitializedPropertyAccessException("pwdEmpty");
}
textView1.setText(((CharSequence)s1));
((EditText)((FragmentActivity)this.getContext()).findViewById(id.et_password)).requestFocus();
return;
}
LoginBody body = new LoginBody();
EditText account = (EditText)((FragmentActivity)this.getContext()).findViewById(id.et_loginAccount);
Intrinsics.checkExpressionValueIsNotNull(account, "context.et_loginAccount");
String account_ = account.getText().toString();
if(account_ != null) {
body.setUserPhone(StringsKt.trim(((CharSequence)account_)).toString());
Companion mD5Digest$Companion0 = MD5Digest.Companion;
EditText password = (EditText)((FragmentActivity)this.getContext()).findViewById(id.et_password);
Intrinsics.checkExpressionValueIsNotNull(password, "context.et_password");
String password_ = password.getText().toString();
if(password_ != null) {
body.setPassword(mD5Digest$Companion0.encodeByMD5(StringsKt.trim(((CharSequence)password_)).toString()));
body.setDeviceToken(((String)new PreUtil("sp_name_app").getValue("sp_device_token", Reflection.getOrCreateKotlinClass(String.class), "")));
body.setDeviceType("1");
Context context0 = (Context)this.getContext();
body.setAppVersions(APKVersionUtils.INSTANCE.getVersionName(context0));
body.setMobileType(APKVersionUtils.INSTANCE.getSystemModel());
body.setBrand(APKVersionUtils.INSTANCE.getDeviceBrand());
body.setSysVersions(APKVersionUtils.INSTANCE.getSystemVersion());
this.getModelImpl().login(body);
return;
}
throw new TypeCastException("null cannot be cast to non-null type kotlin.CharSequence");
}
throw new TypeCastException("null cannot be cast to non-null type kotlin.CharSequence");
}
其他资料
CheckBox
复选框控件, 用来选择"请同意“用户协议”和“隐私政策”"
Intrinsics类(内在的)
主要讲的是checkExpressionValueIsNotNull()
静态方法, 在用Kotlin编写的程序, 反编译出来的Java程序会出现该类.
Intrinsices是Kotlin内部的一个类, 包括了如下方法:
-
checkParameterIsNotNull()
检查参数是否为null -
checkExpressionValueIsNotNull()
检查表达式结果是否是null - ...剩下的还没碰到, 就不展开了
checkExpressionValueIsNotNull()
检查表达式结果是否是null
Intrinsics.checkExpressionValueIsNotNull(checkBox0, "context.checkbox");
com.tanma.unirun.utils.MD5Digest
上一节中构造实体体用到的加密类
实现的是标准的md5加密, 没有魔改
方法
encodeByMD5()
public final String encodeByMD5(String inputKey) {
Intrinsics.checkParameterIsNotNull(inputKey, "inputKey");
try {
byte[] input = inputKey.getBytes(Charsets.UTF_8);
Intrinsics.checkExpressionValueIsNotNull(input, "(this as java.lang.String).getBytes(charset)");
MessageDigest messageDigest0 = MessageDigest.getInstance("MD5");
Intrinsics.checkExpressionValueIsNotNull(messageDigest0, "MessageDigest.getInstance(\"MD5\")");
messageDigest0.update(input);
byte[] arr_b1 = messageDigest0.digest();
char[] str = new char[arr_b1.length * 2];
int k = 0; // 经过了另外的加密
for(int v1 = 0; v1 < arr_b1.length; ++v1) {
byte byte0 = arr_b1[v1];
int k = k + 1;
str[k] = MD5Digest.hexDigits[byte0 >>> 4 & 15];
k = k + 1;
str[k] = MD5Digest.hexDigits[((byte)(byte0 & 15))];
}
return new String(str);
}
catch(Exception ex) {
return "";
}
}
算法复现
package com.lamecrow.APK.Unirun.Login;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
public class Login {
public static void main(String[] args) {
String password = "1749571140";
byte[] input = password.getBytes(StandardCharsets.UTF_8);
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest messageDigest0 = MessageDigest.getInstance("MD5");
messageDigest0.update(input);
byte[] midput = messageDigest0.digest();
char[] str = new char[midput.length * 2];
int k0 = 0;
for (int v1 = 0; v1 < midput.length; ++v1) {
byte element = midput[v1];
int k1 = k0 + 1;
str[k0] = hexDigits[element >>> 4 & 15];
k0 = k1 + 1;
str[k1] = hexDigits[element & 15];
}
System.out.println(new String(str));
} catch (Exception e) {
e.printStackTrace();
}
}
}