sign参数分析
2024-11-27 17:59:0 Author: mp.weixin.qq.com(查看原文) 阅读量:1 收藏

目标接口:搜索https://api.m.ooxx.com/client.action?functionId=search

版本:13.1.0

目标参数:body 中的 x-api-eid-token  和 sign

定位接口参数的方法

◆搜索大发

◆hook Hashmap

◆Hook StringBuilder

这里我们直接使用最朴素的搜索大法,一次就中。

继续跟踪,进最下面这个看看。

下面拼接了 urlEncodeUTF82  ,看英文就是一个 utf 编码之后的数据。继续向上跟踪

进入第一个,函数返回的是一个字符串。还不是加密的地方。

JDHttpTookit.getEngine().getSignatureHandlerImpl().signature 这就是签名函数。

接口函数一般是 implement 或者直接 new 出来。这里跟进 new 出来的

Hook 获取参数

通过 frida 获取传入的参数

主动调用,方便后续做测试

function call_by_java() {
Java.perform(function(){
let BitmapkitUtils = Java.use("com.ooxx.common.utils.BitmapkitUtils");
let context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
let str = 'search'
let str2 = '{"addrFilter":"1","addressId":"0","articleEssay":"1","attrRet":"0","buriedExpLabel":"","deviceidTail":"38","exposedCount":"0","filterServiceIds":"1468131091","first_search":"1","frontExpids":"F_001","gcAreaId":"1,72,55674,0","gcLat":"39.944093","gcLng":"116.482276","imagesize":{"gridImg":"531x531","listImg":"358x358","longImg":"531x708"},"insertArticle":"1","insertScene":"1","insertedCount":"0","isCorrect":"1","jdv":"0|kong|t_2018512525_cpv_nopay|tuiguang|17303608941925019140008|1730360893","keyword":"空气加湿器","localNum":"2","newMiddleTag":"1","newVersion":"3","oneBoxMod":"1","orignalSearch":"1","orignalSelect":"1","page":"1","pageEntrance":"1","pagesize":"10","populationType":"232","pvid":"","searchVersionCode":"10110","secondInsedCount":"0","showShopTab":"yes","showStoreTab":"1","show_posnum":"0","sourceRef":[{"action":"","eventId":"MyJD_WordSizeResult","isDirectSearch":"0","logid":"","pageId":"Home_Main","pvId":""},{"action":"","eventId":"Search_History","isDirectSearch":"0","logid":"","pageId":"Search_Activity","pvId":"632ba208e4854bb1839e6e32a5e6b841"}],"stock":"1","ver":"142"}'
let str3 = "bd132c578e85c7cd";
let str4 = "android";
let str5 = "13.1.0";

let result = BitmapkitUtils.getSignFromJni(context, str, str2, str3, str4, str5);
console.log("BitmapkitUtils.getSignFromJni result = " + result);
})
}

BitmapkitUtils.getSignFromJni result = st=1731485532078&sign=3c9820dce84fc15ceaf29bf0e0630306&sv=111

结果中就包含了我们今天的主角 ==sign==  。长度为 32 脑海中就冒出 MD5了。

确定 so

调用到了 native 层了。java 层的分析就到这里了。这里类中并没有看到加载 so 。需要通过 frida hook 导出的符号表,反查 so 的名字。

得到模板 so 的名字为  libjdbitmapkit.so

IDA 打开可以看到 getSignFromJni 的导出函数 。通过 IDA frida-trace 先 trace 一份日志方便后面分析

工具:https://github.com/Pr0214/trace_natives

把下载到的 traceNatives.py 放到 ida 根目录的 plugin 目录下重启 IDA。点击 edit -> plugins -> traceNatives 会生成一个文件夹给frida-trace 使用。

 frida-trace -H iP:port -F -O D:\ooxx\libjdbitmapkit_1731482430.txt

frida 就会开始trace 此时不要关闭命令行,直接调用刚才的主动触发的函数 call_by_java() 就会生成追踪数据,复制保存成一个 log 文件方便后面分析。

再用 findHash 插件看看能不能找到什么有用的信息

