发布于8月23日8月23日 千里之行,始于某瓣,web是从某瓣开始的,app也不忘初心,练手试试{:1_932:}环境准备firda:16.5.6frida-tools:13.6.0jadx小黄鸟Reqable(看大佬们自己喜好)scrcpy某瓣app 7.5.0算法助手一、抓包分析 直接打开小黄鸟,选择手机协助pc的抓包分析,小黄鸟直接梭哈证书安装挺方便的 手机上这样显示就是跑通了(记得抓包的时候打开黄色小飞机),然后我们打开某瓣app 下拉刷新抓包 很明显就可以看到一个抓包信息,以及返回值,这里可以勾选json数据进行过滤 本次逆向核心是_sig参数,先验验长度 猜一下可能是hash算法,看到熟悉的=号,可以base64解码测试一下 base64out了,可以正式干活分析了:dizzy:二、参数分析 把apk包丢进jadx中,直接搜索关键参数_sig 搜索我们发现有一个看起来非常可疑 非常熟悉的一个reuest,这是在构造请求体,同时传进去了还有_ts参数,进去分析瞅瞅 简单对结构进行分析 复制代码 隐藏代码 public Response intercept(Interceptor.Chain chain) { Request request = chain.request(); if (request != null) { if (!this.a.contains(request.url().host())) { return chain.proceed(request); } if (!TextUtils.equals(request.method(), "GET")) { RequestBody body = request.body(); if (body != null && (body instanceof ProgressRequestBody)) { request = a(request); } else if (body == null || !(body instanceof FormBody)) { if (body != null && (body instanceof MultipartBody)) { request = b(request); } } else { Pair<String, String> a = ApiSignatureHelper.a(request); if (a != null) { FormBody formBody = (FormBody) request.body(); FormBody.Builder builder = new FormBody.Builder(); int size = formBody.size(); for (int i = 0; i < size; i++) { String name = formBody.name(i); if (!TextUtils.equals(name, "_sig") && !TextUtils.equals(name, "_ts")) { builder.add(formBody.name(i), formBody.value(i)); } } builder.add("_sig", (String) a.first); builder.add("_ts", (String) a.second); FormBody build = builder.build(); request = TextUtils.equals(request.method(), com.douban.push.internal.api.Request.METHOD_PUT) ? request.newBuilder().put(build).removeHeader(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH).header(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH, String.valueOf(build.contentLength())).build() : TextUtils.equals(request.method(), com.douban.push.internal.api.Request.METHOD_DELETE) ? request.newBuilder().delete(build).removeHeader(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH).header(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH, String.valueOf(build.contentLength())).build() : request.newBuilder().post(build).removeHeader(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH).header(com.douban.push.internal.api.Request.HEADER_CONTENT_LENGTH, String.valueOf(build.contentLength())).build(); } } } else { Pair<String, String> a2 = ApiSignatureHelper.a(request); if (a2 != null) { request = request.newBuilder().url(request.url().newBuilder().setQueryParameter("_sig", (String) a2.first).setQueryParameter("_ts", (String) a2.second).build()).build(); } } } return chain.proceed(request); 这段代码是一个拦截器的实现,同时根据请求方式GET,POST的判断进入不同的分支,进行签名校验,很明确我们使用到的请求方式是GET,走的分支应该是 复制代码 隐藏代码 else { Pair<String, String> a2 = ApiSignatureHelper.a(request); if (a2 != null) { request = request.newBuilder() .url(request.url() .newBuilder() .setQueryParameter("_sig", (String) a2.first) .setQueryParameter("_ts", (String) a2.second) .build() ) .build(); } } 可以看到_sig参数是从a2中取到的,a2是 ApiSignatureHelper.a(request)产生的,跟进去看看 这里的返回值继续调用了一个a方法,继续跟一下,发现就在下面一点 复制代码 隐藏代码 public static Pair<String, String> a(String str, String str2, String str3) { String decode; if (TextUtils.isEmpty(str)) { return null; } String str4 = FrodoApi.a().e.b; if (TextUtils.isEmpty(str4)) { return null; } StringBuilder sb = new StringBuilder(); sb.append(str2); String encodedPath = HttpUrl.parse(str).encodedPath(); if (encodedPath == null || (decode = Uri.decode(encodedPath)) == null) { return null; } if (decode.endsWith("/")) { decode = decode.substring(0, decode.length() - 1); } sb.append(StringPool.AMPERSAND); sb.append(Uri.encode(decode)); if (!TextUtils.isEmpty(str3)) { sb.append(StringPool.AMPERSAND); sb.append(str3); } long currentTimeMillis = System.currentTimeMillis() / 1000; sb.append(StringPool.AMPERSAND); sb.append(currentTimeMillis); return new Pair<>(HMACHash1.a(str4, sb.toString()), String.valueOf(currentTimeMillis)); } } 一眼梭哈过去,可以看到一个HMACHash1,很明显的一个加密方法,跟进去看看 看着像是调用的标准库,我们直接hook一下试试,同时打印传入的参数 [JavaScript] 纯文本查看 复制代码?1frida -U -f com.douban.frodo -l .\demo_douban.js 复制代码 隐藏代码 function main() { Java.perform(function () { let HMACHash1 = Java.use("com.douban.frodo.utils.crypto.HMACHash1"); HMACHash1["a"].implementation = function (str, str2) { let result = this["a"](str, str2); console.log('参数1---->', str) console.log('参数2---->', str2) console.log('返回值---->', result) console.log('---------------') return result; }; }) } setImmediate(main) 可以看到,我们刷新页面请求接口确实调用了这个函数,我们再刷新hook一次和抓包返回的_sig值进行对比 还是很明显,都是一样的,说明他确实是HMACSHA1加密,我们用在线网站测试一下参数的密钥和参数是不是标准的加密方法,记得还要用base64编码处理一下 复制代码 隐藏代码 def hmac_sha1_encode(key: str, data: str) -> str: key_bytes = key.encode('utf-8') data_bytes = data.encode('utf-8') hmac_obj = hmac.new(key_bytes, data_bytes, hashlib.sha1) hash_result = hmac_obj.digest() base64_result = base64.b64encode(hash_result).decode('utf-8') return base64_result print(hmac_sha1_encode('bf7dddc7c9cfe6f7', '自己测试参数')) frida一把嗦这个参数就搞定了 重新换个姿势来试试,算法助手一把嗦,需要刷入lsp 把这个打开,同时再lsp勾选需要注入的应用 当我们进入app是看到这个内容就知道注入成功了,我们继续打开抓包工具,刷新抓包 复制我们需要的参数的值,到我们算法助手日志中去搜索 如果全部内容搜索不到可以,取前面一部分测试 非常熟悉的内容参数,虽然有着细微的偏差,但是我们可以找到他的调用堆栈直接一把梭哈
参与讨论
你可以现在发布并稍后注册. 如果你有帐户,现在就登录发布帖子.