为了账号安全,请及时绑定邮箱和手机立即绑定

【APP逆向百例】某品会 app 逆向分析

标签:
Python

7ITdI9.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

  • 目标:某品会 APP

  • apk 版本:v9.42.8

  • 逆向参数:authorization

  • 下载地址:aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzMxNTgzL2hpc3Rvcnlfdjk0MjA4

抓包分析

打开 app 随便搜索一样东西,然后通过 charles 配合 SocksDroid 进行抓包,结果如下:

7jzDrs.png

python 重放请求,发现请求头需要 authorization 参数。

java 层分析

这个版本有 frida 检测,还是老方法,hook dlopen 查看在哪个 app 闪退了:

function hook_dlopen() {
var android_dlopen_ext = Module.findExportByName(null, “android_dlopen_ext”);
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[android_dlopen_ext -> enter", path);
},
onLeave: function (retval) {
console.log(“android_dlopen_ext -> leave”)

}
});
}
setImmediate(hook_dlopen)

frida -U -f com.achievo.vipshop -l .\demo.js

上面代码启动,发现还是 libmsaoaidsec.so 检测,关于该检测,往期文章中有详细的介绍:

把该 so 文件丢到 ida 工具,直接通过交叉引用,找到上层的调用函数 hook 掉就行:

7jzOea.png

hook 检测代码如下:

function hook_dlopen() {
var android_dlopen_ext = Module.findExportByName(null, “android_dlopen_ext”);
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[android_dlopen_ext -> enter", path);
},
onLeave: function (retval) {
console.log(“android_dlopen_ext -> leave”)

}
});
}

function hook_call_constructors() {
var linker64_base_addr = Module.getBaseAddress(“linker64”)
var call_constructors_func_off = 0x000000000004a174
var call_constructors_func_addr = linker64_base_addr.add(call_constructors_func_off)
var listener = Interceptor.attach(call_constructors_func_addr, {
onEnter: function (args) {
console.log(“call_constructors -> enter”)
var module = Process.findModuleByName(“libmsaoaidsec.so”)
if (module != null) {
Interceptor.replace(module.base.add(0x000000000001B924), new NativeCallback(function () {
console.log(“replace sub_1BEC4”)
}, “void”, []))
listener.detach()
}
},
})
}

setImmediate(hook_dlopen)

注意 call_constructors_func_off 的地址需要改成大伙自己的,这个地址可以在手机执行以下命令得到:

readelf -s /apex/com.android.runtime/bin/linker64 | grep call_constructors

7jzUy7.png

这个参数在头部,因此我们优先选择 hook 头部,而不是 hook HashMap,hashMap 输出内容较多,当我们hook 头部没有相关信息时,可以再考虑 hook hashMap,hook 代码如下:

function showStacks() {
Java.perform(function () {
console.log(“打印堆栈”)
console.log(Java.use(“android.util.Log”).getStackTraceString(
Java.use(“java.lang.Throwable”).KaTeX parse error: Expected 'EOF', got '}' at position 17: …ew() )); }̲) } ​ fun…Builder");
Builder[“addHeader”].implementation = function (str, str2) {
console.log("key: " + str)
console.log("val: " + str2)
// showStacks()
if (str ===“Authorization”){
showStacks()
}
var result = this[“addHeader”](str, str2);
return result;
};
}

frida -U -f com.achievo.vipshop -l .\demo.js -o 1.txt

找到我们需要的参数的值:

7jz1SI.png

7jzPsV.png

apk 文件放入 jadx,根据第二层堆栈找到如下位置:

7jzYEL.png

值是 str,str 又通过 b.b 方法得到的,我们这里是 post 方法就走 if 逻辑,hook 一下:

let b = Java.use(“a9.b”);
b[“b”].implementation = function (context, treeMap, str, str2) {
console.log(b.b is called: context=${context}, treeMap=${treeMap}, str=${str}, str2=${str2});
let result = this[“b”](context, treeMap, str, str2);
console.log(b.b result=${result});
return result;
};

7jzrWJ.png

位置没错,进入方法:

7jztjG.png

7jz4pB.png

一直走,最后定位到了这里:

7jzC8t.png

进入 getSignHash 方法然后调用了 gs 方法,但是 gs 方法是报错,按道理 gs 函数应该有返回值,返回我们的加密结果,我们可以直接把代码丢到 GPT 让他帮忙分析一下:

7jzJtb.png

gpt 给了我们答案,说是通过反射调用了一个隐藏的方法,那我们先了解一下 java 反射是什么:

7jzXee.png

上面也说了 java 的反射创建类不通过 new,而是通过 newInstance 方法创建,那我们向下翻,会看到 initInstance 方法:

