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
当然其他学校数据也能看. 同理