【蓝牙】CVE-2019-2209 未检测PIN码长度导致越界读造成信息泄露
补丁
https://android.googlesource.com/platform/packages/apps/Bluetooth/+/5a426f6ed5620a39388aa78ed1d01c465f47958c
CVE-2019-2209
A-139287605
ID
高
8.0、8.1、9、10
补丁描述使用PIN码前先进行长度检查,所以我们猜测PIN码长度是一个关键的地方
AdapterService: Check the PIN code length before using
The length is assigned by the framework. We should be better to check
again before using, and dropped any unexcepted input.
Bug: 139287605
Test: PoC, atest -t BluetoothInstrumentationTests:com.android.bluetooth.btservice
Change-Id: Ie2dd01e0b192e7ed1fe4b464618ddfa415dbf15c
(cherry picked from commit d6c84aa34962333448e0ed8e4ddbc9de8b73c5ac)
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 508eacf..53cf723 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -2233,6 +2233,12 @@
return false;
}
+ if (pinCode.length != len) {
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "PIN code length mismatch");
+ return false;
+ }
+
StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
@@ -2249,6 +2255,12 @@
return false;
}
+ if (passkey.length != len) {
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "Passkey length mismatch");
+ return false;
+ }
+
StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
Android官方文档
https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html#setPin(byte[])
一般来说我们进行蓝牙连接,需要先输入一个PIN码,但是现在有的设备为了配对方便,会进行自动配对,自动配对使用的是反射调用setPin()
try {
Method setPinMethod = btClass.getDeclaredMethod("setPin", new Class[] {byte[].class});
Boolean returnValue = (Boolean) setPinMethod.invoke(btDevice, new Object[] {pinCode.getBytes()});
} catch (Exception e) {
e.printStackTrace();
}
跟入源码,调用系统函数IBluetooth.setPin()
/**
* Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @return true pin has been set false for error
*/
public boolean setPin(byte[] pin) {
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "BT not enabled. Cannot set Remote Device pin");
return false;
}
try {
return service.setPin(this, true, pin.length, pin); // <-- 1
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
return false;
}
找到系统蓝牙组件源码,这里面先进行调用者的检查,然后调用AdapterService.setPin()
@Override
public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setPin() - Not allowed for non-active user");
return false;
}
AdapterService service = getService();
if (service == null) {
return false;
}
return service.setPin(device, accept, len, pinCode); // <-- 2
}
到达补丁函数,我并没有看出来这里的pinCode.length
和len
能不相等,因为len
是由参数传入,回溯回去可以看到是pin.length
,并且没有其它地方能够改变这个值,所以在什么情况下这两个值能不相等呢?
boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
// Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
if (deviceProp == null || (deviceProp.getBondState() != BluetoothDevice.BOND_BONDING
&& deviceProp.getBondState() != BluetoothDevice.BOND_BONDED)) {
return false;
}
+ if (pinCode.length != len) { // <-- 3
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "PIN code length mismatch");
+ return false;
+ }
StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED,
accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
byte[] addr = Utils.getBytesFromAddress(device.getAddress());
return pinReplyNative(addr, accept, len, pinCode);
}
这里的AdapterServiceBinder
定义如下,它使用的是AIDL,定义为IBluetooth
接口,也就是说,这个Service里的方法可以通过Binder进行调用,所以这里的四个参数我们可控,就可以造成pinCode.length
与len
不相等
/**
* Handlers for incoming service calls
*/
private AdapterServiceBinder mBinder;
@Override
public IBinder onBind(Intent intent) {
debugLog("onBind()");
return mBinder;
}
/**
* The Binder implementation must be declared to be a static class, with
* the AdapterService instance passed in the constructor. Furthermore,
* when the AdapterService shuts down, the reference to the AdapterService
* must be explicitly removed.
*
* Otherwise, a memory leak can occur from repeated starting/stopping the
* service...Please refer to android.os.Binder for further details on
* why an inner instance class should be avoided.
*
*/
private static class AdapterServiceBinder extends IBluetooth.Stub {
private AdapterService mService;
AdapterServiceBinder(AdapterService svc) {
mService = svc;
}
...
@Override
public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setPin() - Not allowed for non-active user");
return false;
}
AdapterService service = getService();
if (service == null) {
return false;
}
return service.setPin(device, accept, len, pinCode);
}
...
我们继续往下看代码,通过定义可以看到pinReplyNative()
是Native函数
private native boolean pinReplyNative(byte[] address, boolean accept, int len, byte[] pin);
pinReplyNative()
的Native实现如下,细节注释都写在代码里了
static jboolean pinReplyNative(JNIEnv* env, jobject obj, jbyteArray address,
jboolean accept, jint len, jbyteArray pinArray) {
ALOGV("%s", __func__);
if (!sBluetoothInterface) return JNI_FALSE;
// 获取设备地址的字节数组对象
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (addr == NULL) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
// 获取PIN码的字节数组对象
jbyte* pinPtr = NULL;
if (accept) {
pinPtr = env->GetByteArrayElements(pinArray, NULL);
if (pinPtr == NULL) {
jniThrowIOException(env, EINVAL);
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_FALSE;
}
}
// 调用pin_reply()
int ret = sBluetoothInterface->pin_reply((RawAddress*)addr, accept, len,
(bt_pin_code_t*)pinPtr);
env->ReleaseByteArrayElements(address, addr, 0);
env->ReleaseByteArrayElements(pinArray, pinPtr, 0);
return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
继续调用函数,此处将第四个参数pin_code
转为bt_pin_code_t*
类型,这里影响不大,做了个检查继续调用btif_dm_pin_reply()
/** Bluetooth PinKey Code */
typedef struct {
uint8_t pin[16];
} __attribute__((packed))bt_pin_code_t;
static int pin_reply(const bt_bdaddr_t* bd_addr, uint8_t accept,
uint8_t pin_len, bt_pin_code_t* pin_code) {
/* sanity check */
if (interface_ready() == false) return BT_STATUS_NOT_READY;
return btif_dm_pin_reply(bd_addr, accept, pin_len, pin_code);
}
先判断pin_len
的长度是否大于PIN_CODE_LEN
,通过宏定义可以看到PIN_CODE_LEN
的长度为16
,同时我们也可以推出len
的值我们不可以改变,也就是说,补丁进行对比的两个值,有问题的是pinCode
/*******************************************************************************
*
* Function btif_dm_pin_reply
*
* Description BT legacy pairing - PIN code reply
*
* Returns bt_status_t
*
******************************************************************************/
bt_status_t btif_dm_pin_reply(const bt_bdaddr_t* bd_addr, uint8_t accept,
uint8_t pin_len, bt_pin_code_t* pin_code) {
BTIF_TRACE_EVENT("%s: accept=%d", __func__, accept);
// #define PIN_CODE_LEN 16
if (pin_code == NULL || pin_len > PIN_CODE_LEN) return BT_STATUS_FAIL;
if (pairing_cb.is_le_only) {
int i;
uint32_t passkey = 0;
int multi[] = {100000, 10000, 1000, 100, 10, 1};
BD_ADDR remote_bd_addr;
bdcpy(remote_bd_addr, bd_addr->address);
for (i = 0; i < 6; i++) {
passkey += (multi[i] * (pin_code->pin[i] - '0'));
}
BTIF_TRACE_DEBUG("btif_dm_pin_reply: passkey: %d", passkey);
BTA_DmBlePasskeyReply(remote_bd_addr, accept, passkey);
} else {
BTA_DmPinReply((uint8_t*)bd_addr->address, accept, pin_len, pin_code->pin);
if (accept) pairing_cb.pin_code_len = pin_len;
}
return BT_STATUS_SUCCESS;
}
在CVE官网有一段关于该漏洞的描述
In BTA_DmPinReply of bta_dm_api.cc, there is a possible out of bounds read due to an incorrect bounds check. This could lead to local information disclosure with User execution privileges needed. User interaction is not needed for exploitation.Product: AndroidVersions: Android-8.0 Android-8.1 Android-9 Android-10Android ID: A-139287605
所以我们跟入BTA_DmPinReply()
,定义了一个tBTA_DM_API_PIN_REPLY*
类型指针,accept
一开始传进来就是true
,调用memcpy()
拷贝数据,p_msg->p_pin
的长度是PIN_CODE_LEN
,也就是16
,上面已经看过源码了,如果pin_len
为4,但是p_pin
为16,所以就会多拷贝
typedef struct {
BT_HDR hdr;
BD_ADDR bd_addr;
bool accept;
uint8_t pin_len;
uint8_t p_pin[PIN_CODE_LEN];
} tBTA_DM_API_PIN_REPLY;
/*******************************************************************************
*
* Function BTA_DmPinReply
*
* Description This function provides a pincode for a remote device when
* one is requested by DM through BTA_DM_PIN_REQ_EVT
*
*
* Returns void
*
******************************************************************************/
void BTA_DmPinReply(BD_ADDR bd_addr, bool accept, uint8_t pin_len,
uint8_t* p_pin)
{
tBTA_DM_API_PIN_REPLY* p_msg =
(tBTA_DM_API_PIN_REPLY*)osi_malloc(sizeof(tBTA_DM_API_PIN_REPLY));
p_msg->hdr.event = BTA_DM_API_PIN_REPLY_EVT;
bdcpy(p_msg->bd_addr, bd_addr);
p_msg->accept = accept;
if (accept) {
p_msg->pin_len = pin_len;
memcpy(p_msg->p_pin, p_pin, pin_len);
}
bta_sys_sendmsg(p_msg);
}
我们来使用代码模拟一下进行调试
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <android/log.h>
#define LOG_TAG "HELLO_JNI"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG ,__VA_ARGS__)
#define PIN_CODE_LEN 16
typedef struct {
bool accept;
uint8_t pin_len;
uint8_t p_pin[PIN_CODE_LEN];
} tBTA_DM_API_PIN_REPLY;
void* osi_malloc(size_t size) {
// size_t real_size = allocation_tracker_resize_for_canary(size);
void* ptr = malloc(size);
// CHECK(ptr);
return ptr;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_wnagzihxa1n_bluetooth_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
tBTA_DM_API_PIN_REPLY* p_msg =(tBTA_DM_API_PIN_REPLY*) osi_malloc(sizeof(tBTA_DM_API_PIN_REPLY));
// uint8_t p_pin[16] = {0x01, 0x02, 0x03, 0x04};
uint8_t p_pin[16];
p_pin[0] = 0x01;
p_pin[1] = 0x02;
p_pin[2] = 0x03;
p_pin[3] = 0x4;
memcpy(p_msg->p_pin, p_pin, PIN_CODE_LEN);
for (int i = 0; i < 16; i++) {
LOGE("666 TEST index = %d p_msg->p_pin[%d] = 0x%x\n", i, i, p_msg->p_pin[i]);
}
return env->NewStringUTF(hello.c_str());
}
输出数据如下,可以看到打出了未初始化的数据
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 0 p_msg->p_pin[0] = 0x1
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 1 p_msg->p_pin[1] = 0x2
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 2 p_msg->p_pin[2] = 0x3
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 3 p_msg->p_pin[3] = 0x4
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 4 p_msg->p_pin[4] = 0x4d
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 5 p_msg->p_pin[5] = 0x8e
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 6 p_msg->p_pin[6] = 0x93
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 7 p_msg->p_pin[7] = 0xb2
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 8 p_msg->p_pin[8] = 0x1
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 9 p_msg->p_pin[9] = 0x0
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 10 p_msg->p_pin[10] = 0x0
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 11 p_msg->p_pin[11] = 0x0
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 12 p_msg->p_pin[12] = 0xeb
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 13 p_msg->p_pin[13] = 0x63
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 14 p_msg->p_pin[14] = 0x6c
02-08 17:26:33.040 21778-21778/com.wnagzihxa1n.bluetooth E/CVE-2019-2209: index = 15 p_msg->p_pin[15] = 0xb2
/** @hide */
public boolean setPasskey(int passkey) {
//TODO(BT)
/*
try {
return sService.setPasskey(this, true, 4, passkey);
} catch (RemoteException e) {Log.e(TAG, "", e);}*/
return false;
}
@Override
public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setPasskey() - Not allowed for non-active user");
return false;
}
AdapterService service = getService();
if (service == null) {
return false;
}
return service.setPasskey(device, accept, len, passkey);
}
boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
return false;
}
+ if (passkey.length != len) {
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "Passkey length mismatch");
+ return false;
+ }
StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
byte[] addr = Utils.getBytesFromAddress(device.getAddress());
return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY, accept,
Utils.byteArrayToInt(passkey));
}
Reference
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-2209
Last updated