Paving the way for 0days
Dedicated to:
Zix, who spent precious-time guiding me within the last two-months.
Mohammed, Jalil, Zakariae, Yasser, Jeff keep fighting!
Hakirat, knchofkom.
The registered handler for IRP_MJ_DEVICE_CONTROL (BthHandleControlDispatch) supports a multitude of control-codes to permit applications to interact with SDP, HCI and L2CAP protocols. However, most operations require local Radio to be active and [&H411180] can be issued to query its state.
enum _BTH_RADIO_STATE { RADIO_STATE_UNKNOWN, RADIO_ON_PENDING, RADIO_ON, RADIO_OFF_PENDING, RADIO_OFF, RADIO_DISABLED_BY_POLICY };
BLUETOOTH_FIND_RADIO_PARAMS params; HANDLE hRadio; DWORD state, returned; params.dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS); if (BluetoothFindFirstRadio(¶ms, &hRadio)) { if (DeviceIoControl(hRadio, 0x411180, NULL, 0, &state, sizeof(state), &returned, NULL)) printf("Radio state: %d\n", state); CloseHandle(hRadio); }
BluetoothFindFirstRadio invokes functions from devobj.dll to acquire an Object-path using a GUID that uniquely identifies Bluetooth radios and calls CreateFileW with FILE_SHARE_READ | FILE_SHARE_WRITE to get a HANDLE;
wchar_t* GetRadioPath() { unsigned int flags[8]= { [0] = 32, [6] = 0 }, size = 0; unsigned char* tmp_path; HANDLE hDeviceInfoList; wchar_t* path = NULL; if ((hDeviceInfoList = (HANDLE) DevObjCreateDeviceInfoList(NULL, NULL, 0, 0, 0)) != INVALID_HANDLE_VALUE) { if (DevObjGetClassDevs(hDeviceInfoList, &GUID_BTHPORT_DEVICE_INTERFACE, NULL, 18, 0, 0) && DevObjEnumDeviceInterfaces(hDeviceInfoList, NULL, &GUID_BTHPORT_DEVICE_INTERFACE, 0, &flags)) { while (! DevObjGetDeviceInterfaceDetail(hDeviceInfoList, &flags, tmppath, size, &size, NULL)) { if (tmppath) break; tmppath = (unsigned char*) malloc(size); memset(tmppath, 0, size); *((unsigned int*) tmppath) = 8; } path = (wchar_t*) (tmppath + 4); } DevObjDestroyDeviceInfoList(hDeviceInfoList); } return path; }
To perform device-inquiry (scan for devices in proximity), [&H411000] takes BTH_DEVICE_INQUIRY in lpInBuffer and a PDWORD to receive device-count in lpOutBuffer, it winds up in HCI_DoInquiry calling HCI_SendCommandEx:
#define GIAC 0x009E8B33 #define LIAC 0x009E8B00
typedef struct { ULONG Lap; BYTE TimeoutMultiplier; BYTE TransmitPwrLvl; } __attribute__((packed)) BTH_DEVICE_INQUIRY; DWORD Inquire(HANDLE hRadio, unsigned char multiplier, unsigned char power) { BTH_DEVICE_INQUIRY inquiry; DWORD ndevice =0, returned; if (multiplier <= 0x30 && power <= 0x14) { inquiry.Lap = GIAC; inquiry.TimeoutMultiplier = multiplier; inquiry.TransmitPwrLvl = power; DeviceIoControl(hRadio, 0x411000, &inquiry, sizeof(inquiry), &ndevice, sizeof(ndevice), &returned, NULL); } return ndevice; }
Retrieve devices with [&H410008] which writes into a BTH_DEVICE_INFO_LIST structure, an initial call with sizeof(ULONG) in nOutBufferSize gives numOfDevices, used to allocate a sufficiently large block. Noteworthy is that if BTH_DEVICE_INFO is not a packed structure and its size is different than 272, this procedure fails.
BTH_DEVICE_INFO_LIST* GetRemoteDevice(HANDLE hRadio)
{
BTH_DEVICE_INFO_LIST* devices;
ULONG count;
DWORD size, returned;
devices = NULL;
if (DeviceIoControl(hRadio,
0x410008,
NULL, 0,
&count, sizeof(count),
&returned, NULL))
{
if (count)
{
size = sizeof(ULONG) + sizeof(BTH_DEVICE_INFO) * (count-1);
devices = (BTH_DEVICE_INFO_LIST*) malloc(size);
DeviceIoControl(hRadio,
0x410008,
NULL, 0,
devices, size,
&returned, NULL);
}
}
return devices;
}
To make this device discoverable, [&H411020] will perform HCI_Write_Scan_Enable with HCI_UpdateScanEnable:
int ScanEnable(HANDLE hRadio, unsigned char bEnable) { unsigned short scanFlags[2]; DWORD returned; scanFlags[0] = 0x0103; scanFlags[1] = bEnable & 1; return DeviceIoControl(hRadio, 0x411020, scanFlags, sizeof(scanFlags), NULL, 0, &returned, NULL); }
A local paged SDP Database (TAG: SdpD) stores the available services, each its own set of attributes and SecurityFlags, Winsock provides functions to lookup services on remote Devices:
- WSALookupServiceBeginW
- WSALookupServiceNextW
- WSALookupServiceEnd
A local service with a GUID assigned can bind to BT_PORT_ANY and listen for incoming connections, but lookups won't be able to reveal the record until it's registered with WSASetService which ends up in NSPSetService from wshbth.dll.
A valid SDP Record in lpBlob can be used if supplied, otherwise an SDP Tree is constructed from WSAQUERYSET, converted to a stream and passed to SdpPublishRecord or SdpRemoveRecord depending on (essoperation).
Each SDP_NODE begins with an SDP_NODE_HEADER containing descriptors to describe data elements constant or variable in nature, a leading-byte must be calculated as (Type << 3) | ((SpecificType >> 8) & 7).
- 0x35 (SEQUENCE, VARBYTE) 0x3A (SIZE)
- 0x09 (UINT, WORD) 0x0001 (VALUE)
- 0x1C (UUID, OWORD) 00001101-0000-1000-8000-00805F9B34FB
- 0x19 (UUID, WORD) 0x0100
- 0x08 (UINT, BYTE) 0x04
- 0x25 (STRING, VARBYTE) 0x0E (LENGTH)
Attributes encoded as UINT16 quantize a stream, for better visualization, a hierarchical model can be constructed:
Channel #(0x04) which is stored as UINT8 in ProtocolDescriptorList(4) should be altered, getsockname populates a _SOCKADDR_BTH structure with socket-information including ->port.
- [&H410214] and [&H41021c] call SdpDB_AddRecord
- [&H410218] calls SdpDB_RemoveRecord
These IOCTLs with their respective names are given in [1] and [2], a valid BLOB must be sent in lpInBuffer and a HANDLE in lpOutBuffer (to unlink record from _SDP_DATABASE) for this operation to be successful.
unsigned char blobSdp[] = { 0x35, 0x3A, 0x09, 0x00, 0x01, 0x35, 0x11, 0x1C, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB, 0x09, 0x00, 0x04, 0x35, 0x0C, 0x35, 0x03, 0x19, 0x01, 0x00, 0x35, 0x05, 0x19, 0x00, 0x03, 0x08, 0xFF, 0x09, 0x01, 0x00, 0x25, 0x0E, 0x53, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65 }; HANDLE AddRecord(HANDLE hRadio, int hSocket) { SOCKADDR_BTH socketaddress; HANDLE hRecord; DWORD returned; int socketsize = sizeof(socketaddress); getsockname(hSocket, (sockaddr*) &socketaddress, &socketsize); blobSdp[40] = socketaddress.port; if (DeviceIoControl(hRadio, 0x410214, &blobSdp, sizeof(blobSdp), &hRecord, sizeof(hRecord), &returned, NULL)) return hRecord; return INVALID_HANDLE_VALUE; }
To send SDP requests to a remote party, an L2CAP connection needs to be established first with [&H410200] taking in the CONNECTION_REQUEST structure, writing ExAllocatePool kernel-pointer back to userspace as an identifier in L2CapConnectionList and can be used for disconnecting [&H410204].
💩
typedef struct { BTH_ADDR BthAddress; DWORD ConnType; void* Connection; BYTE Flags; } __attribute__((packed)) CONNECTION_REQUEST;
void* GetConnection(HANDLE hRadio, BTH_ADDR address) { CONNECTION_REQUEST request; DWORD returned; memset(&request, 0, sizeof(request)); request.ConnType = 2; // [Bit#0: new(0)/cached(1)] request.BthAddress = address; DeviceIoControl(hRadio, 0x410200, &request, sizeof(request), &request, sizeof(request), &returned, NULL); return request.Connection; } int TerminateConnection(HANDLE hRadio, void* Connection) { DWORD returned; return DeviceIoControl(hRadio, 0x410204, &Connection, sizeof(Connection), NULL, 0, &returned, NULL); }
This requires an active device to be in proximity for session-establishment, service-lookup can be executed with [&H410208], UUIDs to be searched-for in lpInBuffer, record-handles will be written to lpOutBuffer;
If UUID is 16/32bit, it will replace HIDWORD of BASE_UUID(00000000-0000-1000-8000-00805F9B34FB).
typedef struct { void* Connection; SdpQueryUuid Uuids[MAX_UUIDS_IN_QUERY]; } __attribute__((packed)) SERVICE_SEARCH; unsigned int* GetServices16(HANDLE hRadio, void* Connection, USHORT uuid) { SERVICE_SEARCH query; unsigned int* services, size; DWORD returned; size = sizeof(int) * 32; services = (unsigned int*) malloc(size); memset(services, 0, size); memset(&query, 0, sizeof(query)); query.Connection = Connection; query.Uuids[0].uuidType = SDP_ST_UUID16; query.Uuids[0].u.uuid16 = uuid; if (DeviceIoControl(hRadio, 0x410208, &query, sizeof(query), services, size, &returned, NULL)) return services; free(services); return NULL; }
// RFCOMM Protocol if ((services = GetServices16(hRadio, request.Connection, 0x0003))) { unsigned int handle, i = 0; while ((i < 32) && (handle = services[i++])) printf("Service Handle: 0x%X\n", handle); }
Once a service-handle is obtained, attributes can be searched-for with [&H41020C] which takes attribute-ranges in input and outputs a sequence-blob:
typedef struct { void* Connection; DWORD Flags; DWORD ServiceHandle; SdpAttributeRange Range[1]; } __attribute__((packed)) ATTRIBUTE_SEARCH; typedef struct { DWORD BlobSize[2]; unsigned char Blob[0]; } __attribute__((packed)) ATTRIBUTE_RESPONSE; ATTRIBUTE_RESPONSE* GetAttribute(HANDLE hRadio,
void* Connection,
unsigned hService,
unsigned AttributeId) { ATTRIBUTE_SEARCH request; ATTRIBUTE_RESPONSE* response; DWORD returned, size; request.Connection = Connection; request.Flags = 0; request.Range[0].minAttribute = AttributeId; request.Range[0].maxAttribute = AttributeId; request.ServiceHandle = hService; size = sizeof(ATTRIBUTE_RESPONSE) + 512; response = (ATTRIBUTE_RESPONSE*) malloc(size); if (DeviceIoControl(hRadio, 0x41020C, &request, sizeof(request), response, size, &returned, NULL)) return response; free(response); return NULL; }
// Get Service-Name ATTRIBUTE_RESPONSE* response; response = GetAttribute(hRadio, request.Connection, services[0], 0x100); if (response) { int i, upper; i = -1; upper = response->BlobSize[0] - 1; while (i < upper) { printf("%02X ", response->Blob[++i]); if ((i & 7) != 7) continue; printf("\n"); } printf("\n"); free(response); }
HCI packets begin with an indicator to identify their type:
HCI commands are variable-length structures containing an OpCode (composed of Opcode Group Field(OGF) and Opcode Command Field(OCF)) and a finite set of Parameters, with Length specified.
#define Opcode(OGF, OCF) ((OGF << 10) | OCF) void SplitOpcode(unsigned short opcode, unsigned char* ogf, unsigned char* ocf) { *ogf = opcode >> 10; *ocf = opcode & 1023; }
Vendor-specific commands have OGF &B111111, bthport.sys provides [&H410050] to call HCI_VendorCommand which requires providing the correct ManufacturerId and LmpVersion(0) in a PBTH_VENDOR_SPECIFIC_COMMAND structure, obtainable with [&H410000].
int GetLocalInfo(HANDLE hRadio, BTH_LOCAL_RADIO_INFO* pDeviceInfo) { DWORD returned; return DeviceIoControl(hRadio, 0x410000, NULL, 0, pDeviceInfo, sizeof(*pDeviceInfo), &returned, NULL); }
This component prohibits non-authorized processes and limited to a specific OGF, it should be patched to permit arbitrary HCI commands instead:
Make sure to backup the original Driver
To modify a file from %WINDIR%\\System32\\drivers, transfer ownership from NT SERVICE\TrustedInstaller to current user and give Users full-control, reboot in Safe Mode (4) to replace it before rebooting with Driver Signature Enforcement (7) disabled.
Terms:
BluetoothApis.dll
No comments:
Post a Comment