app 版本:12.2.2

抓包

用Charles抓包,可以用Structure查看 api.m.jd.com 里面的接口,很多 client.action 的包

QQ_1725592965134

看一下contents,可以根据这个 x-api-eid-token 定位签名逻辑

image-20240906113323944

x-api-eid-token

x-api-eid-token 是一个用于身份验证和授权的关键参数,通过API网关传递给后端服务,实现流程大概分为三步:

  1. 客户端发起请求到API网关
  2. API网关验证并生成 x-api-eid-token
  3. API网关把 x-api-eid-token 传递给后端

静态分析

扔 jadx 搜索 EID_TOKEN,再找到交叉引用

QQ_1725414391371

定位到这个函数,输入一个参数 z,生成一个包含各种设备和用户信息的URL参数字符串,下面逐段分析一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
private static String buildPublicStateUri(boolean z) {

StringBuffer stringBuffer = new StringBuffer();
try {
// 获取设备指纹,如果不为空,就添加到 REPORT_PARAM_EID 字段
String urlEncodeUTF8 = urlEncodeUTF8(DeviceFinger.getFinger(JdSdk.getInstance().getApplicationContext()));
if (!TextUtils.isEmpty(urlEncodeUTF8)) {
stringBuffer.append(EncryptStatParamController.REPORT_PARAM_EID);
stringBuffer.append(urlEncodeUTF8);
}
} catch (Throwable unused) {
}
String str = null;
try {
// 获得用户的pin码
if (UserUtil.getWJLoginHelper() != null) {
str = UserUtil.getWJLoginHelper().getPin();
}
// 从 BiometricManager 里面获得 token 缓存,通过UTF-8编码后作为参数 eid_token 连带上面的 pin 添加到 stringBuffer 中
// 通过交叉引用可以找到
// public static final String REPORT_PARAM_EID = "&eid=";
// public static final String REPORT_PARAM_EID_TOKEN = "&x-api-eid-token=";
// eid 就是设备指纹,x-api-eid-token 就是 下面的urlEncodeUTF82
String urlEncodeUTF82 = urlEncodeUTF8(BiometricManager.getInstance().getCacheTokenByBizId(JdSdk.getInstance().getApplicationContext(), "CCO-WAAP", str));
if (!TextUtils.isEmpty(urlEncodeUTF82)) {
stringBuffer.append(EncryptStatParamController.REPORT_PARAM_EID_TOKEN);
stringBuffer.append(urlEncodeUTF82);
}
} catch (Throwable unused2) {
}
String str2 = "0";
// z 是该函数的参数
if (z) {
// 获取设备的地理位置参数并进行一些替换处理,最后作为 area 参数添加到 stringBuffer 中。
String commonLbsParameter = LocManager.getCommonLbsParameter();
if (!TextUtils.isEmpty(commonLbsParameter)) {
String replace = commonLbsParameter.replace("-1", str2);
stringBuffer.append("&area=");
stringBuffer.append(replace);
}
}
// 获取设备的网络类型和WiFi的BSSID,并分别作为 networkType 和 wifiBssid 参数添加到 stringBuffer 中。
stringBuffer.append("&networkType=");
stringBuffer.append(DeviceInfoHelper.getNetworkTypeAccordingPrivacy());
stringBuffer.append("&wifiBssid=");
stringBuffer.append(BssidFetcher.getInstance().getBssid());
try {
// 获取用户的 uts 信息(可能是用户标识或追踪标识),并作为 uts 参数添加到 stringBuffer 中
String uts = PersonalInfoManager.getInstance().getUts();
if (!TextUtils.isEmpty(uts)) {
stringBuffer.append("&uts=");
stringBuffer.append(uts);
}
} catch (Throwable unused3) {
}
try {
// 老年模式信息 uemps
String uemps = JDElderModeUtils.getUemps();
if (!TextUtils.isEmpty(uemps)) {
stringBuffer.append("&uemps=");
stringBuffer.append(uemps);
}
} catch (Throwable unused4) {
}
// 是否是 harmonyOs
stringBuffer.append("&harmonyOs=");
JdSdk.getInstance();
if (JdSdk.isHarmonyOS()) {
str2 = "1";
}
// 扩展参数 ext
stringBuffer.append(str2);
stringBuffer.append("&ext=");
stringBuffer.append(ExtParamUtils.getExtParamsStr());
String verifyToken = JDHttpTookit.getEngine().getGuardVerifyPlugin().getVerifyToken();
if (!TextUtils.isEmpty(verifyToken)) {
// 获取验证Token
stringBuffer.append("&vt=");
stringBuffer.append(verifyToken);
}
// 检查设备是否支持AVIF格式图片
if (AvifImageUtils.avifEnable()) {
stringBuffer.append("&avifSupport=");
stringBuffer.append("1");
}
if (AvifImageUtils.avifBitmapConvertEnable()) {
stringBuffer.append("&acs=");
stringBuffer.append("1");
}
return stringBuffer.toString();
}

看起来就是一个组包字符串的函数,获取各种信息组成一个字符串并返回,那么不出意外后面可能会有加密

查看其交叉引用,也是在拼接字符串组包,继续找交叉引用

QQ_1725419897050

接着找交叉引用

QQ_1725420001680

QQ_1725420362085

再网上交叉引用,找到加密函数

QQ_1725420409104

前面都是在检查是否是第三方应用然后进行一些操作,看一下后面的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
   // 获取统计报告字符串
String statisticReportString = JDHttpTookit.getEngine().getStatInfoConfigImpl().getStatisticReportString("", true, true, z, null, null);
// 如果统计报告字符串不为空,将其解析为查询参数并添加到 builder 中
if (!TextUtils.isEmpty(statisticReportString) && (urlParams2 = getUrlParams(statisticReportString)) != null && !urlParams2.isEmpty()) {
for (String str8 : urlParams2.keySet()) {
if (TextUtils.equals(str8, "ep")) {
builder.addEncodedQueryParameter(str8, urlParams2.get(str8));
} else {
builder.addQueryParameter(str8, urlParams2.get(str8));
}
}
}
// 获取其他信息并计算签名
String queryParameter = request.url().queryParameter("functionId");
String property = InternalConfiguration.getProperty("client", str2);
String versionName = JDHttpTookit.getEngine().getStatInfoConfigImpl().getVersionName();
String deviceUUID = JDHttpTookit.getEngine().getStatInfoConfigImpl().getDeviceUUID(z);
if (OKLog.D) {
OKLog.d(TAG, "- ..functionId -->> " + queryParameter);
OKLog.d(TAG, "- ..body -->> " + str);
OKLog.d(TAG, "- ..uuid -->> " + deviceUUID);
OKLog.d(TAG, "- ..client -->> " + property);
OKLog.d(TAG, "- ..clientVersion -->> " + versionName);
}
try {
// 计算签名
String signature = JDHttpTookit.getEngine().getSignatureHandlerImpl().signature(JDHttpTookit.getEngine().getApplicationContext(), queryParameter, str, deviceUUID, property, versionName);
if (OKLog.D) {
OKLog.d("Signature", "native signature sucess " + signature);
}
if (TextUtils.isEmpty(signature) || (urlParams = getUrlParams(signature)) == null || urlParams.isEmpty()) {
return;
}
// 如果签名不为空,将其解析为查询参数并添加到 builder 中
for (String str9 : urlParams.keySet()) {
builder.addQueryParameter(str9, urlParams.get(str9));
}
} catch (Exception unused) {
}

跟进 signature 函数

QQ_1725421085696

查找 ISignatureHandler 交叉引用找到这个类

QQ_1725421356101

这里是初始化函数

QQ_1725421494813

查看其交叉引用发现了很长的一个初始化代码,是在 initialize,点进 getSignatureHandler

1
2
3
JDHttpTookit.initialize(JDHttpTookit.newBuilder(JdSdk.getInstance().getApplication()).setAppProxy(JDNetworkDependencyFactory.getAppProxy()).setRuntimeConfigImpl(JDNetworkDependencyFactory.getRuntimeConfigImpl()).setStatInfoConfigImpl(JDNetworkDependencyFactory.getStatInfoConfigImpl()).
setSignatureHandler(JDNetworkDependencyFactory.getSignatureHandler())
.setLoginUserControllerImpl(JDNetworkDependencyFactory.getLoginUserControllerImpl()).setExceptionReporter(JDNetworkDependencyFactory.getExceptionReportDelegate()).setNetworkControllerImpl(JDNetworkDependencyFactory.getNetworkControllerImpl()).setExternalDebugConfigImpl(JDNetworkDependencyFactory.getExternalDebugConfigImpl()).setCustomUIComponentImpl(JDNetworkDependencyFactory.getCustomUIComponentDependency()).setHttpDnsController(JDNetworkDependencyFactory.getHttpDnsControllerImpl()).setPhcEncryptionPlugin(JDNetworkDependencyFactory.getPhcEncryptionPlugin()).setHttpErrorDialogImpl(JDNetworkDependencyFactory.getHttpErrorAlertDialog()).setGateWayRespHeaderListener(JDNetworkDependencyFactory.getGatewayRespHeaderListenerImpl()).setGuardVerifyPlugin(JDNetworkDependencyFactory.getGuardVerifyPlugin()).setJDGuardPlugin(JDGuardHelper.getJDGuardPlugin()).setDownloadDomainResolver(JDNetworkDependencyFactory.getDownloadDomainResolver()).setBusinessJsonCodeListener(JDNetworkDependencyFactory.getBusinessJsonCodeEventListener()).setHardGuardVerifyPlugin(JDNetworkDependencyFactory.getHardGuardVerifyPlugin()).setCronetProvider(JDNetworkDependencyFactory.getCronetProvider()).setPerformanceReporter(JDNetworkDependencyFactory.getPerformanceReporter()).isPrintLog(Configuration.isBeta()).build());

QQ_1725425681513

点进 getSignFromJni 方法

QQ_1725425743620

看到 native 方法,好似,有的忙了

SO 层分析

看一下 package 是com.jingdong.common.utils,我们要找到这些 native 函数是在哪个 so 文件里被声明的。

这里可以 hook dlsym 然后在通过加载的 so 文件枚举每个 so 文件的导出表来定位,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hook_dlsym() {
var dlsymAddr = Module.findExportByName("libdl.so", "dlsym");
Interceptor.attach(dlsymAddr, {
onEnter: function(args) {
this.args1 = args[1];
},
onLeave: function(retval) {
var module = Process.findModuleByAddress(retval);
if (module === null) return;
console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base));
let exports = Module.enumerateExports(module.name);
for(let i = 0; i < exports.length; i++) {
console.log(JSON.stringify(exports[i]));
}
console.log("----------------------------------------------------------------")
}
});
}
setImmediate(hook_dlsym);

打印日志信息到文件中

1
frida -U -f com.jingdong.app.mall  -l hookJD.js --no > hookJD_output.log 2>&1

image-20240906230808807

定位到 getSignFromJni 是在 libjdbitmapkit.so 这个文件中被调用的

这里遇到一个问题: Process.enumerateModules()hook dlsym 两种方法都是为了得到 modules,那么这两种方法有什么区别呢?

先来看官方文档的解释:

image-20240906231716319

而 dlsym 则涉及到运行时的动态链接,尽管加载时的动态链接是大部分编译加载系统都在使用的方式,操作系统同样提供了运行时加载和链接共享库的方式。其可以通过共享库的方式来让引用程序在下次运行时执行不同的代码,这也为应用程序 Hook 系统函数提供了基础。

Unix-like 系统提供了 dlopendlsym 系列函数来供程序在运行时操作外部的动态链接库,从而获取动态链接库中的函数或者功能调用。

GPT 答:

Process.enumerateModules() 的工作机制

  • 工作原理Process.enumerateModules() 直接从操作系统中获取当前进程的内存映射(类似于读取 /proc/<pid>/maps 文件)。它枚举了进程中已经加载到内存的所有模块(如 SO 文件、可执行文件和动态库)。
  • 时间点:只能获取到当前已经加载进内存的模块。这些模块通常是在进程启动时或静态加载时加载的模块。
  • 局限性:
    • 不能捕获动态加载的模块,尤其是使用 dlopen 在运行时动态加载的模块。如果某个模块是在你调用 Process.enumerateModules() 之后才加载的,它不会出现在结果中。
    • 也不会捕获那些没有经过 dlopendlsym 的模块符号。

Hook dlsym 的工作机制

  • 工作原理dlsym 是一个标准的动态链接库符号查找函数。当应用程序动态查找符号时(尤其是使用 dlopen 加载动态库时),它会调用 dlsym 查找导出的函数和变量的地址。通过 Hook dlsym,可以拦截每次符号解析的操作,从而获取运行时加载的动态库及其符号。
  • 时间点dlsym 可以捕获动态加载的模块,即便它们是在应用程序运行时才加载的。每当 dlsym 解析符号时,无论符号在哪个模块中(包括动态加载的 SO 文件),都可以通过 Hook 获取这些符号和模块。
  • 优势:
    • 可以捕获通过 dlopen 动态加载的库和符号,即使这些库是在脚本运行后才被加载。
    • 能捕获所有使用 dlsym 的符号解析,无论何时加载模块。

区别总结:

特性 Process.enumerateModules() Hook dlsym
作用时间 列举已经加载的模块 捕获运行时动态加载的模块
获取方式 通过操作系统的内存映射表获取 通过拦截 dlsym 符号解析获取
捕获范围 只能捕获已经加载的模块 捕获所有动态加载和解析的符号及模块
动态库支持 不能捕获运行时加载的动态库 可以捕获通过 dlopen 加载的库
适用场景 快速列举进程中当前的所有模块 动态追踪应用程序加载的库和符号

JNI_OnLoad有动态注册,看一下结构体

image-20240907001646042

1
2
3
4
5
typedef struct {
const char* name; //动态注册的Java方法名
const char* signature; //描述方法参数和返回值
void* fnPtr; //指向实现Java方法的C/C++函数指针
} JNINativeMethod;

我们找到这个 libjdbitmapkit.so 文件中的 getSignFromJni 函数

image-20240906232331579

findhash 查一下(一个ida插件)

image-20240906234938265

把函数 sub_1900 丢个GPT分析

image-20240906235859364

是一个 MD5 算法

image-20240908215214389

这个函数是根据方法名称进行调用,我们给他命名成 callJavaObjectMethod,前面还有一个新建 object 的函数

