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 |