Atorch DL24 |Hack #01
Hey there! 👋
In this post I’m going to present you how to hack the Atorch DL24. Happy reading! 📚
At first, I use Visual Studio with C# to test the hacked communication protocol with the Atorch DL24. You will need a computer with integrated Bluetooth. You can use other programming languages that support Bluetooth communication.
Init Visual Studio Project
- Create new C# Console project
- Add C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd to your Assemblies (for Bluetooth)
- Add C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETCore\v4.5\System.Runtime.WindowsRuntime.dll to your Assemblies (to avoid error with async)
Device Data
I used the app nRF Connect to read the device name, available services and characteristics. The app is available for Android and iOS and a is great start to hack the protocol.
Download “nRF Connect” for Android.
Turn on Bluetooth and connect to “DL24-BLE”.
You will see on Atorch DL24 the bluetooth connected icon.
Following values can be extracted from the nRF Connect app:
Device name = "DL24-BLE"
Device address = "19:12:02:14:04:cc" (in this case)
Device ID = "BluetoothLE#BluetoothLE00:1a:7d:da:71:11-19:12:02:14:04:cc"
Services
There is only one service “0000ffe0-0000-1000-8000-00805f9b34fb”.
Following code snippet can be used to connect to the bluetooth service and get characteristics programmatically:
using Windows.Devices.Bluetooth;
using Windows;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage.Streams;
string deviceAddress = "19:12:02:14:04:cc";
static void Main(string[] args){new Program();}
public Program()
{
string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };
DeviceWatcher deviceWatcher =
DeviceInformation.CreateWatcher(
BluetoothLEDevice.GetDeviceSelectorFromPairingState(false),
requestedProperties,
DeviceInformationKind.AssociationEndpoint);
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Updated += DeviceWatcher_Updated;
deviceWatcher.Removed += DeviceWatcher_Removed;
deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
deviceWatcher.Stopped += DeviceWatcher_Stopped;
deviceWatcher.Start();
System.Console.ReadLine();
}
private void DeviceWatcher_Stopped(DeviceWatcher sender, object args){int a = 0;}
private void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args){int a = 0;}
private void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args){int a = 0;}
private void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args){int i = 0;}
private void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args){if (args.Id.Contains(deviceAddress)){ConnectDevice(args.Id);}}
async void ConnectDevice(string id)
{
BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromIdAsync(id);
var srvResultList = await bluetoothLeDevice.GetGattServicesAsync();
var srvResult = await bluetoothLeDevice.GetGattServicesForUuidAsync(new Guid("0000ffe0-0000-1000-8000-00805f9b34fb"), BluetoothCacheMode.Cached);
if (srvResult.Status != GattCommunicationStatus.Success || !srvResult.Services.Any())
{
Console.WriteLine("Cannot find service for device");
return;
}
var service = srvResult.Services.First();
var chrResult = await service.GetCharacteristicsAsync();
if (chrResult.Status != GattCommunicationStatus.Success){return;}
var chrs = from x in chrResult.Characteristics
select x;
var gattCharacteristics = chrs as GattCharacteristic[] ?? chrs.ToArray();
if (!gattCharacteristics.Any()){return;}
}
Characteristics
There are two Characteristics:
The first characteristic can be used to subscribe for notifications to get the current DL24 discharge data. The second one can be used to send commands to the DL24 which allows you to simulate input buttons on the device.
Read notifications
The first Characteristic (UUID: 0000ffe1-0000-1000-8000-00805f9b34fb) can be used to subscribe for notifications.
await gattCharacteristics[0].WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
gattCharacteristics[0].ValueChanged += gattCharacteristics_0_ValueChanged;
private void gattCharacteristics_0_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
byte[] buffer = args.CharacteristicValue.ToArray();
int counter = 0;
foreach (var elem in buffer)
{
string name = " ";
if (counter == 6 && buffer.Length == 20)
{
name = " Vol";
}
else if (counter == 12 && buffer.Length == 20)
{
name = " Cap";
}
else if (counter == 7 && buffer.Length == 16)
{
name = " HH";
}
else if (counter == 8 && buffer.Length == 16)
{
name = " MM";
}
else if (counter == 9 && buffer.Length == 16)
{
name = " SS";
}
else if (counter == 8 && buffer.Length == 20)
{
name = "Cur1";
}
else if (counter == 9 && buffer.Length == 20)
{
name = "Cur2";
}
else if (counter == 5 && buffer.Length == 16)
{
name = "Temp";
}
Console.WriteLine(String.Format("{0, 0:d3} {1, 0:s4}: {2, 0:d3} 0x{3, 0:X2}", counter, name, elem, elem));
counter++;
}
var t = 0;
}
DL24 device is sending 2 messages each time, the first has 20 bytes and the second one 16 bytes. The messages will be sent continuously each 1 second.
The messages have the following format:
index | hex | dec | description |
---|---|---|---|
000 | 255 | 0xFF | Unknown |
001 | 085 | 0x55 | Unknown |
002 | 001 | 0x01 | Unknown |
003 | 002 | 0x02 | Unknown |
004 | 000 | 0x00 | Vol3? |
005 | 000 | 0x00 | Vol2? |
006 | 212 | 0xD4 | Vol1 = current Voltage (21,2V; Display: Vol:21.272V) |
007 | 000 | 0x00 | Cur3? |
008 | 002 | 0x02 | Cur2 = Current (high byte e.g. 2*256 + 9 = 521 = 0,521A; Display: Cur:00.521A) |
009 | 009 | 0x09 | Cur1 = Current (low byte e.g. 2*256 + 9 = 521 = 0,521A: Cur:00.521A) |
010 | 000 | 0x00 | Cap3? |
011 | 000 | 0x00 | Cap2 |
012 | 128 | 0x00 | Cap1 = Current Capacity (1,28Ah; Display: Cap:01286mAh) |
013 | 000 | 0x00 | |
014 | 000 | 0x00 | |
015 | 000 | 0x00 | |
016 | 000 | 0x00 | |
017 | 000 | 0x00 | |
018 | 000 | 0x00 | |
019 | 000 | 0x00 |
index | hex | dec | description |
---|---|---|---|
000 | 000 | 0x00 | |
001 | 000 | 0x00 | |
002 | 000 | 0x00 | |
003 | 000 | 0x00 | |
004 | 000 | 0x00 | |
005 | 025 | 0x19 | Temp: 25C (Display: 025.6C) |
006 | 000 | 0x00 | |
007 | 001 | 0x01 | Hours of running since start (e.g. 1 hours; Display: Timing:001:04:09) |
008 | 004 | 0x04 | Minutes of running since start (e.g. 4 minutes) |
009 | 009 | 0x09 | Seconds of running since start (e.g. 9 seconds) |
010 | 060 | 0x3C | |
011 | 000 | 0x00 | |
012 | 000 | 0x00 | |
013 | 000 | 0x00 | |
014 | 000 | 0x00 | |
015 | 118 | 0x76 |
Change settings
The second Characteristic (UUID: 0000ffe2-0000-1000-8000-00805f9b34fb) can be used to control the device. Use the following code to write byte array command to the characteristic. Running of this command will simulate pushing the “+” button:
IDataWriter dataWriter = new DataWriter();
dataWriter.WriteBytes(new byte[] { 0xff, 0x55, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x02 });
Windows.Storage.Streams.IBuffer buffer = dataWriter.DetachBuffer();
result = await gattCharacteristics[1].WriteValueWithResultAsync(buffer);
An example how to write a command value in nRF connect app:
Reverse engineering the E-Test App
The E-Test App is provided by the Atorch. After connecting the device in the app, you can control the device on the limited way.
E-Test App:
Atorch DL24 display:
If you compare the values from the notifications with the values from the app, they seem to be the same.
The time record appears to be different from the time values for hours, minutes and seconds, it is because it is running all the time, while the sent values are only carried out when the device is switched on.
Electricity, carbon dioxide, electricity charges values on the E-Test app seem to be calculated from other values.
Wireshark
Android can be configured to save log file, which contains captured raw bluetooth communication between original app from Atorch and the DL24 device. I connected the E-Test app and clicked around to capture all supported communication data.
To copy the bugreport file which contains btsnoop_hci.log run the following command: ...\Android\sdk\platform-tools\adb.exe bugreport C:\Temp\
Then unzip the created file and locate the file .\FS\data\log\bt\btsnoop_hci.log , which should be imported into wireshark.
The filter: ‘btatt.opcode.method == 0x12’ shows only the write requests, which I want to decode.
Analysis of the captures communication data results in the following table:
bytes | meaning |
---|---|
0xff, 0x55, 0x11, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x50 | energy reset |
0xff, 0x55, 0x11, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x51 | cap reset |
0xff, 0x55, 0x11, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x52 | timing |
0xff, 0x55, 0x11, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x53 | no reaction |
0xff, 0x55, 0x11, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x54 | timing, energy, cap reset |
0xff, 0x55, 0x11, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x55 | no reaction |
——– | ——- |
0xff, 0x55, 0x11, 0x02, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00 | Setup button (move cursor to left) |
0xff, 0x55, 0x11, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x01 | ON/OFF button |
0xff, 0x55, 0x11, 0x02, 0x33, 0x00, 0x00, 0x00, 0x00, 0x02 | + button |
0xff, 0x55, 0x11, 0x02, 0x34, 0x00, 0x00, 0x00, 0x00, 0x03 | – button |
Links
Good start with reverse engineering
Android Software E-Test to download
I really hope you enjoyed the post, you can now read other posts! 👋
Nice article,
Do you knnow if it’s possible to get more decimals from the voltage?
Vol1 = current Voltage (21,2V; Display: Vol:21.272V).
The display show more decimals but the com scanner won’t show me more data.
Hi Stefan,
unfortunatelly I didn’t find any way to get more decimals to the sent data.