libpng 漏洞分析
相关资源
PNG文件格式文档
http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
https://www.myway5.com/index.php/2017/11/10/png%E6%A0%BC%E5%BC%8F%E5%88%86%E6%9E%90%E4%B8%8E%E5%8E%8B%E7%BC%A9%E5%8E%9F%E7%90%86/
源码下载
http://78.108.103.11/MIRROR/ftp/png/src/history/libpng12/
测试样本
https://gitee.com/hac425/data/tree/master/libpng
CVE-2004-0597
分析
漏洞代码
void /* PRIVATE */
png_handle_tRNS(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
{
png_byte readbuf[PNG_MAX_PALETTE_LENGTH]; // 0x100
..........................
..........................
png_crc_read(png_ptr, readbuf, (png_size_t)length);
png_ptr->num_trans = (png_uint_16)length;
readbuf 是一个 0x100字节的缓冲区, length从 png 文件中读取,最大可以为 0x7fffffff , 典型的栈溢出。
测试用例:
trns_stack_bof.png
修复
对 length进行校验避免大于 PNG_MAX_PALETTE_LENGTH.
CVE-2007-5266
补丁地址
https://sourceforge.net/p/png-mng/mailman/png-mng-implement/thread/5122753600C3E94F87FBDFFCC090D1FF0400EA68@MERCMBX07.na.sas.com/
iCCP 的格式
iccp_name字符串+"\x00" + "\x00" + zlib压缩后的数据 endata
endata 解压后的格式
profile_size: 4个字节
iCCP chunk 处理堆越界(基本无影响)
分析
调试环境
ubuntu 16.04 64bit
测试用例
附件\libpng\iccp_memleak*.png
漏洞代码
#if defined(PNG_iCCP_SUPPORTED)
void PNGAPI
png_set_iCCP(png_structp png_ptr, png_infop info_ptr,
png_charp name, int compression_type,
png_charp profile, png_uint_32 proflen)
{
new_iccp_name = (png_charp)png_malloc_warn(png_ptr, png_strlen(name)+1);
png_strncpy(new_iccp_name, name, png_sizeof(new_iccp_name)); //当 name 大于 8 字节时, strncpy 拷贝字符串不会再末尾添0 , 可能内存泄露
strncpy 的工作为
char* strncpy(char *dest, const char *src, size_t n){
size_t i;
for (i = 0 ; i < n && src[i] != '\0' ; i++)
dest[i] = src[i];
for ( ; i < n ; i++)
dest[i] = '\0';
return dest;
}
- 如果src的前n个字符里面没有'\0',那么它不会在末尾补上这个结束符
- 如果拷贝的数据不满n个字符,那么它会用 '\0' 在末尾填充
漏洞是存在的,无影响的原因是在 linux x64 下分配内存的最小数据块为 0x20 , 可用的数据区域为 0x10.而 png_sizeof(new_iccp_name) 的大小为 8 , 所以不会溢出到其他内存块的数据里面。
Case1
当 iccp_name
的长度大于 8
时, strncpy
不会再字符串末尾填\x00
, 后面的使用可能会导致内存数据泄露(比如分配到的的内存块是位于 unsorted bin 中)。
iccp_memleak1.png
泄露堆块的指针
Case2
当 iccp_name
的长度小于 8
时 , malloc
的大小会小于 png_sizeof(new_iccp_name)
, 这个会造成越界。
iccp_memleak2.png
当 iccp_name 为 k\x00 时, 分配 2 字节
后面拷贝时会拷贝 8 字节
修复方式
png_strncpy(new_iccp_name, name, png_strlen(new_iccp_name)+1);
总结: strncpy 要小心使用
sPLT Chunk处理
分析
测试用例
附件\libpng\splt.png
sPLT 的数据域的格式
字符串 + '\x00' + entries
entries 的结构
depth, 用来表示每个entry的size: 1字节
entry 数组
entry 的结构
typedef struct png_sPLT_entry_struct
{
png_uint_16 red;
png_uint_16 green;
png_uint_16 blue;
png_uint_16 alpha;
png_uint_16 frequency;
} png_sPLT_entry;
还是 strncpy 的使用, 没有 设置 '\x00' 可能会 leak.
png_set_sPLT(png_structp png_ptr,
png_infop info_ptr, png_sPLT_tp entries, int nentries)
{
to->name = (png_charp)png_malloc_warn(png_ptr,png_strlen(from->name) + 1);
png_strncpy(to->name, from->name, png_strlen(from->name));
假设 from->name
为 8 字节
修复
png_strncpy(to->name, from->name, png_strlen(from->name)+1);
CVE-2007-5269
公告地址
https://sourceforge.net/p/png-mng/mailman/png-mng-implement/thread/3.0.6.32.20071004082318.012a7628@mail.comcast.net/
zTXt
分析
测试用例
附件\libpng\ztxt.png
漏洞代码
void /* PRIVATE */
png_handle_zTXt(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
{
png_crc_read(png_ptr, (png_bytep)chunkdata, slength);
if (png_crc_finish(png_ptr, 0))
{
png_free(png_ptr, chunkdata);
return;
}
chunkdata[slength] = 0x00;
for (text = chunkdata; *text; text++)
/* empty loop */ ;
/* zTXt must have some text after the chunkdataword */
if (text == chunkdata + slength - 1)
{
png_warning(png_ptr, "Truncated zTXt chunk");
png_free(png_ptr, chunkdata);
return;
}
首先读取 chunkdata , 然后末尾填 '\x00', 然后会在 chunkdata 开始位置找字符串
for (text = chunkdata; *text; text++)
/* empty loop */ ;
后面的判断条件出现了问题
if (text == chunkdata + slength - 1)
当chunkdata
中的字符全部都不是'\x00
' 时, text
会等于 chunkdata + slength
后面就会越界读了。
修复
/* zTXt must have some text after the chunkdataword */
if (text >= chunkdata + slength - 2)
{
png_warning(png_ptr, "Truncated zTXt chunk");
png_free(png_ptr, chunkdata);
return;
}
总结:用 == 号来判断是否出现数组越界是不安全的
sCAL
测试用例
附件\libpng\scal.png
分析
漏洞代码
png_handle_sCAL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
{
png_crc_read(png_ptr, (png_bytep)buffer, slength);
buffer[slength] = 0x00; /* null terminate the last string */
ep = buffer + 1; /* skip unit byte */
width = png_strtod(png_ptr, ep, &vp);
if (*vp)
{
png_warning(png_ptr, "malformed width string in sCAL chunk");
return;
}
for (ep = buffer; *ep; ep++)
/* empty loop */ ;
ep++;
当 buffer 里面的每个字符都不是 \x00 时, 最后会执行这一部分代码后, ep 会超过分配的内存块的大小,造成越界访问。
修复
在后面增加校验
if (buffer + slength < ep)
{
png_warning(png_ptr, "Truncated sCAL chunk");
#if defined(PNG_FIXED_POINT_SUPPORTED) && \
!defined(PNG_FLOATING_POINT_SUPPORTED)
png_free(png_ptr, swidth);
#endif
png_free(png_ptr, buffer);
return;
}
CVE-2008-1382
测试用例
附件\libpng\unknown.png
分析
漏洞代码
void /* PRIVATE */
png_handle_unknown(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
{
png_uint_32 skip = 0;
png_ptr->unknown_chunk.data = (png_bytep)png_malloc(png_ptr, length);
png_ptr->unknown_chunk.size = (png_size_t)length;
png_crc_read(png_ptr, (png_bytep)png_ptr->unknown_chunk.data, length);
在处理 unknown
类型的 chunk
时, 如果 length
为0
, png_malloc
会返回0
, 然后后面的代码没有校验png_malloc
的返回值直接使用,导致空指针引用。
修复
对 length
进行校验
if (length == 0)
png_ptr->unknown_chunk.data = NULL;
else
{
png_ptr->unknown_chunk.data = (png_bytep)png_malloc(png_ptr, length);
png_crc_read(png_ptr, (png_bytep)png_ptr->unknown_chunk.data, length);
}
PS: 对1.2.19 用测试样本跑时,会触发栈溢出,溢出在 strncpy 函数内部,很神奇。
CVE-2008-3964
测试用例
ztxt_off_by_one.png
分析
漏洞代码
void /* PRIVATE */
png_push_read_zTXt(png_structp png_ptr, png_infop info_ptr)
{
if (!(png_ptr->zstream.avail_out) || ret == Z_STREAM_END)
{
if (text == NULL)
{
text = (png_charp)png_malloc(png_ptr,
(png_uint_32)(png_ptr->zbuf_size
- png_ptr->zstream.avail_out + key_size + 1));
png_memcpy(text + key_size, png_ptr->zbuf,
png_ptr->zbuf_size - png_ptr->zstream.avail_out);
png_memcpy(text, key, key_size);
text_size = key_size + png_ptr->zbuf_size -
png_ptr->zstream.avail_out;
*(text + text_size) = '\0';
}
else
{
png_charp tmp;
tmp = text;
text = (png_charp)png_malloc(png_ptr, text_size +
(png_uint_32)(png_ptr->zbuf_size
- png_ptr->zstream.avail_out));
png_memcpy(text, tmp, text_size);
png_free(png_ptr, tmp);
png_memcpy(text + text_size, png_ptr->zbuf,
png_ptr->zbuf_size - png_ptr->zstream.avail_out);
text_size += png_ptr->zbuf_size - png_ptr->zstream.avail_out;
*(text + text_size) = '\0';
分配内存时
png_malloc(png_ptr, text_size +
(png_uint_32)(png_ptr->zbuf_size
- png_ptr->zstream.avail_out));
最后一步给解压后的字符串末尾赋值时
*(text + text_size) = '\0';
通过代码可以知道
text_size = text_size +
(png_uint_32)(png_ptr->zbuf_size
- png_ptr->zstream.avail_out)
典型的单字节数组越界即
buf[buf_length]
分配内存时 ,分配了 0x4006
最后赋值 \x00 时 , 使用 0x4006作为索引 off-by-one
这个漏洞的样本构造需要让 zTXt 的压缩数据的大小大于 0x2000 , 因为zstream.avail_out初始值为 2000.zTXt 的压缩数据的大小大于 0x2000 时才能进入漏洞分支。
修复
分配的时候多分配一个字节
tmp = text;
text = (png_charp)png_malloc(png_ptr, text_size +
(png_uint_32)(png_ptr->zbuf_size
- png_ptr->zstream.avail_out + 1));
CVE-2008-5907
测试样本
iccp_longkeyword.png
分析
漏洞代码
png_size_t /* PRIVATE */
png_check_keyword(png_structp png_ptr, png_charp key, png_charpp new_key)
{
key_len = strlen(key);
............
............
if (key_len > 79)
{
png_warning(png_ptr, "keyword length must be 1 - 79 characters");
new_key[79] = '\0'; // new_key 是一个指针数组
key_len = 79;
}
当 key_len 大于 79时,会使用
new_key[79] = '\0';
往地址写 0
, 注意到 new_key
是一个 char**p
, 所以上面的代码实际是往一个随机的位置写 8 字节的 0
.
对应的汇编代码
lea rsi, aKeywordLengthM ; "keyword length must be 1 - 79 character"...
mov rdi, png_ptr ; png_ptr
call _png_warning
mov qword ptr [new_key+278h], 0 // new_key[79] = '\0';
mov eax, 4Fh ; 'O'
jmp loc_12237
以 png_write_tEXt
为例
void /* PRIVATE */
png_write_tEXt(png_structp png_ptr, png_charp key, png_charp text,
png_size_t text_len)
{
if (key == NULL || (key_len = png_check_keyword(png_ptr, key, &new_key))==0)
{
png_warning(png_ptr, "Empty keyword in tEXt chunk");
return;
}
这里 new_key
是一个栈变量 ,当触发漏洞时 ,就会往 png_write_tEXt
函数栈帧某个位置写8字节 0 。
调试时可以看到往栈里面写 0x000000000000000
修复
正确使用指针
if (key_len > 79)
{
png_warning(png_ptr, "keyword length must be 1 - 79 characters");
(*new_key)[79] = '\0';
key_len = 79;
}
CVE-2009-0040
分析
漏洞代码
png_read_png(png_structp png_ptr, png_infop info_ptr,
int transforms,
voidp params)
{
info_ptr->row_pointers = (png_bytepp)png_malloc(png_ptr,
info_ptr->height * png_sizeof(png_bytep));
for (row = 0; row < (int)info_ptr->height; row++)
{
info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr,
png_get_rowbytes(png_ptr, info_ptr));
}
}
这里会分配多个 row_pointer
, 当内存不足时 , png_malloc
会使用 longjmp
去释放掉row_pointers
数组内的指针,row_pointers
中后面的一些没有初始化的内存区域中的残留数据也有可能会被当做指针而 free
。
修复
分配内存前,初始化为 0
png_memset(info_ptr->row_pointers, 0, info_ptr->height
* png_sizeof(png_bytep));
for (row = 0; row < (int)info_ptr->height; row++)
info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr,
png_get_rowbytes(png_ptr, info_ptr));
}
CVE-2009-5063
分析
漏洞代码
png_write_iCCP(png_structp png_ptr, png_charp name, int compression_type,
png_charp profile, int profile_len)
{
png_size_t name_len;
png_charp new_name;
compression_state comp;
int embedded_profile_len = 0;
if (profile == NULL)
profile_len = 0;
if (profile_len > 3)
embedded_profile_len =
((*( (png_bytep)profile ))<<24) |
((*( (png_bytep)profile + 1))<<16) |
((*( (png_bytep)profile + 2))<< 8) |
((*( (png_bytep)profile + 3)) );
if (profile_len < embedded_profile_len)
{
png_warning(png_ptr,
"Embedded profile length too large in iCCP chunk");
return;
}
if (profile_len > embedded_profile_len)
{
png_warning(png_ptr,
"Truncating profile to actual length in iCCP chunk");
profile_len = embedded_profile_len;
}
if (profile_len)
profile_len = png_text_compress(png_ptr, profile,
(png_size_t)profile_len, PNG_COMPRESSION_TYPE_BASE, &comp);
可以看到这里的 profile_len 和 embedded_profile_len 都是 int 类型, embedded_profile_len从png图片的数据里面取出,当embedded_profile_len为负数时 比如(0xffffffff) , 最终会进入
profile_len = embedded_profile_len;
之后会将profile_len 传入
profile_len = png_text_compress(png_ptr, profile,
(png_size_t)profile_len, PNG_COMPRESSION_TYPE_BASE, &comp);
而 png_text_compress 接收的参数为 png_size_t 即无符号整数,所以会造成越界。
修复
修改类型为 png_size_t.
CVE-2010-1205
处理 PNG 的 IDAT数据时会发生堆溢出。测试样本
xploit.png
分析
处理PNG 图片中的 IDAT 数据时,会把 IDAT 中的数据一行一行的取出来保存后,然后进行处理。程序在一开始会使用 rpng2_info.height
(即IHDR chunk 中的 heigth) 分配一些内存,用来保存每一行的数据。
static void rpng2_x_init(void)
{
rpng2_info.image_data = (uch *)malloc(rowbytes * rpng2_info.height); // 0xaf0
rpng2_info.row_pointers = (uch **)malloc(rpng2_info.height * sizeof(uch *));// 这里只分配一个指针空间, 因为 heigh 为 1, 而且是 malloc 会有内存残留
以上图为例,rpng2_info.height
为 1
, 首先会分配 rowbytes
的空间用来存储所有的 IDAT
数据, 然后会分配 1 个指针数组 row_pointers
, 用来保存指向保存每一行数据的内存区域。其中 rowbytes
是通过 IHDR
里面的字段计算出来的
void __cdecl png_handle_IHDR(png_structp png_ptr, png_infop info_ptr, png_uint_32 length)
{
png_ptr->pixel_depth = png_ptr->channels * png_ptr->bit_depth;// 4*8
v4 = png_ptr->width * (png_ptr->pixel_depth >> 3);
png_ptr->rowbytes = v4; // 0xaf0
还是上图为例, 最终计算的结果为 0xaf0
. 之后程序会每次读取 0xaf0
数据,然后从 rpng2_info.row_pointers
取出一个指针, 然后往指针对应的内存空间里面写数据, 直到读取完所有的 IDAT
数据。后面会使用越界的指针进行内存拷贝,导致内存写。
触发越界访问的代码如下:
static void readpng2_row_callback(png_structp png_ptr, png_bytep new_row,
png_uint_32 row_num, int pass)
{
png_progressive_combine_row(png_ptr, mainprog_ptr->row_pointers[row_num],// row_num 会为1, 而 row_pointers的长度为1, 典型溢出
new_row);
在溢出前,row_pointers[1]
后面有残留的内存指针,因为 row_pointers
的分配使用的是 malloc
,所以会有内存残留。0x612020
是一个堆上的指针。
执行完毕后会触发堆溢出把堆上的数据给覆盖了。
总结:分配内存空间时使用的是 png
图片中的字段, 然后实际使用的空间是根据数据长度进行计算的,两者的不一致导致了漏洞。
修复
在 readpng2_row_callback
对 row_num
进行判断。
CVE-2011-2692
分析
漏洞代码
void /* PRIVATE */
png_handle_sCAL(png_structp png_ptr, png_infop info_ptr, png_uint_32
length) {
png_charp ep;
...
png_ptr->chunkdata = (png_charp)png_malloc_warn(png_ptr, length + 1);
...
slength = (png_size_t)length;
...
png_ptr->chunkdata[slength] = 0x00; /* Null terminate the last
string */
ep = png_ptr->chunkdata + 1; /* Skip unit byte */
...
width = png_strtod(png_ptr, ep, &vp);
...
swidth = (png_charp)png_malloc_warn(png_ptr, png_strlen(ep) + 1);
--
当 length
为 0
时, ep
会出现越界访问。
修复
对 length 检查
libpng 漏洞分析的更多相关文章
- Zabbix 漏洞分析
之前看到Zabbix 出现SQL注入漏洞,自己来尝试分析. PS:我没找到3.0.3版本的 Zabbix ,暂用的是zabbix 2.2.0版本,如果有问题,请大牛指点. 0x00 Zabbix简介 ...
- PHPCMS \phpcms\modules\member\index.php 用户登陆SQL注入漏洞分析
catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述2. 漏洞触发条件 0x1: POC http://localhost/p ...
- CVE-2016-0143 漏洞分析(2016.4)
CVE-2016-0143漏洞分析 0x00 背景 4月20日,Nils Sommer在exploitdb上爆出了一枚新的Windows内核漏洞PoC.该漏洞影响所有版本的Windows操作系统,攻击 ...
- Java反序列化漏洞分析
相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...
- CVE-2014-1767 漏洞分析(2015.1)
CVE-2014-1767 漏洞分析 1. 简介 该漏洞是由于Windows的afd.sys驱动在对系统内存的管理操作中,存在着悬垂指针的问题.在特定情况下攻击者可以通过该悬垂指针造成内存的doubl ...
- CVE-2014-4115漏洞分析(2014.11)
CVE-2014-4115漏洞分析 一.简介 该漏洞是由于Windows的Fastfat.sys组件在处理FAT32格式的硬盘分区存在问题.攻击者利用成功可导致权限提升. 影响的系统包括: Windo ...
- FFmpeg任意文件读取漏洞分析
这次的漏洞实际上与之前曝出的一个 CVE 非常之类似,可以说是旧瓶装新酒,老树开新花. 之前漏洞的一篇分析文章: SSRF 和本地文件泄露(CVE-2016-1897/8)http://static. ...
- CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用
作者:栈长@蚂蚁金服巴斯光年安全实验室 -------- 1. 背景 FFmpeg是一个著名的处理音视频的开源项目,非常多的播放器.转码器以及视频网站都用到了FFmpeg作为内核或者是处理流媒体的工具 ...
- Oracle漏洞分析(tns_auth_sesskey)
p216 Oracle漏洞分析: 开启oracle: C:\oracle\product\\db_1\BIN\sqlplus.exe /nolog conn sys/mima1234 as sysdb ...
随机推荐
- 【Spring Boot学习之六】Spring Boot整合定时任务&异步调用
环境 eclipse 4.7 jdk 1.8 Spring Boot 1.5.2一.定时任务1.启动类添加注解@EnableScheduling 用于开启定时任务 package com.wjy; i ...
- remote origin already exists解决办法
如图翻译过来就是:致命:远程来源已经存在 此时,我们可以先 git remote -v 查看远程库信息: 可以看到,本地库已经关联了origin的远程库,并且,该远程库指向GitHub. 解决办法如下 ...
- understanding backpropagation
几个有助于加深对反向传播算法直观理解的网页,包括普通前向神经网络,卷积神经网络以及利用BP对一般性函数求导 A Visual Explanation of the Back Propagation A ...
- [转帖](区块链补习班)ERC20很多人都听过,但ERC是什么你真的了解吗?
(区块链补习班)ERC20很多人都听过,但ERC是什么你真的了解吗? http://baijiahao.baidu.com/s?id=1600948969290990883&wfr=spide ...
- Django框架第七篇(模型层)--多表操作:一对多/多对多增删改,跨表查询(基于对象、基于双下划线跨表查询),聚合查询,分组查询,F查询与Q查询
一.多表操作 一对多字段的增删改(book表和publish表是一对多关系,publish_id字段) 增 create publish_id 传数字 (publish_id是数据库显示的字段名 ...
- AVR单片机教程——旋转编码器
好久没写这个系列了.今天讲讲旋转编码器. 旋转编码器好像不是单片机玩家很常用的器件,但是我们的开发板上有,原因如下: 旋转编码器挺好用的.电位器能旋转的角度有限,旋转编码器可以无限圈旋转:旋转时不连续 ...
- Intellij IDEA最全的热键表(default keymap)
Editing Ctrl + Space Basic code completion (the name of any class, method or variable) Ctrl + Shift ...
- maven的setting配置文件中mirror和repository的区别
当maven需要到的依赖jar包不在本地仓库时, 就需要到远程仓库下载 . 这个时候如果mavensetting.xml中配置了镜像 , 而且镜像配置的规则中匹配到目标仓库时 , maven认为目标仓 ...
- idea 用鼠标滚轮调整代码文字大小
File > Settings > Keymap > Editor Actions 下,我们可以找到 “Decrease Font Size”和“Increase Font Size ...
- Node.js到底是什么
接触前端也有一段时间了,逐渐开始接触Node.js,刚刚接触Node.js的时候一直都以为Node.js就是JavaScript,当对Node.js有一定的了解之后,其实并不然两者之间有关系,其中的关 ...