看一下前面的逻辑,建了一个空的对象,然后把 json 信息提取出来用 append 方法连接起来,再转成 string,丢尽函数 sub_18C9C 里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
v82[3] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v13 = (*a1)->FindClass(a1, "java/lang/StringBuffer");
v14 = (*a1)->GetMethodID(a1, v13, "<init>", "()V");
JavaObject = (void *)createJavaObject(a1, v13, v14);
appendFunc = (*a1)->GetMethodID(a1, v13, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
v73 = v13;
toStringFunc = (*a1)->GetMethodID(a1, v13, "toString", "()Ljava/lang/String;");
v17 = (*a1)->NewStringUTF(a1, "&");
v18 = (*a1)->NewStringUTF(a1, "functionId=");
if ( v18 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v18);
v70 = v18;
if ( a3 )
callJavaObjectMethod(a1, JavaObject, appendFunc, a3);
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
v19 = (*a1)->NewStringUTF(a1, "body=");
if ( v19 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v19);
if ( a4 )
callJavaObjectMethod(a1, JavaObject, appendFunc, a4);
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
v20 = (*a1)->NewStringUTF(a1, "uuid=");
if ( v20 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v20);
if ( a5 )
callJavaObjectMethod(a1, JavaObject, appendFunc, a5);
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
v21 = (*a1)->NewStringUTF(a1, "client=");
if ( v21 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v21);
if ( a6 )
callJavaObjectMethod(a1, JavaObject, appendFunc, a6);
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
v22 = (*a1)->NewStringUTF(a1, "clientVersion=");
if ( v22 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v22);
if ( a7 )
callJavaObjectMethod(a1, JavaObject, appendFunc, a7);
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
memset(v82, 0, 21);
v23 = sub_18F7C((__int64)v82);
v24 = (*a1)->NewStringUTF(a1, v23);
v25 = (*a1)->NewStringUTF(a1, "st=");
if ( v25 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v25);
if ( v24 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v24);
v72 = v25;
v74 = v24;
v67 = v22;
v68 = v21;
if ( v17 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v17);
v65 = v17;
v26 = rand() % 3;
v27 = rand() % 3;
__android_log_print(4, "SignJni", "encryptId=%d,offset=%d", v26, v27);
v28 = (*a1)->NewStringUTF(a1, "sv=");
v29 = createIntegerObject(a1, 1u);
v30 = createIntegerObject(a1, v27);
v77 = (void *)createIntegerObject(a1, v26);
if ( v28 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v28);
if ( v29 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v29);
if ( v30 )
callJavaObjectMethod(a1, JavaObject, appendFunc, v30);
v71 = v29;
v66 = v28;
v69 = v20;
if ( v77 )
callJavaObjectMethod(a1, JavaObject, appendFunc);
str = (void *)callJavaObjectMethod(a1, JavaObject, toStringFunc);
v32 = (*a1)->GetStringUTFChars(a1, str, 0LL);
__android_log_print(4, "SignJni", "info = %s", v32);
v78 = (*a1)->FindClass(a1, "java/lang/String");
v33 = (*a1)->GetMethodID(a1, v78, "getBytes", "(Ljava/lang/String;)[B");
v79 = str;
v76 = (*a1)->NewStringUTF(a1, "UTF-8");
v34 = (void *)callJavaObjectMethod(a1, str, v33);
v35 = (*a1)->GetArrayLength(a1, v34);
v64 = v34;
v36 = sub_18C9C(a1, v34, v35, 1, v26, v27);
v37 = (*a1)->GetArrayLength(a1, v36);
__android_log_print(4, "SignJni", "begin base64 theArrayLengthJ = %d", v37);// v37 是length
v38 = (*a1)->GetByteArrayElements(a1, v36, 0LL);// b64加密对象是 v38
v39 = malloc(2 * v37);
memset(v39, 0, 2 * v37);
if ( v39 )
{
v63 = v19;
base64_encode(v39, (__int64)v38, v37); // 签名之前有个b64
memset(v81, 0, 32);
v40 = strlen(v39);
sub_25E0((int *)v39, v40, (__int64)v81); // 签名算法主体
v41 = s;
memset(s, 0, sizeof(s));
for ( i = 0LL; i != 32; ++i )
{
sprintf(v41, "%02x", (unsigned __int8)v81[i]);
v41 += 2;
}
s[32] = 0;
v43 = (*a1)->GetMethodID(a1, v73, "<init>", "()V");
v44 = (void *)createJavaObject(a1, v73, v43);
v45 = v44;
if ( v72 )
callJavaObjectMethod(a1, v44, appendFunc);
if ( v74 )
callJavaObjectMethod(a1, v45, appendFunc);
v46 = (*a1)->NewStringUTF(a1, s);
v47 = (*a1)->NewStringUTF(a1, "&sign=");
if ( v47 )
callJavaObjectMethod(a1, v45, appendFunc, v47);
if ( v46 )
callJavaObjectMethod(a1, v45, appendFunc, v46);
v48 = (*a1)->NewStringUTF(a1, "&sv=");
if ( v48 )
callJavaObjectMethod(a1, v45, appendFunc, v48);
if ( v71 )
callJavaObjectMethod(a1, v45, appendFunc);
if ( v30 )
callJavaObjectMethod(a1, v45, appendFunc, v30);
if ( v77 )
callJavaObjectMethod(a1, v45, appendFunc);
sub_18ACC((__int64)a1, (__int64)v45);
v49 = callJavaObjectMethod(a1, v45, toStringFunc);
if ( v48 )
(*a1)->DeleteLocalRef(a1, v48);
if ( v47 )
(*a1)->DeleteLocalRef(a1, v47);
v50 = v73;
if ( v46 )
(*a1)->DeleteLocalRef(a1, v46);
v51 = v69;
if ( v45 )
(*a1)->DeleteLocalRef(a1, v45);
free(v39);
v19 = v63;
v52 = v68;
}

但是前面有两个随机数也进去了,看起来在进入 base64 和 md5 之前还有一个加密操作,我们重点分析 sub_18C9C

image-20240908220354187

该函数前面是利用随机数计算出一个 v19,看起来是随机使用了 3 种方法进行加密,我们先分析 case 2 分支的加密函数

image-20240908221506852

修一下变量声明

image-20240908222010394

就一个 sub_1882C 有逻辑,第一个参数 v8 不用看,看起来该算法不是很复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
_QWORD *__fastcall sub_1882C(_QWORD *result, _BYTE *a2, char a3, jbyte *input, int len)
{
__int64 v9; // x8
int v10; // w13
int v11; // w14
jbyte v12; // w12
_QWORD v13[2]; // [xsp+0h] [xbp-40h] BYREF

v13[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( len )
{
result = sub_187DC(result, (__int64)a2, v13);
v9 = 0LL;
do
{
v10 = byte_1A1E0[v9 & 0xF];
if ( (a3 & 1) != 0 )
LOBYTE(v11) = byte_1A1E0[v9 & 0xF];
else
v11 = -v10;
v12 = ((v10 ^ input[v9] ^ a2[v9 & 7]) + v11) ^ v10;
input[v9] = v12;
input[v9] = v12 ^ a2[v9 & 7];
++v9;
}
while ( len != v9 );
}
return result;
}

直接用 frida 对该函数进行 hook ,偷一个脚本,对入参和函数执行结束后参数的值进行 hook

在 TypeScript 中,// @ts-ignore 是一个注释指令,用于告诉 TypeScript 编译器忽略下一行代码的类型检查。

它的主要作用是在某些情况下,当你明确知道代码的行为是正确的,但 TypeScript 编译器可能会发出类型错误或警告时,使用 // @ts-ignore 可以暂时禁用这些类型检查,以便代码能够顺利编译和运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function hook_dlsym() {
var dlsymAddr = Module.findExportByName("libdl.so", "dlsym");
Interceptor.attach(dlsymAddr, {
onEnter: function (args) {
this.args1 = args[1];
},
onLeave: function (retval) {
var module = Process.findModuleByAddress(retval);
if (module === null) return;
console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base));
let exports = Module.enumerateExports(module.name);
for (let i = 0; i < exports.length; i++) {
console.log(JSON.stringify(exports[i]));
}
console.log("----------------------------------------------------------------")
}
});
}

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

