某壳 wll-kgsa参数算法分析及还原

简单记录一下~

版本信息:2.71.0

目标API:

用于查询二手房信息

1
https://apps.api.ke.com/house/ershoufang/detailpart0v1?houseCode={houseCode}&cityId={city_id}&fb_expo_id={fb_expo_id}

一、Headers中的签名参数

去除掉无用字段的headers 如下:

字段名称示例释义
Hostapps.api.ke.com
Cookielianjia_udid=0123456789123456;cookie中测试必须包含lianjie_udid,一个随机的16长度的字符串就可以
refererhomepage1
lianjia-version2.71.0
user-agentBeike2.71.0;google Pixel+3; Android 12
authorization签名字段
device-id-s65e11ee695f….设备ID
channel-sAndroid_ke_oppo
appinfo-sBeike;2.71.0;2710100
hardware-sgoogle;Pixel 3
systeminfo-sandroid;12
wll-kgsaLJAPPVA accessKeyId=sjoe98HI099dhdD7; nonce=Xvuq0VOC6JJ7s4Qwz7ZRtI2OEocaEkfF; timestamp=1745485231; signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s; signature=6Bc6i9srrjNug3w3Wt5xv4MzDs29zOdXkW2g+BKv0lM=签名字段

核心字段有两个:

  1. wll-kgsa

    这个参数之前是不校验的,大概是25.3月份左右开始检验了

  2. authorization

    最早只校验这个参数

二、wll-kgsa分析

老规矩,准备好jadx-gui以及frida和objection,涉及到so层的算法,还需要准备unidbg以及ida pro。

2.0 初步分析wll-kgsa值的组成

这是一个完整的wll-kgsa值示例,我在;位置做了换行处理,看起来容易些。

1
2
3
4
5
LJAPPVA accessKeyId=sjoe98HI099dhdD7;
nonce=Xvuq0VOC6JJ7s4Qwz7ZRtI2OEocaEkfF;
timestamp=1745485231;
signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s;
signature=6Bc6i9srrjNug3w3Wt5xv4MzDs29zOdXkW2g+BKv0lM=

看到signature和signedHeaders就可以大胆猜测一下signature这就是算法的核心计算结果,signedheaders和算法入参有关。

2.1 反编译APP寻找wll-kgsa生成位置

这里浪费了不少时间,因为直接在jadx中全局搜索wll-kgsa是找不到的,可以通过搜索LJAPPVA来定位,如图先定位到:

然后定位到:

最后定位到:

