Landon's Blog
source link: https://blog.landon.li/2020/06/21/%E5%8C%97%E4%BA%AC%E5%AE%9E%E6%97%B6%E5%85%AC%E4%BA%A4/?amp%3Butm_medium=rss&%3Butm_campaign=%25e5%258c%2597%25e4%25ba%25ac%25e5%25ae%259e%25e6%2597%25b6%25e5%2585%25ac%25e4%25ba%25a4
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Landon's Blog
公司离地铁站有一站路的距离,每天下班的时候等一班公交的时间有点长。如果能掌握下一班车的准确到达时间,就能合理地安排出发时间,减少在车站里等候的时间。
有个叫北京实时公交的 app 提供北京市内许多公交的实时车况信息。
安装 app 后,通过抓包工具,可以获取 app 从接口请求的数据
全部线路信息
Request Head:
GET /ssgj/v1.0.0/checkupdate?version=0 HTTP/1.1
ABTOKEN: 5e428c5daf4c297d1cec300af7684361
NETWORK: gprs
PKG_SOURCE: 1
PID: 5
IMEI: ***已移除***
CTYPE: json
TIME: 1592734115
HEADER_KEY_SECRET: bjjw_jtcx
UA: ***已移除***
SID:
VID: 6
PLATFORM: android
UID:
CUSTOM: aibang
SOURCE: 1
IMSI: ***已移除***
CID: 67a88ec31de7a589a2344cc5d0469074
Host: transapp.btic.org.cn:8512
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.3.1
Body:
Response Head:
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 21 Jun 2020 10:05:09 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.0
Content-Encoding: gzip
Body:
{"errcode":"200","errmsg":"success","updateNum":"2941","lineNum":"1192","dataversion":"v1.1.0","lines":{"line":[{"id":"1","linename":"1(四惠枢纽站-老山公交场站)","classify":"1-999路","status":"0","version":"163"},{"id":"2","linename":"1(老山公交场站-四惠枢纽站)","classify":"1-999路","status":"0","version":"163"},{"id":"1195","linename":"机场巴士1(T3-方庄)","classify":"1-999路","status":"1","version":"5"},{"id":"1196","linename":"机场巴士1(方庄-T3)","classify":"1-999路","status":"1","version":"5"},{"id":"15","linename":"10(南菜园-航天桥东)","classify":"1-999路","status":"0","version":"114"},{"id":"16","linename":"10(航天桥东-南菜园)","classify":"1-999路","status":"0","version":"114"},{"id":"1201","linename":"机场巴士10(T3-北京南站)","classify":"1-999路","status":"1","version":"5"},{"id":"1202","linename":"机场巴士10(北京南站-T3)","classify":"1-999路","status":"1","version":"5"},
线路详细信息
Request Head:
GET /ssgj/v1.0.0/update?id=2361 HTTP/1.1
ABTOKEN: 9aa487c715154be434557d7865cd6ee4
NETWORK: gprs
PKG_SOURCE: 1
PID: 5
IMEI: ***已移除***
CTYPE: json
TIME: 1592734149
HEADER_KEY_SECRET: bjjw_jtcx
UA: ***已移除***
SID:
VID: 6
PLATFORM: android
UID:
CUSTOM: aibang
SOURCE: 1
IMSI: ***已移除***
CID: 67a88ec31de7a589a2344cc5d0469074
Host: transapp.btic.org.cn:8512
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.3.1
Body:
Response Head:
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 21 Jun 2020 10:05:43 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.0
Content-Encoding: gzip
Body:
{
"errcode":"200",
"errmsg":"success",
"busline":[
{
"lineid":"2361",
"shotname":"ekSl",
"linename":"ekSl2mI79LuvNBh06CJ77O4BqzyJgjjCKkJnWeEw",
"distince":"0.00",
"ticket":"分段计价",
"totalPrice":"0.00",
"time":"5:30-23:00",
"type":"0",
"coord":"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",
"status":"0",
"version":"211",
"stations":{
"station":[
{
"name":"rP83Fj0LsMS6ZVhQ",
"no":"eg==",
"lon":"ekeg3LaEYmsu",
"lat":"eE+4y7KEYGwh"
},
{
"name":"rP83Fj0LsMS6",
"no":"eQ==",
"lon":"ekeg3LaEbGci",
"lat":"eE+4y7KHYGg="
},
{
"name":"rvoBFyEYsOaka1xKoXJC",
"no":"eA==",
"lon":"ekeg3LaFZ2gv",
"lat":"eE+4y7OFYm4m"
},
{
"name":"ot86FREKs/6yaHB4",
"no":"fw==",
"lon":"ekeg3LaFbGYm",
"lat":"eE+4y7OEZW0="
},
{
"name":"rsghFyEksNqkaHNN",
"no":"fg==",
"lon":"ekeg3LaFbG4vuA==",
"lat":"eE+4y7CLYGol"
},
{
"name":"os0SFyoIsPuwZVx4rW9htkxL",
"no":"fQ==",
"lon":"ekeg3LaKZWov",
"lat":"eE+4y7OAbW0="
},
{
"name":"ou4lFwA7seeqa2RAoHpRtk9S",
"no":"fA==",
"lon":"ekeg3LaKYGch",
"lat":"eE+4y7OAbGs="
},
{
"name":"os0SFyoI",
"no":"cw==",
"lon":"ekeg3LaKbG0h",
"lat":"eE+4y7OBZW0v"
},
{
"name":"os0SFyoIsPuwZVx4oXJCtkxL",
"no":"cg==",
"lon":"ekeg3LaLZm4i",
"lat":"eE+4y7OBZGs="
},
{
"name":"rtgfFwg8vfqoZHpj",
"no":"ekY=",
"lon":"ekeg3LaLYm0g",
"lat":"eE+4y7OBZ2o="
},
{
"name":"rtIAGyM0s8mLZVx4",
"no":"ekc=",
"lon":"ekeg3LGCZ24l",
"lat":"eE+4y7OBZmk="
},
{
"name":"rsYZGz42sOWT",
"no":"ekQ=",
"lon":"ekeg3LGCbG4=",
"lat":"eE+4y7OAZW4="
},
{
"name":"ousEFzwGs+2IZVhQoEV9",
"no":"ekU=",
"lon":"ekeg3LGDZ2gv",
"lat":"eE+4y7ODbGgh"
},
{
"name":"ruQaFzwBvNibZVhQrWtJ",
"no":"ekI=",
"lon":"ekeg3LGDY2Yj",
"lat":"eE+4y7OAZWs="
},
{
"name":"ousEFzwGs+2IaUVzoEV9",
"no":"ekM=",
"lon":"ekeg3LGAYW0k",
"lat":"eE+4y7OAZGgi"
},
{
"name":"ruQaFzwBvNibZUpAoEV9t3t0",
"no":"ekA=",
"lon":"ekeg3LGAY28g",
"lat":"eE+4y7CLZmo="
},
{
"name":"ruomGxYzs8CkZXdcomFH",
"no":"ekE=",
"lon":"ekeg3LGBZ28jvg==",
"lat":"eE+4y7CKY2sv"
},
{
"name":"r84KFxktsMCZZUpA",
"no":"ek4=",
"lon":"ekeg3LGBZ2wl",
"lat":"eE+4y7CGbWY="
},
{
"name":"r84KFxktsMCZZUpAoEdJtkxL",
"no":"ek8=",
"lon":"ekeg3LGBZ2sm",
"lat":"eE+4y7CAbGs="
},
{
"name":"rvoBFysqsNGS",
"no":"eUY=",
"lon":"ekeg3LGBZWsl",
"lat":"eE+4y7GLbGo="
},
{
"name":"r84KFR4GvMi/aHF4",
"no":"eUc=",
"lon":"ekeg3LGBZmo=",
"lat":"eE+4y7GHZm4="
},
{
"name":"r84KFR4GvMi/",
"no":"eUQ=",
"lon":"ekeg3LGBY2wu",
"lat":"eE+4y7GDZWg="
},
{
"name":"re4zFSIyvei4aHJMoEZJ",
"no":"eUU=",
"lon":"ekeg3LGGYWg=",
"lat":"eE+4y7GAYA=="
},
{
"name":"re4zFSIyvei4aHF4oEV9",
"no":"eUI=",
"lon":"ekeg3LGGYWsi",
"lat":"eE+4y7GEYmk="
},
{
"name":"otAPFDcBsMS6a1xK",
"no":"eUM=",
"lon":"ekeg3LGGZGgv",
"lat":"eE+4y7GLZg=="
}
]
}
}
]
}
实时公交信息
Request Head:
GET /ssgj/bus.php?no=22&versionid=6&city=%E5%8C%97%E4%BA%AC&datatype=json&encrypt=1&id=2361&type=0 HTTP/1.1
ABTOKEN: 2c0f7826f353da66b014fc570933721a
NETWORK: gprs
PKG_SOURCE: 1
PID: 5
IMEI: ***已移除***
CTYPE: json
TIME: 1592734157
HEADER_KEY_SECRET: bjjw_jtcx
UA: ***已移除***
SID:
VID: 6
PLATFORM: android
UID:
CUSTOM: aibang
SOURCE: 1
IMSI: ***已移除***
CID: 67a88ec31de7a589a2344cc5d0469074
Host: transapp.btic.org.cn:8512
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.3.1
Body:
Response Head:
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 21 Jun 2020 10:05:51 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.0
Content-Encoding: gzip
Body:
{
"root":{
"status":"200",
"message":"success",
"encrypt":"1",
"num":"4",
"lid":"2361",
"data":{
"bus":[
{
"gt":"1592734135",
"id":"19934",
"t":"0",
"ns":"3Nhrf0eYcf0vlVos",
"nsn":"CE0=",
"nsd":"250",
"nsrt":"63",
"nst":"1592734198",
"sd":"D0rNpA==",
"srt":"CE3Org==",
"st":"CEnEpNYtolmETg==",
"crowding":"0",
"x":"CE3LuNInrlqL",
"y":"CkXTr9ctpFmL",
"lt":"0",
"ut":"1592734106"
},
{
"gt":"1592734112",
"id":"19990",
"t":"0",
"ns":"rUSGzlzCPnp1",
"nsn":"e84=",
"nsd":"-1",
"nsrt":"-1",
"nst":"-1",
"sd":"ZM0=",
"srt":"ZM0=",
"st":"ZM0=",
"crowding":"0",
"x":"eM0sB/NF4d/v",
"y":"esU0EPNH59vk",
"lt":"0",
"ut":"1592734086"
},
{
"gt":"1592734094",
"id":"20404",
"t":"0",
"ns":"1W3aRWz7ReWblKCC",
"nsn":"CA==",
"nsd":"50",
"nsrt":"3",
"nst":"1592734097",
"sd":"BfJElQ==",
"srt":"DfJAkQ==",
"st":"DfFPkM9wlnMLRg==",
"crowding":"0",
"x":"DfVAjMt0mn0LRg==",
"y":"D/1Ym851l3MJ",
"lt":"0",
"ut":"1592734061"
},
{
"gt":"1592733994",
"id":"19933",
"t":"0",
"ns":"P5ohr7huq7FuvGlhKF8p",
"nsn":"6zE=",
"nsd":"414",
"nsrt":"73",
"nst":"1592734067",
"sd":"9DM=",
"srt":"9DM=",
"st":"9DM=",
"crowding":"0",
"x":"6DOyZivacjP3bQ==",
"y":"6juqcSvfcjf3",
"lt":"0",
"ut":"1592733991"
}
]
}
}
}
从上面三个接口的返回数据可以看到,除了全部线路信息,线路详细信息和实时公交信息返回的数据都是加密过的,仅从抓包结果无法得到解密后的信息。这个时候就需要反编译 apk,通过查看源码来分析解密算法了。
请出 dex2jar 和 JD-GUI,通过 dex2jar 把 apk 导出为 jar 包,通过 JD-GUI 反编译出源码。
经过在源码中的一番探索,可以发现线路详细信息的类对应的是 com.aibang.nextbus.modle.DetailLine
,实时公交信息对应的类是 com.aibang.nextbus.modle.Bus
。
线路详细信息
首先查看 DetailLine
类,发现里面有个 decode
方法在解密数据。此方法调用了 com.aibang.nextbus.security.NextBusSecurityUtils
类的 decry
方法,并传入了加密的数据和 getKey
方法的返回值。
查看 getKey
方法,返回的是拼接字符串 aibang
和线路 ID 的 MD5 值,即 md5("aibang"+lineid)
。
lineid
从全部线路信息返回数据中获取
查看 decry
方法,先将加密的数据进行 base64.decode
,然后和 getKey
的返回值一起进行 RC4Base
。
Java 和 Python 的 byte 范围不一样。Java 中 byte 的范围是 - 127-128,Python 中 byte 的范围是 0-256。在进行 string 和 bytes 互转时要考虑到这个问题。
有了源码,很容易地能写出对应的 Python 版
class RC4:
def initKey(self, paramString: str):
arrayOfByte2 = paramString.encode()
arrayOfByte1 = []
for i in range(256):
if i <= 127:
arrayOfByte1.append(i)
else:
arrayOfByte1.append(-128 + i - 128)
j = k = 0
if arrayOfByte2 is None or len(arrayOfByte2) == 0:
return None
i = 0
while True:
arrayOfByte = arrayOfByte1
if (i < 256):
k = (arrayOfByte2[j] & 0xFF) + (arrayOfByte1[i] & 0xFF) + k & 0xFF
arrayOfByte1[i], arrayOfByte1[k] = arrayOfByte1[k], arrayOfByte1[i]
j = (j + 1) % len(arrayOfByte2)
i += 1
continue
return arrayOfByte
def RC4Base(self, paramArrayOfByte: List[int], paramString: str) -> List[int]:
k = j = 0
arrayOfByte1 = self.initKey(paramString)
arrayOfByte2 = [0] * len(paramArrayOfByte)
for i in range(len(paramArrayOfByte)):
k = k + 1 & 0xFF
j = (arrayOfByte1[k] & 0xFF) + j & 0xFF
arrayOfByte1[j], arrayOfByte1[k] = arrayOfByte1[k], arrayOfByte1[j]
b2 = arrayOfByte1[k]
b3 = arrayOfByte1[j]
arrayOfByte2[i] = (paramArrayOfByte[i] ^ arrayOfByte1[(b2 & 0xFF) + (b3 & 0xFF) & 0xFF])
i += 1
return arrayOfByte2
RC4
部分照抄 Java 版的代码,要注意的是 str 和 bytes 互转的时候需要额外地处理。
def decode(ciphertext: str, param: str) -> str:
key = get_md5(f'aibang{param}')
step1 = [byte if byte <= 127 else byte - 256 for byte in base64.b64decode(ciphertext.encode())]
step2 = [byte if byte > 0 else byte + 256 for byte in rc4.RC4Base(step1, key)]
result = bytes(step2).decode('utf-8')
return result
这样就能写出根据 lineid
从接口的返回值中解密数据的代码
def get_line_detail(line_id):
line_id = str(line_id)
url = base_url + f'/ssgj/v1.0.0/update?id={line_id}'
result = do_get(url)
if result is None or result.get('errcode', '-1') != '200':
raise RuntimeError('返回数据错误!')
busline = result.get('busline')[0]
line_name = decode(busline.get('linename'), line_id)
runtime = busline.get('time')
datas = []
datas.append(f'{line_name} {runtime}\n')
stations = busline.get('stations', {}).get('station')
for station in stations:
station_name = decode(station.get('name'), line_id)
station_id = decode(station.get('no'), line_id)
datas.append(f'{int(station_id):2d} {station_name}\n')
with open(f'line_{line_id}.txt', 'w', encoding='utf-8') as f:
f.writelines(datas)
比如 123 路公交牡丹园西 - 香河园桥方向的线路信息是
123(牡丹园西-香河园桥) 5:30-23:00
1 牡丹园西
2 牡丹园
3 北太平桥东
4 马甸桥南
5 德外关厢
6 黄寺大街西口
7 阳光丽景小区
8 黄寺
9 黄寺大街东口
10 安华西里
11 外馆斜街
12 小黄庄
13 青年沟西口
14 和平里西街
15 青年沟东口
16 和平里路口东
17 地铁柳芳站
18 东土城路
19 东土城路南口
20 北官厅
21 东直门北
22 东直门
23 春秀路口北
24 春秀路北口
25 香河园桥
实时公交信息
上一步最后获取了 lineid
对应线路的站点信息,通过 lineid
和 stationid
,可以获取线路上运行的公交与指定站点之间的实时信息。
在 com.aibang.nextbus.modle.Bus
中,也有一个 decode
方法。可以看到此方法的实现和之前 DetailLine
中的实现类似,不过 Bus
类中的 getKey
方法传入的是 aibang
和 gpsupdateTime
拼接的字符串。
通过类成员的定义,可以看到 gpsupdateTime
对应的是返回数据中的 gt
字段。
至此,不难写出根据 lineid
和 stationid
获取实时公交信息的代码
def get_realtime_bus(line_id, station_id):
url = base_url + f'/ssgj/bus.php?no={station_id}&versionid=6&city=%E5%8C%97%E4%BA%AC&datatype=json&encrypt=1&id={line_id}&type=1 '
result = do_get(url)
if result is None or result.get('root', {}).get('status', '-1') != '200':
raise RuntimeError('获取数据错误')
buses = result.get('root', {}).get('data', {}).get('bus')
for bus in buses:
gps_update_time = bus.get('gt')
bus_id = bus.get('id')
next_station = decode(bus.get('ns'), gps_update_time)
next_station_no = decode(bus.get('nsn'), gps_update_time)
distance = decode(bus.get('sd'), gps_update_time)
remain_seconds = decode(bus.get('srt'), gps_update_time)
arriving_time = decode(bus.get('st'), gps_update_time)
if arriving_time == '-1':
continue
arriving_time = datetime.fromtimestamp(int(arriving_time))
print(
f'车辆No.{bus_id}下一站{next_station_no}-{next_station},距离目的站点还有{int(station_id) - int(next_station_no) + 1}站、{distance}米,预计{arriving_time}到达,还有{remain_seconds}s'
)
例如 123 路公交牡丹园西 - 香河园桥方向东直门站在 2020-06-21 18:55 时的实时公交信息是
ABTOKEN?
然而问题还没有彻底解决,之前直接用的从抓包信息中获取的 Header。其中 TIME
字段是时间戳,ABTOKEN
字段每次都会改变,可以猜测是根据时间戳计算而来的。超过一定时间之后,原有的 TIME
和 ABTOKEN
的组合会失效。所以还需要找到 ABTOKEN
的计算方法。
继续在源码中探索,发现 com.aibang.nextbus.okhttp.HeaderSetHelper
这个类的 setHeader
方法在操作 Header。
查看代码可以得知,ABTOKEN
这个参数是给 Utils.generateToken
方法传入 5 个参数:bjjw_jtcx
、android
、67a88ec31de7a589a2344cc5d0469074
、时间戳
、paramString
后的返回值。其中前三个参数是固定值,时间戳容易计算。
在 com.aibang.nextbus.okhttp.NextbusHttpRequest
中可以看到调用 setHeader
方法时传入的实参,第 5 个参数 paramString
是 getPath
方法的返回值,即发送 GET 请求时的 URL 中的 Path 部分。
继续查看 com.aibang.common.util.Utils
类中的 generateToken
方法,可以看到返回值 是将传入的参数全部拼接后先 sha1 再 md5 的结果。
根据原理,可以写出对应的代码
def get_abtoken(timestamp, path):
text = f'bjjw_jtcxandroid67a88ec31de7a589a2344cc5d0469074{timestamp}{path}'
abtoken = get_md5(get_sha1(text))
return abtoken
至此,可以通过 Python 代码获取指定线路指定站点的实时公交信息了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK