1 //
2 // Copyright (c) 2010-2023 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using Antmicro.Renode.Peripherals.Wireless;
10 using Antmicro.Renode.Logging;
11 using Antmicro.Renode.Utilities;
12 
13 namespace Antmicro.Renode.Plugins.WiresharkPlugin
14 {
15     public class BLESniffer
16     {
InsertHeaderToPacket(IRadio sender, byte[] originalPacket)17         public byte[] InsertHeaderToPacket(IRadio sender, byte[] originalPacket)
18         {
19             byte[] completePacket = new byte[originalPacket.Length + SnifferMetadataSizeInBytes];
20 
21             // If packet is shorter than minimal acceptable or channel is out of range then communication is broken
22             // and we can abort sniffing anything.
23             if(originalPacket.Length < MinimalBLEPacketLength)
24             {
25                 sender.Log(LogLevel.Error, "BLE packet size is {0} bytes. Expected at least {1} bytes.", originalPacket.Length, MinimalBLEPacketLength);
26                 FillRestOfData(originalPacket, completePacket, SnifferMetadataSizeInBytes);
27                 return completePacket;
28             }
29 
30             if(!channelToWiresharkIndex.TryGetValue(sender.Channel, out var wiresharkIndex))
31             {
32                 sender.Log(LogLevel.Error, "Channel number {0} doesn't exist in bluetooth specification.", sender.Channel);
33                 FillRestOfData(originalPacket, completePacket, SnifferMetadataSizeInBytes);
34                 return completePacket;
35             }
36 
37             completePacket[0] = wiresharkIndex;
38 
39             // Signal Power
40             completePacket[1] = 0x0;
41 
42             // Noise Power
43             completePacket[2] = 0x0;
44 
45             // Access Address Offenses
46             completePacket[3] = 0x0;
47 
48             // Reference Access Address
49             completePacket[4] = originalPacket[0];
50             completePacket[5] = originalPacket[1];
51             completePacket[6] = originalPacket[2];
52             completePacket[7] = originalPacket[3];
53 
54             var flags = (short)SnifferFlags.PacketDeWhitened | (short)SnifferFlags.SignalPowerIsValid | (short)SnifferFlags.NoisePowerIsValid | (short)SnifferFlags.PacketIsDecrypted
55                                     | (short)SnifferFlags.ReferenceAccessAddressIsValid | (short)SnifferFlags.AccessAddressOffensesIsValid | (short)SnifferFlags.CRCWasChecked
56                                     | (short)SnifferFlags.CRCIsValid | (short)SnifferFlags.MICWasChecked | (short)SnifferFlags.MICIsValid;
57 
58             var accessAddress = BitHelper.ToUInt32(originalPacket, 0, 4, true);
59 
60             // Here we check whether we have an advertisement packet or data packet. We're using access address embedded in packet.
61             if(accessAddress != AdvertisementAccessAddress)
62             {
63                 // If an accessAddress is a key in the dictionary then we know that sender belongs to this connection.
64                 // There is an assumption that data packets are sent after advertisement packets. If there is no key that matches access address then something went wrong.
65                 // If sender is a value for this key then it's a master, otherwise it's a slave.
66                 if(!mastersInConnections.TryGetValue(accessAddress, out var masterRadio))
67                 {
68                     sender.Log(LogLevel.Error, "There is no connection associated with accessAddress: 0x{0:X}.", accessAddress);
69                     FillRestOfData(originalPacket, completePacket, SnifferMetadataSizeInBytes);
70                     return completePacket;
71                 }
72 
73                 if(masterRadio == sender)
74                 {
75                     flags |= (short)PDUTypeNumbers.DataPacketMasterToSlave;
76                 }
77                 else
78                 {
79                     flags |= (short)PDUTypeNumbers.DataPacketSlaveToMaster;
80                 }
81             }
82             else
83             {
84                 flags |= (short)PDUTypeNumbers.Advertisement;
85 
86                 // When CONNECT_IND packet (PDU type 5) is sent we can read from PDU payload what access address will be used for this connection.
87                 // This way we can save connection's access address and which radio is a master for this connection.
88                 var pduType = originalPacket[4] & 0xF;
89                 if(pduType == ConnectPDUType)
90                 {
91                     if(originalPacket.Length < ConnectBLEPacketLength)
92                     {
93                         sender.Log(LogLevel.Error, "Connect packet size is {0} bytes. Expected at least {1} bytes.", originalPacket.Length, ConnectBLEPacketLength);
94                         FillRestOfData(originalPacket, completePacket, SnifferMetadataSizeInBytes);
95                         return completePacket;
96                     }
97 
98                     // Extract an accessAddress for connection from PDU payload.
99                     var newAccessAddress = BitHelper.ToUInt32(originalPacket, 18, 4, true);
100                     if(mastersInConnections.ContainsKey(newAccessAddress))
101                     {
102                         // connection with this access address already exists, so we override it
103                         sender.Log(LogLevel.Warning, "There is already a connection associated with access address: 0x{0:X}. Overriding old connection.", newAccessAddress);
104                         mastersInConnections[newAccessAddress] = sender;
105                     }
106                     else
107                     {
108                         mastersInConnections.Add(newAccessAddress, sender);
109                     }
110                 }
111 
112                 // Sniffer just sets an appropriate flag in header to indicate that it's auxiliary advertisement packet.
113                 else if(pduType == AuxiliaryAdvertisementPDUType)
114                 {
115                     flags |= (short)PDUTypeNumbers.AuxiliaryAdvertisement;
116                 }
117             }
118 
119             // Embed flags to the packet.
120             completePacket[8] = (byte)flags;
121             completePacket[9] = (byte)(flags >> 8);
122 
123             FillRestOfData(originalPacket, completePacket, SnifferMetadataSizeInBytes);
124             return completePacket;
125         }
126 
FillRestOfData(byte[] src, byte[] dest, int offset)127         private void FillRestOfData(byte[] src, byte[] dest, int offset)
128         {
129             for(int i = 0; i < src.Length; i++)
130             {
131                 dest[i + offset] = src[i];
132             }
133         }
134 
135         private const int SnifferMetadataSizeInBytes = 10;
136         private const int MinimalBLEPacketLength = 9;
137         private const int ConnectBLEPacketLength = 43;
138         private const uint AdvertisementAccessAddress = 0x8e89bed6;
139         private const byte ConnectPDUType = 5;
140         private const byte AuxiliaryAdvertisementPDUType = 7;
141 
142         // Wireshark indexes channels from 0 to 39 basing on the frequency meanwhile in reality channels 37, 38 and 39
143         // are in different places of frequency spectrum. This is why we have to remap channel numbers to indexed for Wireshark.
144         private readonly Dictionary<int, byte> channelToWiresharkIndex = new Dictionary<int, byte>()
145         {
146             { 37, 0 },
147             { 0, 1 },
148             { 1, 2 },
149             { 2, 3 },
150             { 3, 4 },
151             { 4, 5 },
152             { 5, 6 },
153             { 6, 7 },
154             { 7, 8 },
155             { 8, 9 },
156             { 9, 10 },
157             { 10, 11 },
158             { 38, 12 },
159             { 11, 13 },
160             { 12, 14 },
161             { 13, 15 },
162             { 14, 16 },
163             { 15, 17 },
164             { 16, 18 },
165             { 17, 19 },
166             { 18, 20 },
167             { 19, 21 },
168             { 20, 22 },
169             { 21, 23 },
170             { 22, 24 },
171             { 23, 25 },
172             { 24, 26 },
173             { 25, 27 },
174             { 26, 28 },
175             { 27, 29 },
176             { 28, 30 },
177             { 29, 31 },
178             { 30, 32 },
179             { 31, 33 },
180             { 32, 34 },
181             { 33, 35 },
182             { 34, 36 },
183             { 35, 37 },
184             { 36, 38 },
185             { 39, 39 },
186         };
187 
188         // This dictionary holds an information about all connections. Each connection is identified by an access address
189         // and there is only one master device associated with each access address. Other devices associated with access addressses
190         // are considered to be slaves.
191         private readonly Dictionary<uint, IRadio> mastersInConnections = new Dictionary<uint, IRadio>();
192 
193         // Flags registers are made of a bitfield and pdu type numbers
194         [Flags]
195         private enum SnifferFlags : short
196         {
197             None = 0x0,
198             PacketDeWhitened = 0x1,
199             SignalPowerIsValid = 0x2,
200             NoisePowerIsValid = 0x4,
201             PacketIsDecrypted = 0x8,
202             ReferenceAccessAddressIsValid = 0x10,
203             AccessAddressOffensesIsValid = 0x20,
204             RFChannelIsLikelyToBeDistorted = 0x40,
205             CRCWasChecked = 0x400,
206             CRCIsValid = 0x800,
207             MICWasChecked = 0x1000,
208             MICIsValid = 0x2000,
209         }
210 
211         private enum PDUTypeNumbers : short
212         {
213             Advertisement = 0x0 << 7,
214             AuxiliaryAdvertisement = 0x1 << 7,
215             DataPacketMasterToSlave = 0x2 << 7,
216             DataPacketSlaveToMaster = 0x3 << 7
217         }
218     }
219 }
220