对捷某特app的逆向

由于上一篇文章爬虫返回数据为无法常规解析的二进制数据

爬取捷某特骑行app 的骑行记录数据

所以必须对捷安特骑行app做反编译,查看数据的具体解析方式

所需工具

通用

  • jadx : apk反编译软件(windows)

    下载地址: https://github.com/skylot/jadx/releases

  • intellij idea : java代码IDE(可选)

仅针对捷安特骑行app

  • flatbuffers : 数据序列化方案

    下载地址,直接安装,只需要手动配置环境变量

https://github.com/google/flatbuffers/releases

环境:

  • python 3.10

  • java 17.0.2 2022-01-18 LTS

  • flatbuffers 2.0.0

  • 捷安特骑行apk 2.04

1.反编译捷安特骑行apk

使用jadx 打开apk:

把反混淆打开

2.定位需要的代码

2.1.搜索url

已知爬虫的url : xxxx/xxx/xxx/{id}/bin

所以用/bin为关键词进行搜索.注:类似python的Path库在拼接url时可以隐藏掉斜杠/,所以如果/bin搜索不到想要的结果,就需要使用bin作为关键词搜索.

可以看到搜索结果中有一条记录看起来非常符合我们的预期

双击进入该文件

可以看到这是一个接口,接下来右键点击该文件标头,复制类名,然后记下该接口的’函数’名称m6426b

2.2.搜索使用rul接口的具体位置

使用搜索框搜索上一步复制的类名,发现只有一个对应的文件,点进去

CTRL + F 打开文件内搜索,输入上一步记下的接口函数名,找到了一个调用该接口的地方

定位中的汉字"下载骑行记录"说明大概率是找对了地方

接着查看它的逻辑:

Log.i(str, "下載騎行紀錄 file = " + C6023g.this.f30290b);
byte[] data = responseBody.bytes();    // 从后端获取bin数据,就是我们爬虫爬取的bin文件
HistoryFileManager aVar = HistoryFileManager.f5170a;
String str2 = C6023g.this.f30290b;
Intrinsics.checkExpressionValueIsNotNull(data, "data");    // 这里是在验证数据的有效性
aVar.m5644a(str2, data);
return BicyclingRecord.m6074a(ByteBuffer.wrap(data)); // 最终将data交给了m6074a,我们点进去看一下

2.3.查看处理逻辑

这里的逻辑显示,最终返回了 BicyclingRecord 的一个对象

这是一个什么对象呢? 我们看 class BicyclingRecord extends Table

这个 Table 是由 import com.google.flatbuffers.Table;导入的

由百度查询可知,flatbuffers 是一种序列化方式, 我们之前爬取的bin文件就是通过 flatbuffers 格式化后的数据,还不理解的可以想象一下如果爬取的是json文件,那就是json格式化后的数据.

2.4.提取 flatbuffers 的构造文件

在上一步打开的文件,所在的包路径下,可以看到三个以 BicyclingRecord…开头的文件,他们都拥有非常相似的文件结构,我们可以猜测这三个文件就是 flatbuffers 的自定义构造文件.

于是我们拷贝这三个文件,与其依赖的包,使用intellij idea新建一个java项目

其中有一些常量依赖需要导入包的,我们可以直接复制常量的值进行替换,而无需导入包

3.java环境下使用bin文件进行测试

由于原java文件是经过反编译的代码,所以充斥着类似 m6074a这种混乱名称的接口

我们想要找到正确的接口进行测试,就需要对 flatbuffers 的构造文件与调用方式,有一定的了解

这里给出一个非常简单的例子(比官方的例子简单很多,非常容易理解):

python 使用 flatbuffers 序列化数据,然后java 使用 flatbuffers 对该数据进行反序列化

https://www.cnblogs.com/zhouyang209117/p/8087258.html?utm_source=tuicool

文中所说生成一堆XXX代码,即是生成对应语种的构造文件,我们需要生成这些构造文件,并与拷贝的捷安特骑行app构造文件进行对比观察,就能找出正确的接口名称.

