某听书请求签名分析

admin 2022年6月5日11:07:36移动安全评论6 views6359字阅读21分11秒阅读模式

某听书请求签名分析

某听书sc参数

sc参数用于对请求签名验证  

使用的工具

工具名 链接
jadx 用于反编译apk为java代码 https://github.com/skylot/jadx/releases/
逍遥模拟器 (初学安卓逆向先用模拟器,真机更好) https://www.memuplay.com/tw/
Frida 用于hook https://github.com/frida/frida
Charles 或Fiddler 用于抓包

抓包数据

某听书请求签名分析

目标是有声书的播放地址,所以是下面这个请求

GET /yyting/gateway/entityPath.action?entityId=46122241&entityType=3&opType=1§ions=%5B38%5D&type=0&token=HCvrt6XKdUjP1SZKF6GjFg**_5Q-vbHrlEjiKL1oDFkTxMA**&imei=bHJpZGEzRWxwMWU1TUw5M3F4bURCcFlibURnPT0%3D&nwt=1&q=377&mode=0&sc=c0ba56d16a055eeb60c166a32238c1f0 HTTP/1.1User-Agent: Android7.1.2/yyting/OPPO/PCRT00/ch_qutoutiao/214/AndroidHDReferer: yytingting.comAccept-Encoding: gzip,deflate,sdchClientVersion: 6.6.3Host: dapis.mting.infoConnection: Keep-Alive

经过多次抓包测试
entityId 是有声书的id
sections 有声书章节
token 登录的凭证(可以固定)
sc 其他参数变动时变动(后面知道是签名)
其他的可以固定

正向寻找

先使用jadx把apk反编译成java代码
可以尝试搜"sc",内容很多

private HttpUrl.Builder a(tingshu.bubei.a.c cVar, HttpUrl httpUrl) {        HttpUrl build = httpUrl.newBuilder().addQueryParameter(Constants.KEY_IMEI, cVar.c()).addQueryParameter("nwt", cVar.d()).addQueryParameter(WidgetRequestParam.REQ_PARAM_COMMENT_TOPIC, String.valueOf(cVar.e())).addQueryParameter(Constants.KEY_MODE, String.valueOf(cVar.f())).build().newBuilder().removeAllQueryParameters("sc").build();        return build.newBuilder().addQueryParameter("sc", cVar.a(build));    }

最后可以找到下面这段cVar.a是一个okhttp的拦截器点进去是一个接口,线索到这里就断了

如果根据sc的长度猜测md5,可以直接搜java.security.MessageDigestmd5
可以找到

package tingshu.bubei.a;import java.security.MessageDigest;/* compiled from: MD5.java *//* loaded from: classes4.dex */public class d {    public static String a(String str) {        if (str == null) {            return "";        }        try {            MessageDigest messageDigest = MessageDigest.getInstance("MD5");            messageDigest.reset();            messageDigest.update(str.getBytes("UTF-8"));            byte[] digest = messageDigest.digest();            StringBuilder sb = new StringBuilder();            for (int i = 0; i < digest.length; i++) {                if (Integer.toHexString(digest[i] & 255).length() == 1) {                    sb.append("0");                    sb.append(Integer.toHexString(digest[i] & 255));                } else {                    sb.append(Integer.toHexString(digest[i] & 255));                }            }            return sb.toString().toLowerCase();        } catch (Exception unused) {            return "";        }    }}

然后查找用例可以找到

