我的团队最近发现
该团队不断监控开源项目,以发现新的漏洞和恶意软件包,并将其与更广泛的社区分享,以帮助改善其整体安全状况。
这个由两部分组成的博客系列提供了易受攻击的 Xpm 文件格式的内部工作原理的详细信息,并深入探讨了如何利用这些漏洞。
Xorg X11,通常称为 X Window System,是一种开源图形服务器协议,可以在类 Unix 操作系统中创建和管理图形用户界面。它提供了一个用于在网络环境中运行图形应用程序、管理 Windows 和处理用户输入的框架。
libx11 包提供了客户端应用程序在桌面上呈现和呈现数据所需的基本共享库。
libXpm 提供了读取、写入和显示X Pixmap (XPM) 格式图像的函数。
XPM 主要旨在生成支持透明像素的图标像素图。它基于 XBM 语法,可以是 XPM2 格式的纯文本文件,也可以使用 C 编程语言语法,因此适合包含在 C 程序文件中。
XPM 图像格式 – 版本
前身——XBM
在1989年XPM(X PixMap)出现之前,就有XBM格式(X BitMap)。
一种纯文本二进制图像格式,用于存储 X GUI 中使用的图标和光标位图。
XBM 文件的结构为 C 源文件。这是它们与当今大多数图像格式的主要区别。这样,它们就可以直接合并到应用程序中。然而,由于这一点以及无法采用压缩的事实(1 个字符到 1 个字节的映射),它们也远远大于其原始像素数据。
X BitMap (XBM) 数据包含一系列静态无符号字符数组,用于存储原始单色像素信息。
例如,以下C代码是图像中hello
的XBM文件:
#define hello_width 35 #define hello_height 25 static char hello_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0xD0, 0xE1, 0x88, 0x78, 0x00, 0x30, 0x13, 0x89, 0xC4, 0x00, 0x10, 0x12, 0x89, 0x84, 0x00, 0x10, 0xF2, 0x89, 0x84, 0x00, 0x10, 0x12, 0x88, 0x84, 0x00, 0x10, 0x12, 0x88, 0x44, 0x00, 0x10, 0xE2, 0x89, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
XBM 文件具有#define
语句,而不是通常的图像文件格式标头。第一对值定义位图的像素尺寸,表示其高度和宽度。
X BitMap (XBM) 图像数据被构造为连续的像素值序列,这些像素值存储在静态数组中。鉴于每个像素由单个位表示(0 表示白色,1 表示黑色),数组中的每个字节包含八个单独像素的数据。值得注意的是,第一个字节的最低有效位用作位图中左上角像素的锚点。
认识XPM版本
它于 1989 年首次发布,与上述 XBM 格式有很多相似之处。 XBM 是单色的,而 XPM1 则为图像引入了色彩。
它使用额外的宏和变量来表示颜色,并将字节替换为像素数据的字符。
例如,下面是 XPM1 格式的同一张hello
图片:
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
这里的XFACE_ncolors
表示图像中的颜色数量, XFACE_chars_per_pixel
表示每个像素的字符数量。
每个a
将被白色“#ffffff”替换,每个b
将被黑色“#000000”替换。
一年后,即 1990 年,该格式的第二个版本被设置来改进。它简化了事情并从图像格式中删除了所有 C 代码。
简化结构:
! XPM2 <Header> <Colors> <Pixels>
标题行表示图像尺寸,类似于 XPM1 的 #define 语句。
颜色部分定义字符值。
引入了新的类型概念:
c
– 用于彩色像素
m
– 表示单色
g
– 灰度冰
s
– 代表符号
符号功能用于根据上下文分配颜色,并为它们创建变量名称以便于阅读。
XPM2 图像的示例,具有与前面示例中所示相同的hello
:
! XPM2 35 25 2 1 ac #000000 bc #ffffff aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaababbbaaaabbbaaabaaabaaabbbbaaaa aaaabbaabbaabaaabaabaaabaabaaabbaaa aaaabaaaabaabaaabaabaaabaabaaaabaaa aaaabaaaabaabbbbbaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaabaaaa aaaabaaaabaaabbbbaabaaabaaabbbaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
除了十六进制颜色代码之外,还可以使用任何 X11 颜色名称(例如red
)来指定颜色,其中None
表示透明度。
这是 XPM 格式的当前版本。
1991年发布的版本,带回了C代码,但不是采用纯C代码风格,里面的值与XPM2格式基本相同。
例子:
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
正如所讨论的,XPM 格式还可以表示更复杂的图像,例如 JFrog 徽标:
DoS 漏洞 – CVE-2023-43786
CVE-2023-43786 漏洞本质上是由于递归停止条件计算不正确而导致的无限循环。
固定提交:
https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86
XPutImage
是 libX11 中的一个函数,可让您将图像放置到 X Drawable(通常是 X Window)上。使用此函数,可以将像素信息从 XImage 结构传输到指定的可绘制对象(如窗口或像素图),并根据需要对其进行定位。
xpmCreatePixmapFromImage
xpmCreatePixmapFromImage
libXpm 函数调用此XPutImage
函数:
void xpmCreatePixmapFromImage( Display *display, Drawable d, XImage *ximage, Pixmap *pixmap_return) { GC gc; XGCValues values; *pixmap_return = XCreatePixmap(display, d, ximage->width, ximage->height, ximage->depth); /* set fg and bg in case we have an XYBitmap */ values.foreground = 1; values.background = 0; gc = XCreateGC(display, *pixmap_return, GCForeground | GCBackground, &values); XPutImage(display, *pixmap_return, gc, ximage, 0, 0, 0, 0, ximage->width, ximage->height); XFreeGC(display, gc); }
在此函数中, ximage
是要显示的源图像像素数据,并被复制到X Drawable
对象(在本例中为pixmap_return
)。
XPutImage
这是XPutImage
libX11 函数:
int XPutImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height){ ..... PutSubImage(dpy, d, gc, &img, 0, 0, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad); UnlockDisplay(dpy); SyncHandle(); Xfree(img.data); return 0; } }
LockDisplay(dpy); FlushGC(dpy, gc); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad);......... }
它调用PutSubImage
函数:
static void PutSubImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height, int dest_bits_per_pixel, int dest_scanline_pad) { int left_pad, BytesPerRow, Available; if ((req_width == 0) || (req_height == 0)) return; Available = ((65536 < dpy->max_request_size) ? (65536 << 2) : (dpy->max_request_size << 2)) - SIZEOF(xPutImageReq); if ((image->bits_per_pixel == 1) || (image->format != ZPixmap)) { [1] left_pad = (image->xoffset + req_xoffset) & (dpy->bitmap_unit - 1); BytesPerRow = (ROUNDUP((long)req_width + left_pad, dpy->bitmap_pad) >> 3) * image->depth; } else { [2] left_pad = 0; BytesPerRow = ROUNDUP((long)req_width * dest_bits_per_pixel, [3] dest_scanline_pad) >> 3; } if ((BytesPerRow * req_height) <= Available) { [4] PutImageRequest(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, req_height, dest_bits_per_pixel, dest_scanline_pad); } else if (req_height > 1) { int SubImageHeight = Available / BytesPerRow; if (SubImageHeight == 0) SubImageHeight = 1; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, (unsigned int) SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset + SubImageHeight, x, y + SubImageHeight, req_width, req_height - SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); } else { [5] int SubImageWidth = (((Available << 3) / dest_scanline_pad) [6] * dest_scanline_pad) - left_pad; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset + SubImageWidth, req_yoffset, x + SubImageWidth, y, req_width - SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); } }
让我们看下面的示例图像:
Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel
由于图像bits_per_pixel
为32,[1]中的条件语句将不会通过,导致我们进入[2]中定义的替代代码块。
然后计算 [3] 上的BytesPerRow
,然后将其除以 8。在我们的示例中:BytesPerRow = 90000 * 32 / 8 = 360,0
在示例中,对 [4] 的检查不会通过,因为 360000 不小于请求的大小 262116,并且无法将请求宽度的单行放入单个请求中 - 这会启动 [5] 上的else
]。
这决定了单个请求中可以包含的像素数量。然后,它启动对PutSubImage
函数的递归调用以仅传递该子集,然后进行后续递归调用以管理该行的其余部分。如果需要,还可以通过额外的递归调用进一步划分该剩余部分。
然而,[6] 的计算未能考虑每像素的位数,并且递归调用使请求发送 2096928 像素而不是 2096928 位——这比单个请求所能容纳的要大。
这会导致尝试分割像素线的无限循环,始终导致数字太大而无法容纳,并再次尝试该过程以使用相同的值重试。这种递归一直持续到调用堆栈耗尽为止。
该错误修复更改了 [6] 的计算并考虑了bits_per_pixel
。在示例中,这将导致递归调用请求仅发送 65529 个像素,从而导致 BytesPerRow 为 262116,完全适合可用空间,从而允许递归向前推进并只需 2 次调用即可完成。
触发错误的概念验证图像示例: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
调用易受攻击的 libXpm 库函数的应用程序示例是 CLI 实用程序sxpm
,它用于在屏幕上显示 Xpm 图像。
它调用易受攻击的xpmCreatePixmapFromImage
XPM 函数,该函数随后调用易受攻击的 libX11 函数XPutImage
和PutSubImage
。
也发布在这里。