基于经典蓝牙 RFCOMM 的私有协议 Fuzzing

应用开发者可基于 BLE 提供的 ATT/GATT 协议,开发自己的私有协议。就像我们可以基于 TCP 创建 HTTP 协议一样。同样道理,对于经典蓝牙,厂家也可以在 L2CAP/RFCOMM 协议的基础上,封装自己的私有协议,实现额外的消息加密、身份认证等。 我们在上一篇文章 使用 Python 模块 bluepy 玩转 BLE 说过低功耗蓝牙应用层协议的测试方法。本次就来聊聊经典蓝牙(BR,Basic Rate)一些私有协议的测试方法。

01 Python 脚本实现 RFCOMM 数据包收发


python 提供的蓝牙模块
  • pybluez: Python code to access the host machine’s Bluetooth resources. 用于经典蓝牙
  • bluepy: Python interface to Bluetooth LE on Linux. 用于低功耗蓝牙

Linux 启用蓝牙适配器

插入蓝牙适配器,hciconfig 命令可以查看当前 Linux 存在的 HCI 设备

在这里插入图片描述

1.当前设备默认关闭,开启蓝牙

$ sudo hciconfig hci1 up

2.扫描周围蓝牙设备

└─$ hcitool -i hci1 scan   
Scanning ...
        50:E0:85:XX:XX:XX       n/a
        80:CF:A2:XX:XX:XX       n/a
        A4:83:E7:XX:XX:XX       LZX

3.扫描目标蓝牙设备支持的服务

$ sdptool browse A4:83:E7:XX:XX:XX

简单了解蓝牙渗透的基本命令之后,我们使用脚本实现上述命令

PyBluez 实现经典蓝牙收发

安装 pybluez(建议使用 Linux,Windows 可能会出现奇怪的问题)

 pip install pybluez2	# > python3.6
 pip install pybluez	# < python3.6

pybluez 官方提供了几个使用案例,我们稍稍整合一下,扫描当前经典蓝牙设备

import bluetooth

print("Performing inquiry...")

nearby_devices = bluetooth.discover_devices(duration=8, lookup_names=True,
                                            flush_cache=True, lookup_class=False)

print("Found {} devices".format(len(nearby_devices)))

for addr, name in nearby_devices:
    try:
        print("   {} - {}".format(addr, name))
    except UnicodeEncodeError:
        print("   {} - {}".format(addr, name.encode("utf-8", "replace")))

发现的设备

123

扫描特定设备支持的服务

import sys

import bluetooth

if len(sys.argv) < 2:
    print("Usage: sdp-browse.py <addr>")
    print("   addr - can be a bluetooth address, \"localhost\", or \"all\"")
    sys.exit(2)

target = sys.argv[1]
if target == "all":
    target = None

services = bluetooth.find_service(address=target)

if len(services) > 0:
    print("Found {} services on {}.".format(len(services), sys.argv[1]))
else:
    print("No services found.")

for svc in services:
    print("\nService Name:", svc["name"])
    print("    Host:       ", svc["host"])
    print("    Description:", svc["description"])
    print("    Provided By:", svc["provider"])
    print("    Protocol:   ", svc["protocol"])
    print("    channel/PSM:", svc["port"])
    print("    svc classes:", svc["service-classes"])
    print("    profiles:   ", svc["profiles"])
    print("    service id: ", svc["service-id"])

发送 L2CAP/RFCOMM 协议报文

socket = bluetooth.BluetoothSocket(bluetooth.L2CAP)
socket.connect(("aa:bb:cc:dd:ee:ff", 23))		# Channel:23
socket.send(b"\x00\x01\x02")					# 可以定制化 Fuzzing
socket.recv(100)

02 Android App 实现 RFCOMM 数据包收发

Android SDK 提供了较为丰富的蓝牙 API,我们仍然以 RFCOMM 协议为例,因为很多私有协议建立在这层协议基础上。主要代码如下

