该系列文章将通过逆向的方式分析Tesla远程api,并自己编写代码实现远程控制Tesla汽车。该篇文章为第二篇,将主要讲解websocket抓包分析与编程实现对Tesla的远程控制。如果你还没看第一篇,请先查看第一篇内容Hacking All The Cars - Tesla 远程API分析与利用(上)。
0x00 Websocket通信分析
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
外缺少一个过程都将会导致召唤失败。当然,实际过程中还会有其他数据包,但是不发送也可以成功召唤。
0x01 系统整体设计
我将程序的名字命名为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
对于本次的需求,这些已经足够。
0x02 编程实现
该代码仅在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_function
try:
import sys
sys.dont_write_bytecode = True
from teslasploit.lib import TeslasploitConsole
except KeyboardInterrupt:
sys.exit("")
if __name__ == '__main__':
TeslasploitConsole("tesla").start()
在teslasploit/lib/ 文件夹下创建__init__.py
文件,写入如下代码:
#!/usr/bin/python3
import re
from sploitkit import FrameworkConsole
class 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 Banner
print(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: TeslaSploit
Authorization: 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列表就可以实现批量控制了。其他模块与其相似,改动其选项和调用的方法等即可。
编写完成后即可运行测试,使用流程如下:
0x03 成果展示
一切准备就绪,录制了两小段视频给大家。我在安全客10月21日的直播中有分享,大家可以查看链接 https://www.anquanke.com/post/id/220554,有录屏直播、ppt和两个演示视频。
0x04 总结
本系列文章通过逆向分析并编程实现对特斯拉汽车的批量远程控制功能。重点讲述了针对web和websocket的抓包与python3协程编程的知识,同时也讲述了如何利用第三方库快速高效的构建系统框架。
注:本文中仅提供了部分代码,有些可以单独运行,有些则需要自己补充或根据自己思路去进行实现。