这里我们使用python的爬虫,爬取一份bin数据,并保存为 ceshi.bin 文件

下面直接贴出java的测试代码:(java 代码写的丑轻喷,我不会java 的语法…)

import p380tw.com.program.ridelifegc.biking.data.BicyclingRecord;
import p380tw.com.program.ridelifegc.biking.data.BicyclingRecordLap;
import p380tw.com.program.ridelifegc.biking.data.BicyclingRecordSecData;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    private void process() {
        Path path = Paths.get("C:\\Users\\55363\\Desktop\\test2\\src\\ceshi.bin");
        try {
            byte[] data = Files.readAllBytes(path);
            ByteBuffer buf = java.nio.ByteBuffer.wrap(data);
            BicyclingRecord new_rcord = BicyclingRecord.m6074a(buf);

            get_total_data(new_rcord);
            get_lap_data(new_rcord);
            get_sec_data(new_rcord);
        } catch (IOException e) {
            System.out.println("IOException");
        }
    }

    private void get_total_data(BicyclingRecord new_rcord) {
        System.out.println(new_rcord.m6084a()); // 开始时间(时间戳/秒)
        System.out.println(new_rcord.m6071b()); // 结束时间(时间戳/秒)
        System.out.println(new_rcord.m6062c()); // 运动时间(秒)
        System.out.println(new_rcord.m6058d()); // 里程 m
        System.out.println(new_rcord.m6054e()); // 卡路里(kcal)
        System.out.println(new_rcord.m6051f()); // 最高速度(km*10/h)
        System.out.println(new_rcord.m6048g());    // 运动均速(单位同上)
        System.out.println(new_rcord.m6046h());    // 总体均速(单位同上)
        System.out.println(new_rcord.m6044i());    // 最高踏频
        System.out.println(new_rcord.m6042j());    // 平均踏频
        System.out.println(new_rcord.m6040k());    // 最高心率
        System.out.println(new_rcord.m6038l());    // 平均心率
        System.out.println(new_rcord.m6036m());    // 最大功率
        System.out.println(new_rcord.m6034n());    // 平均功率
        System.out.println(new_rcord.m6032o()); // 累计爬升(m)
        System.out.println(new_rcord.m6030p()); // 累计下降(m)
        System.out.println(new_rcord.m6028q()); // 最大海拔高度(m)
        System.out.println(new_rcord.m6027r()); // 最低海拔
        System.out.println(new_rcord.m6026s());
        System.out.println(new_rcord.m6025t());
        System.out.println(new_rcord.m6024u());
        System.out.println(new_rcord.m6023v());
        System.out.println("************************************************************");
    }

    private void get_lap_data(BicyclingRecord new_rcord) {
        for (int i = 0; true; i++) {
            try {
                BicyclingRecordLap new_lap = new_rcord.m6083a(i);
                // 获取每一段的数据(猜测使用手动暂停和接续骑行时会形成多段记录)

                System.out.println(new_lap.m6020a());
                System.out.println(new_lap.m6007b());    // 运动时间(秒)
                System.out.println(new_lap.m6001c());   // 里程
                System.out.println(new_lap.m5997d());   // 卡路里(kcal)
                System.out.println(new_lap.m5993e());   // 最高速度(km*10/h)
                System.out.println(new_lap.m5990f());   // 运动均速
                System.out.println(new_lap.m5987g());    // 总体均速
                System.out.println(new_lap.m5985h());    // 最高踏频
                System.out.println(new_lap.m5983i());    // 平均踏频
                System.out.println(new_lap.m5981j());    // 最高心率
                System.out.println(new_lap.m5979k());    // 平均心率
                System.out.println(new_lap.m5977l());    // 最大功率
                System.out.println(new_lap.m5975m());    // 平均功率
                System.out.println(new_lap.m5973n());   // 累计爬升(m)
                System.out.println(new_lap.m5972o());   // 累计下降(m)
                System.out.println(new_lap.m5971p());   // 最大海拔高度(m)
                System.out.println(new_lap.m5970q());   // 最低海拔
                System.out.println(new_lap.m5969r());
                System.out.println(new_lap.m5968s());
                System.out.println(new_lap.m5967t());
                System.out.println(new_lap.m5966u());
                System.out.println("--------------------------------------------------");
            } catch (Exception e) {
                System.out.println("lap: " + i);
                break;
            }
        }
    }

    private void get_sec_data(BicyclingRecord new_rcord) {
        for (int i = 0; true; i++) {
            try {
                BicyclingRecordLap new_lap = new_rcord.m6083a(i);
                new_lap.m6007b();
                for (int n = 0; true; n++) {
                    try {
                        BicyclingRecordSecData new_sec = new_lap.m6019a(n);

                        System.out.println(new_sec.getCurrentTime());    // 时间戳(s)
                        System.out.println(new_sec.getDataType());
                        System.out.println(new_sec.getCumulativeTotalSecond());    // 累计耗时
                        System.out.println(new_sec.getCumulativeTotalDistance());    // 累计里程(m)
                        System.out.println(new_sec.getCumulativeTotalCalories());    // 累计卡路里
                        System.out.println(new_sec.getSpeed());    // 速度(km*10/h)
                        System.out.println(new_sec.getCadence());    // 踏频
                        System.out.println(new_sec.getHr());    // 心率
                        System.out.println(new_sec.getPower());    // 功率
                        System.out.println(new_sec.getAltitude());    // 海拔
                        System.out.println(new_sec.getLatitude());    // 纬度
                        System.out.println(new_sec.getLongitude());    // 经度
                        System.out.println(new_sec.getHorizontalAccuracy());    // 水平精度
                        System.out.println(new_sec.getVerticalAccuracy());    // 垂直精度
                        System.out.println("#######################################");
                    } catch (Exception e) {
                        System.out.println("count: " + n);
                        break;
                    }
                }
            } catch (Exception e) {
                System.out.println("lap: " + i);
                break;
            }
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.process();
    }
}