// @ts-ignore
function hook_native_func() {
var soAddr = Module.findBaseAddress("libjdbitmapkit.so");
var funcAddr = soAddr.add(0x1882C);
var module = Process.findModuleByAddress(funcAddr);
var paramsNum = 4;
try {
Interceptor.attach(funcAddr, {
onEnter: function (args) {
this.logs = "";
this.params = [];
// @ts-ignore
this.logs = this.logs.concat("So: " + module.name + " Method: sub_1882C offset: " + ptr(funcAddr).sub(module.base) + "\n");
for (let i = 0; i < paramsNum; i++) {
this.params.push(args[i]);
if (i == 0) {
// this.logs=this.logs.concat("this.args" + i + " onEnter: " +args[i].readCString());
}
else if (i == 3) {
this.logs = this.logs.concat("this.args" + i + " onEnter: \n" + hexdump(args[3]));
}
else {
this.logs = this.logs.concat("this.args" + i + " onEnter: \n" + print_arg(args[i]));
}
}
}, onLeave: function (retval) {
for (let i = 0; i < paramsNum; i++) {
this.logs = this.logs.concat("\nthis.args" + i + " onLeave: \n" + print_arg(this.params[i]));
}
this.logs = this.logs.concat("retval onLeave: \n" + print_arg(retval) + "\n");
console.log(this.logs);
}
})
} catch (e) {
console.log(e);
}
}

setImmediate(hook_native_func);

