查看原文
其他

宝马i3 车载系统API逆向分析报告

2015-12-02 E安全

我个人真的很享受宝马i3带来的驾驶乐趣。


宝马i3的车载系统可以将运作状态整理成文字发送到微博上、或者将其位置信息上传到我的服务器当中、抑或是在温度太高时允许我以远程方式启动其空调系统—— 总而言之,我们可以利用宝马i3车载系统的车辆数据实现上百种炫酷有趣的应用效果。不过必须承认,虽然宝马官方的应用已经提供了其中部分功能,但其速度极 慢、界面丑陋而且难于使用。


宝马公司曾经对外向技术人员开放了一项API,不过随后又将其关闭了。


目标


我们的最终目标在于利用该API访问车辆自带的全部功能——这样我们就能实现以下预期效果:


设备


为了实现这项看起来并不困难的任务,我们需要准备以下设备……


  • 一辆宝马i3。

  • 宝马ConnectedDrive账户。

  • 宝马I Remote App的Android版本。(我使用的是非美国版。)

  • 一台Android手机或者平板设备。

  • 一套能够抓取加密数据包的工具——我个人推荐由Grey Shirts推出的Packet Capture。


在Packet Capture安装完成之后,关闭其它所有应用,开户登录,而后登录至I Remote应用。点击其中几项功能,接下来切换回Packet Capture应用。


大家会看到其顺利捕捉到了一大堆API调用信息。




点击其中一项,我们就能查看到完整的API调用信息外加JSON响应。我在其中隐去了部分与个人相关的敏感信息。



我们需要的最为重要的实现要素就是Authorization:Bearer令牌。这是一条长度为32个字符的字母加数字字符串。

描述


大家可以点击此处在GitHub上下载宝马I Remote的最新版本。


这些API数据接口在设计思路上用于帮助大家与自己的宝马i3进行交互。而在此次尝试当中,我们将通过官方发布的宝马I Remote Android应用对其进行逆向分析。


当然,如何使用这些API数据接口则完全取决于大家的意愿——包括由此带来的风险。宝马公司对此不负任何责任但也并不禁止我们加以利用。


该软件需要以其“原始形式”加以使用,且不以明示或者默示方式做出任何保证,包括但不限于被用于实现任何与适销性、特定用途以及非侵权性行为相关的目的。在 任何情况下,软件二次编写者或版权所有者须承担一切关于合同、侵权或者其它不当行为所产生的,或者由软件在使用或者其它相关场景当造成的索赔、损害或者其 它连带责任。

服务器


有三个宝马的API服务器地址。


https://b2vapi.bmwgroup.cn:8592 China

https://b2vapi.bmwgroup.us USA

https://b2vapi.bmwgroup.com Europe / Rest of World

授权


为了能够通过该API的授权验证,大家需要在宝马的ConnectedDrive服务上完成注册。


在这里我们需要:

  1. 用于注册ConnectedDrive的电子邮箱地址。

  2. 用于ConnectedDrive注册的密码内容。

  3. i Remote API密钥。

  4. I Remote API Secret。


大家可以通过对宝马I Remote 安卓版App应用进行反编译或者拦截手机与宝马API服务器间的通信数据来了解i Remote的细节信息。这部分工作就留给各位读者朋友自行探索吧。


首先,我们要使用Basic授权。这意味着我们需要获取该API密钥及Secret,并利用Base64对其进行编码。


这样一来key:secret就变成了2V5OnNlY3JldA==的形式。


我们还需要发送以下参数:

  • Content-Type: application/x-www-form-urlencoded


grant_type=password

&username=whatever%40example.com

&password=p4ssw0rd

&scope=remote_services+vehicle_data


下面是我们如何利用Curl对其进行处理:


curl \

-H "Authorization: Basic a2V5OnNlY3JldA==" \

-H "Content-Type: application/x-www-form-urlencoded" \

-d "grant_type=password&username=whatever%40example.com&password=p4ssw0rd&scope=remote_services+vehicle_data" \

"https://b2vapi.bmwgroup.com/webapi/oauth/token/"


如果一切进展顺利,那么大家应该会收到以下JSON响应:


{

"access_token": "RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R",

"token_type": "Bearer",

"expires_in": 28800,

"refresh_token": "7WgKmEJ2kD1ydl9Hefp01eS8qDGzKnzjeORpA6vtsoFIEanz",

"scope": "vehicle_data remote_services"

}


注意:我们必须将

  • Authorization: Bearer RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R

添加到每一条请求头当中。


另外expires_in是以秒来计算的——这意味着我们每八个小时就需要对令牌进行一次更新。


我到现在也没弄明白refresh_token到底有什么用。因为一旦access_toke过期,大家可以直接对其重新授权并获取到一条新令牌。

API


我们必须将

  • Authorization: Bearer RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R

添加到每一条请求头当中。


获取车辆数据

  • /webapi/v1/user/vehicles/

  • Remember to include the Authorization: Bearer


在这里,最重要的就是VIN一项——也就是车辆识别号码。大家需要在全部其它API调用以及Authorization Bearer当中使用VIN。


>>>>响应结果

{

"vehicleStatus": {

"vin": "WAB1C23456V123456",

"mileage": 1234,

"updateReason": "VEHICLE_SHUTDOWN_SECURED",

"updateTime": "2015-10-30T18:45:04+0100",

"doorDriverFront": "CLOSED",

"doorDriverRear": "CLOSED",

"doorPassengerFront": "CLOSED",

"doorPassengerRear": "CLOSED",

"windowDriverFront": "CLOSED",

"windowDriverRear": "CLOSED",

"windowPassengerFront": "CLOSED ",

"windowPassengerRear": "CLOSED",

"trunk": "CLOSED",

"rearWindow": "INVALID",

"convertibleRoofState": "INVALID",

"hood": "CLOSED",

"doorLockState": "SECURED",

"parkingLight": "OFF",

"positionLight": "OFF",

"remainingFuel": 8.9,

"remainingRangeElectric": 73,

"remainingRangeElectricMls": 45,

"remainingRangeFuel": 126,

"remainingRangeFuelMls": 78,

"maxRangeElectric": 134,

"maxRangeElectricMls": 83,

"fuelPercent": 99,

"maxFuel": 9,

"connectionStatus": "DISCONNECTED",

"chargingStatus": "INVALID",

"chargingLevelHv": 58,

"lastChargingEndReason": "UNKNOWN",

"lastChargingEndResult": "FAILED",

"position": {

"lat": 51.123456,

"lon": -1.2345678,

"heading": 211,

"status": "OK"

},

"internalDataTimeUTC": "2015-10- 30T18:47:44"

}

}


>>>>值说明

  • Mileage的单位为公里。

  • remainingFuel的单位为升。

  • maxRangeElectric的单位为公里。

  • maxRangeElectricMls的单位为英里。

  • chargingLevelHv代表电池当前剩余电量的百分比(Hv可能代表的是高压?)。

  • maxFuel的单位为升。

  • heading的单位为度(角度)。



>>>>有效的chargingStatuses包括以下几种:

  • CHARGING

  • ERROR

  • FINISHED_FULLY_CHARGED

  • FINISHED_NOT_FULL

  • INVALID

  • NOT_CHARGING

  • WAITING_FOR_CHARGING



>>>>有效的connectionStatuses包括以下几种:

  • CHARGING_DONE

  • CHARGING_INTERRUPED [sic]

  • CHARGING_PAUSED

  • CHARGIN_STARTED [sic]

  • CYCLIC_RECHARGING

  • DOOR_STATE_CHANGED

  • NO_CYCLIC_RECHARGING

  • NO_LSC_TRIGGER

  • ON_DEMAND

  • PREDICTION_UPDATE

  • TEMPORARY_POWER_SUPPLY_FAILURE

  • UNKNOWN

  • VEHICLE_MOVING

  • VEHICLE_SECURED

  • VEHICLE_SHUTDOWN

  • VEHICLE_SHUTDOWN_SECURED

  • VEHICLE_UNSECURED


上一次的车辆行程


这里显示的是最近一次行程的相关细节信息。

  • /webapi/v1/user/vehicles/:VIN/statistics/lastTrip

    • 其中VIN代表的是我们车辆的VIN。

    • 请记得在报头当中包含Authorization: Bearer。


>>>>响应结果

{

"lastTrip":{

"efficiencyValue":0.53,

"totalDistance":141,

"electricDistance":100.1,

"avgElectricConsumption":16.6,

"avgRecuperation":2,

"drivingModeValue":0,

"accelerationValue":0.39,

"anticipationValue ":0.81,

"totalConsumptionValue":0.79,

"auxiliaryConsumptionValue":0.66,

"avgCombinedConsumption":1.9,

"electricDistanceRatio":71,

"savedFuel":0,

"date":"2015-12-01T20:44:00+0100 ",

"duration":124

}

}


>>>>值说明

这里的距离计算单位似乎是公里而非英里,因此请确保对具体值进行相应调整。将公里乘以0.621371就得到了对应的英里数字。

  • totalDistance的单位为公里。

  • electricDistance的单位为公里。

  • avgElectricConsumption的单位为千瓦时/百公里。

  • avgRecuperation的单位为千瓦时/百公里。

  • duration的单位为分钟。