7jznyP.png

根据上面了解到的反射的相关知识,这里很明显是在创建对象,我们直接点击 KeyInfo 这个类,找到真正的加密函数 gs:

7jzIUw.png

最后调用了 native 方法,so 的名称为 keyinfo:

7jzMs6.png

我们可以 hook 验证一下:

let KeyInfo = Java.use(“com.vip.vcsp.KeyInfo”);
KeyInfo[“gsNav”].implementation = function (context, map, str, z10) {
console.log(KeyInfo.gsNav is called: context=${context}, map=${map}, str=${str}, z10=${z10});
let result = this[“gsNav”](context, map, str, z10);
console.log(KeyInfo.gsNav result=${result});
return result;
};

7jziEO.png

7jzscQ.png

最后改成主动调用的形式,方便我们后续分析:

function hook_zhu() {
Java.perform(function () {
const KeyInfo = Java.use(“com.vip.vcsp.KeyInfo”);
const TreeMap = Java.use(“java.util.TreeMap”);
const String = Java.use(‘java.lang.String’);
// 构造 context
const currentApplication = Java.use(“android.app.ActivityThread”).currentApplication();
const context = currentApplication.getApplicationContext();
let map = TreeMap.new();map.put(String.new(); map.put(String.new();map.put(String.new(“api_key”), String.new("23e7f28019e8407b98b84cd05b5aef2c"));map.put(String.new("23e7f28019e8407b98b84cd05b5aef2c")); map.put(String.new("23e7f28019e8407b98b84cd05b5aef2c"));map.put(String.new(“app_name”), String.new("shopandroid"));map.put(String.new("shop_android")); map.put(String.new("shopandroid"));map.put(String.new(“app_version”), String.new("9.42.8"));map.put(String.new("9.42.8")); map.put(String.new("9.42.8"));map.put(String.new(“bigSaleTagIds”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“brandIds”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“brandStoreSns”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“categoryId”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“channelId”), String.new("1"));map.put(String.new("1")); map.put(String.new("1"));map.put(String.new(“channel_flag”), String.new("01"));map.put(String.new("0_1")); map.put(String.new("01"));map.put(String.new(“clickFrom”), String.new("listuserword"));map.put(String.new("list_userword")); map.put(String.new("listuserword"));map.put(String.new(“client”), String.new("android"));map.put(String.new("android")); map.put(String.new("android"));map.put(String.new(“client_type”), String.new("android"));map.put(String.new("android")); map.put(String.new("android"));map.put(String.new(“couponIds”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“darkmode”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“deeplink_cps”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“device_model”), String.new("GooglePixel3"));map.put(String.new("Google Pixel 3")); map.put(String.new("GooglePixel3"));map.put(String.new(“did”), String.new("0.0.488b303f75e2323522a8ed6c9da1d86f.2e28ab"));map.put(String.new("0.0.488b303f75e2323522a8ed6c9da1d86f.2e28ab")); map.put(String.new("0.0.488b303f75e2323522a8ed6c9da1d86f.2e28ab"));map.put(String.new(“elder”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“evgid”), String.new("itg0e6C7desc9WOI7whauxkrce1Tnmectkj0f9gbLh7l8Yy8fZS8ASg24uPgOT1UB+J9Pn37EebSRw/foKbqomTNnj0y5EDKLAXFfooxVlQ="));map.put(String.new("itg0e6C7desc9WOI7whauxkrce1Tnmectkj0f9gbLh7l8Yy8fZS8ASg24uPgOT1UB+J9Pn37EebSRw/foKbqomTNnj0y5EDKLAXFfooxVlQ=")); map.put(String.new("itg0e6C7desc9WOI7whauxkrce1Tnmectkj0f9gbLh7l8Yy8fZS8ASg24uPgOT1UB+J9Pn37EebSRw/foKbqomTNnj0y5EDKLAXFfooxVlQ="));map.put(String.new(“extParams”), String.KaTeX parse error: Can't use function '\"' in math mode at position 7: new("{\̲"̲priceVer\":\"2\…new(“fdc_area_id”), String.new("104104"));map.put(String.new("104104")); map.put(String.new("104104"));map.put(String.new(“functions”), String.new("RTRecomm,flagshipInfo,couponBarV2,lowPriceTabs,feedbackV2,otdAds,zoneCode,slotOp,survey,outfit,aiRealtime,floaterParams,tabGroupV2,bsAndSeason,propAndOpTag,parallelCall"));map.put(String.new("RTRecomm,flagshipInfo,couponBarV2,lowPriceTabs,feedbackV2,otdAds,zoneCode,slotOp,survey,outfit,aiRealtime,floaterParams,tabGroupV2,bsAndSeason,propAndOpTag,parallelCall")); map.put(String.new("RTRecomm,flagshipInfo,couponBarV2,lowPriceTabs,feedbackV2,otdAds,zoneCode,slotOp,survey,outfit,aiRealtime,floaterParams,tabGroupV2,bsAndSeason,propAndOpTag,parallelCall"));map.put(String.new(“harmony_app”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“harmony_os”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“height”), String.new("2028"));map.put(String.new("2028")); map.put(String.new("2028"));map.put(String.new(“isMultiTab”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“is_default_area”), String.new("1"));map.put(String.new("1")); map.put(String.new("1"));map.put(String.new(“keyword”), String.KaTeX parse error: Expected 'EOF', got '纸' at position 6: new("纸̲")); map.put…new(“lastPageProperty”), String.KaTeX parse error: Can't use function '\"' in math mode at position 7: new("{\̲"̲isBgToFront\":\…new(“maker”), String.new("GOOGLE"));map.put(String.new("GOOGLE")); map.put(String.new("GOOGLE"));map.put(String.new(“mars_cid”), String.new("2760c2a5−07c5−3a1c−a409−66e37ebaf574"));map.put(String.new("2760c2a5-07c5-3a1c-a409-66e37ebaf574")); map.put(String.new("2760c2a507c53a1ca40966e37ebaf574"));map.put(String.new(“mobile_channel”), String.new("rjx5hknt:::"));map.put(String.new("rjx5hknt:::")); map.put(String.new("rjx5hknt:::"));map.put(String.new(“mobile_platform”), String.new("3"));map.put(String.new("3")); map.put(String.new("3"));map.put(String.new(“net”), String.new("WIFI"));map.put(String.new("WIFI")); map.put(String.new("WIFI"));map.put(String.new(“operator”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“os”), String.new("Android"));map.put(String.new("Android")); map.put(String.new("Android"));map.put(String.new(“osv”), String.new("11"));map.put(String.new("11")); map.put(String.new("11"));map.put(String.new(“otddid”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“other_cps”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“page_id”), String.new("pagetecommoditysearch1749796274004"));map.put(String.new("page_te_commodity_search_1749796274004")); map.put(String.new("pagetecommoditysearch1749796274004"));map.put(String.new(“page_init_ts”), String.new("1749796253199"));map.put(String.new("1749796253199")); map.put(String.new("1749796253199"));map.put(String.new(“phone_brand”), String.new("google"));map.put(String.new("google")); map.put(String.new("google"));map.put(String.new(“phone_model”), String.new("pixel3"));map.put(String.new("pixel 3")); map.put(String.new("pixel3"));map.put(String.new(“priceMax”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“priceMin”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“props”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“province_id”), String.new("104104"));map.put(String.new("104104")); map.put(String.new("104104"));map.put(String.new(“referer”), String.new("com.achievo.vipshop.search.activity.SearchActivity"));map.put(String.new("com.achievo.vipshop.search.activity.SearchActivity")); map.put(String.new("com.achievo.vipshop.search.activity.SearchActivity"));map.put(String.new(“rom”), String.new("Dalvik/2.1.0(Linux;U;Android11;Pixel3Build/RQ1D.210205.004)"));map.put(String.new("Dalvik/2.1.0 (Linux; U; Android 11; Pixel 3 Build/RQ1D.210205.004)")); map.put(String.new("Dalvik/2.1.0(Linux;U;Android11;Pixel3Build/RQ1D.210205.004)"));map.put(String.new(“sd_tuijian”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“service_provider”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“session_id”), String.new("2760c2a5−07c5−3a1c−a409−66e37ebaf574shopandroid1749796345730"));map.put(String.new("2760c2a5-07c5-3a1c-a409-66e37ebaf574_shop_android_1749796345730")); map.put(String.new("2760c2a507c53a1ca40966e37ebaf574shopandroid1749796345730"));map.put(String.new(“skey”), String.new("6692c461c3810ab150c9a980d0c275ec"));map.put(String.new("6692c461c3810ab150c9a980d0c275ec")); map.put(String.new("6692c461c3810ab150c9a980d0c275ec"));map.put(String.new(“sort”), String.new("0"));map.put(String.new("0")); map.put(String.new("0"));map.put(String.new(“source”), String.new("app"));map.put(String.new("app")); map.put(String.new("app"));map.put(String.new(“source_app”), String.new("android"));map.put(String.new("android")); map.put(String.new("android"));map.put(String.new(“standby_id”), String.new("rjx5hknt:::"));map.put(String.new("rjx5hknt:::")); map.put(String.new("rjx5hknt:::"));map.put(String.new(“sys_version”), String.new("30"));map.put(String.new("30")); map.put(String.new("30"));map.put(String.new(“tabFields”), String.new("gender,tabs,priceTabs,discountTabs,tabGroupV2"));map.put(String.new("gender,tabs,priceTabs,discountTabs,tabGroupV2")); map.put(String.new("gender,tabs,priceTabs,discountTabs,tabGroupV2"));map.put(String.new(“tfs_fp_token”), String.new("Ba1bX4G5LVCvZX/keQ+jKakGAtkmcd5uF3mqx1MgFxED+ki6Tt8pN7kHPc01QhDUUEKPZujxXveye0E3jIKnUvA=="));map.put(String.new("Ba1bX4G5LVCvZX/keQ+jKakGAtkmcd5uF3mqx1MgFxED+ki6Tt8pN7kHPc01QhDUUEKPZujxXveye0E3jIKnUvA==")); map.put(String.new("Ba1bX4G5LVCvZX/keQ+jKakGAtkmcd5uF3mqx1MgFxED+ki6Tt8pN7kHPc01QhDUUEKPZujxXveye0E3jIKnUvA=="));map.put(String.new(“timestamp”), String.new("1749796274"));map.put(String.new("1749796274")); map.put(String.new("1749796274"));map.put(String.new(“union_mark”), String.KaTeX parse error: Expected 'EOF', got '&' at position 11: new("blank&̲_&blank&_&rjx5h…new(“vipService”), String.new(""));map.put(String.new("")); map.put(String.new(""));map.put(String.new(“warehouse”), String.new("VIPNH"));map.put(String.new("VIP_NH")); map.put(String.new("VIPNH"));map.put(String.new(“width”), String.$new(“1080”));

let result = KeyInfo[“gsNav”](context, map, null, false);
console.log(“java---->”, result);
})
}

so 层分析

老样子,keyinfo.so 文件拖到 ida 里面去,在 Exports 表搜索 java 看看是不是静态注册的:

7jz2jf.png

是静态注册,点进去:

7jzFpc.png

返回 v7,点进去 Function_gs,并按 y 修改 a1 类型:

7jzZG3.png

7jzdtj.png

7jzeo5.png

代码没有什么混淆,基本上全是明文,遇到这种,我们可以找根据名字定位,也可以 frida trace 看看调用堆栈,因为这代码没多少行,我们可以直接丢给 ai 帮我们分析,这里用的元宝的 deepseek:

7j49Hm.png

7j4kU4.png

根据 ai 说的,我们只需要关心 getByteHash 函数做了什么操作,先找到 getByteHash 函数看看,按住 alt + T 搜索 getByteHash,有两处,点进去:

7j4B2h.png

很明显的 sha1 算法,还是直接先 hook 一下 getByteHash 方法,看看传递了什么参数:

7j4mQ9.png

我们重点看第三个参数和第五个参数就行,hook 代码如下:

function hook_so_byhash() {
var addr = Module.findBaseAddress(‘libkeyinfo.so’);
var func = addr.add(0xF2260)
// console.log(func)
let number = 0;
Interceptor.attach(func, {
onEnter: function (args) {
number += 1;
console.log(\x1b[31m第${number} 次 hook getByteHash-----------------------------------------------\x1b[0m);
this.arg5 = args[4]
console.log(hexdump(args[2]))
console.log(args[2].readCString())
},
onLeave: function (retval) {
console.log(“this.arg5”,hexdump(this.arg5));
}
})
}

setImmediate(hook_so_byhash)

在 frida 里面执行我们的主动调用:

7j4pcY.png

打印结果如下:

7j4LnH.png

第一次,输出的参数暂且不知道是什么,返回的结果为 1ed562e1e90b23ae3f9a40f8b2a65382b95a4752

7j43pZ.png

7j4lGU.png

第二次入参为我们的 TreeMap 数据,前面盐值为 aee4c425dbb2288b80c71347cc37d04b,经分析,该值写死即可,返回值为 d98aee7e972029d163709d41538d5bdcb2fe290f

我们到 K 哥工具站(https://www.kgtools.cn),对比发现,并没有魔改,标准算法:

7j47zq.png

7j4Eos.png

第三次的入参为我们前面的盐加上第二次 sha1 算法加密的返回的结果。

第三次返回的结果为 0ae492780a1e7aefafa23efae2357faafc98af51

7j4NHa.png

和我们主动调用的结果一样,最后用 python 改写发包即可,至此,加密参数分析完毕,相关代码,会放到知识星球中,仅供学习交流。

结果验证

图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消