某东sign算法分析记录(萌新向)
app 版本:12.2.2
抓包
用Charles抓包,可以用Structure查看 api.m.jd.com
里面的接口,很多 client.action
的包
看一下contents,可以根据这个 x-api-eid-token
定位签名逻辑
x-api-eid-token
x-api-eid-token 是一个用于身份验证和授权的关键参数,通过API网关传递给后端服务,实现流程大概分为三步:
- 客户端发起请求到API网关
- API网关验证并生成 x-api-eid-token
- API网关把 x-api-eid-token 传递给后端
静态分析
扔 jadx 搜索 EID_TOKEN
,再找到交叉引用
定位到这个函数,输入一个参数 z
,生成一个包含各种设备和用户信息的URL参数字符串,下面逐段分析一下
1 | private static String buildPublicStateUri(boolean z) { |
看起来就是一个组包字符串的函数,获取各种信息组成一个字符串并返回,那么不出意外后面可能会有加密
查看其交叉引用,也是在拼接字符串组包,继续找交叉引用
接着找交叉引用
再网上交叉引用,找到加密函数
前面都是在检查是否是第三方应用然后进行一些操作,看一下后面的逻辑
1 | // 获取统计报告字符串 |
跟进 signature
函数
查找 ISignatureHandler
交叉引用找到这个类
这里是初始化函数
查看其交叉引用发现了很长的一个初始化代码,是在 initialize
,点进 getSignatureHandler
1 | JDHttpTookit.initialize(JDHttpTookit.newBuilder(JdSdk.getInstance().getApplication()).setAppProxy(JDNetworkDependencyFactory.getAppProxy()).setRuntimeConfigImpl(JDNetworkDependencyFactory.getRuntimeConfigImpl()).setStatInfoConfigImpl(JDNetworkDependencyFactory.getStatInfoConfigImpl()). |
点进 getSignFromJni
方法
看到 native
方法,好似,有的忙了
SO 层分析
看一下 package 是com.jingdong.common.utils
,我们要找到这些 native 函数是在哪个 so 文件里被声明的。
这里可以 hook dlsym 然后在通过加载的 so 文件枚举每个 so 文件的导出表来定位,脚本如下
1 | function hook_dlsym() { |
打印日志信息到文件中
1 | frida -U -f com.jingdong.app.mall -l hookJD.js --no > hookJD_output.log 2>&1 |
定位到 getSignFromJni
是在 libjdbitmapkit.so
这个文件中被调用的
这里遇到一个问题: Process.enumerateModules()
和 hook dlsym
两种方法都是为了得到 modules,那么这两种方法有什么区别呢?
先来看官方文档的解释:
而 dlsym 则涉及到运行时的动态链接,尽管加载时的动态链接是大部分编译加载系统都在使用的方式,操作系统同样提供了运行时加载和链接共享库的方式。其可以通过共享库的方式来让引用程序在下次运行时执行不同的代码,这也为应用程序 Hook 系统函数提供了基础。
Unix-like 系统提供了 dlopen
,dlsym
系列函数来供程序在运行时操作外部的动态链接库,从而获取动态链接库中的函数或者功能调用。
GPT 答:
Process.enumerateModules()
的工作机制
- 工作原理:
Process.enumerateModules()
直接从操作系统中获取当前进程的内存映射(类似于读取/proc/<pid>/maps
文件)。它枚举了进程中已经加载到内存的所有模块(如 SO 文件、可执行文件和动态库)。 - 时间点:只能获取到当前已经加载进内存的模块。这些模块通常是在进程启动时或静态加载时加载的模块。
- 局限性:
- 不能捕获动态加载的模块,尤其是使用
dlopen
在运行时动态加载的模块。如果某个模块是在你调用Process.enumerateModules()
之后才加载的,它不会出现在结果中。 - 也不会捕获那些没有经过
dlopen
或dlsym
的模块符号。
- 不能捕获动态加载的模块,尤其是使用
Hook dlsym
的工作机制
- 工作原理:
dlsym
是一个标准的动态链接库符号查找函数。当应用程序动态查找符号时(尤其是使用dlopen
加载动态库时),它会调用dlsym
查找导出的函数和变量的地址。通过 Hookdlsym
,可以拦截每次符号解析的操作,从而获取运行时加载的动态库及其符号。 - 时间点:
dlsym
可以捕获动态加载的模块,即便它们是在应用程序运行时才加载的。每当dlsym
解析符号时,无论符号在哪个模块中(包括动态加载的 SO 文件),都可以通过 Hook 获取这些符号和模块。 - 优势:
- 可以捕获通过
dlopen
动态加载的库和符号,即使这些库是在脚本运行后才被加载。 - 能捕获所有使用
dlsym
的符号解析,无论何时加载模块。
- 可以捕获通过
区别总结:
特性 | Process.enumerateModules() |
Hook dlsym |
---|---|---|
作用时间 | 列举已经加载的模块 | 捕获运行时动态加载的模块 |
获取方式 | 通过操作系统的内存映射表获取 | 通过拦截 dlsym 符号解析获取 |
捕获范围 | 只能捕获已经加载的模块 | 捕获所有动态加载和解析的符号及模块 |
动态库支持 | 不能捕获运行时加载的动态库 | 可以捕获通过 dlopen 加载的库 |
适用场景 | 快速列举进程中当前的所有模块 | 动态追踪应用程序加载的库和符号 |
JNI_OnLoad有动态注册,看一下结构体
1 | typedef struct { |
我们找到这个 libjdbitmapkit.so
文件中的 getSignFromJni
函数
findhash
查一下(一个ida插件)
把函数 sub_1900
丢个GPT分析
是一个 MD5
算法
这个函数是根据方法名称进行调用,我们给他命名成 callJavaObjectMethod
,前面还有一个新建 object
的函数
看一下前面的逻辑,建了一个空的对象,然后把 json 信息提取出来用 append 方法连接起来,再转成 string,丢尽函数 sub_18C9C
里面
1 | v82[3] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); |
但是前面有两个随机数也进去了,看起来在进入 base64 和 md5 之前还有一个加密操作,我们重点分析 sub_18C9C
该函数前面是利用随机数计算出一个 v19
,看起来是随机使用了 3 种方法进行加密,我们先分析 case 2
分支的加密函数
修一下变量声明
就一个 sub_1882C
有逻辑,第一个参数 v8 不用看,看起来该算法不是很复杂
1 | _QWORD *__fastcall sub_1882C(_QWORD *result, _BYTE *a2, char a3, jbyte *input, int len) |
直接用 frida 对该函数进行 hook ,偷一个脚本,对入参和函数执行结束后参数的值进行 hook
在 TypeScript 中,
// @ts-ignore
是一个注释指令,用于告诉 TypeScript 编译器忽略下一行代码的类型检查。它的主要作用是在某些情况下,当你明确知道代码的行为是正确的,但 TypeScript 编译器可能会发出类型错误或警告时,使用
// @ts-ignore
可以暂时禁用这些类型检查,以便代码能够顺利编译和运行。
1 | function hook_dlsym() { |
看一下结果,对比函数声明sub_1882C(_QWORD *result, _BYTE *a2, char a3, jbyte *input, int len)
,a2 是密钥80306f4370b39fd5630ad0529f77adb6
,input 是一个json转成的字符串,返回的时候明文被加密成了密文,也就是 args3 指向的地址,最后是长度
1 | [Pixel 6 Pro::京东 ]-> So: libjdbitmapkit.so Method: sub_1882C offset: 0x1882c |
拿到密钥以后就能对该加密算法进行复现了
1 |
|
流程就是加密 -> base64 -> md5,不过本次复现只实现了 case 2 的,剩下两个加密有点看不懂了,看文章说得 unidbg,赶紧学学
参考
[原创]记录一次某app的sign分析过程(篇章一)新手推荐-Android安全-看雪-安全社区|安全招聘|kanxue.com