大家可以利用以下公式将千瓦时/百公里换算成千瓦时/百英里。

1 / (0.01609344 * avgElectricConsumption)

获取充电时间


显示车辆何时应该进行充电。

  • /webapi/v1/user/vehicles/:VIN/chargingprofile

    • 其中VIN代表的是我们车辆的VIN。

    • 请记得在报头当中包含Authorization: Bearer。


>>>>响应结果

{

"weeklyPlanner":{

"climatizationEnabled":true,

"chargingMode":"DELAYED_CHARGING",

"chargingPreferences":"CHARGING_WINDOW",

"timer1":{

"departureTime":"07:30",

"timerEnabled":true,

" weekdays":[

"MONDAY"

]

},

"timer2":{

"departureTime":"13:00",

"timerEnabled":false,

"weekdays":[

"SATURDAY"

]

},

"timer3":{

"departureTime" :"08:00",

"timerEnabled":false,

"weekdays":[


]

},

"overrideTimer":{

"departureTime":"07:30",

"timerEnabled":false,

"weekdays":[

"MONDAY"

]

},

"preferredChargingWindow":{

"enabled":true,

"startTime":"05:02",

"endTime":"17:31"

}

}

}


>>>>值说明

  • departureTime似乎代表的是车辆的的当地时间。

获取车辆目的地信息


显示我们此前发送至车辆的目的地信息。

  • /webapi/v1/user/vehicles/:VIN/destinations

    • 其中VIN代表的是我们车辆的VIN。

    • 请记得在报头当中包含Authorization: Bearer。


>>>>响应结果

{

"destinations":[

{

"lat":51.53053283691406,

"lon":-0.08362331241369247,

"country":"UNITED KINGDOM",

"city":"LONDON",

"street":"PITFIELD STREET",

"type": "DESTINATION",

"createdAt":"2015-09-25T08:06:11+0200"

}

]

}


>>>>值说明

  • 一组位置数据。

获取全部行程细节信息


显示车辆获取到的全部行程统计信息。

  • /webapi/v1/user/vehicles/:VIN/statistics/allTrips

    • 其中VIN代表的是我们车辆的VIN。

    • 请记得在报头当中包含Authorization:Bearer。


>>>>响应结果

{
"allTrips": {
"avgElectricConsumption": {
"communityLow": 0,
"communityAverage": 16.33,
"communityHigh": 35.53,
"userAverage": 14.76
},
"avgRecuperation": {
"communityLow": 0,
"communityAverage" : 3.76,
"communityHigh": 14.03,
"userAverage": 2.3
},
"chargecycleRange": {
"communityAverage": 121.58,
"communityHigh": 200,
"userAverage": 72.62,
"userHigh": 135,
"userCurrentChargeCycle": 60
},
"totalElectricDistance": {
"communityLow": 1,
"communityAverage": 12293.65,
"communityHigh": 77533.6,
"userTotal": 3158.66
},
"avgCombinedConsumption": {
"communityLow": 0,
"communityAverage": 1.21,
" communityHigh": 6.2,
"userAverage": 0.36
},
"savedCO2": 87.58,
"savedCO2greenEnergy": 515.177,
"totalSavedFuel": 0,
"resetDate": "1970-01-01T01:00:00+0100"
}
}


>>>>值说明

  • chargecycleRange的单位为公里。

  • totalElectricDistance的单位为公里。

我目前还不太确定其它数值的确切含义。

获取范围地图


生成一份折线图,用于显示该车辆的预计行驶范围。

  • /webapi/v1/user/vehicles/:VIN/rangemap

    • 其中VIN代表的是我们车辆的VIN。

    • 请记得在报头当中包含Authorization: Bearer。


>>>>响应结果

{

"rangemap": {

"center": {

"lat": 51.123456,

"lon": -1.2345678

},

"quality": "AVERAGE",

"rangemaps": [

{

"type": "ECO_PRO_PLUS",

"polyline" : [

{

"lat": 51.6991281509399,

"lon": -2.00423240661621

},

{

"lat": 51.6909098625183,

"lon": -1.91526889801025

},

...

]

},

{

"type": "COMFORT",

"polyline" : [

{

"lat": 51.7212295532227,

"lon": -1.7363977432251

},

{

"lat": 51.6991496086121,

"lon": -1.73077583312988

},

...

]

}

]

}

}


>>>>值说明

  • ECO_PRO_PLUS代表以经济模式行驶。

  • COMFORT代表以舒适模式行驶。

向车辆发送信息


向车辆发送信息相对更复杂一些。


我们的应用会与该API进行通信,而API随后再与车辆的3G模块进行通信,接下来我们就可以等待其做出响应了。


