Skip to main content
Start hacking Bluetooth Low Energy today! (part 1)
  • How Tos

Start hacking Bluetooth Low Energy today! (part 1)

Sam Thom

21 Aug 2025 15 Min Read

TL;DR

  • Get started with BLE hacking for a couple of quid using a key finder tag and free tools.
  • Capture traffic between the Android app and the tag with ADB to spot the command that makes it beep.
  • This post shows the free route. Next, we’ll cover hardware that makes testing easier and enables fuzzing.

Before you start

First off, before we start  Bluetooth hacking can be confusing, there are lots of references to tools that are a bit outdated, scripts that don’t run, and more. The actual Bluetooth specs are huge, but honestly, who cares as long as you can make a device do a thing?

If we want to hack on Bluetooth *stuff*, there are basically three main options:

  1. Use Linux and a Bluetooth adapter card—this could be a built-in or cheap USB dongle. It’s probably FREE to most practicing hackers. Remember that Raspberry Pi 3 (or newer) sitting in your drawer? Perfect for the job.
  2. Use a phone- Android is ideal. iOS technically works, but if you value sleep and sanity, maybe just give it a miss. Again, likely free
  3. Use external devices to do your Bluetooth interactions:
  • Minimalist dev board options start at around £40
  • Around £100 gets you a much more capable setup with two dev boards.
  • You can spend far more on an Ubertooth, but honestly they’re not that great in 2025 now specs have moved on a little and they can only really help with reconnaissance.
  • You can try and use a Flipper Zero, but I wouldn’t bother – the Bluetooth stack is very minimal and development is tricky.

Don’t be put off

All this can lead to a bit of confusion and paralysis when getting started, so we’ll hack a device together three ways for three budgets:

  1. Free (ish)
  2. <£40
  3. <£100

You can follow along and play as much as you want to pay. We’ll cover each option in a three-post series. This post will cover the free-ish option, with the other two to come in the future.

With that being said, if you are considering purchasing kit, you may want to hold off till the final post before deciding. As a hacking magpie myself, I understand this is a big ask… no judgement.

What will we do for this part… for free (ish)?

We are going to hack on these little BLE car key finders, which are available on Aliexpress for under £2. Sorry the free tier isn’t *quite* free! 

With these thingies, my goal is to trigger them to do the one thing they do – go beep. They beep when your device loses a connection, but more interestingly, they beep when you press a button in the mobile app – Let’s make that happen without using the app!

So! If you’re following along, the first job is to get yourself one of these fobs, any that work with the ‘iSearching’ app should be OK. Make sure you get BLE ones and not a ‘find my’ version.

Now let’s do a mini Bluetooth primer so that we can come up with a plan on how to operate these devices.

These devices use Bluetooth Low Energy BLE which is a newer simplified Bluetooth spec (vs Bluetooth Classic). BLE devices use the Generic Attribute Profile (GATT) protocol for pretty much all the communications that we care about as hackers. These devices use GATT in a typical but very simple way which makes them ideal to learn with. 

If you’ve not come across GATT before, I’d suggest some further reading after you work through this workshop, such as our intro to BLE which is helpful, but TL;DR, GATT is like a structure for the services that a Bluetooth device offers. Each service has characteristics and optional descriptors, something like this:

So, now we know just enough about GATT, we can work out a generic process for interacting with BLE devices:

  • Check the device uses BLE, not legacy Bluetooth – DONE, we know from the product listing
  • Work out what services exist on the device
  • Work out what data needs to pass to and from those services to make things happen
  • Use our Bluetooth toolbox to try and send/receive that data
  • Debug that data going between devices as needed

Let’s do it! (for free-ish)

Let’s try and follow that methodology for as little money as possible. If you have a Bluetooth dongle, and Android phone and a Linux device this will be free (except the BLE tag cost of a couple of quid), but worst case you’ll need a USB Bluetooth dongle; we have success with the CSR USB dongles, which go for less than a tenner. The UGreen BT5.4 dongles also seem to be working OK for me on most sane Linuxes (BTW).

