0x00 前言
最近学习安卓逆向,刚好发现有个小时候玩过的页游,现在最近一段时间出了手游,拿来练练手也挺好。写的不是很好,请各位看官看个热闹就好。
0x01 使用工具
-
Fiddler 任意版本 、jadx、IDA
-
雷电模拟器
-
代理工具 Postern
-
任意一款编程工具
0x02 教程内容
0x20 首先配置好 Fiddler 这里就不赘述了,论坛里有很多相关教程,模拟器里设置好VPN
0x21 打开游戏,随便登录个账号密码抓个登录包。
可以发现返回内容是没经过加密,然后需要的参数 这里就列举几个比较重要的参数
表单名称 |
值 |
说明 |
channelNo |
110001 |
登录渠道,如IOS渠道为 210009 |
key |
1881c326106056afee3da488560d75b9 |
经过 username与pwd计算得出的md5 |
username |
123123123 |
游戏账号 |
userpwd |
13038c34b00c2645f1cf4e5f087c1681 |
密码 DES_ECB |
versionName |
0.13.21082101S |
安装包的版本号 |
返回关键信息
表单名称 |
值 |
说明 |
sid |
eaps7chp |
游戏ID |
token |
5779c00f1e0a8cd6393b66f6f8930b0c |
登录令牌 |
登录成功后 二次令牌登录游戏
提交格式为 JSON 需要的关键数据为 sid 和 token
提交地址已匿。可以通过自行复现找到,接下来可以分析dex了。
0x22 寻找加密过程
将APK丢入 Apktool Box 先查个壳
然后丢入 jadx 直接载入apk 这里采用最简单的方式,全局搜索提交地址的部分信息 < mobile!sdkLogin.action > 试着找出关键登录函数
继续跟进搜索 xxx_LOGIN 被谁调用了
可以看到有个调用函数 getLoginUrl 提交的时候肯定也有地方引用了这个函数
可以看到 他有个 XXXSDKUser
直接在这里面找就大概率能找到登录封装过程了。
这里有SDK登录函数,然后我们仔细分析
public void doSdkLogin(String str, String str2) {
UserBean userBean = new UserBean();// new一个 用户数据
userBean.setUsername(str.trim());//可以看出 str 为用户名 去掉字符串前面和后面的空格.
str = DesUtil.encrypt(str2.trim());// 将密码进行加密 可以深入查一下
if (TextUtils.isEmpty(str) != null) {
loginFailNotify(String.valueOf(-3), ResUtil.getString(this.mActivity, "lt_encrypt_fail_msg"), false);
return;
}
userBean.setUserpwd(str);//设置密码 也可以看出 str2 为明文密码
doSdkLogin(userBean, false);
}
public void doSdkLogin(final UserBean userBean, final boolean z) {
if (SdkConfigManager.getInstanse().getLoginUrl() == null) {
this.mHandler.postDelayed(new Runnable() {
public void run() {
LeitingSdkUser.this.doSdkLogin(userBean, z);
}
}, 1000);
} else {
handleLogin(setUserBean(userBean, z), z, 0);//先设置其他用户信息后再进行发送登录
}
}
public UserBean setUserBean(UserBean userBean, boolean z) {
userBean.setGame(SdkConfigManager.getInstanse().getGame());//设置游戏名
userBean.setChannelNo(SdkConfigManager.getInstanse().getChannelNo());//登录渠道
userBean.setVersionCode(ApkUtil.getVersionCode(this.mActivity));//设置版本编号
userBean.setVersionName(ApkUtil.getVersionName(this.mActivity));//设置版本名
String str = "1";
userBean.setCheckAuth(str);//检查验证
userBean.setOs(str);//
userBean.setMmid(LeitingUserManager.getInstance().getMimiId(this.mActivity, userBean.getUsername()));//设置米米号
if (z) {//是否开启验证
userBean.setKey(CookieUtil.encryptMobileCookie(userBean.getSid(), BaseConstantUtil.G_1));//设置Key
z = new StringBuilder();
z.append(SdkConfigManager.getInstanse().getLoginUrl());//添加登录地址
z.append(SdkConfigManager.getInstanse().getUrlApi(LeitingConstant.CHECK_LOGIN_API));//添加检验API
userBean.setUrl(z.toString());//设置提交网址
} else {
z = new StringBuilder();
z.append(SdkConfigManager.getInstanse().getLoginUrl());
z.append(SdkConfigManager.getInstanse().getUrlApi(LeitingConstant.LOGIN_API));//添加登录API
userBean.setUrl(z.toString());//设置提交网址
userBean.setSerial(PhoneUtil.getUniqueSerial(this.mActivity));//设置 设备唯一序列号
z = new StringBuilder();
z.append(userBean.getUsername());//添加账号
str = "|";
z.append(str);//添加分隔符
z.append(userBean.getUserpwd());//添加密码 此时的密码是密文
z.append(str);//添加分隔符
z.append(userBean.getGame());//添加游戏名
userBean.setKey(CookieUtil.encryptMsgMd5(z.toString(), BaseConstantUtil.G_1));//设置Key 计算MD5
}
return userBean;
}
搜索 encryptMsgMd5 找到对应加密函数 可读性还是挺高的
public static java.lang.String encryptMsgMd5(java.lang.String r4, java.lang.String r5) {
/* JADX: method processing error */
/*
Error: java.lang.NullPointerException
at jadx.core.dex.visitors.regions.ProcessTryCatchRegions.searchTryCatchDominators(ProcessTryCatchRegions.java:75)
at jadx.core.dex.visitors.regions.ProcessTryCatchRegions.process(ProcessTryCatchRegions.java:45)
at jadx.core.dex.visitors.regions.RegionMakerVisitor.postProcessRegions(RegionMakerVisitor.java:63)
at jadx.core.dex.visitors.regions.RegionMakerVisitor.visit(RegionMakerVisitor.java:58)
at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:17)
at jadx.core.ProcessClass.process(ProcessClass.java:34)
at jadx.core.ProcessClass.processDependencies(ProcessClass.java:56)
at jadx.core.ProcessClass.process(ProcessClass.java:39)
at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:323)
at jadx.api.JavaClass.decompile(JavaClass.java:62)
*/
r0 = "%";
r1 = "";
r2 = new java.lang.StringBuffer; Catch:{ Exception -> 0x0033 }
r2.<init>(r1); Catch:{ Exception -> 0x0033 }
r3 = "("; Catch:{ Exception -> 0x0033 } // 先加入一个括号
r2.append(r3); Catch:{ Exception -> 0x0033 } // 先加入一个括号
r2.append(r4); Catch:{ Exception -> 0x0033 }// 加入 r4
r2.append(r0); Catch:{ Exception -> 0x0033 } // 加入 %
r2.append(r5); Catch:{ Exception -> 0x0033 } // 加入 r5
r2.append(r0); Catch:{ Exception -> 0x0033 } // 加入%
r4 = new java.util.Date; Catch:{ Exception -> 0x0033 }
r4.<init>(); Catch:{ Exception -> 0x0033 }
r4 = com.leiting.sdk.util.DateUtil.getDateStrCompact(r4); Catch:{ Exception -> 0x0033 }// 取时间 SimpleDateFormat("yyyyMMdd", Locale.CHINA).format(date);
r2.append(r4); Catch:{ Exception -> 0x0033 }//加入时间 格式为 yyyyMMdd
r4 = ")"; Catch:{ Exception -> 0x0033 }
r2.append(r4); Catch:{ Exception -> 0x0033 }//加入 )
r4 = r2.toString(); Catch:{ Exception -> 0x0033 }
r1 = com.leiting.sdk.util.MD5Util.getMd5(r4); Catch:{ Exception -> 0x0033 } // 取一下MD5
L_0x0033:
return r1;
throw new UnsupportedOperationException("Method not decompiled: com.leiting.sdk.util.CookieUtil.encryptMsgMd5(java.lang.String, java.lang.String):java.lang.String");
}
搜索 DesUtil.encrypt
public static String encrypt(String str) {
try {
return SocketHelper.m5514E(str);
} catch (String str2) {
str2.printStackTrace();
return null;
}
}
继续跟进 SocketHelper.m5514E
发现新版的apk已经将加密函数放到另外加载SocketHelper库文件里 那我们在lib/armeabi-v7a里找到libSocketHelper.so 拖入 IDA里
这里说明了 Key 为 leiting 然后跟进去就会发现
int __fastcall DesByJ::encodeAndHexToByte(int *a1, int a2, const char *a3)
{
int v3; // r5
const char *v4; // r10
int *v5; // r4
const char *v6; // r0
const char *v7; // r5
size_t v8; // r0
int v9; // r8
size_t v10; // r0
int v11; // r11
size_t v12; // r0
int v13; // r5
size_t v14; // r0
int v15; // r0
int v16; // r9
int v17; // r6
int v18; // r5
int v19; // r0
int v20; // r10
int v21; // r0
int v22; // r0
int v23; // r1
int v24; // r5
int v25; // r0
const char *v26; // r2
int v27; // r5
int v28; // r0
int v29; // r5
int v30; // r9
int v32; // r0
int v33; // r8
int v34; // [sp+4h] [bp-24h]
int v35; // [sp+8h] [bp-20h]
v3 = a2;
v4 = a3;
v5 = a1;
if ( a2 && (*(int (**)(void))(*a1 + 656))() >= 1 )
{
v6 = (const char *)(*(int (__fastcall **)(int *, int, _DWORD))(*v5 + 676))(v5, v3, 0);
v7 = v6;
v8 = strlen(v6);
v9 = (*(int (__fastcall **)(int *, size_t))(*v5 + 704))(v5, v8);
v10 = strlen(v7);
(*(void (__fastcall **)(int *, int, _DWORD, size_t, const char *))(*v5 + 832))(v5, v9, 0, v10, v7);
v11 = (*(int (__fastcall **)(int *, const char *))(*v5 + 24))(v5, "com/leiting/sdk/SocketHelper");
v12 = strlen(v4);
v13 = (*(int (__fastcall **)(int *, size_t))(*v5 + 704))(v5, v12);
v14 = strlen(v4);
(*(void (__fastcall **)(int *, int, _DWORD, size_t, const char *))(*v5 + 832))(v5, v13, 0, v14, v4);
v15 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*v5 + 452))(
v5,
v11,
"getKey",
"([B)Ljava/security/Key;");//生成加密Key
v34 = v13;
v16 = _JNIEnv::CallStaticObjectMethod(v5, v11, v15, v13);
v17 = (*(int (__fastcall **)(int *, const char *))(*v5 + 24))(v5, "javax/crypto/Cipher");
v18 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*v5 + 452))(
v5,
v17,
"getInstance",
"(Ljava/lang/String;)Ljavax/crypto/Cipher;");//初始化 Cipher
v19 = (*(int (__fastcall **)(int *, const char *))(*v5 + 668))(v5, "DES/ECB/PKCS5Padding");//使用DES_ECB算法
v20 = _JNIEnv::CallStaticObjectMethod(v5, v17, v18, v19);
v21 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*v5 + 132))(
v5,
v17,
"init",
"(ILjava/security/Key;)V");
v35 = v16;
_JNIEnv::CallVoidMethod(v5, v20, v21, 1, v16);
v22 = (*(int (__fastcall **)(int *))(*v5 + 60))(v5);
v23 = *v5;
if ( v22 )
{
(*(void (__fastcall **)(int *))(v23 + 64))(v5);
(*(void (__fastcall **)(int *))(*v5 + 68))(v5);
v24 = (*(int (__fastcall **)(int *, const char *))(*v5 + 24))(v5, "java/lang/Exception");
v25 = *v5;
v26 = "Des init fail!!";
}
else
{
v28 = (*(int (__fastcall **)(int *, int, const char *, const char *))(v23 + 132))(v5, v17, "doFinal", "([B)[B");
v29 = _JNIEnv::CallObjectMethod(v5, v20, v28, v9);
if ( !(*(int (__fastcall **)(int *))(*v5 + 60))(v5) )
{
v30 = v34;
if ( v29 )
{
v32 = (*(int (__fastcall **)(int *, int, const char *, const char *))(*v5 + 452))(
v5,
v11,
"byteArr2HexStr",
"([B)Ljava/lang/String;");//封装成十六进制字符串
v27 = _JNIEnv::CallStaticObjectMethod(v5, v11, v32, v29);
if ( (*(int (__fastcall **)(int *))(*v5 + 60))(v5) )
{
(*(void (__fastcall **)(int *))(*v5 + 64))(v5);
(*(void (__fastcall **)(int *))(*v5 + 68))(v5);
v33 = (*(int (__fastcall **)(int *, const char *))(*v5 + 24))(v5, "java/lang/Exception");
(*(void (__fastcall **)(int *, int, const char *))(*v5 + 56))(v5, v33, "Encode byteArr2HexStr fail !!");
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v33);
}
}
else
{
v27 = 0;
}
goto LABEL_9;
}
(*(void (__fastcall **)(int *))(*v5 + 64))(v5);
(*(void (__fastcall **)(int *))(*v5 + 68))(v5);
v24 = (*(int (__fastcall **)(int *, const char *))(*v5 + 24))(v5, "java/lang/Exception");
v25 = *v5;
v26 = "please check input argument, last block incomplete in decryption";
}
(*(void (__fastcall **)(int *, int, const char *))(v25 + 56))(v5, v24, v26);
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v24);
v27 = 0;
v30 = v34;
LABEL_9:
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v17);
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v11);
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v30);
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v35);
(*(void (__fastcall **)(int *, int))(*v5 + 92))(v5, v20);
return v27;
}
return 0;
}
以上的伪代码 还是比较好分析的,既然知道了加密方法 就可以写程序跑一下了。
0x03 封装程序
使用易语言 简单又方便
0x04 写在最后
本篇文章仅供学习交流,提供一个学习思路。致谢所有为逆向工作做出贡献的所有大佬。
转载请注明来处。