运行后能够正确打印各项数据,说明测试成功.

4.使用python解析bin文件

两种思路:

4.1.python调用上面测试好的java 代码

奈何 jpype 包始终安装不上,放弃

4.2.根据 java 的构造文件,反推出 该项目所使用的数据格式的文件.fbs

这里需要将第3步中

python 使用 flatbuffers 序列化数据,然后java 使用 flatbuffers 对该数据进行反序列化

https://www.cnblogs.com/zhouyang209117/p/8087258.html?utm_source=tuicool

与该项目的代码反复对比观察,才能够手动反推.fbs 文件

下面贴出完整的.fbs文件代码:

注: 其中 类似于 m6026s 这样的未更名字段,暂时不知道代表什么意义,我解析过几份bin文件,这些字段都没有被赋值

// Example IDL file for our record's schema.
namespace record;
table BicyclingRecord {
    start_timestamp:long;
    end_timestamp:long;
    total_timer_time:int;
    total_distance:float;
    total_calories:float;
    max_speed:int;
    avg_speed:int;
    total_avg_speed:int;
    max_cadence:int;
    avg_cadence:float;
    max_heart_rate:int;
    avg_heart_rate:float;
    max_power:float;
    avg_power:float;
    total_ascent:double;
    total_descent:double;
    max_altitude:double;
    min_altitude:double;
    m6026s:int;
    m6025t:int;
    m6024u:int;
    m6023v:int;
    bicycling_record_lap:[BicyclingRecordLap];
}

table BicyclingRecordLap {
    m6020a:short;
    lap_timer_time:int;
    lap_distance:float;
    lap_calories:float;
    max_speed:int;
    avg_speed:int;
    lap_avg_speed:int;
    max_cadence:int;
    avg_cadence:float;
    max_heart_rate:int;
    avg_heart_rate:float;
    max_power:float;
    avg_power:float;
    lap_ascent:double;
    lap_descent:double;
    max_altitude:double;
    min_altitude:double;
    m5969r:int;
    m5968s:int;
    m5967t:int;
    m5966u:int;
    bicycling_record_sec_data:[BicyclingRecordSecData];
}