If you’re considering a dongle, but would rather not spend, are you SURE you don’t have a Pi3 or newer or a Pi Zero W kicking about? They are (as my very very [very] experienced colleague Dave would say) a perfectly cromulent device.

Recon

Our devices are operated by an Android application, so  you’ll need an Android device to operate the BLE tags anyway. We can monitor the connection between that Android device and the fob using the phone itself fairly easily and it’s free, so let’s use that for this section!

First you’ll need to configure ADB on your device. If you want it to set it up as a long-term testing device consider rooting the device for deeper access while you’re at it?

To get sniffing, you just need to connect the phone to your hacking PC (just use Linux, honestly) and confirm the device is connected with ADB:

Once connected to ADB you should see the Phone’s BTsnoop interface as a capture option in Wireshark.

If you have any issues, try refreshing capture interfaces or enabling the HCI snoop log in developer settings.

Now we can start capturing in Wireshark, and we should see packets when we use the companion application. Then it’s a game of trying to map characteristic reads and writes to the real-world actions you took in the app.

This app doesn’t send much traffic other than that required to actually take control actions, so our capture is mercifully concise. When we click the alarm button we get a handful of packets, one of which is a Write command, where 0x01 is written to a characteristic whose handle is 0x0b:

Remember  from our Bluetooth primer that handles are a kind of short-hand to point to data for a given characteristic. This lets us send a two byte shorthand instead of a UUID and improves readability.

When we cancel the alarm, we see the characteristic value is set to 0x00:

So wasn’t that easy! Now we know what we need to do to trigger and cancel the alarm, send a one or a zero to some characteristic to set the alarm state. If you are struggling to spot the packets, you can probably filter for _ws.col.info contains “Write Command” in Wireshark, which will filter things like reads, scans and advertisements out.

Let’s take a minute and imagine we couldn’t work that out from the PCAPs, what options do we have? A common approach is to use something like jadx to reverse the APK, so let’s do that! Don’t worry though I’m a very lazy reverse engineer so I just searched for the string ‘Alarm’ within the main app. Pretty quickly I found an OnClick listener for the alarm button in the app, which toggles the alarm on (1) or off (2) based on current alarm state:

Inside the alarm methods, we can see those raw bytes being sent to a characteristic to turn the alarm on (1) and off (2):

The only thing we’re missing from this analysis is the characteristic we need to write to, so to find that I looked for put() calls to the bleWrireCharaterMap object.

Why did I do that? Because the call to setValue() in (1) and (2) above sets the value of a characteristic and it seems to be accessed via this bleWrireCharacterMap.get(str) object, which is a hashmap. If we want to know the value at runtime we can look to see if any data is put into that hashmap and how. There is only one put operation that happens in the app and we see a static UUID (1) being put (2):

We’ll see later that this UUID refers to the alarm charactersitic which has a handle 0x0b. So we have also discovered the same thing via jadx as we did via Wireshark. When we connect to devices we can map UUIDs to handles, so don’t panic about which one you find during recon. Most tools will allow you do to writes by handle or UUID so you should be all good either way.

Now we just need to control this interaction from our hacker devices…

Control the device (Linux)

If you have a Bluetooth card and a Linux PC, you should be able to trigger these characteristic writes fairly quickly. Built in cards work sometimes, the old CSR dongles should work fine for this device, I have a newer UGREEN ripoff:

First off find your Bluetooth dongle and target device:

~$ sudo systemctl start bluetooth
~$ sudo hcitool dev               
Devices:
        hci0    38:7A:0E:A0:13:73
~$sudo hcitool -i hci0 lescan
Scanning ...
5B:B1:7F:47:A7:00 <-- our device

Then we’ll use gatttool to connect to the device and write to that characteristic:

gatttool -i hci0 -b 5B:B1:7F:47:A7:00 --char-write-req --handle 0x0b --value 0x01

Where we are using the handle value we saw in wireshark. Let’s imagine you’re not sure about handle to UUID mapping, don’t worry gatttool has commands to enumerate the GATT and list characteristic to handle mappings out, I’ll leave this as an exercise for you to do.

