某网课平台sign签名校验破解过程

本文章仅为技术交流,禁止非法用途

这个软件比较奇葩,所有的post包都是通过同一个链接,各种参数就在同一个表单里面,里面还有一个sign签名校验

这个是获取学校目录的数据包

POST http://xxxxxxxxxxxxx/aliothprovider/router HTTP/1.1
Host: xueqiplus.chinaedu.net
Connection: keep-alive
Content-Length: 173
Origin: http://xueqiplus.chinaedu.net
tenant-type:
special-key: 1
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MuMu Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.70 Mobile Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept: application/json, text/plain, */*
user-token:
Referer: http://xxxxxxxxxxxxxxxx/alioth/index2.28.7.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,en-US;q=0.9
Cookie: _pk_testcookie..b305=1; _pk_ses..b305=1; _pk_testcookie.450.b305=1; _pk_ses.450.b305=1; _pk_testcookie.355.b305=1; _pk_id.355.b305=5ef46af8153c11cb.1629171813.1.1629171816.1629171813.; _pk_ses.355.b305=1; _pk_id.450.b305=7310116000e197c1.1629171227.1.1629171816.1629171227.
X-Requested-With: net.chinaedu.alioth

appKey=00000001&format=json&v=1.0&timestamp=1629171826.633&ns=&service=alioth.login.customer.listValid&deviceCode=2&tenantCode=&sign=AA59994AF10B7279E57DD9212C8DC51DC55600C7  这里就是sign加密位置

分析:

抓了好几个数据包里面都包含"timestamp","ns","service","appKey","format"字段,并且这个app的所有sign肯定是同一个算法

于是可以通过对app客户端进行反编译,搜索以上几个字串符关键字,找到为sign字段赋值的位置

实践:

软件是有360加固的,先对软件进行脱壳,脱出dex文件。


.method public static getCommonRequestParams(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)Ljava/util/Map;
.registers 8
.annotation system Ldalvik/annotation/Signature;
value = {
"(",
"Ljava/lang/String;",
"Ljava/lang/String;",
"Ljava/util/Map<",
"Ljava/lang/String;",
"Ljava/lang/String;",
">;",
"Ljava/util/List<",
"Ljava/lang/String;",
">;)",
"Ljava/util/Map<",
"Ljava/lang/String;",
"Ljava/lang/String;",
">;"
}
.end annotation
.line 876
new-instance v0, Ljava/util/HashMap;
invoke-direct {v0}, Ljava/util/HashMap;-><init>()V
if-eqz p2, :cond_2f
.line 877
invoke-interface {p2}, Ljava/util/Map;->isEmpty()Z
move-result v1
if-nez v1, :cond_2f
.line 878
invoke-interface {p2}, Ljava/util/Map;->keySet()Ljava/util/Set;
move-result-object v1
invoke-interface {v1}, Ljava/util/Set;->iterator()Ljava/util/Iterator;
move-result-object v1
.line 879
:goto_15
invoke-interface {v1}, Ljava/util/Iterator;->hasNext()Z
move-result v2
if-eqz v2, :cond_2f
.line 880
invoke-interface {v1}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v2
check-cast v2, Ljava/lang/String;
.line 881
invoke-interface {p2, v2}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v3
check-cast v3, Ljava/lang/String;
if-nez v3, :cond_2b
const-string v3, ""
.line 882
:cond_2b
invoke-interface {v0, v2, v3}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
goto :goto_15
.line 886
:cond_2f
sget-object p2, Lnet/chinaedu/alioth/global/Configs;->APP_KEY:Ljava/lang/String;
const-string v1, "appKey"
invoke-interface {v0, v1, p2}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
const-string p2, "method"
.line 887
invoke-interface {v0, p2, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 888
sget-object p0, Lnet/chinaedu/alioth/global/Configs;->FORMAT:Ljava/lang/String;
const-string p2, "format"
invoke-interface {v0, p2, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
const-string p0, "v"
.line 889
invoke-interface {v0, p0, p1}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 890
invoke-static {}, Ljava/lang/System;->currentTimeMillis()J
move-result-wide p0
const-wide/16 v1, 0x3e8
div-long/2addr p0, v1
invoke-static {p0, p1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String;
move-result-object p0
const-string p1, "timestamp"
invoke-interface {v0, p1, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 891
invoke-static {}, Lnet/chinaedu/alioth/tenantmanager/TenantManager;->getInstance()Lnet/chinaedu/alioth/tenantmanager/TenantManager;
move-result-object p0
invoke-virtual {p0}, Lnet/chinaedu/alioth/tenantmanager/TenantManager;->getCurrentTenantCode()Ljava/lang/String;
move-result-object p0
const-string p1, "tenantCode"
invoke-interface {v0, p1, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 892
invoke-static {}, Lnet/chinaedu/alioth/global/AliothApplication;->getInstance()Lnet/chinaedu/alioth/global/AliothApplication;
move-result-object p0
invoke-static {p0}, Lnet/chinaedu/lib/utils/AndroidUtils;->getDeviceId(Landroid/content/Context;)Ljava/lang/String;
move-result-object p0
const-string p1, "deviceId"
invoke-interface {v0, p1, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 894
sget-object p0, Lnet/chinaedu/alioth/global/Configs;->SECRET_KEY:Ljava/lang/String;
invoke-static {v0, p3, p0}, Lnet/chinaedu/alioth/utils/SignUtils;->sign(Ljava/util/Map;Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;
move-result-object p0
const-string p1, "sign"
invoke-interface {v0, p1, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
return-object v0
.end method

通过这些smali代码,可以发现,以上我们确定的关键词都有。


.line 894
sget-object p0, Lnet/chinaedu/alioth/global/Configs;->SECRET_KEY:Ljava/lang/String;
invoke-static {v0, p3, p0}, Lnet/chinaedu/alioth/utils/SignUtils;->sign(Ljava/util/Map;Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;
#↑调用sign()函数计算出sign的值  将结果在下面一行赋值给p0
#这里需要注意的有一点,传给sign函数里面的参数有一个SECRET_KEY,可以想到这个就是加密的密钥
move-result-object p0
const-string p1, "sign"
invoke-interface {v0, p1, p0}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#↑将sign字段和计算出的sign值存放入MAP里面
return-object v0

上面看到了传参里面有SECRET_KEY的值,现在再搜索一下这个常量的值

得到SECRET_KEY的值为“52c203760cf28798a44f6ac4”

下面开始分析sign()函数

smali代码如下:

.method public static sign(Ljava/util/Map;Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;
    .registers 6
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/util/Map<", "Ljava/lang/String;", "Ljava/lang/String;", ">;",
            "Ljava/util/List<", "Ljava/lang/String;", ">;",
            "Ljava/lang/String;",
            ")",
            "Ljava/lang/String;"
        }
    .end annotation
    .line 31
    :try_start_0
    new-instance v0, Ljava/lang/StringBuilder;
    invoke-direct {v0}, Ljava/lang/StringBuilder;->()V
    .line 32
    new-instance v1, Ljava/util/ArrayList;
    invoke-interface {p0}, Ljava/util/Map;->size()I
    move-result v2
    invoke-direct {v1, v2}, Ljava/util/ArrayList;->(I)V
    .line 33
    invoke-interface {p0}, Ljava/util/Map;->keySet()Ljava/util/Set;
    move-result-object v2
    invoke-interface {v1, v2}, Ljava/util/List;->addAll(Ljava/util/Collection;)Z
    if-eqz p1, :cond_31
    .line 34
    invoke-interface {p1}, Ljava/util/List;->size()I
    move-result v2
    if-lez v2, :cond_31
    .line 35
    invoke-interface {p1}, Ljava/util/List;->iterator()Ljava/util/Iterator;
    move-result-object p1
    :goto_21
    invoke-interface {p1}, Ljava/util/Iterator;->hasNext()Z
    move-result v2
    if-eqz v2, :cond_31
    invoke-interface {p1}, Ljava/util/Iterator;->next()Ljava/lang/Object;
    move-result-object v2
    check-cast v2, Ljava/lang/String;
    .line 36
    invoke-interface {v1, v2}, Ljava/util/List;->remove(Ljava/lang/Object;)Z
    goto :goto_21
    .line 39
    :cond_31
    invoke-static {v1}, Ljava/util/Collections;->sort(Ljava/util/List;)V
    .line 40
    invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    .line 41
    invoke-interface {v1}, Ljava/util/List;->iterator()Ljava/util/Iterator;
    move-result-object p1
    :goto_3b
    invoke-interface {p1}, Ljava/util/Iterator;->hasNext()Z
    move-result v1
    if-eqz v1, :cond_54
    invoke-interface {p1}, Ljava/util/Iterator;->next()Ljava/lang/Object;
    move-result-object v1
    check-cast v1, Ljava/lang/String;
    .line 42
    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    invoke-interface {p0, v1}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;
    move-result-object v1
    check-cast v1, Ljava/lang/String;
    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    goto :goto_3b
    .line 44
    :cond_54
    invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    .line 45
    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object p0
    invoke-static {p0}, Lnet/chinaedu/alioth/utils/SignUtils;->getSHA1Digest(Ljava/lang/String;)[B
    move-result-object p0
    .line 46
    invoke-static {p0}, Lnet/chinaedu/alioth/utils/SignUtils;->byte2hex([B)Ljava/lang/String;
    move-result-object p0
    :try_end_63
    .catch Ljava/io/IOException; {:try_start_0 .. :try_end_63} :catch_64
    return-object p0
    :catch_64
    move-exception p0
    .line 48
    new-instance p1, Ljava/lang/RuntimeException;
    invoke-direct {p1, p0}, Ljava/lang/RuntimeException;->(Ljava/lang/Throwable;)V
    throw p1
.end method

通过smali代码不好看出来里面的逻辑下面钻换成Java代码:

    public static String sign(Map<String, String> paramMap, List paramList, String paramString) {
        try {
            StringBuilder stringBuilder = new StringBuilder();
            this();
            ArrayList arrayList = new ArrayList();
            this(paramMap.size());
            arrayList.addAll(paramMap.keySet());
            if (paramList != null && paramList.size() > 0) {
                Iterator iterator = paramList.iterator();
                while (iterator.hasNext())
                    arrayList.remove(iterator.next()); 
            } 
            Collections.sort(arrayList);
            stringBuilder.append(paramString);
            for (String str : arrayList) {
                stringBuilder.append(str);
                stringBuilder.append(paramMap.get(str));
            } 
            stringBuilder.append(paramString);
            return byte2hex(getSHA1Digest(stringBuilder.toString()));
        } catch (IOException iOException) {
            throw new RuntimeException(iOException);
        } 
    }

通过对代码的逻辑进行分析,可以看出来计算sign的过程是:

1.将map的key进行升序排序

2.然后用一个文本将排序后的key和velue进行储存

3.在该文本前后加上SECRET_KEY的值

4.对该文本取sha1值,得到sign

最后再对该算法进行一下测试,就以文章开头的这个数据的sign进行测试

计算sign值完全相同,sign计算完成~~~~

本文章仅为个人调试过程记录,仅供学习参考使用,禁止非法用途,未经许可禁止转载

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注