通信分析题/通信分析软件-新手网安笔记
在七月底举行了一个大会:, 大会上, 团队向大家介绍了即将推出的 3.0, 赶在会议开始之前,团队完成了对micro:bit的官方支持,项目页连同使用说明也正式上线:
与micro:bit作为全球最有名的两个少儿编程项目(分别是软件和硬件),能够结合在一起,太振奋人心。之前社区里大家就围绕这块在做许多探索,如:
尝鲜
只要你手边有micro:bit就可以开始体验了.
按照使用说明,将micro:bit接入.0毫无障碍:
完成连接后就可以开始你的创作了,使用说明页面里给出了几个例子,大家可以从这儿入手
原理
体验完与micro:bit的互动,我们来分析一下官方是如何做到的。
回顾使用说明和体验过程,容易猜到 Link起代理的作用, Link在内部跑了一个服务,允许网页与其交互,同时在启动时扫描周围的BLE设备
思路和-几乎完全一致
Link与-的不同之处有:
Link在易用性上做得非常好,这也-准备向它学习的地方。-的目标之一是完全兼容 Link的功能
这篇文章就来分析一下官方在这块的巧思。以便于我们可以将其用到其他地方。
分析推断
从的micro:bit 来看, Link仅仅只是一个代理,与micro:bit的交互逻辑都在micro:bit 中。
所以我们暂时不必使用抓包分析,而仅仅通过阅读micro:bit ,应该就能知道通信的细节,之后我们使用来验证。
如果你对BLE/GATT相关的概念不熟悉,可以看看我之前的文章:BLE学习笔记
从micro:bit 源码里我们可以找到micro:bit里跑的服务和属性的uuid,也正是这个证据,让我们猜测 Link只是个透明代理
const BLEUUID = {
service: 0xf005,
rxChar: '5261da01-fa7e-42ab-850b-7c80220097cc',
txChar: '5261da02-fa7e-42ab-850b-7c80220097cc'
};
对比大学的 Level 3 ,可知自己重写了很多东西,而没有使用现成的UART ,这点我颇为不解。
展开追踪
我们接着来跟踪一下A ?这个积木涉及的通信过程,从一个具体例子里突破。经过这个例子,我们对整个通信流程应该会有一个整体的了解,之后我们再对不同类型的积木逐类探索。
GATT 通信的双方是 C/S 关系, 为了知道micr:bit上A按钮的状态,一般采用两种方式:
-gui/应该是用这两种方式中的一种
但micro:bit 有些特殊,它构建了UART ,之后的的数据都走UART ,逻辑上这更像经典的串口通信。只是实现在GATT上而已。
从源码中可以看到是否按下取决于
而
顺藤摸瓜,可以跟踪到
/**
* Process the sensor data from the incoming BLE characteristic.
* @param {object} base64 - the incoming BLE data.
* @private
*/
_processSessionData (base64) {
// parse data
const data = Base64Util.base64ToUint8Array(base64);
this._sensors.tiltX = data[1] | (data[0] << 8);
if (this._sensors.tiltX > (1 << 15)) this._sensors.tiltX -= (1 << 16);
this._sensors.tiltY = data[3] | (data[2] << 8);
if (this._sensors.tiltY > (1 << 15)) this._sensors.tiltY -= (1 << 16);
this._sensors.buttonA = data[4];
this._sensors.buttonB = data[5];
this._sensors.touchPins[0] = data[6];
this._sensors.touchPins[1] = data[7];
this._sensors.touchPins[2] = data[8];
this._sensors.gestureState = data[9];
// cancel disconnect timeout and start a new one
window.clearInterval(this._timeoutID);
this._timeoutID = window.setInterval(this.disconnectSession.bind(this), BLETimeout);
}
注释里说得很清楚:
* Process the sensor data from the incoming BLE characteristic.
* @param {object} base64 - the incoming BLE data.
从逻辑和语义上,可以看出是个回调函数,micro:bit会通过串口源源不断把它自身的状态数据( data)不断发给, 如此一来,就能得知的A按钮是否按下,为了验证我们的想法,我们得继续跟踪:this._ble.read(., ., true, );
/**
* Starts reading data from device after BLE has connected to it.
*/
_onSessionConnect () {
const callback = this._processSessionData.bind(this);
this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, callback);
this._timeoutID = window.setInterval(this.disconnectSession.bind(this), BLETimeout);
}
_ble看去是个通用的抽象io(),_ble.read在语义上类似UART read,只是实现上是基于GATT的,如果你熟悉GATT,至此应该基本都猜到了。当然我们会继续剖析。
/**
* Called by the runtime when user wants to scan for a device.
*/
startDeviceScan () {
this._ble = new BLESession(this._runtime, {
filters: [
{services: [BLEUUID.service]}
]
}, this._onSessionConnect.bind(this));
}
跟踪到类里, 继承自, 这里提示我们与 Link是如何通信的,基于,同时使用远程调用的概念, RPC使用起来要比流简单很多。这是官方很聪明的举措之一,我们在文末的tips里还将列出官方其他的聪明做法
如果你不打算自己实现类似 Link的东西,不必太关注。我实现了类似 Link的-,但使用的是消息通信,策略上和团队不大一样。这块我们先不细说
回到上边,我们前头关注_ble.read,在此将看到它的实现:
read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged) {
const params = {
serviceId,
characteristicId
};
if (optStartNotifications) {
params.startNotifications = true;
}
this._characteristicDidChangeCallback = onCharacteristicChanged;
return this.sendRemoteRequest('read', params)
.catch(e => {
this._sendError(e);
});
}
micro:bit 对它的调用是:
this._ble.read(., ., true, );
至此,我们就搞懂了A ?是如何实现的,s被设置为True,语义上是接受通知,当micro:bit上数据变化时,及时通知给。技术层面使用了GATT的
客户端可以请求服务器通知一项特征
关于这点,我们在BLE学习笔记有提到
因为弄懂了A ?,所以When A 积木也不难理解,当然这需要你熟悉:的HAT类型的积木(事件风格)。源码一目了然
whenButtonPressed (args) {
if (args.BTN === 'any') {
return this._device.buttonA | this._device.buttonB;
} else if (args.BTN === 'A') {
return this._device.buttonA;
} else if (args.BTN === 'B') {
return this._device.buttonB;
}
return false;
}
关于write
既然我们分析完read,顺手看一下write的实现,直接上源码
write (serviceId, characteristicId, message, encoding = null) {
const params = {serviceId, characteristicId, message};
if (encoding) {
params.encoding = encoding;
}
return this.sendRemoteRequest('write', params)
.catch(e => {
this._sendError(e);
});
}
没什么需要特别说的
我们以一个使用write的积木为例,来看看具体的细节,以 text为例:
displayText (text) {
const output = new Uint8Array(text.length);
for (let i = 0; i < text.length; i++) {
output[i] = text.charCodeAt(i);
}
return this._writeSessionData(BLECommand.CMD_DISPLAY_TEXT, output);
}
_writeSessionData (command, message) {
if (!this.getPeripheralIsConnected()) return;
const output = new Uint8Array(message.length + 1);
output[0] = command; // attach command to beginning of message
for (let i = 0; i < message.length; i++) {
output[i + 1] = message[i];
}
const data = Base64Util.uint8ArrayToBase64(output);
return this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64');
}
使用 text打印hello字符串,观察传输的数据:
{"jsonrpc":"2.0","method":"write","params":{"serviceId":61445,"characteristicId":"5261da02-fa7e-42ab-850b-7c80220097cc","message":"gWhlbGxv","encoding":"base64"},"id":4}
这里值得一提的是编码方式:: 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0
: 方法可返回指定位置的字符的 编码。这个返回值是 0 - 65535 之间的整数。
硬件的通信使用的编码可能部位web开发者熟悉,我对底层编码也不熟,多是现学现用,基本也够用.说到编码,想起一本书特别赞:编码
使用做些实验
我在BLE学习笔记有演示的使用
我们在分析了/ Link与micro:bit的通信之后,使用ble工具来做些分析,我在树莓派里使用,你可可以选择其他工具
首先扫描micro:bit的地址
pi@cozmo1:~ $ sudo hcitool lescan
DF:48:87:86:93:20 BBC micro:bit [zuzop]
连接它并进入交互模式:
pi@cozmo1:~ $ gatttool -I -b DF:48:87:86:93:20 -t random
[DF:48:87:86:93:20][LE]> connect
Attempting to connect to DF:48:87:86:93:20
Connection successful
连接成功!
接着我们来看一下UART 的相关信息:
[DF:48:87:86:93:20][LE]> primary 0xf005 # 输入0xf005和f005相同,都被处理为16进制
Starting handle: 0x0013 Ending handle: 0xffff
[DF:48:87:86:93:20][LE]> char-desc 0x0013 0xffff
handle: 0x0013, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0015, uuid: 5261da01-fa7e-42ab-850b-7c80220097cc
handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0018, uuid: 5261da02-fa7e-42ab-850b-7c80220097cc
前头我们从源码里读到:
const BLEUUID = {
service: 0xf005,
rxChar: '5261da01-fa7e-42ab-850b-7c80220097cc',
txChar: '5261da02-fa7e-42ab-850b-7c80220097cc'
};
可知我们的猜测完全正确! Link是个透明代理。
接着让我们来读取micro:bit的数据,: '-fa7e-42ab-850b-'对应的为
[DF:48:87:86:93:20][LE]> char-read-hnd 0x0015
Characteristic value/descriptor: 00 5f ff f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
当我们按住按钮A时读到的数据为
[DF:48:87:86:93:20][LE]> char-read-hnd 0x0015
Characteristic value/descriptor: 00 44 fe 57 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
多次按下和松开,并观察,我们猜测按钮存储在00 44 fe 57 01中的1这个位置上
回忆一下前头的函数,据此我们就弄懂了数据的编解码方式,我们可以还原出从读到的经编码的数据
_processSessionData (base64) {
// parse data
const data = Base64Util.base64ToUint8Array(base64);
this._sensors.tiltX = data[1] | (data[0] << 8);
if (this._sensors.tiltX > (1 << 15)) this._sensors.tiltX -= (1 << 16);
this._sensors.tiltY = data[3] | (data[2] << 8);
if (this._sensors.tiltY > (1 << 15)) this._sensors.tiltY -= (1 << 16);
this._sensors.buttonA = data[4];
this._sensors.buttonB = data[5];
this._sensors.touchPins[0] = data[6];
this._sensors.touchPins[1] = data[7];
this._sensors.touchPins[2] = data[8];
this._sensors.gestureState = data[9];
// cancel disconnect timeout and start a new one
window.clearInterval(this._timeoutID);
this._timeoutID = window.setInterval(this.disconnectSession.bind(this), BLETimeout);
}
我们也可以开启通知
[DF:48:87:86:93:20][LE]> char-write-req 0x0016 0100
Characteristic value was written successfully
Notification handle = 0x0015 value: 00 29 00 8b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2d 00 85 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2d 00 85 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2d 00 86 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2d 00 8b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2c 00 8d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2a 00 8d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Notification handle = 0x0015 value: 00 2c 00 89 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
接着我们来实验往中写数据,我们前头提道 text积木,我们对其稍作调整,使其可在运行,观察编码后的内容是什么:
/**
* Enum for micro:bit BLE command protocol.
* https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md
* @readonly
* @enum {number}
*/
var CMD_DISPLAY_TEXT = 0x81;
function Uint8ToBase64(u8Arr){
var CHUNK_SIZE = 0x8000; //arbitrary number
var index = 0;
var length = u8Arr.length;
var result = '';
var slice;
while (index < length) {
slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
result += String.fromCharCode.apply(null, slice);
index += CHUNK_SIZE;
}
return btoa(result);
}
function displayText (text) {
const output = new Uint8Array(text.length);
for (let i = 0; i < text.length; i++) {
output[i] = text.charCodeAt(i);
}
return _writeSessionData(CMD_DISPLAY_TEXT, output);
}
function _writeSessionData (command, message) {
const output = new Uint8Array(message.length + 1);
output[0] = command; // attach command to beginning of message
for (let i = 0; i < message.length; i++) {
output[i + 1] = message[i];
}
// const data = Base64Util.uint8ArrayToBase64(output);
const data = Uint8ToBase64(output);
console.log(data)
console.log(output)
// return this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64');
}
displayText ("hello")
hello被编码后的为,发现和前头捕获的一致:
但应该是 Link与通信时的编解码方式,为了使用与micro:bit通信,我们需要猜测 :bit里的固件是如何如何约定编解码的,关于这点,官方采取了闭源的策略,估计是有意为之,我们稍后来hack它
hello 被编码后分别为:
gWhlbGxv //base64
Uint8Array(6) [129, 104, 101, 108, 108, 111] //Uint8Array
试着以几种方式将他们转为16进制,都没有成功在micro:bit中显示
char-write-cmd 0x0018 xxx
这导致我们需要使用一些嗅探工具抓包(BLE ),之后用来分析,不过我手边暂时没有相应硬件,准备淘宝上买一个
破解纯粹出于好玩,我们理解了官方的思路之后,自己重写一个micro:bit固件和是适配器也许比破解来得简单.使用可以很轻松把gatt服务都搭了出来, 参考:--
---2018年8月2号更新---
我昨晚回去路上一致在想如何在没买到嗅探工具之前,进行破解,网购到货得几天,路上想到几个策略,洗澡的时候又想到几个策略,兴奋不易,可惜晚上没带电脑和树莓派回去,没法做实验
我想到的策略有:
今早一来试了下第一条猜想就成功了,事实证明我想多了,官方并没有做加密
我们来看看在micro:bit ble 中,官方是如何发送 text数据的
displayText (args) {
const text = String(args.TEXT).substring(0, 19);
const output = new Uint8Array(text.length + 1);
output[0] = BLECommand.CMD_DISPLAY_TEXT;
for (let i = 0; i < text.length; i++) {
output[i + 1] = text.charCodeAt(i);
}
window.postMessage({type: 'command', buffer: output}, '*');
return;
}
window.addEventListener('message', (event) => {
if (event.data.type === 'command') {
txChar.writeValue(event.data.buffer);
} else if (event.data.type === 'status') {
if (event.data.status === 'connected')
document.getElementById('gui.menuBar.bluetoothIndicator').src = greenIndicatorIcon;
else if (event.data.status === 'disconnected')
document.getElementById('gui.menuBar.bluetoothIndicator').src = orangeIndicatorIcon;
}
}, false);
可以看出官方啥也没做: .(event.data.);
我们从简单的字符串分析入手,先试试a
显示从前端发往 Link的是:
{"jsonrpc":"2.0","method":"write","params":{"serviceId":61445,"characteristicId":"5261da02-fa7e-42ab-850b-7c80220097cc","message":"gWE=","encoding":"base64"},"id":3}
在前端被编码后结果分别为:
gWE= //base64
Uint8Array(2) [129, 97] //Uint8Array
我们只需要把转为hex就行
function buf2hex(buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
// EXAMPLE:
const buffer = new Uint8Array([129, 97]).buffer; // display a -> [129, 97]
console.log(buf2hex(buffer)); // = 8161
在树莓派的中:
[DF:48:87:86:93:20][LE]> char-write-cmd 0x0018 8161
大功告成!
tips
记录一些团队的机智做法
连接设备
用户选中之后开始连接,只扫描出对应的设备,而不是把周围的BLE都扫描出来,体验十分友好
网络安全学习路线图(思维导图)
网络安全学习路线图可以是一个有助于你规划学习进程的工具。你可以在思维导图上列出不同的主题和技能,然后按照逻辑顺序逐步学习和掌握它们。这可以帮助你更清晰地了解自己的学习进展和下一步计划。
1. 网络安全视频资料
2. 网络安全笔记/面试题
3. 网安电子书PDF资料
~