把两个数据做对比发现了 sub_27A4 这个函数在两边都有出现。

IDA 中按 g 跳转过去看看

看上去很像 MD5 哦,点击数字按 h 转换成十六进制然后去搜索一下

有点意思!应该是MD5。查看 trace  日志,sub_8134() 是最开始的地方,IDA 中看看是什么内容。

经过对比发现,我们的入口函数也就是 8134 。那还有什么说的,盘他咯!

在 trace 日志中 8134  这层调用的最后一个函数是 33b4,进入 33b4 之后就是 MD5算法了。我们先 hook 7e08 查看入参。

function print_arg(addr) {
try {
var module = Process.findRangeByAddress(addr);
if (module != null) return "\n"+hexdump(addr) + "\n";
return ptr(addr) + "\n";
} catch (e) {
return addr + "\n";
}
}

function hook_native(funptr,paramsNum) {
var md = Process.findModuleByAddress(funptr);
console.log("hook func ");

try {
//hook 指定函数
Interceptor.attach(funptr,{
onEnter: function(args){
this.logs =""
this.params = [];
this.logs = this.logs.concat("So: "+md.name +" Method: " + ptr(funptr).sub(md.base) + "\n")
for (var i = 0; i < paramsNum; i++) {
//参数
this.params.push(args[i]);
this.logs = this.logs.concat("this.args "+i+" onEnter: " +print_arg(args[i])+"\n")
}
},
onLeave: function(retval){
for (let i = 0; i < paramsNum; i++) {
this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i]));
}
this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\n");
console.log(this.logs);
}
});
} catch (error) {
console.log(error);

}
}

hook_native(Module.findBaseAddress("libjdbitmapkit.so").add(0x33B4), 0x3);

传入的参数很像 base64 但是解密之后还是乱码。传入之前做了处理。向上追踪,在 trace 日志,上一个被调用的函数是 2698

hook  2698 看看返回值,刚好就是 33B4 的入参

跟踪 2698  第二个参数,它来自 v37 , v37 又是来自 7E08 这个函数 。而且 在 trace 日志中也有它的身影。

经过分析可以发现 7E08 最后两个参数是两个随机数,就是这两个随机数决定了之后使用不同的分支算法。

进入 7E08 。先判断最后一个参数的随机数,用不同方式在生成一个随机数,然后与倒数第二参数的随机数相加产生新的随机数。

使用新的随机数来决定最终进入那个分支的算法。特别注意一下 gen_str_44e这是一个固定的字符串。在进入不同分支的时候,根据新的随机数取了这个字符串中不同的数据

hook 7E08 得到的参数

在我们的 trace log 中 进入了 case2 分支,就先分析这个分支。

这里要特别注意一下,因为这里是根据随机数来进入不同的分支的。所以在测试的时候不是每次都进入到这个 8cc8 分支。多调用几次,总会有一个进入的。

通过代码看到各参数都进入到 1ADCC这个函数,优先分析这个函数。hook 查看参数

hook_native(Module.findBaseAddress("libjdbitmapkit.so").add(0x1ADCC), 0x5);

修改一下hook代码把第二参数输出一下

第二参数是字符串,第四个参数是传入的数据,第五个参数是传入数据的长度。所以可以让 hexdump 根据这个参数 dump 出完整的数据。

析之后 1ADCC 就是最终的算法

下面是还原之后的算法

#include <stdio.h>
void alg2(char* rawdata, int input_len){
int v4,v10;
char v12 ;
// 分支 2 的算法
char* key = "80306f4370b39fd5630ad0529f77adb6";
unsigned char table[0x10] = {0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB,0xF, 0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A};
for (int i = 0; i != input_len; ++i) {
v4 = i&7;
// v10 = byte_FA0[v9 & 0xF];
v10 = table[i& 0xF];
// v12 = ((v10 ^ *(_BYTE *)(rawdata + v9) ^ *(_BYTE *)(randondata + (v9 & 7))) + v11) ^ v10;
v12 = ((v10 ^ *(unsigned char *)(rawdata + i) ^ *(unsigned char *)(key + (i & 7))) + v10) ^ v10;

// *(_BYTE *)(rawdata + v9) = v12;
*(unsigned char *)(rawdata + i) = v12;
// *(_BYTE *)(rawdata + v9) = v12 ^ *(_BYTE *)(randondata + (v9 & 7));
*(unsigned char *)(rawdata + i) = v12 ^ *(unsigned char *)(key + (i & 7));
}
}

