该系列文章将通过逆向的方式分析Tesla远程api,并自己编写代码实现远程控制Tesla汽车。该篇文章为第二篇,将主要讲解websocket抓包分析与编程实现对Tesla的远程控制。
Tesla 的召唤功能除自动召唤外,还支持手动的前进与后退功能。要想手机 APP 可以使用召唤功能,需要先在车机中开启召唤功能。
在测试时,建议寻找一块比较大的空地,然后设置抓包,使用手机 APP 进行操作。通过 burpsuite 抓到的数据包可知,召唤功能主要通过 websocket 来实现。但是分析过程中可以发现,burpsuite 只显示了连接地址和发送的数据内容,并没有显示其请求的头,所以,当直接去连接时会返回 401 错误。
在这个时候就需要还一个工具了,本文选择使用 charles。这个工具针对 websocket 的支持非常友好,不仅可以看到请求头,还针对发送与接收以不同的颜色区分显示出来,十分方便分析。
对于抓包环境的设置,这里说一下,因为 Tesla 是 tls+websocket 实现,所以我们抓包时需要替换为自己的证书。安卓和 ios 设置方法存在差异,不过 Tesla 并不需要 sslpin bypass。添加自己的受信任的证书即可。由于我在测试时,安卓机 gps 有问题,所以我换了 iphone,并通过设置 sock5 类型代理来实现抓包。这些都是抓包常规操作,这里只是提一下,不知道如何设置的朋友自己去搜索一下吧,这里不做赘述。
为了更好的分析,建议多抓几次前进或者后退的包,便于进行对比。通过对 websocket 的通信数据分析,请求的数据通信格式为 json,每条数据都有时间戳,还有 msg_type
字段,经过去重统计,一共有 7 种 msg_type
,分别有 control:ping
、autopark:info
、autopark:device_location
、autopark:cmd_forward
、autopark:cmd_reverse
、autopark:heartbeat_app
、autopark:cmd_abort
。其中 autopark:device_location
又有两种 summon_type
,分别为 find_me
、pin_drop
。
分析多次抓包的结果,确定如下流程:
以上流程除 autopark:cmd_abort
外缺少一个过程都将会导致召唤失败。当然,实际过程中还会有其他数据包,但是不发送也可以成功召唤。
我将程序的名字命名为 TeslaSploit Framework。代码实现以 Python3 实现。使用协程来完成网络请求,使用的库为 aiohttp 和 websockets。对于程序界面还是选择了交互式控制台的方式,操作和风格与 metasploit 框架类似。专业叫法为 REPL(交互式解释器),即 Read(读取),Evaluate(执行),Print(打印),Loop(循环)。这里为了快速实现,选择了 sploitkit 这个库。
程序界面如下:
目录结构则分为 data 目录,teslasploit 目录,入口文件为程序根目录下 main.py。data 目录下分别放了 config 文件夹和 token,前者存放账号密码,后者存放登录成功后的 token 等配置信息。teslasploit 目录下又 lib、banners、modules,分别存在库文件、bannner 和利用模块。
![](/Users/aliceclaudia/work/Blog/远程控制 tesla 实现/下/images/ppt/截屏 2020-10-21 下午 12.06.03.png)
操作的命令主要以下几个
show modules
use exploit/tesla/*
run
back
exit
set [key] [value]
show options
对于本次的需求,这些已经足够。
该代码仅在 macos 系统,python3.8.2 的系统环境下运行,其他系统未进行测试。
安装 sploitkit 库,pip install sploitkit
。
新建 main.py 文件,写入如下代码
#!/usr/bin/python3"""
Copyright (c) 2020 Ingeek Tiger-team (https://ingeek.com/)
"""from __future__ import print_functiontry: import sys
sys.dont_write_bytecode = Truefrom teslasploit.lib import TeslasploitConsoleexcept KeyboardInterrupt:
sys.exit("")if __name__ == '__main__':
TeslasploitConsole("tesla").start()
在 teslasploit/lib/ 文件夹下创建 __init__.py
文件,写入如下代码:
#!/usr/bin/python3import refrom sploitkit import FrameworkConsoleclass TeslasploitConsole(FrameworkConsole):
sources = { 'banners': "./teslasploit/banners", 'entities': ["./teslasploit/modules"], 'libraries': "./teslasploit",
}
def __init__(self, *args, **kwargs):
super(TeslasploitConsole, self).__init__(*args, **kwargs)
这样,程序的控制台界面就可以运行了。可以输入 python3 main.py
看看效果了。以上两段代码直接参考 https://github.com/dhondta/python-sploitkit 这里的例子。
为了程序界面更加酷炫一些,可以增加 ascii 字形或 ascii 图形。python 有第三方库可以用。当然网络上也有在线转换的工具。这里选择使用 python 的 asciistuff 库。
使用如下两行代码,可随机风格的 ascii 字形:
from asciistuff import Bannerprint(Banner("Test"))
如果想生成固定的图形,则可以查看 https://github.com/dhondta/python-asciistuff/blob/master/asciistuff/fonts.txt。详细用法自行查看 https://github.com/dhondta/python-asciistuff。这里还有另外一个库,cowpy,是 cow say 风格的图形,可根据自己喜好选择是否使用。
控制台界面已经有了,先来创建一个名为 CarBase 的类,后面每台车都基于该类创建一个车辆对象,便于进行批量控制。这里的网络通信主要是用 aiohttp
和 websockets
两个库来完成。直接使用 pip 安装即可。
该类主要包括登录、api 请求、获取车辆列表、车辆详细信息、控制(开锁、锁车、空调、温度等)、召唤功能。
以上功能请求需要用到 4 个常量,声明如下:
TESLA_CLIENT_ID ="81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384"TESLA_CLIENT_SECRET ="c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3"TESLA_BASE_URL ="https://owner-api.teslamotors.com/"TESLA_BASE_WS ="streaming.vn.teslamotors.com"
具体功能定义如下:
对于 aiohttp 的基础使用,建议大家去了解一下开发文档。其用法与 requests 库类似,很容易上手。先以登录来举例子,该代码就是 post 请求 oauth/token?grant_type=password
登录,登录成功会返回 token 信息。代码如下:
async def login(self):
data={ "grant_type": "password", "client_id": TESLA_CLIENT_ID, "client_secret": TESLA_CLIENT_SECRET, "email": self.email, "password": self.password,
}
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.post(TESLA_BASE_URL+"oauth/token?grant_type=password", data=data) as resp: if resp.status == 200:
text = await resp.text() self.token = json.loads(text)
首先要获取车辆列表,请求地址为 /api/1/vehicles
,请求方式为 GET。发送后,根据返回的 json 内容提取 id、display_name、vin 字段的值。根据 id 可以获取车辆详细信息。获取车辆详细信息之前还需要发送 /api/1/vehicles/{id}/wake_up
(替换{id}为实际 id 即可)请求,不然车辆如果在没被唤醒状态下会返回错误。
车辆详细信息的请求路径为 /api/1/vehicles/{id}/vehicle_data
(替换{id}为实际 id 即可)。在发送请求时都加上 Authorization
请求头。获取成功信息以 json 格式返回,其中包含了很多信息,这里只需要提取 vin、display_name、gps、电量、续航、车内温度、车外温度。
User-Agent: TeslaSploitAuthorization: Bearer [access_token]
返回的信息为了便于在控制台下显示,采用了 terminaltables
这个库来。显示格式如下:
对应的代码部分如下:
carinfo = self.carinfo["response"]
if carinfo["state"]=="online":
state = "在线"else:
state = "离线"print("\n%s (%s)\n" % (carinfo["display_name"], state))gps = "%s, %s" % (carinfo["drive_state"]["latitude"],carinfo["drive_state"]["longitude"])
battery_level = str(carinfo["charge_state"]["battery_level"])+"%"battery_range = str(carinfo["charge_state"]["battery_range"])+"英里"inside_temp = carinfo["climate_state"]["inside_temp"]
outside_temp = carinfo["climate_state"]["outside_temp"]
vin = carinfo["vin"]car_table = [
["vin",vin,"电量",battery_level,"电池续航",battery_range],
["GPS",gps,"车外温度",outside_temp,"车内温度",inside_temp],
]self.print_table(car_table)
经过分析,诸如开锁、锁门、开/关空调、设置温度、开启后备箱等请求格式均相似,可以统一使用一个方法来实现。其 url 地址格式为 /api/1/vehicles/{id}/command/{command}
,请求方法为 POST,请求数据格式为 json 格式,如果没有参数可以使用 {}
来表示。
这里的 {command}
为操作指令,主要有 door_unlock( 解锁)、door_lock( 锁车)、flash_lights( 闪灯)、honk_horn(鸣笛)
、auto_conditioning_start(开空调)、auto_conditioning_stop(关空调)、set_temps(设置温度)、actuate_trunk_rear(开后备箱)。除 set_temps
操作指令外,均不需要 post data。set_temps
我们之前抓过包,有 driver_temp
和 passenger_temp
两个参数,分别为驾驶位温度和副驾驶位温度。
召唤功能的流程在上面已经分析清楚,我们只需要按照格式发送和接收数据就可以了。先来个 websockets 的例子:
async def main():
async with websockets.connect('ws://10.10.6.91:5678') as websocket:
timestamp = round(time.time()*1000)
ping = '{"timestamp":%d,"msg_type":"control:ping","created:timestamp":%d}'%(timestamp,timestamp)
await websocket.send(ping)
recv = await websocket.recv()asyncio.get_event_loop().run_until_complete(main())
接下来就根据上面分析的流程构造发送数据包即可。这里需要注意的是在发送 autopark:cmd_forward
和 autopark:cmd_reverse
类型的消息时,需要 gps 位置信息,这个信息位置需要在车辆的 gps 位置信息附近。所以这里需要先获取车辆的 gps 信息,在此基础之上做加减法操作即可。具体大家可自己动手操作实践。
汽车的类编写完,接下来就要设计业务流程。
为了实现批量控制,这里设计一个 sessions 的列表,存放车辆信息、token 以及车辆的对象。这个 sessions 列表在模块加载时生成。加载的过程则根据 /data/config/account.conf
的账号列表进行遍历,遍历过程中判断存储在 /data/token/
目录下的账号信息文件是否存在,如果存在则直接读取文件,不存在则登录该账户,登录成功后将信息、token 存到 /data/token/
目录下,并加信息和车辆对象 append 到 sessions 列表中,供运行时调用。
先来看下 splpitkit 库模块如何编写,在 /teslasploit/modules/exploit
目录下创建 tesla.py
,并引用相关库,获取车辆信息代码:
class Info(Module,teslaBase):path = "exploit/tesla"
description = "获取车辆信息"
config = Config({
Option( 'vehicle', "车辆编号",
True,
): 1
})
def __init__(self): super().__init__()
print(self.vehicle_list())def run(self):
tasks = []
vehicle = self.config.option("vehicle").value
self.event_loop = asyncio.get_event_loop()
carinfos = [] if vehicle==0: for i in range(0,len(self.sessions)):
task = asyncio.ensure_future(self.sessions[i]["object"].get_info(self.sessions[i]["id"]))
tasks.append(task) self.event_loop.run_until_complete( asyncio.gather(*tasks)) else:
vehicle_session = self.sessions[vehicle - 1]
tesla = vehicle_session["object"]
asyncio.run(tesla.get_info(vehicle_session["id"]))
上面的 path 位 use 加载的路径,description 位利用模块的描述,config 是配置的参数选项,可以使用 show options
查看,使用 set [key] [value]
进行设置。run
方法则是执行 run 命令时调用。
在类初始化的时候调用了获取汽车列表的操作,会打印出要控制的汽车,0 代表所有,其他根据编号可单独控制。在 run
方法中,你只需要遍历 sessions 列表就可以实现批量控制了。其他模块与其相似,改动其选项和调用的方法等即可。
编写完成后即可运行测试,使用流程如下:
一切准备就绪,录制了两小段视频给大家。我在安全客 10 月 21 日的直播中有分享,大家可以查看链接 https://www.anquanke.com/post/id/220554,有录屏直播、ppt 和两个演示视频。
本系列文章通过逆向分析并编程实现对特斯拉汽车的批量远程控制功能。重点讲述了针对 web 和 websocket 的抓包与 python3 协程编程的知识,同时也讲述了如何利用第三方库快速高效的构建系统框架。
注:本文中仅提供了部分代码,有些可以单独运行,有些则需要自己补充或根据自己思路去进行实现。
https://github.com/dhondta/python-asciistuff
https://github.com/dhondta/python-sploitkit
https://github.com/Robpol86/terminaltables
https://github.com/aio-libs/aiohttp
https://github.com/aaugustin/websockets
推荐阅读
如有侵权,请联系删除
文章来源:Bacde's blog
点击阅读原文即可访问
作者:bacde