2020-09-05 Sat

剖析某运动软件api


date: 2019-03-08T03:42:00.000Z updated: 2020-09-04T18:03:17.399Z title: 剖析某运动软件api slug: 剖析某运动软件api id: 15 permalink: notes/15

type: Note

这学期学校采用某款外包公司的运动软件来强制性制定运动项目.
名字我不说了.
下面来剖析该软件与服务器之间的交互.分析出他的api是怎么样的
安卓7以下使用packet capture进行抓包. 安卓7以上SSL通信无法正常进行.
这里没图 自己抓.我把我抓到的和post出去的json解释一遍.
1{
2	"startTime":1551860831865,  //时间截
3	"runningSportId":	18,  // 18对应快走,19对应慢跑,20对应快跑 后来通过某种方式得知 212223也对应 应该是跟学校有关 1 2 3 也是
4	"studentId":	47xxx,  // 应该是本人唯一值
5}
6
7// response start
8{
9	
10	"id": 5457xx,  // 后面的activity id 唯一值
11	"runningSportId": 18,  // 快走id
12	"endRunningSportId": null,
13	"studentId": 47580,
14	"distance": null,
15	"stepCount": null,
16	"costTime": null,
17	"speed": null,
18	"stepPerSecond": null,
19	"distancePerStep": null,
20	"targetFinishedTime": null,
21	"startTime": 1551860832272,  // 当前服务器时间截(毫秒) 13位
22	"kcalConsumed": null,
23	"qualified": null,
24	"isValid": null,
25	"isVerified": null,
26	"qualifiedDistance": 4000,
27	"qualifiedCostTime": 3360,
28	"minCostTime": null,
29	"endedAt": null,
30	"endedBy": null
31
32}
33
34// activitydata
35{
36	"distancePerStep": 0.0,
37	"locationType": 2,
38	"stepCountCal": 0,
39	"longitude": 120.xxxx,
40	"activityId": 551760,
41	"latitude": 27.9xxxxx, //坐标
42	"stepCount": 0,
43	"isNormal": true,
44	"distance": 0,
45	"stepPerSecond": 0.0,
46	
47}
48
49// response data
50{
51
52	"statusMsg": "数据提交成功",
53	"obj": {
54		"id": 64822721, // 随机值
55		"activityId": 5457xx, // start返回唯一值id
56		"acquisitionTime": 1551860832729, //当前服务器时间截(毫秒) 13位
57		"stepCount": 0,
58		"stepCountCal": 0,
59		"distance": 0,
60		"distancePerStep": 0.0,
61		"stepPerSecond": 0.0,
62		"longitude": 120.7071,
63		"latitude": 27.916,
64		"locationType": 4,
65		"isNormal": true
66
67	}
68}
69
70
71// end request
72{
73	"targetFinishedTime":0,
74	"costTime"	:141,  // 秒
75	"distance":	0, // 米
76	"stepCount"	:0, 
77	"id":545712, // 一次运动 唯一值
78}
79// response
80{
81	"id": 545712,
82	"runningSportId": 18,
83	"endRunningSportId": 18,
84	"studentId": 47580,
85	"distance": 0,
86	"stepCount": 0,
87	"costTime": 141,
88	"speed": 0.0,
89	"stepPerSecond": 0.0,
90	"distancePerStep": 0.0,
91	"targetFinishedTime": 0,
92	"startTime": 1551860832000,
93	"kcalConsumed": 4,
94	"qualified": false,
95	"isValid": true,
96	"isVerified": false,
97	"qualifiedDistance": 4000,
98	"qualifiedCostTime": 3360,
99	"minCostTime": 0,
100	"endedAt": 1551860975504,
101	"endedBy": null
102}
Copy
与api的交互包
总体过程:
首先向服务器发送一个start请求,可以看到start的header里有一个Authorization: 后面是JWT加密,通过一个.前面的用base64解密 发现是sha512加密.这里估计是个bug,只要你登陆上去抓到这个Authorization就行了,而且这个不是重点,因为JWT最后一位随便改个大写字母都行.但是这个Authorization不可缺. 确定用户的唯一值是 StudentId 下面看传出的data 首先是有个id 这个id很重要决定了本次运动的id唯一值,只要没有发送end指令,就可以向服务器发送runningactivityData.
然后不停的向服务器发送runningactivityData 这是什么,看看data和回传的json
发现这是实时距离,步数,位置(经纬度)等等
再来看看end
也是这些东西,而且这些东西都可以伪造. 结果取得是end发送的结果. 也就是说前面的runningactivityData只是用来绘制路线而已, 也是判断作弊用的吧

post模拟(v1.0 .路径模拟.)

说干就干 用python 进行模拟
1import requests
2import time
3import json
4import random
5from math import radians, cos, sin, asin, sqrt, pow
6url = 'https://api.guangyangyundong.com/'
7millis = int(round(time.time() * 1000))  # 获取时间截
8r = requests.session()
9start_time = int(time.time())
10
11# 全局变量
12distance = 0
13longitude = 120.70645
14latitude = 27.917463
15lon1 = 120.70645
16lat1 = 27.917463
17lon2 = 120.70645
18lat2 = 27.917463
19stepCount = 0
20targetFinishedTime = 0
21
22# start
23headers = {
24    'User-Agent': 'Mozilla/5.0 (Linux; U; Android 5.1; zh-cn; MX4 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36',
25    'Authorization': '',
26    'Content-Type': 'application/x-www-form-urlencoded'
27}
28data = {
29    'startTime': millis,
30    'runningSportId': 20,  # 跑步模式
31    'studentId': xxxxx,
32}
33
34js = r.post(url + 'api/runningActivities/start',
35            data=data, headers=headers).content
36js = json.loads(js)
37id = js["id"]  # 获取当前运动id
38
39print(id)
40
41# end activity
42
43
44def end():
45    global distance, targetFinishedTime, stepCount
46    time.sleep(random.randint(2, 3))  # 模拟停止动作
47    end_time = int(time.time()) - start_time  # 间隔时间
48    end_data = {
49
50        "targetFinishedTime": targetFinishedTime,  # 预测是目标完成时间
51        "costTime": end_time,
52        "distance": distance,  # 最后一次activity的距离
53        "stepCount": stepCount,  # 步数??
54        "id": id,
55    }
56
57    end_running = r.post(url + 'api/runningActivities/end',
58                         data=end_data, headers=headers).content
59    end_running = json.loads(end_running)
60    print(end_running)
61    exit(0)  # 退出
62
63
64def haversine(lon1, lat1, lon2, lat2):  # 经度1,纬度1,经度2,纬度2 (十进制度数)
65    """
66    Calculate the great circle distance between two points
67    on the earth (specified in decimal degrees)
68    """
69    # 将十进制度数转化为弧度
70    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
71
72    # haversine公式
73    dlon = lon2 - lon1
74    dlat = lat2 - lat1
75    a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
76    c = 2 * asin(sqrt(a))
77    r = 6371  # 地球平均半径,单位为公里
78    return c * r * 1000
79
80# running activity data post
81
82
83def anti_activity():  # 回走函数
84    global longitude, latitude, lat1, lon1, lat2, lon2, stepCount, distance, start_time, targetFinishedTime
85    if distance >= 1000:
86        targetFinishedTime = int(time.time()) - start_time
87        end()  # 1000m 结束 计算用时
88        return
89    rand = round(random.randint(1, 100) / 100) / 4200   # 引入随机值
90    lon2 = lon1 + pow(-1, random.randint(0, 1)) * \
91        round(random.randint(1, 100) / 100) / 1000000  # 左右偏移
92    lat2 = lat1 - rand
93
94    distance = round(distance + haversine(lon1, lat1, lon2, lat2))  # 总距离
95    distance_per = haversine(lon1, lat1, lon2, lat2)
96    stepCount_per = distance_per / 0.6
97    stepCount = round(distance_per / 0.6 + stepCount)
98    activity_data = {
99        # 每秒走了多少步
100        "distancePerStep": round(2.4 - random.randint(10, 60) / 100, 1),
101        "locationType": 1,  # 根据高德api 应返回1 为GPS定位
102        "stepCountCal": stepCount - random.randint(2, 8),  # 比总步数小一点 未证实
103        "longitude": lon1,  # 经纬度
104        "activityId": id,  # id唯一值
105        "latitude": lat1,
106        "stepCount": stepCount,  # 总步数
107        "isNormal": 'true',  # 未证实
108        "distance": distance,  # 距离累加 end应返回最后一个distance
109        # 平均速度
110        "stepPerSecond": round(3.5 - random.randint(100, 200) / 1000, 1),
111    }
112
113    lon1 = lon2
114    lat1 = lat2
115
116    print("当前经纬度", longitude)
117    print(latitude)
118    print("总距离", distance)
119    print("每次位移差", distance_per)
120    print('总步数', stepCount)
121    print("每次步数", stepCount_per)
122    print("每秒走多少步", activity_data["distancePerStep"])
123    print("平均速度", activity_data["stepPerSecond"])
124    log = r.post(url + 'api/runningActivityData',
125                 headers=headers, data=activity_data).content
126    log = json.loads(log)
127    print(log)
128    return
129
130
131def activity():
132    global longitude, latitude, lat1, lon1, lat2, lon2, stepCount, distance, start_time, targetFinishedTime
133    if distance >= 600:  # 如果大于630米要转弯 我觉得返回跑比较好 差不多这个点
134        anti_activity()
135        # targetFinishedTime = int(time.time()) - start_time
136        return
137    rand = random.randint(1, 100000) / 100000000
138    lon2 = lon1 + pow(-1, random.randint(0, 1)) * \
139        round(random.randint(1, 100) / 100) / 1000000  # 左右偏移 # 引入随机值
140    lat2 = lat1 + rand
141
142    distance = round(distance + haversine(lon1, lat1, lon2, lat2))  # 总距离
143    distance_per = haversine(lon1, lat1, lon2, lat2)
144    stepCount_per = distance_per / 0.6
145    stepCount = round(distance_per / 0.6 + stepCount)
146    activity_data = {
147        # 每秒走了多少步
148        "distancePerStep": round(2.4 - random.randint(10, 60) / 100, 1),
149        "locationType": 1,  # 根据高德api 应返回1 为GPS定位
150        "stepCountCal": stepCount - random.randint(2, 8),  # 比总步数小一点 未证实
151        "longitude": lon1,  # 经纬度
152        "activityId": id,  # id唯一值
153        "latitude": lat1,
154        "stepCount": stepCount,  # 总步数
155        "isNormal": 'true',  # 未证实
156        "distance": distance,  # 距离累加 end应返回最后一个distance
157        # 平均速度
158        "stepPerSecond": round(3.5 - random.randint(100, 200) / 1000, 1),
159    }
160    lon1 = lon2
161    lat1 = lat2
162    print("当前经纬度", longitude)
163    print(latitude)
164    print("总距离", distance)
165    print("每次位移差", distance_per)
166    print('总步数', stepCount)
167    print("每次步数", stepCount_per)
168    print("每秒走多少步", activity_data["distancePerStep"])
169    log = r.post(url + 'api/runningActivityData',
170                 headers=headers, data=activity_data).content
171    log = json.loads(log)
172    print(log)
173
174
175n = 0
176while n < 90:
177    activity()
178    n = n + 1
179    rand_time = random.randint(300, 400) / 100
180    print('sleep ', rand_time, ' s\n')
181    time.sleep(rand_time)  # 睡眠然后再次提交数据
182end()
Copy
希望有大佬可以进行完善,等等我会在github开源 希望有交流讨论

