某安X-App-Token参数逆向|基于unidbg模拟调用生成

记录使用unidbg还原so层加密函数的一次尝试

目标

使用unidbg对APP的接口签名字段X-App-Token进行生成。

目标APP: 6YW35a6JVjEzLjEuMQ==

1. 抓包分析

这一步很简单,使用Postern配置charles就可以完成抓包,找到获取动态详情的接口https://api2.coolapk.com/v6/feed/detail?id=44874711其中id为发文的id,这是一个接受post请求的接口,其中在请求headers中有两个醒目的字段X-App-VersionX-App-Device,经过测试两个字段缺一不可。

缺少字段的其中任一字段或进行篡改会提示请求未验证

1
2
3
4
5
{
"status": 1004,
"error": null,
"message": "请求未验证"
}

2. 反编译APP

这一步我们使用jadx进行反编译,需要注意这个app使用了360的加固,需要进行脱壳处理,我这使用https://github.com/hluwa/frida-dexdump进行脱壳。

安装frida-dexdump可以直接使用pip进行安装pip install frida-dexdump,要注意版本需要和你使用的frida-server版本对应。

这里提供我的版本信息供参考

版本备注
python3.7
frida-server16.0.0
frida-dexdump2.0.1
frida-tools12.0.1

之后在手机端打开某安APP,电脑端命令行中执行frida-dexdump -FU进行脱壳,-FU的含义是指定前台应用。

image-20230410153805568

完成之后会在当前目录下的{APP_NAME}目录中写入脱出来的dex文件。不过这时候脱出来的dex文件不能直接拿到jadx中进行反编译还需要使用https://github.com/luoyesiqiu/DexRepair进行头部修复。这里我写了一个小脚本来调用

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

import click
import pathlib
import os
from loguru import logger
from tqdm import tqdm


@click.command()
@click.option("--path", default="./", help="dex文件目录所有路径")
def main(path):
logger.info("""
需要在当前目录下放置文件"DexRepair.jar"\n
可在: "https://github.com/luoyesiqiu/DexRepair" 下载
""")
path = pathlib.Path(path)
for file_path in tqdm(path.rglob("*.dex")):
logger.info("start {}".format(file_path))
os.system("java -jar DexRepair.jar {}".format(file_path))

logger.info("completed")
if __name__ == "__main__":
main()

将修复后的dex文件拖入到jadx中进行分析。

3. 分析字段生成逻辑

jadx中全局搜索X-App-Token(这一步操作可能会很卡)。结果只有一条,不用纠结,就是它

image-20230410155154209

点进去可以看到appTokengetAS函数生成

image-20230410155340324

接着使用objection对这个函数进行hook

步骤:

  1. adb 连接手机,开启frida-server
  2. 重开一个 shell窗口指定命令objection -g "com.coolapk.market" explore
  3. 进入objection后在交互窗口指定命令android hooking watch class_method 'com.coolapk.market.util.AuthUtils.getAS' --dump-args --dump-return

没有问题的话会输出提示:

image-20230411094050346

📢这里在使用Objection hook前有个小细节,需要先将app卸载,之后断网环境下重装app,hook成功后再打开网络。如果不这么做的话会提示你Not found class。猜测是由于360加固的问题,断网是为了防止360加固通过网络更新。

打开网络后,我试着手机上滑动一下app,在objection的窗口那,会显示一些信息。可以看到已经打印出了getAS函数的入参和返回值。

image-20230411094423885

对比一下请求中的headers不难发现第二个参数就是X-App-Device返回值就是X-App-Token。接着我们进一步分析m44554函数

image-20230411094728525

有两个关键字:

  1. base64
  2. reverse

合理猜测X-App-Device是经过反转后的base64编码,接着我们X-App-Device反转回去并尝试base64解码.

image-20230411094945237 image-20230411094954980

最终拿到x-app-device的明文值DUzuPzp3eMbe8yfL6IeDaAfOOI0Q7ts0KSbc; ; ; ; Google; google; Pixel 3; SP1A.210812.016.C2; null,这个字符串分了好几个部分,测试后发现第一部分的字符串可以进行篡改不应该最终结果,这里就不再深入了。

总结一下,X-App-Device参数是由一些信息经过base64编码后又反转得到的;X-App-Token是由getAS函数进行加密得到的。

4. 使用unidbg进行模拟调用生成X-App-Token

在jadx中查看一下getAS函数,可以发现这是一个由jni进行注册的函数,对应so文件的名称是native-lib。接下来编写unidbg脚本进行模拟调用。

image-20230411095804756

这里不多介绍unidbg的使用,调用脚本源码如下:

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
package com.kuan;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.lvzhou.xvideo;

import java.io.File;

public class xAppToken extends AbstractJni{
private final AndroidEmulator emulator;
private final DvmClass NativeApi;
private final VM vm;
public xAppToken(){
// 初始化一个模拟器
// for64Bit代表使用arm64,一般arm32更常见
// setProcessName 设置进程名,推荐如实设置。如果不设置样本在调用getprogname时会返回'unidbg'可能会触发反爬
emulator = AndroidEmulatorBuilder
.for32Bit()
// .addBackendFactory(new Unicorn2Factory(true)) // 设置指令执行引擎.
.setProcessName("com.coolapk.market ")
.build();

Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));

vm = emulator.createDalvikVM(new File("unidbg/unidbg-android/src/test/resources/kuan/kuan.apk"));
vm.setJni(this);
vm.setVerbose(true);

DalvikModule dm = vm.loadLibrary("native-lib", true);

// 声明一个DvmClass类
// NativeApi = vm.resolveClass("com/weibo/xvideo/NativeApi");
NativeApi = vm.resolveClass("com/coolapk/market/util/AuthUtils");

dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
xAppToken t = new xAppToken();
// String result = xv.calls();
// System.out.println(result);

t.callGetAS();
}
public String callGetAS(){
// emulator.traceCode();

DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);

String xAppDevice = "RFU5Q1Bsd2FGcTVKS0gxYU1KSHI4bkZaNWxhd1BYbzFnazk1OyA7IDsgOTA6RjA6NTI6OTQ6NUQ6QzU7IG1laXp1OyBtZWl6dTsgMTZzIFBybzsgRmx5bWUgOC4xLjguMEE7IDg0YzFkN2U2ODcwMjBjY2U4ODIxM2ExZmFjMjE2OTU0";
String res = NativeApi.newObject(null).callJniMethodObject(emulator, "getAS(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;", context, xAppDevice).getValue().toString();
System.out.println(res);
return res;
}
}

执行结果如图:

image-20230411100220608

这里用的知识只是unidbg的基本使用,并没有涉及到补环境,还是非常简单的。

5. 总结

unidbg模拟调用相对于frida rpc来说成本相当低,只需要一台有java环境的服务器即可。

某安X-App-Token参数逆向|基于unidbg模拟调用生成

https://www.huihuidehui.com/posts/507c1bbe.html

作者

辉辉的辉

发布于

2023-04-10

更新于

2023-04-10

许可协议

评论