网上大多数关于小米手环的auth协议都停留在6,最新的7手环相对之前的认证较为麻烦使用到了除aes之外的ecdh加密?

这里感谢https://github.com/patyork/miband-7-monitor/提供的协议代码参考

首先我们来看authenticate.js文件this.ECC_PUB_KEY_SIZE和this.ECC_PRV_KEY_SIZE分别为48和24具体为什么不知道。大概是逆向小米手环app得到的

this.pub_buf = Module._malloc(this.ECC_PUB_KEY_SIZE);

this.prv_buf = Module._malloc(this.ECC_PRV_KEY_SIZE);获取变量内存地址赋值

this.pub = Module.HEAPU8.subarray(

this.pub_buf,

this.pub_buf + this.ECC_PUB_KEY_SIZE

);

this.prv = Module.HEAPU8.subarray(

this.prv_buf,

this.prv_buf + this.ECC_PRV_KEY_SIZE

);Module.HEAPU8.subarray引用了Module['HEAPU8'] = HEAPU8 = new Uint8Array(buf); 等同于创建了一个新的uint8array,并且从this.pub_buf取到this.pub_buf + this.ECC_PUB_KEY_SIZE,这样修改pub和prv会同样影响module中的值,this.prv同理

crypto.getRandomValues(this.prv);设置this.prv为随机数

Module._ecdh_generate_keys(this.pub_buf, this.prv_buf);生成this.pub的内容

await this.Band.writeChunkedValue(

this.Band.Chars.CHUNKED_WRITE,

CHUNK_ENDPOINTS.AUTH,

this.Band.getNextHandle(),

Uint8Array.from(auth)

)开始发送消息

// 传递数据的大小

let remaining = data.length;

// 循环的次数

let count = 0;

// 固定头部的大小

let header_size = 11;

// 最大能发送的包长度

const mMTU = 23;console.log("剩余包大小:%d,总数据大小",remaining,data)

while (remaining > 0) {

const MAX_CHUNKLENGTH = mMTU - 3 - header_size;

console.log("最大发送的包的大小",MAX_CHUNKLENGTH)

const copybytes = Math.min(remaining, MAX_CHUNKLENGTH);

console.log("获取剩余未发送数据%d和最大发送包%d大小之间最小的%d",remaining,MAX_CHUNKLENGTH,Math.min(remaining, MAX_CHUNKLENGTH))

const chunk = new Uint8Array(copybytes + header_size);

console.log("设置真实发送的包的大小,如果剩余数据小于最大发送包则用剩余数据长度加上固定头长度",chunk)

let flags = base_flags;

if (count == 0) {

console.log("如果是第一次循环")

// Endpoint 0x0a seems to take different flag, and that affects length?

chunk[5] = data.length;

chunk[6] = data.length >> 8; // 位移后为0

chunk[7] = data.length >> 16; // 位移后为0

chunk[8] = data.length >> 24; // 位移后为0

chunk[9] = type;

chunk[10] = type >> 8; // 位移后为0

flags |= 0x01;

}

if (remaining <= MAX_CHUNKLENGTH) {

// 如果发送的数据包小于最大能发送的,就设置flags

flags |= 0x06; // last chunk?

//# 0x07

}

console.log("flags",flags,typeof flags)

chunk[0] = 0x03;

chunk[1] = flags;

chunk[2] = 0;

chunk[3] = handle;

chunk[4] = count;

console.log("设置了固定头部后的包:",chunk)

//# [3, 7, 0, 8, 0, 1, 0, 0, 0, 40, 0, 0]

chunk.set(

data.slice(

data.length - remaining,

data.length - remaining + copybytes

),

header_size // 这个似乎一直没有用到?

);

console.log("设置数据:",data.slice(

data.length - remaining,

data.length - remaining + copybytes

),

header_size)

//# [3, 7, 0, 8, 0, 1, 0, 0, 0, 40, 0, 1

console.log(chunk)

console.log("Sending: " + toHexString(chunk));

await char.writeValue(chunk);

remaining -= copybytes;

header_size = 5;

count++;

console.log("count",count,"remaining",remaining)

console.log("==============================")

}MAX_CHUNKLENGTH为能够传递的数据长度

copybytes如果剩余的数据小于最大能发送的数据,就把剩余的数据发送,而不是按最大发送数据将空的地方补0

chunk包大小

flags标志,告诉手环是第一次发送数据还是最后一次发送数据

chunk[0] = 0x03;

chunk[1] = flags;

chunk[2] = 0;

chunk[3] = handle;

chunk[4] = count;这里是设置固定的头部,如果是第一次发送,那还需要占据更多的位置设置一些信息

chunk[5] = data.length;

chunk[6] = data.length >> 8; // 位移后为0

chunk[7] = data.length >> 16; // 位移后为0

chunk[8] = data.length >> 24; // 位移后为0

chunk[9] = type;

chunk[10] = type >> 8; // 位移后为0

flags |= 0x01; // 不可更改,尝试修改为1但无法登录chunk.set(

data.slice(

data.length - remaining,

data.length - remaining + copybytes

),

header_size // 这个似乎一直没有用到?

);设置剩余的空位置为数据,header_size似乎一直都没用到,但我没尝试删除它

当是最后一条数据包时就设置flags为7,我同样尝试修改flags = 7 但无法登录

if (remaining <= MAX_CHUNKLENGTH) {

// 如果发送的数据包小于最大能发送的,就设置flags

flags |= 0x06; // last chunk?

//# 0x07

}await char.writeValue(chunk); 写入数据包发送

remaining -= copybytes; 将总数据包长度减少本次发送的长度

header_size = 5; 第一次11字节长度的包发送完成,剩下包头部都是 5字节

count++; 计次加一 上方设置头部时有用到

综上所述,我尝试在windows平台下使用python语言的bleak库使用同样的数据包发送给手环,并且监听了 00000017-0000-3512-2118-0009af100700 characteristic,但是能够发送手环没有返回消息,我不知道是否是windows平台下的问题,因为我使用另一个蓝牙库时甚至搜索不到我的手环!如果您知道的话请在评论告诉我,不胜感激!

我仅仅研究了第一次发信,但这也非常接近答案了,因为我粗略了看了一下二次发信,仅仅是重新生成了rpub同时使用到了关键的authkey。

但我只是看他人代码而非逆向手环app,这样会存在许多困惑。同时我希望达成的效果可以通过用xposed框架实现,因此我也不会再继续研究了。如果您阅读了本文章并且愿意继续研究请欢迎在评论留下联系方式,我乐意与您交流。