345 leituras
345 leituras

Revelando uma vulnerabilidade de 35 anos no nix libX11: Parte 1

por Yair Mizrahi18m2024/03/08
Read on Terminal Reader

Muito longo; Para ler

Esta série de blog investiga as vulnerabilidades de segurança encontradas no X.Org libX11, nomeadamente CVE-2023-43786 e CVE-2023-43787, explorando as complexidades do formato de arquivo XPM e demonstrando a exploração dessas vulnerabilidades. Aprenda como proteger seus sistemas com insights e correções abrangentes fornecidas.
featured image - Revelando uma vulnerabilidade de 35 anos no nix libX11: Parte 1
Yair Mizrahi HackerNoon profile picture
0-item


Minha equipe descobriu recentemente duas vulnerabilidades de segurança no X.Org libX11 , a biblioteca gráfica amplamente popular – CVE-2023-43786 e CVE-2023-43787 (com CVSS 7.8 de alta severidade de NVD). Essas vulnerabilidades causam negação de serviço e execução remota de código. As versões mais recentes do X11 contêm correções para essas vulnerabilidades.


A equipe monitora constantemente projetos de código aberto para encontrar novas vulnerabilidades e pacotes maliciosos e os compartilha com a comunidade em geral para ajudar a melhorar sua postura geral de segurança.


Esta série de blog em duas partes fornece detalhes sobre o funcionamento interno do formato de arquivo Xpm vulnerável e se aprofunda na exploração dessas vulnerabilidades.


O que é libX11?

Xorg X11, muitas vezes referido como X Window System, é um protocolo de servidor gráfico de código aberto que permite a criação e gerenciamento de interfaces gráficas de usuário em sistemas operacionais do tipo Unix. Ele fornece uma estrutura para executar aplicativos gráficos, gerenciar o Windows e manipular a entrada do usuário em um ambiente de rede.


O pacote libx11 oferece as bibliotecas compartilhadas essenciais que os aplicativos clientes exigem para renderizar e apresentar dados em seu desktop.


Arquitetura Cliente_Servidor X


Janela Xterm - um aplicativo cliente X11


O que é libXpm?

libXpm fornece funções para ler, escrever e exibir imagens no formato X Pixmap (XPM).

O XPM visa principalmente gerar mapas de pixels de ícones com suporte para pixels transparentes. Ele é baseado na sintaxe XBM e pode ser um arquivo de texto simples no formato XPM2 ou usar uma sintaxe de linguagem de programação C, tornando-o adequado para inclusão em um arquivo de programa C.


Formato de imagem XPM – versões

Antecessor – XBM


Antes do XPM (X PixMap) existir em 1989, existia o formato XBM (X BitMap).


Um formato de imagem binária de texto simples, usado para armazenar bitmaps de ícones e cursores que foram usados na GUI do X.


Os arquivos XBM são estruturados como arquivos de origem C. Esta é a sua principal distinção em relação à maioria dos formatos de imagem atuais. Dessa forma, eles podem ser incorporados diretamente nos aplicativos. No entanto, devido a isso e ao fato de nenhuma compactação poder ser empregada (mapeamento de 1 caractere para 1 byte), eles também são muito maiores do que seus dados brutos de pixel.


Os dados X BitMap (XBM) compreendem uma sequência de matrizes estáticas de caracteres não assinados que armazenam as informações brutas de pixels monocromáticos.


Por exemplo, o código C a seguir é o arquivo XBM para hello na imagem:

Olá imagem Xbm mostrada no XnView

 #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 };

Em vez dos cabeçalhos usuais de formato de arquivo de imagem, os arquivos XBM possuem instruções #define . O primeiro par de valores define as dimensões em pixels do bitmap, indicando sua altura e largura.


Os dados de imagem X BitMap (XBM) são estruturados como uma sequência contínua de valores de pixel, que são armazenados em uma matriz estática. Dado que cada pixel é simbolizado por um único bit (com 0 indicando branco e 1 representando preto), cada byte na matriz abrange os dados de oito pixels individuais. Notavelmente, o bit menos significativo do primeiro byte serve como âncora para o pixel superior esquerdo do bitmap.


XPM1

Conheça a versão XPM


Lançado pela primeira vez em 1989, compartilha muitas semelhanças com o formato XBM descrito acima. Enquanto o XBM era monocromático, o XPM1 introduziu cores nas imagens.


Ele usa macros e variáveis adicionais para cores e substitui bytes por caracteres para dados de pixel.


Por exemplo, aqui está a mesma imagem hello e branco no formato XPM1:


 /* 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 aqui denota o número de cores na imagem e XFACE_chars_per_pixel denota o número de caracteres por pixel.


Cada a será substituído pela cor branca “#ffffff” e cada b será substituído pela cor preta “#000000”.


XPM2

Um ano depois, em 1990, a segunda versão do formato foi criada para melhorar as coisas. Simplificou as coisas e removeu todo o código C do formato de imagem.


A estrutura simplificada:

 ! XPM2 <Header> <Colors> <Pixels>


A linha do cabeçalho indica as dimensões da imagem semelhantes às instruções #define do XPM1.

A seção de cores define os valores dos caracteres.


Um novo conceito de tipo foi introduzido:

c – é para pixel colorido

m – é para monocromático

g – gelo para escala de cinza

s – é para simbólico


O recurso simbólico é usado para atribuir cores por contexto e criar nomes de variáveis para facilitar a leitura.


Exemplo de imagem XPM2, com o mesmo hello mostrado nos exemplos anteriores:

 ! 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

Além dos códigos de cores hexadecimais, as cores também podem ser especificadas usando qualquer um dos nomes de cores X11 (por exemplo, red ), com None indicando transparência.


XPM3

Esta é a versão atual do formato XPM.


A versão lançada em 1991 trouxe de volta o código C, mas em vez de usar um estilo de código C puro, os valores internos são essencialmente os mesmos do formato XPM2.


Exemplo:

 /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " };


Conforme discutido, o formato XPM também pode representar imagens mais sofisticadas, como o logotipo JFrog:

Logotipo JFrog representado usando XPM3


Vulnerabilidade DoS – CVE-2023-43786

A vulnerabilidade CVE-2023-43786 é essencialmente um loop infinito resultante de um cálculo incorreto da condição de parada de recursão.


Confirmação fixa:

https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86


XPutImage é uma função na libX11 que permite colocar imagens em um X Drawable, geralmente um X Window. Com esta função, é possível transferir informações de pixel de uma estrutura XImage para um drawable designado, como uma janela ou um pixmap, e posicioná-lo conforme necessário.


xpmCreatePixmapFromImage

A função xpmCreatePixmapFromImage libXpm chama esta função 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); }


Nesta função, ximage são os dados de pixel da imagem de origem a serem exibidos e são copiados para o objeto X Drawable (neste caso pixmap_return ).


XPutImage

Aqui está a função 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);......... }


Ele chama a função 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); } }


Detalhes técnicos da vulnerabilidade

Tomemos a seguinte imagem de exemplo:


 Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel


Como a imagem bits_per_pixel é 32, a instrução condicional em [1] não será aprovada, levando-nos a inserir o bloco de código alternativo definido em [2].


Em seguida, ele calcula BytesPerRow em [3] e depois divide por 8. Em nosso exemplo: BytesPerRow = 90000 * 32/8 = 360,0


No exemplo, a verificação em [4] não seria aprovada, pois 360000 não é menor que o tamanho solicitado 262116 e não é capaz de encaixar uma única linha da largura solicitada em uma única solicitação – isso inicia o else em [5 ].


Isso determina o número de pixels que podem ser incluídos em uma única solicitação. Em seguida, ele inicia uma chamada recursiva para a função PutSubImage para passar apenas esse subconjunto, seguida por uma chamada recursiva subsequente para gerenciar a parte restante da linha. Se necessário, esta parte restante também pode ser dividida através de chamadas recursivas adicionais.


No entanto, o cálculo em [6] não leva em consideração os bits por pixel, e a chamada recursiva faz solicitações enviando 2.096.928 pixels em vez de 2.096.928 bits – o que é maior do que pode caber em uma única solicitação.


Isso leva a um loop infinito de tentativas de dividir a linha de pixels , resultando consistentemente em um número muito grande para caber e tentando novamente o processo para tentar novamente com os mesmos valores. Essa recursão persiste até que a pilha de chamadas se esgote.


A correção do bug alterou o cálculo em [6] e levou em consideração bits_per_pixel . No exemplo, resultaria em uma chamada recursiva solicitando o envio de apenas 65529 pixels, resultando em um BytesPerRow de 262116 que cabe perfeitamente no espaço disponível, permitindo assim que a recursão avance e termine em apenas 2 chamadas.


Exemplo de imagem de prova de conceito para acionar o bug: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm


Como o bug pode ser acionado

Um exemplo de aplicativo que chama a função vulnerável da biblioteca libXpm é o utilitário CLI sxpm , que é usado para exibir imagens Xpm na tela.


Ele chama a função vulnerável xpmCreatePixmapFromImage XPM, que então chama as funções vulneráveis da libX11 XPutImage e depois PutSubImage .

xpmCreatePixmapFromImage_XPutImage_PutSubImage


Também publicado aqui.


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks