|
A full-featured Rust reimplementation of the deprecated BlueZ gatttool utility for interacting with Bluetooth Low Energy (BLE) devices via GATT (Generic Attribute Profile).
gratttool provides the same CLI argument syntax, input parsing, and output formatting as the original gatttool, while using the modern bluer crate (official BlueZ D-Bus Rust bindings) instead of raw HCI/ATT sockets.
gratttool has been end-to-end tested against BLE CTF and has full feature parity with the original gatttool, plus added enhancements for modern BLE workflows.
The original gatttool was removed from BlueZ in 2017 but remains one of the most referenced BLE tools in documentation, tutorials, and security research. gratttool fills that gap with a drop-in replacement that:
- Produces character-for-character identical output to the original gatttool
- Uses the modern BlueZ D-Bus API instead of deprecated raw sockets
- Is written in safe Rust with no C dependencies beyond libdbus
- Supports both non-interactive (one-shot) and interactive (shell) modes
- Adds enhanced features:
--scandevice discovery,--enumeratetable view,--bdaddrMAC spoofing,--mtuconfiguration, ASCII string writes, and hidden notification capture
- Rust 1.70+ (install via rustup)
- libdbus development headers
- BlueZ (the Linux Bluetooth stack)
# Debian/Ubuntu
sudo apt install libdbus-1-dev pkg-config bluetooth bluez
# Fedora
sudo dnf install dbus-devel pkgconf-pkg-config bluez
# Arch
sudo pacman -S dbus bluezcargo build --releaseThe binary is at ./target/release/gratttool.
| Flag | Long | Description | Default |
|---|---|---|---|
-i |
--adapter |
Local adapter interface | hci0 |
-b |
--device |
Remote device MAC address | (required for non-interactive) |
-t |
--addr-type |
Address type: public or random |
public |
-m |
--mtu |
Set BlueZ ExchangeMTU (23-517), or show |
|
-p |
--psm |
PSM for GATT/ATT over BR/EDR (0 = LE) | 0 |
-l |
--sec-level |
Security level: low, medium, or high |
low |
| Flag | Description |
|---|---|
--primary |
Discover all primary services |
--characteristics |
Discover characteristics |
--char-read |
Read characteristic value by handle or UUID |
--char-write |
Write command (no response) |
--char-write-req |
Write request (with response) |
--char-desc |
Discover characteristic descriptors |
--listen |
Listen for notifications and indications |
-I, --interactive |
Enter interactive shell mode |
| Flag | Long | Description | Default |
|---|---|---|---|
-s |
--start |
Start handle for discovery range | 0x0001 |
-e |
--end |
End handle for discovery range | 0xffff |
-u |
--uuid |
UUID filter (UUID16 like 1800 or full UUID128) |
|
-a |
--handle |
Attribute handle for read/write | |
-n |
--value |
Hex byte string value for writes (e.g. 0102ff) |
These flags are not available in the original gatttool — they are gratttool extensions designed to streamline common BLE CTF and reverse-engineering workflows.
| Flag | Long | Type | Description |
|---|---|---|---|
--scan |
[SECONDS] |
Scan for nearby BLE devices (default 10s) | |
--enumerate |
bool | Enumerate device info, services, characteristics, and values in a table | |
-A |
--ascii |
bool | Output read/notification values as ASCII (. for non-printable) |
-X |
--hex-ascii |
bool | Output read/notification values as hex AND ASCII side-by-side |
-S |
--string |
<TEXT> |
Write an ASCII string value (alternative to -n hex) |
--bdaddr |
<ADDR>|show |
Change adapter BD_ADDR (MAC), or show current address |
|
--bdaddr-no-reset |
bool | Don't reset adapter after BD_ADDR change | |
--bdaddr-transient |
bool | CSR only: transient mode (address lost on power cycle) |
Conflicts: -A and -X are mutually exclusive. -S and -n are mutually exclusive.
Scans for nearby BLE devices without needing to switch to hcitool lescan or bluetoothctl. Devices are printed live as they are discovered, followed by a color-coded summary table sorted by signal strength (RSSI).
# Scan for 10 seconds (default)
gratttool --scan
# Scan for 30 seconds
gratttool --scan 30
# Scan on a specific adapter
gratttool -i hci1 --scanOutput:
LE Scan on hci0 [AA:BB:CC:DD:EE:FF] for 10s ...
11:22:33:44:55:66 MyDevice RSSI: -45
77:88:99:AA:BB:CC (unknown) RSSI: -72
DD:EE:FF:00:11:22 BLE_CTF RSSI: -58
┌ Scan Results (3 devices) ──────────────────────────────────────────────┐
│ Address │ Type │ RSSI │ Name │
├───────────────────┼────────┼──────────┼────────────────────────────────┤
│ 11:22:33:44:55:66 │ public │ -45 dBm │ MyDevice │
│ DD:EE:FF:00:11:22 │ random │ -58 dBm │ BLE_CTF │
│ 77:88:99:AA:BB:CC │ random │ -72 dBm │ (unknown) │
└───────────────────┴────────┴──────────┴────────────────────────────────┘
RSSI values are color-coded: green for strong signal (>= -50 dBm), yellow for medium (>= -70), orange for weak (>= -85), and red for very weak.
Also available as the scan command in interactive mode:
[ ][LE]> scan
[ ][LE]> scan 5
Converts hex output to readable ASCII, replacing non-printable bytes with .:
# Without -A (default gatttool-compatible output):
gratttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x002a
# Characteristic value/descriptor: 66 6c 61 67 5f 76 61 6c 75 65
# With -A:
gratttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x002a -A
# Characteristic value/descriptor: flag_value
# Also works with --listen:
gratttool -b AA:BB:CC:DD:EE:FF --listen -A
# Notification handle = 0x000e value: helloShows both hex and ASCII together, like a hex dump:
gratttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x002a -X
# Characteristic value/descriptor: 66 6c 61 67 5f 76 61 6c 75 65 flag_value
gratttool -b AA:BB:CC:DD:EE:FF --char-read -u 2a00 -X
# handle: 0x0003 value: 48 65 6c 6c 6f HelloEliminates the common echo -n "..." | xxd -ps pipe for writing ASCII values:
# Before (error-prone — forgetting -n adds a trailing 0a newline byte):
gratttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x002c -n $(echo -n "flag_value" | xxd -ps)
# After:
gratttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x002c -S "flag_value"gratttool uses the BlueZ D-Bus API, which auto-negotiates the ATT MTU during connection. Unlike the original gatttool (which used raw ATT sockets), there is no per-connection MTU exchange call. Instead, gratttool modifies the system-wide BlueZ ExchangeMTU setting in /etc/bluetooth/main.conf and restarts the Bluetooth service.
# Show current configured ExchangeMTU value
gratttool -m show
# ExchangeMTU = 517 (from /etc/bluetooth/main.conf)
# Set MTU to a specific value (requires root)
sudo gratttool -m 444
# ExchangeMTU set to 444. Takes effect on next BLE connection.
# Now connect — BlueZ will request MTU 444 during ATT exchange
sudo gratttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x0003In interactive mode, mtu with no arguments displays the actual negotiated MTU for the current connection, and mtu <value> updates the BlueZ config:
[AA:BB:CC:DD:EE:FF][LE]> mtu
MTU was exchanged successfully: 517
[AA:BB:CC:DD:EE:FF][LE]> mtu 444
ExchangeMTU set to 444. Reconnect for it to take effect.
[AA:BB:CC:DD:EE:FF][LE]> mtu show
ExchangeMTU = 444 (from /etc/bluetooth/main.conf)
Important caveats:
- Requires root to modify
/etc/bluetooth/main.confand restartbluetoothd - System-wide setting — affects all Bluetooth connections on the machine, not just gratttool
- Does not affect the current connection — only takes effect on the next BLE connection (disconnect and reconnect)
- The actual negotiated MTU is
min(your value, remote device's supported MTU)— you cannot force a higher MTU than the peripheral supports - The BLE spec range is 23–517; the default of 517 is the maximum
Always reset MTU back to 517 when done testing. Leaving a low MTU configured will degrade performance for all Bluetooth applications on the system (file transfers, audio streaming, other BLE tools, etc.):
sudo gratttool -m 517A pure Rust reimplementation of the deprecated BlueZ bdaddr tool. Changes the Bluetooth adapter's hardware address (BD_ADDR) using vendor-specific HCI commands.
# Show current adapter address and manufacturer
sudo gratttool --bdaddr show
# Manufacturer: Broadcom (15)
# Device address: AA:BB:CC:DD:EE:FF
# Change address (resets adapter automatically)
sudo gratttool --bdaddr 00:11:22:33:44:55
# Change on a specific adapter
sudo gratttool -i hci1 --bdaddr 00:11:22:33:44:55
# CSR transient mode (lost on power cycle)
sudo gratttool --bdaddr 00:11:22:33:44:55 --bdaddr-transient
# Skip adapter reset after change
sudo gratttool --bdaddr 00:11:22:33:44:55 --bdaddr-no-resetRequires root.
Supported chipsets: Ericsson, Cambridge Silicon Radio (CSR), Texas Instruments, Broadcom, Zeevo, ST Microelectronics, Intel, and Cypress. The manufacturer is auto-detected via the BlueZ Management API — if your chipset is not in this list, the command will report it as unsupported.
How it works:
Modern Linux kernels (5.x+) restrict raw HCI sockets to vendor-specific commands only (OGF 0x3F), blocking standard HCI commands like Read_Local_Version. gratttool works around this by using two separate socket channels:
- BlueZ Management API (
HCI_CHANNEL_CONTROL) — reads controller info (manufacturer ID and current address). This channel is not restricted by the kernel. - Raw HCI socket (
HCI_CHANNEL_RAW) — sends the vendor-specific write command to flash the new address. These commands use OGF 0x3F so they pass the kernel's restriction.
Vendor write commands are fire-and-forget — the command is sent but the response is not read, because modern kernels do not reliably deliver command-complete events to userspace raw sockets. The adapter is then reset via ioctl (HCIDEVRESET) to apply the change, unless --bdaddr-no-reset is specified.
Persistence: The address change persists across bluetoothd restarts but is typically lost on a full power cycle (depends on chipset and whether the vendor command writes to persistent storage). CSR --bdaddr-transient mode is always lost on power cycle by design.
Non-interactive mode connects to a device, executes a single operation, prints results, and exits.
# All services
gratttool -b AA:BB:CC:DD:EE:FF --primary
# Output:
# attr handle = 0x0001, end grp handle = 0x0005 uuid: 00001800-0000-1000-8000-00805f9b34fb
# attr handle = 0x0006, end grp handle = 0x0009 uuid: 00001801-0000-1000-8000-00805f9b34fb
# Filter by UUID
gratttool -b AA:BB:CC:DD:EE:FF --primary -u 1800
# Output:
# Starting handle: 0001 Ending handle: 0005# All characteristics
gratttool -b AA:BB:CC:DD:EE:FF --characteristics
# Output:
# handle = 0x0002, char properties = 0x02, char value handle = 0x0003, uuid = 00002a00-...
# Within a handle range
gratttool -b AA:BB:CC:DD:EE:FF --characteristics -s 0x0001 -e 0x0010
# Filter by UUID
gratttool -b AA:BB:CC:DD:EE:FF --characteristics -u 2a00gratttool -b AA:BB:CC:DD:EE:FF --char-desc
gratttool -b AA:BB:CC:DD:EE:FF --char-desc -s 0x000a -e 0x0010
# Output:
# handle = 0x000a, uuid = 00002902-0000-1000-8000-00805f9b34fb# By handle
gratttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x0003
# Output:
# Characteristic value/descriptor: 48 65 6c 6c 6f
# By UUID
gratttool -b AA:BB:CC:DD:EE:FF --char-read -u 2a00
# Output:
# handle: 0x0003 value: 48 65 6c 6c 6f# Write command (no response, fire and forget)
gratttool -b AA:BB:CC:DD:EE:FF --char-write -a 0x000a -n 0100
# Write request (with response)
gratttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x000a -n 0100
# Output:
# Characteristic value was written successfully# Listen until Ctrl-C
gratttool -b AA:BB:CC:DD:EE:FF --listen
# Output:
# Notification handle = 0x000e value: 01 02 03
# Indication handle = 0x0012 value: ff ee dd
# Combine with another operation (e.g. enable notifications then listen)
gratttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x000f -n 0100 --listenHidden notifications (requires root): Some devices (particularly BLE CTF challenges) send notifications on characteristics that don't advertise the NOTIFY property bit. BlueZ's D-Bus API silently drops these. When run as root, gratttool opens an HCI monitor socket (the same kernel interface used by btmon) to passively capture ALL notification PDUs — including ones BlueZ would otherwise discard. If you expect a notification but aren't seeing output, try running with sudo:
sudo gratttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x0052 -n 0x00 --listenThis only affects --listen. All other operations (read, write, discovery) work without root.
Inspired by bleah's -e flag, --enumerate connects to a device and displays a full overview of all services, characteristics, descriptors, and readable values in a color-coded table view using the Catppuccin Mocha color palette.
gratttool -b AA:BB:CC:DD:EE:FF --enumerateA device info table is displayed first with metadata about the target:
┌ AA:BB:CC:DD:EE:FF ──────────────────────────────────────┐
│ Address │ AA:BB:CC:DD:EE:FF │
│ Address Type │ Public │
│ Name │ MyDevice │
│ RSSI │ -52 dBm │
│ Connected │ Yes │
│ Paired │ No │
│ Adapter │ hci0 │
│ Service UUIDs│ 00001800-..., 00001801-..., 000000ff-... │
└──────────────────────────────────────────────────────────┘
Followed by a GATT attribute table with five columns:
| Column | Description |
|---|---|
| Handles | Service handle range (0001 -> 0005) or characteristic/descriptor handle |
| Service > Characteristics | UUIDs organized hierarchically (services, characteristics, descriptors) |
| Properties | Characteristic properties (READ, WRITE, NOTIFY, INDICATE, etc.) |
| Data | Raw hex bytes read from readable characteristics |
| ASCII | ASCII representation of the data (. for non-printable bytes) |
┌──────────────┬────────────────────────────────────────┬───────────┬─────────────────┬───────┐
│ Handles │ Service > Characteristics │ Properties│ Data │ ASCII │
├──────────────┼────────────────────────────────────────┼───────────┼─────────────────┼───────┤
│ 0001 -> 0005 │ 00001801-0000-1000-8000-00805f9b34fb │ │ │ │
│ 0003 │ 00002a05-0000-1000-8000-00805f9b34fb │ INDICATE │ │ │
│ │ │ │ │ │
│ 0014 -> 001c │ 00001800-0000-1000-8000-00805f9b34fb │ │ │ │
│ 0016 │ 00002a00-0000-1000-8000-00805f9b34fb │ READ │ 48 65 6c 6c 6f │ Hello │
│ 0019 │ 00002a01-0000-1000-8000-00805f9b34fb │ READ │ c1 03 │ .. │
│ 1b │ 00002902-0000-1000-8000-00805f9b34fb│ │ │ │
│ │ │ │ │ │
│ 0028 -> ffff │ 000000ff-0000-1000-8000-00805f9b34fb │ │ │ │
│ 002a │ 0000ff01-0000-1000-8000-00805f9b34fb │ READ WRITE│ 01 02 03 │ ... │
└──────────────┴────────────────────────────────────────┴───────────┴─────────────────┴───────┘
The table automatically adapts to your terminal width. Data and ASCII columns wrap across multiple lines instead of truncating, so the full value is always visible. Readable characteristics are automatically read during enumeration (characteristics with only INDICATE are skipped to avoid hangs).
# Use random address type
gratttool -b AA:BB:CC:DD:EE:FF -t random --primary
# Use a specific adapter
gratttool -i hci1 -b AA:BB:CC:DD:EE:FF --primary
# Set security level
gratttool -b AA:BB:CC:DD:EE:FF -l medium --char-read -a 0x0003Interactive mode provides a readline shell for exploring a BLE device. It supports command history, tab completion, and asynchronous notification display.
gratttool -I
gratttool -I -b AA:BB:CC:DD:EE:FF # pre-set device address
gratttool -I -b AA:BB:CC:DD:EE:FF -t random # pre-set with random addressThe prompt shows the connection state and transport type:
[ ][LE]> # disconnected
[AA:BB:CC:DD:EE:FF][LE]> # connected (shown in blue)
| Command | Arguments | Description |
|---|---|---|
help |
Show all available commands | |
exit / quit |
Disconnect and exit | |
scan |
[duration] |
Scan for nearby BLE devices (default 10s) |
connect |
[address [address_type]] |
Connect to a device |
disconnect |
Disconnect from current device | |
primary |
[UUID] |
Discover primary services |
included |
[start [end]] |
Find included services |
characteristics |
[start [end [UUID]]] |
Discover characteristics |
char-desc |
[start [end]] |
Discover descriptors |
char-read-hnd |
<handle> |
Read value by handle |
char-read-uuid |
<UUID> [start [end]] |
Read value by UUID |
char-write-req |
<handle> <value> |
Write with response |
char-write-cmd |
<handle> <value> |
Write without response |
sec-level |
[low|medium|high] |
Get or set security level |
mtu |
[value | show] |
Display negotiated MTU, or set BlueZ ExchangeMTU |
$ gratttool -I
[ ][LE]> connect AA:BB:CC:DD:EE:FF
Attempting to connect to AA:BB:CC:DD:EE:FF
Connection successful
[AA:BB:CC:DD:EE:FF][LE]> primary
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0006, end grp handle: 0x0009 uuid: 00001801-0000-1000-8000-00805f9b34fb
[AA:BB:CC:DD:EE:FF][LE]> characteristics
handle: 0x0002, char properties: 0x02, char value handle: 0x0003, uuid: 00002a00-...
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-...
[AA:BB:CC:DD:EE:FF][LE]> char-read-hnd 0003
Characteristic value/descriptor: 48 65 6c 6c 6f
[AA:BB:CC:DD:EE:FF][LE]> char-write-req 000a 0100
Characteristic value was written successfully
[AA:BB:CC:DD:EE:FF][LE]> sec-level
sec-level: low
[AA:BB:CC:DD:EE:FF][LE]> mtu
MTU was exchanged successfully: 517
[AA:BB:CC:DD:EE:FF][LE]> disconnect
[ ][LE]> quit
Notifications and indications are printed asynchronously between prompts while connected.
gratttool matches the original gatttool output format exactly:
Non-interactive mode (uses =):
attr handle = 0x0001, end grp handle = 0x0005 uuid: 00001800-...
handle = 0x0002, char properties = 0x02, char value handle = 0x0003, uuid = 00002a00-...
handle = 0x000a, uuid = 00002902-...
Characteristic value/descriptor: 48 65 6c 6c 6f
handle: 0x0003 value: 48 65 6c 6c 6f
Characteristic value was written successfully
Notification handle = 0x000e value: 01 02 03
Indication handle = 0x0012 value: ff ee dd
Starting handle: 0001 Ending handle: 0005
Interactive mode (uses :):
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001800-...
handle: 0x0002, char properties: 0x02, char value handle: 0x0003, uuid: 00002a00-...
handle: 0x000a, uuid: 00002902-...
src/
main.rs Entry point, argument parsing, mode dispatch
cli.rs Clap argument definitions (matches gatttool syntax)
connection.rs BLE connection management via bluer + MTU config
handle_table.rs ATT handle-to-bluer-object mapping table
gatt.rs GATT operations (discover, read, write, notify)
interactive.rs Interactive readline shell with async notifications
output.rs Output formatting (gatttool formats + Catppuccin enumerate/scan tables)
error.rs ATT error codes and application error types
scan.rs BLE device scanning via bluer discovery API
bdaddr.rs BD_ADDR change via vendor-specific HCI commands
The original gatttool is handle-centric (--handle 0x0004, output shows handle = 0x0003), but bluer operates on D-Bus object paths. After connecting, gratttool performs full GATT discovery and builds an internal handle table mapping BlueZ's ATT handle identifiers to bluer Service/Characteristic/Descriptor objects. This allows handle-based input and output while using bluer's safe API underneath.
MIT