private static final String uuid = "82ff3820-8411-400c-b85a-ffbdb32cf0ff";
public void connectBt() {
    
    
        try {
    
    
            /* 获取本地蓝牙适配器 */
            BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            /* 打开蓝牙 */
            if (!mBluetoothAdapter.isEnabled()) {
    
    
                mBluetoothAdapter.isEnabled();
            }
            /* 获取当前已绑定的设备 */
            Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
            Log.i(TAG, "device nubmer: " + devices.size());

            for (BluetoothDevice device:devices) {
    
    
                Log.i(TAG, "name: " + device.getName() + "\naddress: " + device.getAddress());
                /* 与已绑定设备建立RFCOMM连接 */
                if (device.getAddress().equals("94:08:C7:aa:bb:cc")) {
    
    
                    try {
    
    
                        BluetoothSocket socket = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
                        socket.connect();
                        if (socket != null) {
    
    
                            Log.i(TAG, "+++++ Success to create RFCOMM connection +++++");
                            /* 在新的线程里实现异步读 */
                            MainActivity.this.brThread = new BrThread(socket);
                            MainActivity.this.brThread.start();
                            /* 在当前线程里实现发送数据 */
                            /* 将brThread全局化是为了方便同时使用其他方法发送bt-rfcomm数据包 */
                            MainActivity.this.brThread.write(Helper.string_byte("001122334455"));
                        }
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        } catch (SecurityException e) {
    
    
            e.printStackTrace();
        }
    }

发送蓝牙 RFCOMM 消息简单,但是接收消息需要单独启用线程完成

package com.lys.brtest;

import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.lys.brtest.util.Helper;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

public class BrThread extends Thread{
    
    
    private static final String TAG = "BrThread";
    private BluetoothSocket btSocket;
    private InputStream mmInStream;
    private OutputStream mmOutStream;

    public BrThread(BluetoothSocket socket) {
    
    
        btSocket = socket;
        try {
    
    
            mmInStream = btSocket.getInputStream();
            mmOutStream = btSocket.getOutputStream();
        } catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }
    }

    public void run() {
    
    
        // 监听输入流
        while (true) {
    
    
            try {
    
    
                byte[] buffer = new byte[1024];
                // 读取输入流
                int bytes = mmInStream.read(buffer);
                Log.i(TAG, String.format("[%d] Recv <========================\n%s", bytes, Helper.byte_string(Arrays.copyOfRange(buffer, 0, bytes))));
            } catch (IOException e) {
    
    
                Log.e(TAG, "+++++ Disconnected +++++", e);
                break;
            }
        }
    }

    public void write(byte[] bytes) {
    
    
        // 发送数据
        Log.i(TAG, String.format("[%d] Send >========================\n%s", bytes.length, Helper.byte_string(bytes)));
        try {
    
    
            if (mmOutStream != null) {
    
    
                mmOutStream.write(bytes);
            }
        } catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }

    }
}

当然,还需要一些额外的方法完成字节数组与十六进制字符串的转换

public class Helper {
    
    
    // 字节数组转十六进制
    public static String byte_string(byte[] data) {
    
    
        StringBuilder sb = new StringBuilder();
        for (byte b : data) {
    
    
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 十六进制转字节数组
    public static byte[] string_byte(String hexString) {
    
    
        hexString = hexString.replaceAll(" ", "");
        int len = hexString.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
    
    
            // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                    .digit(hexString.charAt(i+1), 16));
        }
        return bytes;
    }

    // 随机十六进制
    public static String get_random_hex(int len, int range) {
    
    
        Random random = new Random();
        if (len == 0) len = random.nextInt(256);
        byte[] data = new byte[len];

        for (int i = 0; i < len; i++) {
    
    
            if (range == 0)
            // -128 ~ 127
                data[i] = (byte) (random.nextInt(0xff) + (-128));
            else
                data[i] = (byte) (random.nextInt(range));
        }
        return Helper.byte_string(data);
    }
}

如果遇到 CRC 校验,请注意,CRC 校验算法有很多,不同算法计算结果并不一致,Android-BLE-Common-Library 项目提供蓝牙领域经常使用的 CRC 校验函数

# 某私有协议格式化
| service Id | feture Id | TLV  |

这个时候就可以按照协议格式进行 Fuzzing 测试了。可以通过目标设备是否下线,或者蓝牙连接是否断开作为反馈。

在这里插入图片描述

也有一些 App 已经集成 RFComm 协议测试,例如 Android RFComm Emulator,蓝牙调试宝,这些应用可以用来收发包,可以用来调试,但是不支持二次开发,所以最好还是自己编写收发包 APK。

03 其他思路

除了上面提到的通过蓝牙发包方式进行 Fuzzing,可以考虑将私有协议解析部分的代码单独解耦,编译,利用 AFL/Libfuzzer 进行有路径反馈的 Fuzzing;在没有源代码的情况下,还可以 patch 协议解析部分的二进制,使用 AFL-qemu 进行 Fuzzing。

猜你喜欢

转载自blog.csdn.net/song_lee/article/details/129887212