很显然,这是so层的算法,java层的定位只能到这了,注意这里它返回值是一个字符串数组,我们回过头来再看一下调用secmanager.sign的调用的位置com.ke.infrastructure.app.signature.algorithm.V1SignAlgorithm.sign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String[] sign(String str, String str2, Long l) {
...
try {
String[] sign = SecManager.sign(str, str2, l.toString());
if (sign != null && sign.length > 0) {
String lowerCase = sign[0].toLowerCase();
...
byte[] hexStrToByteArray = hexStrToByteArray(lowerCase);
...
sign[0] = Base64.encodeToString(hexStrToByteArray, 2);
...
}
return sign;
...
}

我省略了部分代码,可以看到SecManager.sign调用后结果的第一个字符串是一个16进制字符串,然后又进行了base64编码。

然后objection hook一下com.ke.infrastructure.app.signature.algorithm.V1SignAlgorithm.sign,看一下调用参数以及返回值:

1
2
3
(agent) [271610] Called com.ke.infrastructure.app.signature.algorithm.V1SignAlgorithm.sign(java.lang.String, java.lang.String, java.lang.Long)
(agent) [271610] Arguments com.ke.infrastructure.app.signature.algorithm.V1SignAlgorithm.sign(accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;2.71.0;2710100&channel-s=Android_ke_oppo&device-id-s=65e11ee695fe28b9;;020102kHHw3zUzxashorD9kotvrTgmN3s4pBpRzP5X89WHGGYwiMirFdoTwCnFFmq0Q1Qm7RJQc81PfJEu7cpNRq/dpQ==&hardware-s=google;Pixel 3&host=skdg.ke.com&method=POST&nonce=2Z6lmwTxCM5jcIrwkOqgZaPU7BNRp3O3&path=/up/q&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;12&timestamp=1742462622&user-agent=okhttp/3.12.6, sjoe98HI099dhdD7, 1742462622)
(agent) [271610] Return Value: q99+bfKC6EV/sD+kbewV4tGB7ehYLIufjFBbaOqGnOc=,28B22DAC5AAD6BFBDBFD2DBB4F40E878,accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;2.71.0;2710100&channel-s=Android_ke_oppo&device-id-s=65e11ee695fe28b9;;020102kHHw3zUzxashorD9kotvrTgmN3s4pBpRzP5X89WHGGYwiMirFdoTwCnFFmq0Q1Qm7RJQc81PfJEu7cpNRq/dpQ==&hardware-s=google;Pixel 3&host=skdg.ke.com&method=POST&nonce=2Z6lmwTxCM5jcIrwkOqgZaPU7BNRp3O3&path=/up/q&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;12&timestamp=1742462622&user-agent=okhttp/3.12.6,sjoe98HI099dhdD7,1742462622++q99+bfKC6EV/sD+kbewV4tGB7ehYLIufjFBbaOqGnOc=,sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781742462622,FA760E3A2B4DDC499B55EA31001EF21D++abdf7e6df282e8457fb03fa46dec15e2d181ede8582c8b9f8c505b68ea869ce7++-85-33126109-14-126-2469127-8063-92109-2021-30-47-127-19-248844-117-97-1168091104-22-122-100-25,FA760E3A2B4DDC499B55EA31001EF21D,ABDF7E6DF282E8457FB03FA46DEC15E2D181EDE8582C8B9F8C505B68EA869CE7

得到一组入参和出参:

  1. 入参
1
2
3
accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;2.71.0;2710100&channel-s=Android_ke_oppo&device-id-s=65e11ee695fe28b9;;020102kHHw3zUzxashorD9kotvrTgmN3s4pBpRzP5X89WHGGYwiMirFdoTwCnFFmq0Q1Qm7RJQc81PfJEu7cpNRq/dpQ==&hardware-s=google;Pixel 3&host=skdg.ke.com&method=POST&nonce=2Z6lmwTxCM5jcIrwkOqgZaPU7BNRp3O3&path=/up/q&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;12&timestamp=1742462622&user-agent=okhttp/3.12.6
sjoe98HI099dhdD7
1742462622
  1. 出参
1
q99+bfKC6EV/sD+kbewV4tGB7ehYLIufjFBbaOqGnOc=,28B22DAC5AAD6BFBDBFD2DBB4F40E878,accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;2.71.0;2710100&channel-s=Android_ke_oppo&device-id-s=65e11ee695fe28b9;;020102kHHw3zUzxashorD9kotvrTgmN3s4pBpRzP5X89WHGGYwiMirFdoTwCnFFmq0Q1Qm7RJQc81PfJEu7cpNRq/dpQ==&hardware-s=google;Pixel 3&host=skdg.ke.com&method=POST&nonce=2Z6lmwTxCM5jcIrwkOqgZaPU7BNRp3O3&path=/up/q&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;12&timestamp=1742462622&user-agent=okhttp/3.12.6,sjoe98HI099dhdD7,1742462622++q99+bfKC6EV/sD+kbewV4tGB7ehYLIufjFBbaOqGnOc=,sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781742462622,FA760E3A2B4DDC499B55EA31001EF21D++abdf7e6df282e8457fb03fa46dec15e2d181ede8582c8b9f8c505b68ea869ce7++-85-33126109-14-126-2469127-8063-92109-2021-30-47-127-19-248844-117-97-1168091104-22-122-100-25,FA760E3A2B4DDC499B55EA31001EF21D,ABDF7E6DF282E8457FB03FA46DEC15E2D181EDE8582C8B9F8C505B68EA869CE7

可以看出这个函数的返回值的一个值就是我们要的wll-kgsa中的signature,下面就可以使用unidb模拟调用一下secmanager.sign函数


2.2 使用unidbg模拟调用核心函数

前人栽树后人乘凉,大多时候我们能搞定某些样本不是自己有多厉害,而是站了在前辈的肩膀上。

unidbg具体用法就不多说了,搭好架子就可以开始patch了,常规的检测点,哪里报错补哪里就行,以下是一部分:

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

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;": {
return new ApplicationInfo(vm);
}
case "java/lang/Class->getPackage()Ljava/lang/Package;": {
DvmObject<?> e = vm.resolveClass("java/lang/Package").newObject(null);
return e;
}
case "java/lang/Package->getName()Ljava/lang/String;": {
return new StringObject(vm, "com.lianjia.beike");
}
case "java/io/File->getCanonicalPath()Ljava/lang/String;":{
return new StringObject(vm, "/data/data/com.lianjia.beike/files");
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}


@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "com/ke/securitylib/SecManager->getCertificateMD5Digest()Ljava/lang/String;":{
// frida hook一下真机的值
return new StringObject(vm, "28B22DAC5AAD6BFBDBFD2DBB4F40E878");
}
// 这里调用了java层的MD5函数,需要自己实现一下
case "com/ke/infrastructure/app/signature/util/MD5Utils->getMd5(Ljava/lang/String;)Ljava/lang/String;":{
String s = (String) vaList.getObjectArg(0).getValue();
MD5Util md5Util = new MD5Util();
String md5Result = md5Util.getMd5(s);
return new StringObject(vm, md5Result);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
}

有个比较麻烦的地方单独说一下,它内部调用了libc的free函数,这个函数得hook掉,我这里用的是xhook框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

private void handleFree(){
IxHook xHook = XHookImpl.getInstance(emulator);
xHook.register("libkmsec.so", "free", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
// return new HookStatus(0, 0, false);
// e =HookStatus.LR(emulator, 0);
// return HookStatus.LR(emulator, 0);
System.out.println("hook free");

return HookStatus.LR(emulator, 0);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
}
});
xHook.refresh();

}

最后看一下结果:

结果的第一个值和之前分析的一样,是一个16进制字符串,转成base64就是最后的signature

2.5 还原算法

前面补unidbg的时候我们补过一个md5函数,还有一个getCertificateMD5Digest函数调用,那就接着大胆猜测这是hmac md5算法。接着调出新时代逆向神器deepseek。直接把unidbg运行时的日志输出丢给deepseek就可以得到完整的算法了

三、authorization分析

说实话,这个我没这个版本中找到生成位置,但之前有搞过这个APP,试一了一下之前的算法也是可以用的,就是把请求参数排序后加个salt进行sha1,然后base64就出来了。

四、展示结果

某壳 wll-kgsa参数算法分析及还原

https://www.huihuidehui.com/posts/a70d8ab5.html

作者

lalaking

发布于

2025-04-25

更新于

2025-04-25

许可协议

评论