今天一大早打开微信,点进群里,微信一直闪退。
闪退四五次后终于好了,一群屌毛一直转发一个二维码,一旦打开后,微信立马闪退。
测了一会发现,并不是所有的都会闪退,苹果的好像不会。有些一打开看到图片就会闪退,有些是扫码才会闪退。
先放出二维码
这个需要扫码才会崩溃
此bug是利用了微信扫码识别功能的缺陷
微信的扫码代码是开源的,就在 https://github.com/opencv/opencv_contrib/blob/4.x/modules/wechat_qrcode/ ,直接看就好了。
wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.cpp:198 行,有
// try to repair count data if count data is invalid
if (count * 8 > available) {
count = (available + 7 / 8);
}
ArrayRef<char> bytes_(count);
char* readBytes = &(*bytes_)[0]; // CRASH HERE
for (int i = 0; i < count; i++) {
// readBytes[i] = (char) bits.readBits(8);
int readBits = available < 8 ? available : 8;
readBytes[i] = (char)bits.readBits(readBits, err_handler);
}
count 为 0,这个时候还在分配 readBytes 读了它第 1 个元素,所以崩溃了。简单修复方法就是 count <= 0 那么 return 掉。
为什么会这样呢?二维码中数据分为不同区段(segment),其中有一个比较灵活的字节段(byte segment)。在二维码原始信息中字节段这样储存:
Version 1-9:
Mode | Char Count | Data |
---|---|---|
0100 (Byte) | 00000001 (8 bits in Byte or Kanji Mode) | (…) |
所以当我们构建一个这样的二维码:
(正常段)+(字节模式指示位 4 + 长度位 00000001(8位)) ,并让这段数据刚好占用完二维码的所有数据容量,这样扫码的程序看到字符数量应该有1,会来读这个段,但之后又没有数据,从而造成了上面的错误。
为什么要用二维码的字节段呢?因为字节段的长度位正好是8位,其实日语段用的也是8位,同样可以。数字模式是10位、字符数字段是9位,触发不了这个 bug (但没看有没有其他的 bug)。