如果我们的车辆处于信号覆盖较差的地区,那么大家肯定会感受到相当明显的响应延迟——通常其延迟水平远远高于正常驾驶员能够接受的范围。


从基础层面出发,我们可以只发送一条简单请求——例如锁定车门或者设置非峰值充电。


获取请求状态

以下所示为一条POST请求的具体状态:

/webapi/v1/user/vehicles/:VIN/serviceExecutionStatus?serviceType=:SERVICE

  • 其中VIN代表的是我们车辆的VIN。

  • 请记得在报头当中包含Authorization:Bearer。


>>>>响应结果


{
"executionStatus":{
"serviceType":"DOOR_LOCK",
"status":"EXECUTED",
"eventId":" 123456789012345AB1CD1234@bmw.de"
}
}


>>>>值说明

有效状态包括:

  • DELIVERED

  • EXECUTED

  • INITIATED

  • NOT_EXECUTED

  • PENDING

  • TIMED_OUT



>>>>以下所示为有效的:SERVICE类型,但大家的车辆上不一定支持其中全部项目。

  • CHARGE_NOW

  • CHARGING_CONTROL

  • CLIMATE_CONTROL

  • CLIMATE_NOW

  • DOOR_LOCK

  • DOOR_UNLOCK

  • GET_ALL_IMAGES

  • GET_PASSWORD_RESET_INFO

  • GET_VEHICLES

  • GET_VEHICLE_IMAGE

  • GET_VEHICLE_STATUS

  • HORN_BLOW

  • LIGHT_FLASH

  • LOCAL_SEARCH

  • LOCAL_SEARCH_SUGGESTIONS

  • LOGIN

  • LOGOUT

  • SEND_POI_TO_CAR

  • VEHICLE_FINDER

POST一条指令


向车辆发送指令以执行相应行动。

/webapi/v1/user/vehicles/:VIN/executeService

  • 其中VIN代表的是我们车辆的VIN。

  • 请记得在报头当中包含Authorization: Bearer。


可用指令

这些指令皆可通过API实现,不过大家的车辆上不一定支持其中全部项目。

这些只是我截至目前发现的可用指令。

数据必须被POSTed到服务器端。



开始充电


如果车辆已经接入充电桩但未开始正确充电(也许是因此进行了非峰值时段设定),那么我们可以通过以下方式要求其立刻进行充电。

  • serviceType=CLIMATE_NOW



锁定车门


启动中央锁。

  • serviceType=DOOR_LOCK



解锁车门


这项指令将解开车辆上的所有门锁。

请在发送这条指令之前务必认真判断当前状况。确保车辆已经在自己的视野当中,而且有能力在必要时将其恢复锁定。

  • serviceType=DOOR_UNLOCK



开启前端照明灯


如果大家找不到自己的车子,或者需要照亮其附近的某些景物,则可以直接激活前端照明灯。

  • serviceType=LIGHT_FLASH&count=2

    • 我猜其中的count值代表的是车灯亮起的具体秒数——但目前只是猜测。



充电时间表


设置峰值/非峰值充电时间表。

  • serviceType=CHARGING_CONTROL

    • 我还没鼓捣过这部分功能,不过其返回的错误信息应该能给大家带来一些启示:


{
"error":{
"code":500,
"description":"(SmartPhoneUtil-A-102) Bad value(s) for parameter(s): Invalid chargingProfile, expected weeklyPlanner or twoTimesTimer"
}
}


寻车功能


  • serviceType=VEHICLE_FINDER

    • 我不太确定这项功能的具体作用。


>>>>响应结果

所有POST指令的示例响应结果:


{
"executionStatus": {
"serviceType": "LIGHT_FLASH",
"status": "INITIATED",
"eventId": " 123456789012345AB1CD1234@bmw.de"
}
}


最后


利用上述指令,大家应该已经能够重现官方应用所提供的各项功能了。


如大家所见,我已经能够让自己的车辆自动发表微博推文了:



如果宝马公司能够开放官方API就太好了,这样用户们就能够拥有更加自由的车辆调整空间。就目前来看,其API安全性尚佳而且不太可能对车辆造成损害。


我已经将全部说明文档添加到了GitHub当中(点击下方的“阅读原文”进行查看),欢迎大家就此提出问题或者发送任何与变更相关的Pull Request。

本文由E安全编译 转载请注明来自 E安全 (www.easyaq.com)

您也可以下载E安全app获取及时最及时的安全资讯、预警信息等等

微信名:E安全

微信ID:EAQapp

❶ 点击往期内容,查看更多内容
❷ 复制网址在浏览器打开
www.easyaq.com
❸ 长按右侧二维码,关注E安全

点击下方“阅读原文”查看更多内容。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存