看一下结果,对比函数声明sub_1882C(_QWORD *result, _BYTE *a2, char a3, jbyte *input, int len),a2 是密钥80306f4370b39fd5630ad0529f77adb6,input 是一个json转成的字符串,返回的时候明文被加密成了密文,也就是 args3 指向的地址,最后是长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
[Pixel 6 Pro::京东 ]-> So: libjdbitmapkit.so  Method: sub_1882C offset: 0x1882c
this.args0 onEnter:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
733f90a2d8 90 05 13 66 74 00 00 00 60 a0 1a 96 74 00 00 00 ...ft...`...t...
733f90a2e8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
733f90a2f8 5b 1d 13 64 29 de 9f 56 b0 6b 17 06 75 00 00 00 [..d)..V.k..u...
733f90a308 90 a3 90 3f 73 00 00 00 29 05 00 00 00 00 00 00 ...?s...).......
733f90a318 01 00 00 00 00 00 00 00 d9 00 00 00 00 00 00 00 ................
733f90a328 f0 02 0f 16 75 00 00 00 20 a4 90 3f 73 00 00 00 ....u... ..?s...
733f90a338 ec 8e 59 38 74 00 00 00 34 34 65 37 31 35 61 36 ..Y8t...44e715a6
733f90a348 65 33 32 32 63 63 62 37 64 30 32 38 66 37 61 34 e322ccb7d028f7a4
733f90a358 32 66 61 35 35 30 34 30 00 00 00 00 00 00 00 00 2fa55040........
733f90a368 37 64 30 30 36 39 36 36 30 63 39 62 35 64 33 32 7d0069660c9b5d32
733f90a378 30 37 34 66 61 63 66 33 37 63 33 37 33 38 61 31 074facf37c3738a1
733f90a388 00 00 00 00 00 00 00 00 38 30 33 30 36 66 34 33 ........80306f43
733f90a398 37 30 62 33 39 66 64 35 36 33 30 61 64 30 35 32 70b39fd5630ad052
733f90a3a8 39 66 37 37 61 64 62 36 00 00 00 00 00 00 00 00 9f77adb6........
733f90a3b8 00 00 00 00 01 00 00 00 02 00 00 00 75 00 00 b4 ............u...
733f90a3c8 5b 1d 13 64 29 de 9f 56 c0 44 fb 70 00 00 00 00 [..d)..V.D.p....
this.args1 onEnter:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
733f90a390 38 30 33 30 36 66 34 33 37 30 62 33 39 66 64 35 80306f4370b39fd5
733f90a3a0 36 33 30 61 64 30 35 32 39 66 37 37 61 64 62 36 630ad0529f77adb6
733f90a3b0 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
733f90a3c0 02 00 00 00 75 00 00 b4 5b 1d 13 64 29 de 9f 56 ....u...[..d)..V
733f90a3d0 c0 44 fb 70 00 00 00 00 19 01 00 00 00 00 00 00 .D.p............
733f90a3e0 00 00 00 00 00 00 00 00 b5 04 00 00 00 00 00 00 ................
733f90a3f0 34 a2 59 38 74 00 00 00 0d 05 00 00 00 00 00 00 4.Y8t...........
733f90a400 35 04 00 00 00 00 00 00 41 00 00 00 00 00 00 00 5.......A.......
733f90a410 02 00 00 00 00 00 00 00 f0 02 0f 16 75 00 00 00 ............u...
733f90a420 60 a6 90 3f 73 00 00 00 2c 96 59 38 74 00 00 00 `..?s...,.Y8t...
733f90a430 18 00 00 00 00 00 00 00 0d 05 00 00 00 00 00 00 ................
733f90a440 61 00 00 00 00 00 00 00 b5 03 00 00 00 00 00 00 a...............
733f90a450 95 02 00 00 00 00 00 00 15 02 00 00 00 00 00 00 ................
733f90a460 95 01 00 00 00 00 00 00 81 00 00 00 00 00 00 00 ................
733f90a470 00 b0 90 3f 73 00 00 00 15 04 00 00 00 00 00 00 ...?s...........
733f90a480 35 03 00 00 00 00 00 00 2d 00 00 00 00 00 00 00 5.......-.......
this.args2 onEnter:
0x1
this.args3 onEnter:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7506176bb0 66 75 6e 63 74 69 6f 6e 49 64 3d 73 65 61 72 63 functionId=searc
7506176bc0 68 42 6f 78 57 6f 72 64 26 62 6f 64 79 3d 7b 22 hBoxWord&body={"
7506176bd0 61 70 70 54 79 70 65 22 3a 30 2c 22 67 65 6f 22 appType":0,"geo"
7506176be0 3a 22 56 4e 66 4f 32 77 51 48 6d 76 50 34 79 35 :"VNfO2wQHmvP4y5
7506176bf0 6e 4d 7a 47 35 37 61 6f 5a 36 4a 65 4d 54 47 70 nMzG57aoZ6JeMTGp
7506176c00 38 71 55 30 4d 58 73 73 70 37 53 4e 44 4f 46 50 8qU0MXssp7SNDOFP
7506176c10 64 53 53 31 6c 67 31 64 54 4d 62 52 70 43 49 45 dSS1lg1dTMbRpCIE
7506176c20 67 6c 22 2c 22 68 6f 6d 65 41 72 65 61 43 6f 64 gl","homeAreaCod
7506176c30 65 22 3a 22 30 22 7d 26 75 75 69 64 3d 31 62 35 e":"0"}&uuid=1b5
7506176c40 38 65 63 64 39 30 34 32 35 36 32 31 35 26 63 6c 8ecd904256215&cl
7506176c50 69 65 6e 74 3d 61 6e 64 72 6f 69 64 26 63 6c 69 ient=android&cli
7506176c60 65 6e 74 56 65 72 73 69 6f 6e 3d 31 32 2e 32 2e entVersion=12.2.
7506176c70 32 26 73 74 3d 31 37 32 35 38 31 30 33 30 31 32 2&st=17258103012
7506176c80 32 35 26 73 76 3d 31 30 32 00 80 00 00 00 00 00 25&sv=102.......
7506176c90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7506176ca0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
this.args4 onEnter:
0xd9

this.args0 onLeave:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
733f90a2d8 90 05 13 66 74 00 00 00 60 a0 1a 96 74 00 00 00 ...ft...`...t...
733f90a2e8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
733f90a2f8 5b 1d 13 64 29 de 9f 56 b0 6b 17 06 75 00 00 00 [..d)..V.k..u...
733f90a308 90 a3 90 3f 73 00 00 00 29 05 00 00 00 00 00 00 ...?s...).......
733f90a318 01 00 00 00 00 00 00 00 d9 00 00 00 00 00 00 00 ................
733f90a328 f0 02 0f 16 75 00 00 00 20 a4 90 3f 73 00 00 00 ....u... ..?s...
733f90a338 ec 8e 59 38 74 00 00 00 34 34 65 37 31 35 61 36 ..Y8t...44e715a6
733f90a348 65 33 32 32 63 63 62 37 64 30 32 38 66 37 61 34 e322ccb7d028f7a4
733f90a358 32 66 61 35 35 30 34 30 00 00 00 00 00 00 00 00 2fa55040........
733f90a368 37 64 30 30 36 39 36 36 30 63 39 62 35 64 33 32 7d0069660c9b5d32
733f90a378 30 37 34 66 61 63 66 33 37 63 33 37 33 38 61 31 074facf37c3738a1
733f90a388 00 00 00 00 00 00 00 00 38 30 33 30 36 66 34 33 ........80306f43
733f90a398 37 30 62 33 39 66 64 35 36 33 30 61 64 30 35 32 70b39fd5630ad052
733f90a3a8 39 66 37 37 61 64 62 36 00 00 00 00 00 00 00 00 9f77adb6........
733f90a3b8 00 00 00 00 01 00 00 00 02 00 00 00 75 00 00 b4 ............u...
733f90a3c8 5b 1d 13 64 29 de 9f 56 c0 44 fb 70 00 00 00 00 [..d)..V.D.p....

this.args1 onLeave:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
733f90a390 38 30 33 30 36 66 34 33 37 30 62 33 39 66 64 35 80306f4370b39fd5
733f90a3a0 36 33 30 61 64 30 35 32 39 66 37 37 61 64 62 36 630ad0529f77adb6
733f90a3b0 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
733f90a3c0 02 00 00 00 75 00 00 b4 5b 1d 13 64 29 de 9f 56 ....u...[..d)..V
733f90a3d0 c0 44 fb 70 00 00 00 00 19 01 00 00 00 00 00 00 .D.p............
733f90a3e0 00 00 00 00 00 00 00 00 b5 04 00 00 00 00 00 00 ................
733f90a3f0 34 a2 59 38 74 00 00 00 0d 05 00 00 00 00 00 00 4.Y8t...........
733f90a400 35 04 00 00 00 00 00 00 41 00 00 00 00 00 00 00 5.......A.......
733f90a410 02 00 00 00 00 00 00 00 f0 02 0f 16 75 00 00 00 ............u...
733f90a420 60 a6 90 3f 73 00 00 00 2c 96 59 38 74 00 00 00 `..?s...,.Y8t...
733f90a430 18 00 00 00 00 00 00 00 0d 05 00 00 00 00 00 00 ................
733f90a440 61 00 00 00 00 00 00 00 b5 03 00 00 00 00 00 00 a...............
733f90a450 95 02 00 00 00 00 00 00 15 02 00 00 00 00 00 00 ................
733f90a460 95 01 00 00 00 00 00 00 81 00 00 00 00 00 00 00 ................
733f90a470 00 b0 90 3f 73 00 00 00 15 04 00 00 00 00 00 00 ...?s...........
733f90a480 35 03 00 00 00 00 00 00 2d 00 00 00 00 00 00 00 5.......-.......

this.args2 onLeave:
0x1

this.args3 onLeave:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7506176bb0 af cb 2a fb 1f 34 9b ed 06 55 5a eb 73 cb 45 0d ..*..4...UZ.s.E.
7506176bc0 91 d0 2b d0 fa 2a ae eb e3 53 b4 dc 57 a7 52 cc ..+..*...S..W.R.
7506176bd0 aa c6 3c 2c 1c 33 91 a1 f7 21 75 9a 75 cf 46 cc ..<,.3...!u.u.F.
7506176be0 63 b0 12 26 09 0a 6e f6 0e b9 8a ee ae 9e 50 df c..&..n.......P.
7506176bf0 97 23 26 df d8 f2 9d ee 17 27 93 dd 5b be 5e 1a .#&......'..[.^.
7506176c00 61 c7 11 88 10 1b af f2 2d 28 78 c6 52 b1 59 fa a.......-(x.R.Y.
7506176c10 ad 21 1f 89 37 22 6d eb 11 be 8b ca 4e a5 a0 ef .!..7"m.....N...
7506176c20 90 c2 ee 84 c5 2b 9b ec 22 b2 9b dd 7f a5 46 0e .....+..".....F.
7506176c30 ae b0 e6 ba db ed a9 a5 32 66 b6 dc 0b 9b 75 df ........2f....u.
7506176c40 61 fb 2f fc dc f3 60 b1 f2 27 5b a9 03 88 7a 36 a./...`..'[...z6
7506176c50 92 fb 2a cc c0 2c 9a eb 2f 60 b6 dc 34 c5 47 33 ..*..,../`..4.G3
7506176c60 ae fc 30 2e 08 3d af e8 24 5f 5a a9 00 90 05 c8 ..0..=..$_Z.....
7506176c70 7b b4 3f cc c0 fc 63 b1 f2 29 5e a8 01 9a 08 dc {.?...c..)^.....
7506176c80 7b 8b e2 cb 19 f8 6d b7 ef 00 80 00 00 00 00 00 {.....m.........
7506176c90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7506176ca0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

this.args4 onLeave:
0xd9
retval onLeave:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
733f90a2d8 90 05 13 66 74 00 00 00 60 a0 1a 96 74 00 00 00 ...ft...`...t...
733f90a2e8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
733f90a2f8 5b 1d 13 64 29 de 9f 56 b0 6b 17 06 75 00 00 00 [..d)..V.k..u...
733f90a308 90 a3 90 3f 73 00 00 00 29 05 00 00 00 00 00 00 ...?s...).......
733f90a318 01 00 00 00 00 00 00 00 d9 00 00 00 00 00 00 00 ................
733f90a328 f0 02 0f 16 75 00 00 00 20 a4 90 3f 73 00 00 00 ....u... ..?s...
733f90a338 ec 8e 59 38 74 00 00 00 34 34 65 37 31 35 61 36 ..Y8t...44e715a6
733f90a348 65 33 32 32 63 63 62 37 64 30 32 38 66 37 61 34 e322ccb7d028f7a4
733f90a358 32 66 61 35 35 30 34 30 00 00 00 00 00 00 00 00 2fa55040........
733f90a368 37 64 30 30 36 39 36 36 30 63 39 62 35 64 33 32 7d0069660c9b5d32
733f90a378 30 37 34 66 61 63 66 33 37 63 33 37 33 38 61 31 074facf37c3738a1
733f90a388 00 00 00 00 00 00 00 00 38 30 33 30 36 66 34 33 ........80306f43
733f90a398 37 30 62 33 39 66 64 35 36 33 30 61 64 30 35 32 70b39fd5630ad052
733f90a3a8 39 66 37 37 61 64 62 36 00 00 00 00 00 00 00 00 9f77adb6........
733f90a3b8 00 00 00 00 01 00 00 00 02 00 00 00 75 00 00 b4 ............u...
733f90a3c8 5b 1d 13 64 29 de 9f 56 c0 44 fb 70 00 00 00 00 [..d)..V.D.p....

拿到密钥以后就能对该加密算法进行复现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void encrypt(char *input, int len)
{
const char* key = "80306f4370b39fd5630ad0529f77adb6";
unsigned char table[] =
{
0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0x0F,
0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A
};
uint8_t tByte, tmp;

for(int i = 0; i < len; i++)
{
tByte = table[i & 0xF];
tmp = ((tByte ^ input[i] ^ key[i & 7]) + tByte) ^ tByte;
*(uint8_t *)(input + i) = tmp;
*(uint8_t *)(input + i) = tmp ^ key[i & 7];
}
}
int main()
{
char input[] = "functionId=searchBoxWord&body={\"appType\":0,\"geo\":\"VNfO2wQHmvP4y5nMzG57aoZ6JeMTGp8qU0MXssp7SNDOFPdSS1lg1dTMbRpCIEgl\",\"homeAreaCode\":\"0\"}&uuid=1b58ecd904256215&client=android&clientVersion=12.2.2&st=1725810301225&sv=102";
int len = strlen(input);
printf("len: 0x%2x\n", len);
encrypt(input, len);
for(int i = 0; i < len; i++)
{
printf("%2x ", *(uint8_t *)(input + i));
if((i + 1) % 0xF == 0)printf("\n");
}
}

流程就是加密 -> base64 -> md5,不过本次复现只实现了 case 2 的,剩下两个加密有点看不懂了,看文章说得 unidbg,赶紧学学

参考

[原创]记录一次某app的sign分析过程(篇章一)新手推荐-Android安全-看雪-安全社区|安全招聘|kanxue.com

x-api-eid-token_mob64e737fed71c的技术博客_51CTO博客

某电商app的sign分析 - 怎么可以吃突突 - 博客园 (cnblogs.com)