package tingshu.bubei.a;import android.net.Uri;import java.util.Set;import java.util.TreeMap;import okhttp3.HttpUrl;/* compiled from: ParamScGenerator.java *//* loaded from: classes4.dex */public class f {    public static String a(HttpUrl httpUrl) {        if (httpUrl.querySize() > 0) {            HttpUrl b = b(httpUrl);            // 核心md5位置            return d.a((b.uri().getPath() + "?" + Uri.decode(b.uri().getRawQuery())) + "iJ0DgxmdC83#I&[email protected]");        }        // 核心md5位置        return d.a(httpUrl.uri().getPath() + "iJ0DgxmdC83#I&[email protected]");    }
private static HttpUrl b(HttpUrl httpUrl) { Set<String> queryParameterNames = httpUrl.queryParameterNames(); TreeMap treeMap = new TreeMap(); HttpUrl.Builder newBuilder = httpUrl.newBuilder(); for (String str : queryParameterNames) { newBuilder.removeAllQueryParameters(str); treeMap.put(str, httpUrl.queryParameter(str)); } for (String str2 : treeMap.keySet()) { newBuilder.addQueryParameter(str2, (String) treeMap.get(str2)); } return newBuilder.build(); }}
d.a((b.uri().getPath() + "?" + Uri.decode(b.uri().getRawQuery())) + "iJ0DgxmdC83#I&j@iwg");

其中d.a就是md5,然后我们用frida来hook一下d.a,打印参数和返回值。

参数盲猜是请求url加上参数,但是我们不知道参数的拼接顺序,hook一下比较方便,当然可以研究一下下面两个,但是好像还是不知道参数的顺序,还是hook一下最方便。

public String getPath()Returns the decoded path component of this URI.The string returned by this method is equal to that returned by the getRawPath method except that all sequences of escaped octets are decoded.
Returns:The decoded path component of this URI, or null if the path is undefinedpublic String getRawQuery()Returns the raw query component of this URI.The query component of a URI, if defined, only contains legal URI characters.
Returns:The raw query component of this URI, or null if the query is undefined

hook验证

hook代码

br

hook结果

a is called, str: /yyting/gateway/entityPath.action?entityId=46122241&entityType=3&imei=bHJpZGEzRWxwMWU1TUw5M3F4bURCcFlibURnPT0=&mode=0&nwt=1&opType=1&q=379§ions=[40]&token=HCvrt6XKdUjP1SZKF6GjFg**_5Q-vbHrlEjiKL1oDFkTxMA**&type=0iJ0DgxmdC83#I&j@iwga ret value is eeb445260d0fdde00d900c24d0f10256

验证了sc通过md5加密得到加密前的字符串

/yyting/gateway/entityPath.action?entityId=46122241&entityType=3&imei=bHJpZGEzRWxwMWU1TUw5M3F4bURCcFlibURnPT0=&mode=0&nwt=1&opType=1&q=379§ions=[40]&token=HCvrt6XKdUjP1SZKF6GjFg**_5Q-vbHrlEjiKL1oDFkTxMA**&type=0iJ0DgxmdC83#I&[email protected]

python模拟请求

import requestsimport hashlibdef get_paly_url(entityId, sections):    token = 'HCvrt6XKdUjP1SZKF6GjFg**_5Q-vbHrlEjiKL1oDFkTxMA**'    sc_raw = f'/yyting/gateway/entityPath.action?entityId={str(entityId)}&entityType=3&imei=bHJpZGEzRWxwMWU1TUw5M3F4bURCcFlibURnPT0=&mode=0&nwt=1&opType=1&q=377§ions=[{str(sections)}]&token={token}&type=0iJ0DgxmdC83#I&[email protected]'    sc = hashlib.md5(sc_raw.encode()).digest().hex()    url = f"https://dapis.mting.info/yyting/gateway/entityPath.action?entityId={str(entityId)}&entityType=3&opType=1§ions=[{str(sections)}]&type=0&token={token}&imei=bHJpZGEzRWxwMWU1TUw5M3F4bURCcFlibURnPT0%3D&nwt=1&q=377&mode=0&sc={sc}"    payload = {}    headers = {        'User-Agent': 'Android7.1.2/yyting/OPPO/PCRT00/ch_qutoutiao/214/AndroidHD',        'Referer': 'yytingting.com',        'ClientVersion': '6.6.3',        'Host': 'dapis.mting.info'    }    response = requests.request(        "GET", url, headers=headers, data=payload, verify=False)    print(response.text)if __name__ == "__main__":    entityId = 46122241    for sections in range(1,20):        get_paly_url(entityId,sections)

成功留念

某听书请求签名分析

其他经验总结

可以一开始就hook md5就可以秒杀了

Java.perform(function() {// MD SHA var messageDigest=Java.use('java.security.MessageDigest');// updatefor(var i = 0; i < messageDigest.update.overloads.length; i++){    messageDigest.update.overloads[i].implementation = function(){        var name=this.getAlgorithm()        send("================="+name+"====================");        send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
for (var i = 0; i < arguments.length; i++) { send('messageDigest.update') send('JSON.stringify:' + JSON.stringify(arguments[i])) send('str:'+Java.use('java.lang.String').$new(arguments[i])) } var ret = this.update.apply(this, arguments) }}// digestfor(var i = 0; i < messageDigest.digest.overloads.length; i++){ messageDigest.digest.overloads[i].implementation = function(){ var name=this.getAlgorithm() send("================="+name+"===================="); send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); for (var i = 0; i < arguments.length; i++) { send('messageDigest.digestarguments:' + JSON.stringify(arguments[i])) } var ret = this.digest.apply(this, arguments) send('return:' + JSON.stringify(ret)) send('str:'+Java.use('java.lang.String').$new(ret)) // printInfo(ret) return ret }}});

- 可以同时hook Base64 AES RSA SHA1 SHA256等常见编码或加密就更为保险

- jadx支持直接复制为frida片段

该内容转载自网络,更多内容请点击“阅读原文”

某听书请求签名分析

原文始发于微信公众号(web安全工具库):某听书请求签名分析

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月5日11:07:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  某听书请求签名分析 http://cn-sec.com/archives/1086633.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: