小米手环7协议分析
网上大多数关于小米手环的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框架实现,因此我也不会再继续研究了。如果您阅读了本文章并且愿意继续研究请欢迎在评论留下联系方式,我乐意与您交流。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。