本篇文章仅用于学习交流,切勿用于违法犯罪,如使用者违法相关法律与本文作者无关!
soul 这款app 还是有点意思的,作为上帝视角的程序员来说 怎么能不尝试开启金手指模式。
本篇文章主要是对api-sign算法的分析,反编译步骤请参考网络其他文章。
请求接口:https://api.soulapp.cn/v3/post/praise?postId=294348164
请求头:
api-sign:853F2B036105103D5AA7EB43AF94EDC4A0B88F31
os: android
X-Auth-Token: zVbDmdFooc7Ejn9xJer6puAEm8/xtZ51
api-sign-version: v4
device-id:AAAAAAAAAAAAAA
request-nonce:8051d3f7b4944ea380eb8984890b0d67
Connection: close
app-id: 10000003
app-version: 3.0.17
app-time:1540286088314
host: api.soulapp.cn
User-Agent: [WIFI;PAFM00;Android;27;1080*2340;100021;zh_CN]
请求头有误 会提示如下错误消息:
{
"code": 9000006,
"message": "网络错误,联系官方微信Soulandu解决吧!",
"data": null,
"success": false
}
当前分析版本:cn.soulapp.android_3.0.18_18101801
三个 classes.dex 我们主要用到 classes.dex 和 classes3.dex
classes.dex package cn.soulapp.android.api.b 里 有 方法主要用于构造请求头:
private static void b(final aa$a aa$a, final aa aa) {
final SoulApp b = SoulApp.b();
//当前时间戳 cn.soulapp.android.api.a.a(); 可以忽略
final long n = System.currentTimeMillis() - cn.soulapp.android.api.a.a();
// 随机生产UUID 并删除 UUID中的-
final String replaceAll = UUID.randomUUID().toString().replaceAll("-", "");
//无用 暂理解为 记录日志
j.b("requestNonce" + replaceAll, new Object[0]);
//api-sign 生成算法
aa$a.b("api-sign", cn.soulapp.android.api.b.c.a(aa, replaceAll, n));
//手机系统标识
aa$a.b("os", "android");
final String f = cn.soulapp.android.c.f();
//token
if (!TextUtils.isEmpty((CharSequence)f)) {
aa$a.b("X-Auth-Token", f);
}
//api-sign 维护了一个算法的版本 当前算法版本是v4
aa$a.b("api-sign-version", "v4");
//设备号
aa$a.b("device-id", UTDevice.getUtdid((Context)b));
//UUID
aa$a.b("request-nonce", replaceAll);
aa$a.b("Connection", "close");
aa$a.b("app-id", "10000003");
aa$a.b("app-version", "3.0.18");
// 当前时间戳 ,请求接口会对时间做校验 如果 和当前时间不符 会返回错误
aa$a.b("app-time", String.valueOf(n));
while (true) {
try {
if (!TextUtils.isEmpty((CharSequence)cn.soulapp.android.api.a.a(aa.a().i()))) {
aa$a.b("host", cn.soulapp.android.api.a.a(aa.a().i()));
}
aa$a.b("User-Agent", cn.soulapp.android.c.a("[" + l.b((Context)b) + ";" + Build.MODEL + ";Android;" + Build$VERSION.SDK_INT + ";" + cn.soulapp.lib.basic.utils.u.c() + "*" + cn.soulapp.lib.basic.utils.u.d() + ";" + k.a(h.a((Context)b)) + ";" + Locale.getDefault().toString() + ";" + q.a((Context)b) + "]"));
aa$a.b("app-info", "[" + q.d() + "]");
}
catch (Exception ex) {
continue;
}
break;
}
}
然后 分析 cn.soulapp.android.api.b.c.a(aa, replaceAll, n) 生成 sign的算法 代码在 package cn.soulapp.android.api.b;下
public class c
{ //格式化当前时间戳
private static String a(final long n) {
return e.a(n, "Asia/Shanghai", "yyyyMMddHHmm");
}
public static String a(aa a, final String s, final long n) {
//拼接调用加密算法前的参数
final StringBuilder sb = new StringBuilder();
// a.a().a() 部分 是 package okhttp3; 里的HttpUrl 类 return new URL(this.t); 获取当前请求地址
// if (this.query != null) {this.file = this.path + "?" + this.query; } 有一个方法是这么写的 ,path+?+请求参数
所以这里的Path 理解为接口的这部分:https://api.soulapp.cn/v3/post/praise
sb.append(a.a().a().getPath());
final HashMap
然后说一下 请求参数 处理的部分
int i = 0;
try {
while (i < ((s)a.d()).a()) {
final s s2 = (s)a.d();
final String a2 = s2.a(i);
hashMap.put(a2, s2.c(i));
list.add(a2);
++i;
}
1`(s)a.d()).a() 返回的是当前请求参数 数量
2· final s s2 = (s)a.d(); 返回的是 一个 s 它继承 ab 抽象类
3·s 的代码在 classes3.dex package okhttp3; s.class
这个类里维护了一个 参数列表
this.b = okhttp3.internal.c.a(list);
this.c = okhttp3.internal.c.a(list2);
参数列表的数据是这么来的:
this.a.add(HttpUrl.a(s, " \"':;<=>@[]^`{}|/\\?#&!$(),~", true, false, true, true));
this.b.add(HttpUrl.a(s2, " \"':;<=>@[]^`{}|/\\?#&!$(),~", true, false, true, true));
我们看一下 HttpUrl.a()方法做了什么,代码在classes.dex package okhttp3;
static String a(String s, final int n, final int n2, final String s2, final boolean b, final boolean b2, final boolean b3, final boolean b4) {
int codePoint;
for (int i = n; i < n2; i += Character.charCount(codePoint)) {
codePoint = s.codePointAt(i);
if (codePoint < 32 || codePoint == 127 || (codePoint >= 128 && b4) || s2.indexOf(codePoint) != -1 || (codePoint == 37 && (!b || (b2 && !a(s, i, n2)))) || (codePoint == 43 && b3)) {
final c c = new c();
c.a(s, n, i);
a(c, s, i, n2, s2, b, b2, b3, b4);
s = c.s();
return s;
}
}
s = s.substring(n, n2);
return s;
}
c.a(s, n, i); 方法在 package okio; c.class
由于 这几个类引用的错综复杂 加上混淆的比较严重 关键地方 请求参数的处理 并没有彻底搞清楚。
以上分析 有 其他见解的朋友 可以私信我 或 此帖留言讨论