int main(int argc, char const *argv[])
{
char input [] ="functionId=search&body={\"addrFilter\":\"1\",\"addressId\":\"0\",\"articleEssay\":\"1\",\"attrRet\":\"0\",\"buriedExpLabel\":\"\",\"deviceidTail\":\"38\",\"exposedCount\":\"0\",\"filterServiceIds\":\"1468131091\",\"first_search\":\"1\",\"frontExpids\":\"F_001\",\"gcAreaId\":\"1,72,55674,0\",\"gcLat\":\"39.944093\",\"gcLng\":\"116.482276\",\"imagesize\":{\"gridImg\":\"531x531\",\"listImg\":\"358x358\",\"longImg\":\"531x708\"},\"insertArticle\":\"1\",\"insertScene\":\"1\",\"insertedCount\":\"0\",\"isCorrect\":\"1\",\"jdv\":\"0|kong|t_2018512525_cpv_nopay|tuiguang|17303608941925019140008|1730360893\",\"keyword\":\"空气加湿器\",\"localNum\":\"2\",\"newMiddleTag\":\"1\",\"newVersion\":\"3\",\"oneBoxMod\":\"1\",\"orignalSearch\":\"1\",\"orignalSelect\":\"1\",\"page\":\"1\",\"pageEntrance\":\"1\",\"pagesize\":\"10\",\"populationType\":\"232\",\"pvid\":\"\",\"searchVersionCode\":\"10110\",\"secondInsedCount\":\"0\",\"showShopTab\":\"yes\",\"showStoreTab\":\"1\",\"show_posnum\":\"0\",\"sourceRef\":[{\"action\":\"\",\"eventId\":\"MyJD_WordSizeResult\",\"isDirectSearch\":\"0\",\"logid\":\"\",\"pageId\":\"Home_Main\",\"pvId\":\"\"},{\"action\":\"\",\"eventId\":\"Search_History\",\"isDirectSearch\":\"0\",\"logid\":\"\",\"pageId\":\"Search_Activity\",\"pvId\":\"632ba208e4854bb1839e6e32a5e6b841\"}],\"stock\":\"1\",\"ver\":\"142\"}&uuid=bd132c578e85c7cd&client=android&clientVersion=13.1.0&st=1731550738362&sv=102\"";
alg2(input,sizeof(input)-1);
for (int i = 0; i < sizeof(input)-1; i++){
printf("%02x",(unsigned char)input[i]);
}

return 0;
}

可以看到是一致的

最后把这段数据 base64 之后再进行 md5 就是最终的 sign 值。最终拼接上 sv 和 t 值返回到 java 层

今天就先分析case2 的分支,以后有机会再分析其他分支啦!

以 x-api-eid-token 作为入口点,分析 java 层的最终点在 BitmapkitUtils.getSignFromJni。结合 frida-trace 和 findHash 找到关键点 sub_27A4 。在这个函数内部使用了随机数的方式进入不同的分支。使用 CASE2 分支作为今天的入口点,最终成功得到 sign 具体的算法逻辑。

看雪ID:绿豆粥

https://bbs.kanxue.com/user-home-791353.htm

*本文为看雪论坛优秀文章,由 绿豆粥 原创,转载请注明来自看雪社区

# 往期推荐

1、PWN入门-SROP拜师

2、一种apc注入型的Gamarue病毒的变种

3、野蛮fuzz:提升性能

4、关于安卓注入几种方式的讨论,开源注入模块实现

5、2024年KCTF水泊梁山-反混淆

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458584116&idx=1&sn=449e4fc11adc4e47a9aac8dffd0877ab&chksm=b18c34be86fbbda8464bdaf18da7962d229ca9d1d4ecd5704d79e633b7cd37ef4829d1a69227&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh