App信息
包名:com.achievo.vipshop

设备注册
抓包找到有关设备注册的接口

测试一下
1 | import requests |
参数中device_token是变动的,对其进行逆向
反编译app搜索device_token

of

l

a

getMid

MidProvider是一个接口,Injector是静态内部类,MidProvider接口定义两个方法String getMid()和String getMidOnly()
找MidProvider接口的实现类

VipMidManager
在vipMidManager类里面找getMid方法,发现jadx并不能反编译这个函数,使用jeb反编译。

这个方法的解析
1 | getMid()方法根据一些条件生成或获取一个 MID,应该是设备唯一标识符。 |
生成uuid
1 | import uuid |
设备注册逆向
1 | import requests |
参数api_sign
分析数据包,找到Authorization参数的api_sign

jadx反编译
文本搜索api_sign

追踪函数
getApiSign

分析,getApiSign 函数用于为网络请求生成包含 API 签名的授权头。
猜测VCSPCommonsConfig.getIAppInfo().getUserTokenSecret() 的作用是从应用程序的配置中获取当前用户的令牌密钥,该密钥通常用于生成 API 签名或进行身份验证。
apisign

调用 VCSPSecurityConfig.getMapParamsSign(context, treeMap, str, false) 方法生成签名
hook
1 | function hook_function(){ |
hook结果
1 | VCSPSecurityBasicService.apiSign is called! |
getMapParamsSign

getMapParamsSign 函数用于生成 API 请求的签名
调用 getSignHash(context, treeMap, str2, z10) 方法,生成签名并返回。
getSignHash

调用 gs(context.getApplicationContext(), map, str, z10) 来生成签名哈希值。
gs
这里jadx不能正常反编译gs函数,使用jeb进行反编译

调用 VCSPSecurityConfig.initInstance() 方法进行初始化。
通过反射获取 clazz 对应的类中名为 "gs" 的方法,该方法接受 Context、Map、String 和 boolean 类型的参数。
使用反射调用获取到的 gsMethod 方法,传入参数 context0、map0、s 和 z,并将结果转换为 String 类型返回。
initInstance

初始化 VCSPSecurityConfig 类的静态成员 clazz 和 object。
clazz = KeyInfo.class
KeyInfo.gs
1 | public static String gs(Context context, Map<String, String> map, String str, boolean z10) { |
调用 gsNav(context, map, str, z10) 来生成签名。
gsNav
1 | private static native String gsNav(Context context, Map<String, String> map, String str, boolean z10); |
这是一个native方法
so
1 | static { |
hook gsNav
1 | function printMap(param_map){ |
结果
1 | ----hooked gsNav---- |
分析so
导出函数中找gsNav

Java_com_vip_vcsp_KeyInfo_gsNav
1 | __int64 __fastcall Java_com_vip_vcsp_KeyInfo_gsNav( |
Functions_gs
关键代码

getByteHash
1 | char *__fastcall getByteHash(JNIEnv *a1, jobject a2, __int64 a3, unsigned int a4, char *a5) |
这里看出这是SHA1算法
hook getByteHash
1 | function hook_native(){ |
示例一个hook结果
1 | arg2 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF |
如下验证了这是一个标准的SHA1算法

升级一下脚本继续hook
1 | function hook_native(){ |
hook结果
1 | ----hooked getByteHash---- |
主动调用gsNav方法,来观察getByteHash的输出结果
1 | function call_gsNav(){ |
输出
1 | ----call gsNav---- |
发现当主动调用gsNav函数的时候,主动调用了三次getByteHash方法,而第一次调用getByteHash方法时的入参和返回值都没有找到相关的信息,在IDA里查看getByteHash交叉引用,发现

发现在其他函数里也调用了getByteHash函数,溯源一下,最后找到是在Utils_ima方法里调用的,并不影响Function_gs函数调用的getByteHash。

分析
从Functions_gs函数里可以看出来,getByteHash函数被调用了两次,这个函数是标准SHA1算法,那么要分析的话就要拿连续的两个hook结果,从hook结果来看,getByteHash传入的第三个参数是map,但是它的开头加盐了,aee4c425dbb2288b80c71347cc37d04b,这个盐的值是固定的
如下是我对Functions_gs函数变量重命名后的关键代码

SaltMap是加盐后的Map的值,经过第一次SHA1签名之后,得到ByteHash,然后在ByteHash前面加盐,再一次进行SHA1签名,得到最终的api_sign
验证一下
1 | import hashlib |
还原完成
设备注册接口复现
这次我们可以用自己生成的api_sign来进行设备注册
1 | import requests |
搜索接口逆向
搜索接口抓包

逆向还原
1 | import requests |
Unidbg
模拟执行getNavInfo()

so是libkeyinfo.so
hook getNavInfo()
1 | function hook_getNavInfo(){ |
hook结果
1 | KeyInfo.getNavInfo is called: context=com.achievo.vipshop.common.VipApplicationLike@ff37661, str=skey |
call getNavInfo()
1 | function call_getNavInfo(){ |
结果
1 | KeyInfo.getNavInfo result=6692c461c3810ab150c9a980d0c275ec |
Unidbg执行getNavInfo()
1 | public class KeyInfo extends AbstractJni { |
模拟执行gsNav()

hook gsNav()
1 | function hook_gsNav(){ |
hook结果
1 | ----hooked gsNav---- |
unidbg执行gsNav()
1 | public class KeyInfo extends AbstractJni { |
运行一下,需要补环境
补entrySet()
1 | java.lang.UnsupportedOperationException: java/util/TreeMap->entrySet()Ljava/util/Set; |
补
1 | case "java/util/TreeMap->entrySet()Ljava/util/Set;":{ |
运行一下继续补环境
补iterator()
1 | java.lang.UnsupportedOperationException: java/util/Set->iterator()Ljava/util/Iterator; |
补
1 | case "java/util/Set->iterator()Ljava/util/Iterator;":{ |
运行一下继续补环境
补hasNext()
1 | java.lang.UnsupportedOperationException: java/util/Iterator->hasNext()Z |
补
1 | case "java/util/Iterator->hasNext()Z":{ |
运行一下继续补环境
补next()
1 | java.lang.UnsupportedOperationException: java/util/Iterator->next()Ljava/lang/Object; |
补
1 | case "java/util/Iterator->next()Ljava/lang/Object;":{ |
运行一下继续补环境
补getKey()
1 | java.lang.UnsupportedOperationException: java/util/Map$Entry->getKey()Ljava/lang/Object; |
补
1 | case "java/util/Map$Entry->getKey()Ljava/lang/Object;":{ |
补getValue()
1 | java.lang.UnsupportedOperationException: java/util/Map$Entry->getValue()Ljava/lang/Object; |
补
1 | case "java/util/Map$Entry->getValue()Ljava/lang/Object;":{ |
over
出结果了
1 | a05a0d94df429a4cdc0d2cd68bce1291b046d667 |