table BicyclingRecordSecData {
    current_time:long;
    data_type:byte;
    cumulative_total_second:int;
    cumulative_total_distance:float;
    cumulative_total_calories:float;
    speed:int;
    cadence:int;
    hr:int;
    power:float;
    altitude:double;
    latitude:double;
    longitude:double;
    horizontal_accuracy:int;
    vertical_accuracy:int;
}
root_type BicyclingRecord;

4.3.根据.fbs文件生成java 使用的构造文件,对测试数据进行再次验证

flatc --java record.fbs

我跳过了这一步

4.4.根据.fbs文件生成python使用的构造文件,进行测试

flatc --python record.fbs

编写python 的测试文件

下面贴出完整的python解析bin文件代码

from record import BicyclingRecord


def run():
    with open('ceshi.bin', 'rb') as f:
        buf = f.read()
    new_record = BicyclingRecord.BicyclingRecord.GetRootAs(buf)
    get_total_data(new_record)
    get_lap_data(new_record)
    get_sec_data(new_record)


def get_total_data(new_record):
    print(new_record.StartTimestamp())  # 开始时间戳
    print(new_record.EndTimestamp())  # 结束时间戳
    print(new_record.TotalTimerTime())  # 运动时间 秒
    print(new_record.TotalDistance())  # 里程 米
    print(new_record.TotalCalories())  # 卡路里 kcal
    print(new_record.MaxSpeed())  # 最高速度 km * 10 /h
    print(new_record.AvgSpeed())  # 运动均度
    print(new_record.TotalAvgSpeed())  # 总体均速
    print(new_record.MaxCadence())  # 最高踏频 转/分钟
    print(new_record.AvgCadence())  # 平均踏频
    print(new_record.MaxHeartRate())  # 最高心率 跳/分钟
    print(new_record.AvgHeartRate())  # 平均心率
    print(new_record.MaxPower())  # 最大功率 瓦
    print(new_record.AvgPower())  # 平均功率
    print(new_record.TotalAscent())  # 爬升高度 米
    print(new_record.TotalDescent())  # 下降高度 米
    print(new_record.MaxAltitude())  # 最大海拔 米
    print(new_record.MinAltitude())  # 最低海拔 米
    print(new_record.M6026s())
    print(new_record.M6025t())
    print(new_record.M6024u())
    print(new_record.M6023v())
    print('*' * 40)


def get_lap_data(new_record):
    lap_number = 0
    while True:
        try:
            new_lap = new_record.BicyclingRecordLap(lap_number)
            print(new_lap.M6020a())
            print(new_lap.LapTimerTime())  # 运动时间(秒)
            print(new_lap.LapDistance())  # 里程 米
            print(new_lap.LapCalories())  # 卡路里(kcal)
            print(new_lap.MaxSpeed())  # 最高速度 * 10(app显示34.2km / h, 解析数据为342)
            print(new_lap.AvgSpeed())  # 运动均速(单位同上)
            print(new_lap.LapAvgSpeed())  # 段落总体均速
            print(new_lap.MaxCadence())  # 最高踏频 转/分钟
            print(new_lap.AvgCadence())  # 平均踏频
            print(new_lap.MaxHeartRate())  # 最高心率 跳/分钟
            print(new_lap.AvgHeartRate())  # 平均心率
            print(new_lap.MaxPower())  # 最大功率 瓦
            print(new_lap.AvgPower())  # 平均功率
            print(new_lap.LapAscent())  # 累计爬升(m)
            print(new_lap.LapDescent())  # 累计下降(m)
            print(new_lap.MaxAltitude())  # 最大海拔高度(m)
            print(new_lap.MinAltitude())  # 最低海拔
            print(new_lap.M5969r())
            print(new_lap.M5968s())
            print(new_lap.M5967t())
            print(new_lap.M5966u())
            lap_number += 1
            print('-' * 40)
        except Exception as e:
            print(f'lap_number: {lap_number}')
            break


