欢迎进入UG环球官网(环球UG)!

收购usdt(www.caibao.it):DA14531芯片固件逆向系列(4)- L2CAP及ATT层收包再剖析

admin2个月前43

USDT第三方支付平台

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

前言

上一篇文件剖析了DA14531从收包中止最先一直到L2CAP层的数据包处置历程,最近又抽了一点时间将L2CAP层和ATT层收包的历程梳理了一遍,本文将连系BLE的协议规范和代码先容ATT层的报文剖析流程,并先容一下剖析历程中发现的一些破绽。

回首

从上文可知 hci_acl_data_rx_handler 用于处置L2CAP的报文,该函数首先会挪用 l2cc_pdu_unpack 对L2CAP的头部字段举行简朴的检查,实在就是检查LengthChannel ID是否正当

然后将L2CAP的报文拷贝到新分配的内存,最后凭据数据包的类型通过新闻机制将数据交给sub_7F135F6 或者 l2cc_pdu_recv_ind_handler处置,其中sub_7F135F6用于处置ATT报文。

ATT层报文剖析

首先我们看一下BLE协议规范中对 ATT 层的报文花样的界说,下图是一个L2CAP的蓝牙包的示意图

蓝牙协议的数据包分了好几个部门,其中 ATT 层的报文就位于图中的Information payload部门,sub_7F135F6就是在剖析Information payload。

我们看看函数界说

int  sub_7F135F6(int id, l2cc_pdu_recv_ind *l2cc_pdu_recv, unsigned int dest_task)

其中l2cc_pdu_recv_ind是一个对照复杂的结构体,其使用了union来统一示意 L2CAP 内里的各种差别类型的Payload.

struct l2cc_pdu_recv_ind
{
    uint8_t status;
    // 示意数据的巨细
    uint16_t rem_len;
    /// Offset
    uint16_t offset;
    /// 指向详细的数据
    struct l2cc_pdu pdu;
};

l2cc_pdu 用于示意L2CAP层的报文花样

struct l2cc_pdu
{
    /// L2Cap Payload Length
    uint16_t payld_len;
    /// L2Cap Channel ID
    uint16_t chan_id;

    /// Data PDU definition
    union l2cc_pdu_data
    {
        /// L2Cap packet code.
        uint8_t code;

        struct l2cc_lecb_send_data_req send_lecb_data_req;
        struct l2cc_reject           reject;
        struct l2cc_update_param_req update_req;
        .............................
        .............................
    }
}

可以看到和L2CAP的报文花样完全匹配,开头4字节是 Length 和 Channel ID,其中 Length 在hci_acl_data_rx_handler函数中经由检查,示意后面Information payload部门的长度。

Information payload接纳联合体的方式界说,凭据差别的code来选择差别的结构体界说举行示意。

ATT PDU花样如下

由图可知,ATT PDU由一个字节的opcode和变长的数据组成。

下面以 l2cc_att_err_rsp 为例

/// Error response
struct l2cc_att_err_rsp
{
    /// Error Response - 0x01
    uint8_t     code;
    /// The request that generated this error response
    uint8_t     op_code;
    /// The attribute handle that generated this error response
    uint16_t    handle;
    ///The reason why the request has generated an error response
    uint8_t     reason;
};

可以看到Error response 的 ATT PDU的组成为

  1. 1字节的code
  2. 1字节的op_code
  3. 2字节的handle
  4. 1字节的reason

然后去翻看BLE规范中的界说发现完全符合

经由简朴的浏览发现SDK中对于每种PDU的花样界说要比BLE规范中的更详细。

下面从代码角度最先剖析,要害代码如下

int sub_7F135F6(int id, l2cc_pdu_recv_ind *l2cc_pdu_recv, unsigned int dest_task)
{
    ret = atts_l2cc_pdu_recv_handler(dest_task >> 8, l2cc_pdu_recv);
    if ( ret == 255 )
    {
        ret = attc_l2cc_pdu_recv_handler(dest_task >> 8, l2cc_pdu_recv);

函数主要逻辑就是首先挪用atts_l2cc_pdu_recv_handler实验对数据举行剖析,若是函数返回值为255就实验挪用attc_l2cc_pdu_recv_handler举行剖析。

atts_l2cc_pdu_recv_handler 和 attc_l2cc_pdu_recv_handler 现实就是凭据ATT PDU 的 opcode 字段在atts_handlers 和 attc_handlers两个结构体数组中搜索到opcode对应的回调函数,其结构体界说如下

struct att_handler_item
{
  unsigned __int8 code; // 示意 ATT PDU 的 opcode
  unsigned __int8 d[3];
  dummy_func func;  // 函数指针
};

其中func的第二个参数就指向了 ATT PDU 的开头,搜索 opcode 对应处置函数的代码如下

for ( i = 0; i < 0xE; i = (i + 1) )
  {
    if ( atts_handlers_0_0[i].code == code )
    {
      func = atts_handlers_0_0[i].func;
    }
  }
  if ( func )
  {
    result = (func)(dest_id, &l2cc_pdu_recv->pdu.data);

atts_handlers 和 attc_handlers的界说如下

我们以attc_handlers的第一项为例先容剖析ATT PDU剖析代码的流程,可以看的第一项的界说为

,

Usdt第三方支付接口

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

,
att_handler_item <3, 0, sub_7F0FC0C+1>

即这里处置的code为3,回调函数为sub_7F0FC0C,去BLE手册中查找发现code为3示意的是Exchange MTU Response类型的PDU,其界说如下

然后可以去SDK中找到该PDU的结构体界说

/// Exchange MTU Response
struct l2cc_att_mtu_rsp
{
    /// Exchange MTU Response - 0x03
    uint8_t     code;
    /// Server Rx MTU size
    uint16_t    mtu_size;
};

然后我们去看 sub_7F0FC0C 的实现

int sub_7F0FC0C(int dest_id, l2cc_att_mtu_rsp *payload)
{

  mtu_size = gattm_get_max_mtu();
  if ( mtu_size >= payload->mtu_size )
  {
    mtu_size = payload->mtu_size;
  }
  gattc_set_mtu(dest_id, mtu_size);
  if ( gattc_get_operation(dest_id, 1) == 1 )
  {
    v5 = 0;
  }
  else
  {
    v5 = 65;
  }
  gattc_send_complete_evt(dest_id, 1, v5);
  return 0;
}

可以看的主要就是从payload内里取出mtu_size,然后和当前的mtu举行对照,最后会设置新的mtu。我们可以接纳这种方式去剖析其他ATT PDU的处置逻辑。

破绽挖掘

梳理清晰ATT PDU的处置流程后,我们就可以逐个地剖析固件中对每个ATT PDU的代码实现,然后从中发现破绽,为了效率我们可以去剖析每种PDU的界说,然后可以优先去查看结构体界说中带有变长成员、以及Length、offset这类敏感词的PDU处置逻辑,由于破绽主要就处在处置变长数据的地方,好比

/// Find Information Response
struct l2cc_att_find_info_rsp
{
    /// Find Information Response - 0x05
    uint8_t     code;
    /// The format of the information data.
    uint8_t     format;
    /// Data length
    uint16_t    data_len;
    ///The information data whose format is determined by the Format field
    uint8_t     data[__ARRAY_EMPTY];
};

l2cc_att_rd_by_type_rsp 整数溢出导致堆溢出

sub_7F0FDB4函数用于处置 Read By Type Response 类型的PDU,要害代码如下

int __fastcall sub_7F0FDB4(int id, l2cc_att_rd_by_type_rsp *payload)
{

  operation = gattc_get_operation(id, 1);

  if ( operation != 10 )
  {
    gattc_send_complete_evt(id, 1, v5);
    return v30;
  }

  if ( gattc_get_mtu(id) - 2 > payload->each_len )
  {
    msg = ke_msg_alloc(dword_7F102F0 + 4, v23, src_id, payload->each_len + 4);
    *msg = (payload->data[1] << 8) | payload->data[0];
    sub_len = (payload->each_len - 2);
    *(msg + 4) = sub_len;
    *(msg + 2) = 0;
    qmemcpy((msg + 6), &payload->data[2], sub_len); // 整数溢出
    ke_msg_send(msg);
  }

}

若是operation为0,就会最后一个if分支,然后会判断 payload->each_len 小于 gattc_get_mtu(id) - 2 就会去凭据 each_len 分配内存,然后对 each_len - 2 保存到sub_len,最后拷贝sub_len个字节的数据到新分配的内存msg内里。

若是 sub_len 为 1,msg可用内存空间就是 5, 然后 sub_len 会由于整数溢出导致值为 0xFFFF ,然后举行memcpy时就会导致堆溢出。

ATT PDU处置函数多处越界读

ATT PDU处置函数中,许多都没有思量输入数据的长度,直接去接见,若是输入数据长度小于接见的巨细的话就会导致越界读,本节就先容几个典型的例子

好比sub_7F1015C

int __fastcall sub_7F1015C(int a1, l2cc_att_rd_rsp *payload)
{

  v4 = gattc_get_operation(a1, 1);
  if ( v4 == 4 )
  {
    v6 = gattc_get_operation_ptr(a1, 1);
    v7 = co_list_pop_front((off_7F102F4[a1] + 12));
    qmemcpy(&v7[5].next + 3, payload->value, 0x10u);  // 直接拷贝, 越界读

这里直接从 payload->value部门拷贝0x10字节,没有检查payload的现实长度,当payload剩余长度对照小(小于0x10)就会导致越界读。

这个例子是没有校验的,下面看一个校验失败的

void sub_7F11E24(int id, l2cc_att_rd_mult_req *payload)
{

  v2 = 0;
  v4 = co_list_size((_DWORD **)off_7F11FAC[id] + 24);
  p_payload_length = (int)(payload - 1); 获取 &l2cc_pdu->payld_len
  if ( !*p_payload_length )
  {
    *p_payload_length = gattc_get_mtu(v19) - 1;
  }
  while ( payload->nb_handles > v4 && *p_payload_length )
  {
    if ( v2 )
    {
      goto LABEL_13;
    }
    data = (int)payload + 2 * v4;
    v2 = sub_7F1126C(v19, 0, *(unsigned __int16 *)(data + 4), (int)v16);
  }

函数首先凭据 payload - 1 可以获取到 l2cc_pdu 的开头,然后去接见 l2cc_pdu 内里的 payld_len 就可以获得payload的长度(p_payload_length)。

struct l2cc_pdu
{
    /// L2Cap Payload Length
    uint16_t payld_len;
    /// L2Cap Channel ID
    uint16_t chan_id;

然则后续代码只检查 p_payload_length 的值是否为0,假设 p_payload_length 为 1,就会进入循环,然后凭据 v4 盘算 data 的地址,然后去接见,这时就会导致越界读。

其他

没事干在SDK内里瞎浏览的时刻发现gattc_write_req_ind_handler这个函数,然后在google上浏览发现该函数应该是用于处置GATT协议栈的某种报文的,然后在SDK中搜索gattc_write_req_ind_handler的引用

const struct ke_msg_handler pasps_default_state[] =
{
    {GATTC_WRITE_REQ_IND,           (ke_msg_func_t) gattc_write_req_ind_handler},
};

可以看到gattc_write_req_ind_handler对应的新闻ID为 GATTC_WRITE_REQ_IND(值为 0xC15),然后继续搜索GATTC_WRITE_REQ_IND看看是谁会发送该新闻,不外在SDK源码中没有找到,应该是在ROM中的固件会发送该新闻,我们可以使用 之前的剧本 把所有的msg id 使用情况导出到文件,然后在文件中搜索该新闻ID, 0xC15,最后找到位于0x7f114dc的函数会挪用发送该新闻

void __fastcall caller_gattc_write_req_ind(int a1, const void *value, size_t length, uint16_t offset, uint16_t handle, char a6)
{

  msg = ke_msg_alloc(0xc15, v8, (a1 << 8) + 8, length + 6);
  msg->handle = handle;
  v10 = msg;
  msg->length = length;
  msg->offset = offset;
  qmemcpy(msg->value, value, length);
  ke_msg_send(v10);

主要就是把入参的value、length和offset通过新闻发送出去,其中 length示意 value的长度,跟踪该函数的交织引用,可以发现其中一条路径是 0x52 类型的 ATT PDU的会调函数挪用

sub_7F11F42
    caller_gattc_write_req_ind

0x52的结构体界说如下

/// Write Command
struct l2cc_att_wr_cmd
{
    /// Write Command - 0x52
    uint8_t     code;
    /// The handle of the attribute to be written
    uint16_t    handle;
    /// Value length
    uint16_t    value_len;
    /// The value to be written to the attribute
    uint8_t     value[__ARRAY_EMPTY];
};

sub_7F11F42 函数要害代码

void __fastcall sub_7F11F42(int a1, l2cc_att_wr_cmd *payload)
{

  if ( !get_elem_by_handle(a1, 2, payload->handle, elem) 
  && !verify_value_len(elem, payload->value_len, 0, v4) )
  {
    caller_gattc_write_req_ind(a1, payload->value, payload->value_len, 0, payload->handle, 82);
  }

首先凭据handle找到对应的元素,然后检查value_len不能超过elem的最大长度,最后就会通过caller_gattc_write_req_ind把数据发送到gattc_write_req_ind_handler举行后续处置。

在SDK中搜索 gattc_write_req_ind_handler 可以发现该函数有多个实现,预测是针对差别的场景,用户可以重写该函数实现特定的功效,使用 search_msg_handler.py 搜索 0xC15 新闻回调函数,也可以在固件中找到一些实现,其中部门可以和SDK的源码对应。

通过剖析这个流程,我们可以从 gattc_write_req_ind_handler 最先一些破绽挖掘,不外由于这部门在SDK中有源码,以是审计起来会轻松不少。

参考链接

https://www.cnblogs.com/iini/p/8977806.html  详解BLE空口包花样—兼BLE Link layer协议剖析
https://tw511.com/a/01/14340.html
上一篇 下一篇

猜你喜欢

网友评论

随机文章
热门文章
热评文章
热门标签