That may have worked for you, but on Kali and Ubuntu I find it’s no longer that reliable. Sometimes I was getting this response:

gatttool -i hci1 -b 5B:B1:7F:47:A7:00 --char-write-req --handle 0x0b --value 0x01
connect to 5B:B1:7F:47:A7:00: Connection refused (111)

Suggesting an issue with how gatttool was handling connections to the devices, which is consistent with interactive mode, where it just hung:

~$ sudo gatttool -i hci1 -I
[                 ][LE]> connect 5B:B1:7F:47:A7:00
Attempting to connect to 5B:B1:7F:47:A7:00
Error: connect to 5B:B1:7F:47:A7:00: Connection refused (111)
[5B:B1:7F:47:A7:00][LE]>

After a reboot I was able to get a connection:

~$ sudo gatttool -i hci0 -b 5B:B1:7F:47:A7:00 --char-write-req --handle 0x0b --value 0x01
Characteristic value was written successfully
~$ sudo gatttool -i hci0 -b 5B:B1:7F:47:A7:00 --char-write-req --handle 0x0b --value 0x00
Characteristic value was written successfully

SO! Maybe this worked for you, maybe it didn’t. Why? Well gatttool is technically depracated and BLE devices can be weird about connection states blocking further connections.

Sadly there’s no official contender for a replacement to gatttool. There was [talk of btgatt-client being it](https://wiki.archlinux.org/title/Bluetooth#Troubleshooting), but it’s not been that successful; bluetoothctl is a strong contender and is probably already on whatever Linux box you’re using so let’s try that.

Once in bluetoothctl, you can scan for devices with scan le, then connect by MAC address as below, the tool will automatically enumerate the GATT. Highlighted are the actual commands I typed, everything else is stdout (which also gets a prompt because reasons):

~$ bluetoothctl
[bluetooth]# scan le 
[bluetooth]# SetDiscoveryFilter success 
[bluetooth]# Discovery started 
[bluetooth]# [CHG] Controller DE:AD:BE:EE:EE:EF Discovering: yes 
[bluetooth]# [NEW] Device 5B:B1:7F:47:A7:00 iTAG 
[bluetooth]# [CHG] Device 5B:B1:7F:47:A7:00 RSSI: 0xffffffc2 (-62) 
[bluetooth]# scan off 
[bluetooth]# Discovery stopped 
[bluetooth]# [CHG] Device 5B:B1:7F:47:A7:00 RSSI is nil 
[bluetooth]# [CHG] Controller DE:AD:BE:EE:EE:EF Discovering: no 
[bluetooth]# connect 5B:B1:7F:47:A7:00 
Attempting to connect to 5B:B1:7F:47:A7:00 
[iTAG ]# [CHG] Device 5B:B1:7F:47:A7:00 Connected: yes 
[iTAG ]# Connection successful 
[iTAG ]# [NEW] Primary Service (Handle 0x0001) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0001 0000180f-0000-1000-8000-00805f9b34fb Battery Service
(... SNIPPED FOR BREVITY ...)

A weird intricacy of bluetoothctl is that we also need to enter the gatt menu to do gatt stuff:

[iTAG  ]# menu gatt
Menu gatt:
Available commands:
-------------------
list-attributes [dev/local]                       List attributes
select-attribute <attribute/UUID/local> [attribute/UUID] Select attribute
attribute-info [attribute/UUID]                   Select attribute
read [offset]                                     Read attribute value
write <data=xx xx ...> [offset] [type]            Write attribute value
acquire-write                                     Acquire Write file descriptor
release-write                                     Release Write file descriptor
acquire-notify                                    Acquire Notify file descriptor
release-notify                                    Release Notify file descriptor
notify <on/off>                                   Notify attribute value
clone [dev/attribute/UUID]                        Clone a device or attribute
register-application [UUID ...]                   Register profile to connect
unregister-application                            Unregister profile
register-service <UUID> [handle]                  Register application service.
unregister-service <UUID/object>                  Unregister application service
register-includes <UUID> [handle]                 Register as Included service in.
unregister-includes <Service-UUID> <Inc-UUID>     Unregister Included service.
register-characteristic <UUID> <Flags=read,write,notify...> [handle] Register application characteristic
unregister-characteristic <UUID/object>           Unregister application characteristic
register-descriptor <UUID> <Flags=read,write...> [handle] Register application descriptor
unregister-descriptor <UUID/object>               Unregister application descriptor
back                                              Return to main menu
version                                           Display version
quit                                              Quit program
exit                                              Quit program
help                                              Display help about this program
export                                            Print environment variables
script <filename>                                 Run script

Then we can list attributes on the device again. Notice that the handle seems to be off by one here as 0x0a , we also get the UUID, which you may recognise from our app recon:


[iTAG ]# list-attributes 
[0/618] Primary Service (Handle 0x0001) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0001 0000180f-0000-1000-8000-00805f9b34fb Battery Service Characteristic (Handle 0x0002) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0001/char0002 00002a19-0000-1000-8000-00805f9b34fb Battery Level Descriptor (Handle 0x8fdb) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0001/char0002/desc0004 00002902-0000-1000-8000-00805f9b34fb Client Characteristic Configuration Primary Service (Handle 0x0005) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0005 00001804-0000-1000-8000-00805f9b34fb Tx Power Characteristic (Handle 0x0006) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0005/char0006 00002a07-0000-1000-8000-00805f9b34fb Tx Power Level Descriptor (Handle 0x8fdd) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0005/char0006/desc0008 00000229-0000-1000-8000-00805f9b34fb Unknown Primary Service (Handle 0x0009) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0009 00001802-0000-1000-8000-00805f9b34fb Immediate Alert Characteristic (Handle 0x000a) /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0009/char000a 00002a06-0000-1000-8000-00805f9b34fb Alert Level

(... SNIPPED FOR BREVITY ...)

Then we need to select the attribute to work on and use a write command in that submenu:

[iTAG ]# select-attribute 00002a06-0000-1000-8000-00805f9b34fb

[iTAG :/service0009/char000a]# write data=0x01

Invalid value at index 0 Attempting to write /org/bluez/hci0/dev_5B_B1_7F_47_A7_00/service0009/char000a

And this does trigger the alarm for me, yay! But again, for me this is very buggy, I can trigger the alarm but not reliably turn it off with 0x00 write. I also find the menus quite counter-intuitive, so I’m still using gatttool more than bluetoothctl to be honest. I guess watch this space in future?

Control the Device (Android)

If you’re an Android whizz you can ‘just’ write a small app to do this for you if you like. Perhaps you can imagine an activity with a button for on and off, then event listeners that call BLE writes, but for a simple example like this it’s a bit much. If you want to fuzz something, perhaps this will be more relevant.

An easier and free-er option in this case is just to use something like nRFConnect to do the work. I’m going to use Lightblue, which is an android app that works basically the same way. Open the app and scan and connect to the device. Once connected, you’ll see device services:

There is a service that Lightblue has already mapped to a name and it’s called ‘immediate alert’:

We can inspect that Immediate Alert service further and find the ‘Alert Level’ characteristic

So, we have our Alert Level Characteristic, you may also recognise the UUID from our recon (the one starting 2a06 not the one labelled service UUID, that’s associated with the higher level service) Now we can press the ‘write new value’. button there to write stuff:

Writing a 1 there triggers the alarm, yay! So simple and completely free, ideal for simple devices like this. This method just works.

Look forward

So that’s it, you did it (hopefully)!

I hope you feel like interacting with a BLE device (at least to make it work as intended) is not that difficult now, but at the free tier I do feel we’re limited to quite buggy interactions on Linux, that are constrained by tool capabilities. Our options on Android are quite manual but perfectly workable.

In the next installments I’d like to introduce to you to some external hardware that will make it easier to work with BLE devices and maybe open the door to more complex interactions like fuzzing or sending data with scripts. See you there!