App信息
包名:com.aikucun.akapp

关于frida反调试
这个app的反调试我是通过杀线程绕过的,具体过程不再详写,其他文章里有相同的绕过思路。
抓包分析数据
寻找sign参数

jadx反编译apk
搜索"sign",经过一番搜索,并没有找到相关的代码实现位置,换一种思路搜素"svs",定位到了参数的位置。

双击查看
onAttachQueryParameters

这里我们可以双击b.u,查看其内容即svs的值,发现它是一个定值v3

接着向下浏览找到了"&sign=",结合上下文发现就是我们要找的sign

很清晰的看出来了sign的值为signV3,是经过signV3函数签名得到的结果。
signV3
查看signV3的内容,它是一个native函数

hook signV3
我们可以hook signV3函数查看其参数和返回值
1 | function hook_signV3(){ |
hook结果
1 | MXSecurity.signV3 is called: url=https://zuul.aikucun.com/akucun-base-data-new/base/address/selectAddrVersion?appid=38741001&did=d545ae984083237bae9a6358ca29910f&noncestr=3f60cc&svs=v3×tamp=1736605877, nonceStr=3f60cc, timestamp=1736605877, body= |
url
url是signV3的第一个参数,
分析url里的参数,url的第一个参数是appid
如下,appid是在so里面获取的,等分析so的时候再去分析它

url的第二个参数是did
如下,调用getDid方法
getDId

在getDid方法里调用了md5String方法,其参数是"micker.cn"拼接getUdid()的返回值,对参数进行md5加密,得到did
getUdid
获取udid的值

如果udid为空,会重新随机一个uuid,设置为udid
nonceStr
nonceStr是signV3的第二个参数
substring
传入signV3方法的第二个参数是substring,查找这个参数是怎么生成的

可以看出来,这个参数是replace$default的子字符串,也就是其前六个字符。
replace$default
如下

StringsJVM调用的replace$default方法,里面传入了一个参数uuid,它是随机生成的
看一下这个方法是怎么实现的,如下

又调用了replace方法,查看其实现

好长一串啊,这里我选择直接hook replace方法分析其参数和返回值
1 | function hook_replace(){ |
hook 结果
1 | StringsJVM.replace is called: str=14d557b1-496b-4daa-ba2f-d7cffe054397, oldValue=-, newValue=, z=false |
它是把uuid中的-符号给去除了
到这里就知道nonceStr的值就是uuid的前六个字符
timestamp
signV3的第三个参数是timestamp没什么好分析的,是一个时间戳。
body
接下来分析signV3的第四个参数,body
传入signV3的参数是bodyMD5,看名字就与bodyMD5有关系
看这个参数是怎么生成的

调用了bodyMD5()方法,里面传入了一个request参数
bodyMD5
查看这个方法的具体实现

把请求体进行了一个MD5运算
hook bodyMD5
这里可以把bodyMD5方法和md5标准加密算法同时hook,更清晰
1 | function hook_md5(){ |
hook 结果
1 | MXV1Sign.bodyMD5 is called: request=Request{method=GET, url=https://zuul.aikucun.com/akucun-base-data-new/base/address/selectAddrVersion, tags={class retrofit2.Invocation=com.aikucun.akapp.business.address.service.AddressService.selectAddressVersion() []}} |
好了,这里就把signV3方法的参数和返回值分析完成了,接下来就到so里面具体分析这个函数的实现过程了。
so分析
signV3的实现在mx里

定位signV3
IDA打开so文件,在导出函数表里搜索Java,直接就定位到了,双击跳转到函数地址

对反汇编的内容稍作处理,得到如下内容
1 | __int64 __fastcall Java_com_mengxiang_arch_security_MXSecurity_signV3( |
调用了digest方法,是SHA256签名算法,参数是sha256_data,分析这个参数是怎么生成的
它是由appid,svs、noncestr、timestamp、secret、url、body这些内容拼接而成的
目前还有两个未知参数appid和secret
appid

这里看到appid的值是lpAppInfo加上一个偏移,偏移与isDebug有关

可以直接用frida直接打印内存中的数据

isDebug的值为0,所以v21的值为32
接下来打印lpAppInfo的值

lpAppInfo的值为773f4e8b70,lpAppInfo是个二级指针,加上偏移,打印它在内存中的数据

还是指向一个地址,继续打印

这里就得到appid的值为38741001
secret

这里和appid参数一样,只需要修改偏移就可以打印secret的值

继续打印

这里得到secret的值为04fdc5e4d9c7420e896ee92b17c68e9f
hook digest
参数分析完成了,接下来hook digest函数
1 | function hook_dlopen(){ |
hook 结果
1 | hooked |
这里有些小疑惑,args[2]是加密的数据,应该是一个指针,指向一个字符串,而这里是一个数值
找到这个变量定义的地方,原来它的类型是jbyteArray类型,打印出来的值实际上是jbyteArray的句柄值。

打印jbyteArray,我们可以调用如下方法
1 | function jbyteArray2Array(jbyteArray) { |
加入到hook代码中
1 | function hook_dlopen(){ |
hook 结果如下
1 | hooked |
参数还原
1 | import requests |