hitachimako 发表于 2024-12-12 23:32

逆向分析某红色跑步App的API

本帖最后由 hitachimako 于 2024-12-12 23:54 编辑

# 逆向分析某红色跑步App的API

## 前言
群友尝试破解自己学校的某红色校园跑App,来帮个忙

## 准备工具
- (https://hex-rays.com/ida-pro)
- (https://github.com/skylot/jadx)
- (https://reqable.com) 或使用(https://www.telerik.com/fiddler)代替
- (https://frida.re)
- 一台Root的Android手机(Android 13或以下)或虚拟机

## 分析数据包
使用Reqable抓取上传跑步数据时的请求,内容如图:


一眼看到Header存在一个校验字段:`_sign`,内容是一个大写的的16字节HexString

## 分析dex
这里已经怀疑可能是MD5,将Apk拖入jadx搜索`_sign`找到如图请求位置:


继续往下跟踪:



## 分析`libsign.so`
至此找到调用生成`_sign`的函数`getSignatureV2` 这个函数是一个native实现,经过对目录`lib/arm64-v8a`的寻找,发现极为可疑的文件`libsign.so`

将`libsign.so`导入IDA分析,可见JNI函数:


直接点开`Java_co_runner_app_jni_NativeToolImpl_getSignatureV2`查看逻辑:


发现逻辑如下:
1. Java层传入的第1, 2个参数(IDA中为a3, a4)作为内部函数`getSign`的第1, 2个参数使用
2. 内部函数`getSecret()`获取了一个`Secret`值,作为内部函数`getSign`的第3个参数使用

先分析`Secret`是怎么来的:


全部是一些`memcpy`,`strcat`之类的操作,说明`Secret`的生成路径是固定的,没有依靠时间等seed对其进行随机处理(`getSign1`和`getSign3`以及其调用的函数也没有使用随机,这里就先不放图了),所以我们基本可以认为使用`frida`对函数`getSecret()`进行钩取,可以获得其固定返回值。

(假装这里有一张截图)

由于我手上暂时没有 Android 13 及以下的环境,所以让群友代抓了一下`getSecret()`函数的返回值,多次发送请求进行抓取,发现`Secret`确实为固定值`20eca08916b92********2786e315dea`

拿到了三个参数,我们就可以研究函数`getSign`的逻辑了。双击`getSign`,跳转到函数中查看逻辑,如图:


分析起来不难,使用了三个`std::__put_character_sequence<char,std::char_traits<char>>`,基本可以看出来是把三个参数顺序连接了起来,而后面MD5函数的调用也印证了前面的猜想。

继续让群友代劳,抓了一些调用Java层调用`getSignatureV2`的参数和返回值,用来验证猜测是否正确。但是我在这一步卡了很久,因为我认为`Secret`在最后,最后多试了几种排列方法才发现`Secret`实际上在两个Java层传入参数的中间。

## 重新实现Java层参数
接下来看一下Java层传入的两个参数都是什么东西。

### 第一个参数
见上图分析dex时的图片可知,第一个参数是一个`Map<String, String> paramValueMap`经过`sort`得到的顺序,再以该顺序将其每个键与值从前到后拼接在一起得到的。就像这样:`contentheartrate[]lasttime1733995696meter196nodetimenomo...`

尝试在Python中还原该逻辑:
```Python
params = {}
for segment in dataStr.split("&"):
    if "=" in segment:
      k, v = segment.split("=", 1)
    else:
      k, v = segment, ""
    k = urllib.parse.unquote_plus(k)
    v = urllib.parse.unquote_plus(v)
    params = v

sorted_keys = sorted(params.keys())

sb = []
for key in sorted_keys:
    sb.append(key)
    sb.append(params)

dataFinalStr = "".join(sb)
```
库自带的`parse`会导致没有值的键消失,比如解析`&content=&xxx=***`的时候,`content`就离奇失踪了。

### 第二个参数
我们可以看到`uid > 0 ? (uid + sid).getBytes() : new byte` 其实就是把uid和sid直接拼一起而已。

## ~~王牌飞行员~~
接下来的事就是把`_sign`应用到脚本里了,效果如图:


## 一些花絮
- 一些大模型反编译平台(MLM01等)会出现搜索不到函数,还会出现AI瞎编代码逻辑的情况
- 同时使用 Android 14、15 和 KernelSU 的 Zygisk 模块时,会出现frida无法spawn程序的情况,报错为等待zygote程序(的PID)超时,尝试了一些办法(例如kill掉zygote进程)均无法解决,希望有大佬支招
- 这个App的34个dex让我的18G小内存在jadx搜索时不堪重负
- `Secret`的位置是上Linux课的时候摸鱼试出来的

## 尾声
谁写的`getSecret`,建议开除

Hawcett 发表于 2024-12-13 11:00

Zygisk和frida的spawn模式就是经常不对付,但是如果把zygisk卸载的话,lsposed可又用不了了,进而造成算法助手用不了了。我这里有两个解决方案
1. 如果不想放弃zygisk,现在的算法助手也支持导入Frida脚本了,拿算法助手替代frida的spawn模式,缺点就是操作都得在算法助手上进行,肯定是不如电脑hook的方便
2. 把zygisk换成riru,改成magisk + riru方案,我就是采用这种的,lsposed也有riru版的,测试后发现无论是frida的spawn和lsposed都能完美运行,目前还没发现其他BUG,我的设备是安卓9真机,高版本的未测试
希望对你有帮助

心中的影子 发表于 2024-12-13 11:59

感谢分享

wantwill 发表于 2024-12-13 16:17

感谢分享

zyt0339 发表于 2024-12-13 16:30

感谢分享, 给我提供了思路,niubilitity

Ok166 发表于 2024-12-13 21:35

感谢分享

han163426 发表于 2024-12-13 21:38

跑步app真是折磨

wolong11 发表于 2024-12-14 09:51

感谢分享思路

pomxion 发表于 2024-12-14 15:04

若然 发表于 2024-12-14 21:53

56万公里,楼主跑出国了嘛
页: [1] 2
查看完整版本: 逆向分析某红色跑步App的API