def get_sec_data(new_record):
    lap_number = 0
    while True:
        try:
            new_lap = new_record.BicyclingRecordLap(lap_number)
            print(new_lap.M6020a())
            lap_number += 1
            sec_number = 0
            while True:
                try:
                    new_sec = new_lap.BicyclingRecordSecData(sec_number)
                    print(new_sec.CurrentTime())  # 时间戳(s)
                    print(new_sec.DataType())
                    print(new_sec.CumulativeTotalSecond())  # 累计耗时
                    print(new_sec.CumulativeTotalDistance())  # 累计里程(m)
                    print(new_sec.CumulativeTotalCalories())  # 累计卡路里
                    print(new_sec.Speed())  # 速度(km * 10 / h)
                    print(new_sec.Cadence())  # 踏频
                    print(new_sec.Hr())  # 心率
                    print(new_sec.Power())  # 功率
                    print(new_sec.Altitude())  # 海拔
                    print(new_sec.Latitude())  # 纬度
                    print(new_sec.Longitude())  # 经度
                    print(new_sec.HorizontalAccuracy())  # 水平定位精度
                    print(new_sec.VerticalAccuracy())  # 垂直定位精度
                    sec_number += 1
                    print('#' * 40)
                except Exception as e:
                    print(f'sec_number: {sec_number}')
                    break
            print('-' * 40)
        except Exception as e:
            print(f'lap_number: {lap_number}')
            break


if __name__ == '__main__':
    run()

经运行,各项数据打印无误.轨迹点数据提取成功

end

附:flatbuffers 在win10下的使用步骤

FlatBuffers: FlatBuffers (google.github.io)

https://google.github.io/flatbuffers/index.html

1.下载 flatbuffers 主程序

Releases · google/flatbuffers (github.com)

https://github.com/google/flatbuffers/releases

2.解压,安装,添加环境变量

3.编写 Schema (.fbs) (数据格式文件)

深入浅出 FlatBuffers 之 Schema_weixin_34015336的博客-CSDN博客

https://blog.csdn.net/weixin_34015336/article/details/87941406

4.根据 Schema 生成对应语种的构造文件

flatc --[java/python/js/php/...] [test].fbs

5.调用生成的构造文件所暴露的接口

使用flatbuffers - 荷楠仁 - 博客园 (cnblogs.com)

https://www.cnblogs.com/zhouyang209117/p/8087258.html?utm_source=tuicool

某flutter-app逆向分析是指对于一个使用flutter框架开发的应用进行逆向工程分析。逆向工程是通过分析应用的代码、二进制文件等来了解其内部实现细节。 首先,我们需要获取该应用的安装包文件(APK或IPA文件),然后进行解包操作,将其转换为可读取的文件目录结构。 接下来,我们可以使用一些工具来提取应用的资源文件、代码文件等。对于flutter-app来说,可以提取出dart文件,这是flutter的主要代码文件,其中包含了应用的逻辑实现。 通过阅读dart文件,我们可以了解应用的代码结构、数据模型、界面设计等。可以分析应用的逻辑实现方法,包括各种函数、类、方法的调用关系。 同时,还可以通过分析相关配置文件、资源文件等来了解应用的各种设置、资源加载方式等。 在逆向过程中,还可以使用一些调试工具来进一步了解应用的运行机制。例如,hook工具可以拦截应用的函数调用,并捕获输入输出数据,用于进一步分析。 逆向分析的目的可以有很多,比如了解应用的工作原理、发现潜在的漏洞或安全问题、提供参考用于自己的开发等。 需要注意的是,逆向分析需要遵守法律规定。未经授权的逆向分析可能侵犯他人的知识产权,涉及到隐私等方面的问题。因此,在进行逆向分析之前,应该了解并遵守当地相关法律法规,避免产生法律纠纷。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值