后续研究

该api存在严重漏洞. 比如可以根据studentId获取到任意学生的运动数据,不需要传入header. 然后根据id能获取每次运动路径 比如
1id = 544237
2{
3  "errors": [],
4  "data": {
5    "runningActivity": {
6      "distance": 1711,
7      "costTime": 2312,
8      "endedAt": 1551893421000,
9      "qualifiedDistance": 800,
10      "qualifiedCostTime": 300,
11      "kcalConsumed": 387,
12      "qualified": false,
13      "isValid": true,
14      "isVerified": true,
15      "runningSport": {
16        "name": "快跑"
17      },
18      "data": [
19        {
20          "longitude": 120.701575,
21          "latitude": 27.921444,
22          "isNormal": true,
23          "locationType": 5,
24          "stepCount": 0,
25          "distance": 0,
26          "acquisitionTime": 1551850108
27        },
28        {
29          "longitude": 120.70023546006945,
30          "latitude": 27.91632161458333,
31          "isNormal": false,
32          "locationType": 1,
33          "stepCount": 0,
34          "distance": 0,
35          "acquisitionTime": 1551850112
36        },
37        {
38          "longitude": 120.70006863064236,
39          "latitude": 27.91600802951389,
40          "isNormal": true,
41          "locationType": 1,
42          "stepCount": 1,
43          "distance": 38,
44          "acquisitionTime": 1551850116
45        },
46        {
47          "longitude": 120.6999134657118,
48          "latitude": 27.915694444444444,
49          "isNormal": true,
50          "locationType": 1,
51          "stepCount": 1,
52          "distance": 76,
53          "acquisitionTime": 1551850120
54        },
55        {
56          "longitude": 120.69976318359375,
57          "latitude": 27.91541232638889,
58          "isNormal": true,
59          "locationType": 1,
60          "stepCount": 1,
61          "distance": 110,
62          "acquisitionTime": 1551850124
63        },
64        {
65          "longitude": 120.69963433159722,
66          "latitude": 27.915142415364585,
67          "isNormal": true,
68          "locationType": 1,
69          "stepCount": 1,
70          "distance": 142,
71          "acquisitionTime": 1551850128
72        },
73		
74		......
Copy
当然其他学校数据也能看. 同理
Innei7 分钟前