Happy Android Security
  • 前言
  • CTF
    • 2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme01
    • 2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme02
    • 2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme03
    • 2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme04
    • 2015 0CTF Vezel 100
    • 2015 0CTF Simple 150
    • 2015 XCTF&RCTF Flag System 100
    • 2015 XCTF&RCTF Where 300
    • 2015 海峡两岸CTF 一个APK逆向试试吧
    • 2016 LCTF EASY 100
    • 2016 AliCTF Timer 50
    • 2016 AliCTF Loop And Loop 100
    • 2016 ZCTF Android1 200
    • 2016 LCTF EASY EASY 200
    • 2017 ISCC 全国大学生信息安全与对抗技术竞赛 简单到不行
    • 2017 SSCTF 加密勒索软件 100
    • 2017 SSCTF Login 200
    • 2017 XCTF&NJCTF Easy Crack 100
    • 2017 XCTF&NJCTF Safe Box 100
    • 2017 XCTF&NJCTF Little Rotator Game 200
    • 2017 陕西省网络安全大赛 拯救鲁班七号 100
    • 2017 陕西省网络安全大赛 The Marauders Map 150
    • 2017 陕西省网络安全大赛 人民的名义 抓捕赵德汉1 200
    • 2017 陕西省网络安全大赛 人民的名义 抓捕赵德汉2 200
    • 2017 陕西省网络安全大赛 取证密码 200
  • 应用侧安全
    • 任意私有组件启动漏洞的利用
    • [ByteDance] [TikTok] NotificationBroadcastReceiver导出存在任意私有组件启动结合FileProvider机制与FbSoLoader框架导致本地代码执行漏洞
    • [ByteDance] [TikTok] DetailActivity导出存在任意私有组件启动结合FileProvider机制与FbSoLoader框架导致本地代码执行漏洞
    • [ByteDance] [TikTok] WallPaperDataProvider导出存在任意私有文件读取漏洞
    • [Adobe] [Acrobat Reader] AdobeReader处理DeepLink时未正确进行合法性校验导致下载PDF文件过程出现路径穿越可造成远程代码执行
    • [CVE-2019-16253] [Samsung] [SMT] SamsungTTSService导出存在任意私有组件调用提权漏洞
    • [CVE-2021-25390] [Samsung] [Photo Table] PermissionsRequestActivity存在任意私有组件启动漏洞可获取ContentProvider数据
    • [CVE-2021-25391] [Samsung] [Secure Folder] KnoxSettingCheckLockTypeActivity泄露Intent可获取ContentProvider数据
    • [CVE-2021-25397] [Samsung] [TelephonyUI] PhotoringReceiver导出存在任意文件写漏洞结合动态库加载行为可实现本地任意代码执行
    • [CVE-2021-25410] [Samsung] [CallBGProvider] CallBGProvider的调用权限定义为Normal可实现任意私有文件读取
    • [CVE-2021-25413] [Samsung] [Contacts] SetProfilePhotoActivity导出存在任意私有组件启动漏洞可获取ContentProvider数据
    • [CVE-2021-25414] [Samsung] [Contacts] SetProfilePhotoActivity导出存在任意私有文件读写漏洞
    • [CVE-2021-25440] [Samsung] [FactoryCameraFB] CameraTestActivity导出存在文件读写权限泄露漏洞
    • [CVE-2022-22292] [Samsung] [Telecom] 动态注册BroadcastReceiver默认导出存在任意私有组件启动漏洞
  • 系统侧安全
    • REDMI 5 Plus Second Space Password Bypass
    • 【蓝牙】CVE-2017-13258 CVE-2017-13260 CVE-2017-13261 CVE-2017-13262信息泄露
    • 【蓝牙】CVE-2018-9357 BNEP_Write越界写导致RCE
    • 【蓝牙】CVE-2018-9358 信息泄露
    • 【蓝牙】CVE-2018-9359 process_l2cap_cmd_L2CAP_CMD_INFO_REQ未判断缓冲区边界造成信息泄露
    • 【蓝牙】CVE-2018-9360 process_l2cap_cmd_L2CAP_CMD_CONN_REQ未判断缓冲区边界造成信息泄露
    • 【蓝牙】CVE-2018-9361 process_l2cap_cmd_L2CAP_CMD_DISC_REQ未判断缓冲区边界造成信息泄露
    • 【蓝牙】CVE-2018-9365 smp_sm_event数组越界访问导致RCE
    • 【蓝牙】CVE-2018-9381 gatts_process_read_by_type_req未初始化栈变量导致信息泄露
    • 【NFC】CVE-2018-9584 nfc_ncif_set_config_status未检测长度越界读写
    • 【NFC】CVE-2018-9585_nfc_ncif_proc_get_routing未检测长度越界读写
    • 【蓝牙】CVE-2019-2209 未检测PIN码长度导致越界读造成信息泄露
    • 【NFC】CVE-2019-9358 ce_t3t_data_cback越界读写
  • 内核驱动侧安全
Powered by GitBook
On this page
  1. 系统侧安全

【蓝牙】CVE-2019-2209 未检测PIN码长度导致越界读造成信息泄露

补丁

  • https://android.googlesource.com/platform/packages/apps/Bluetooth/+/5a426f6ed5620a39388aa78ed1d01c465f47958c

CVE
参考编号
类型
严重程度
已更新的 AOSP 版本

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

Previous【NFC】CVE-2018-9585_nfc_ncif_proc_get_routing未检测长度越界读写Next【NFC】CVE-2019-9358 ce_t3t_data_cback越界读写

Last updated 2 years ago