BGGP3

Целта на тазгодишния BGGP е да „намери най-малкия файл, който ще срине определена програма“. Хареса ми идеята за това предизвикателство, тъй като ми се стори едновременно интересна и достъпна. След като прочетох правилата и примерите за предизвикателството: https://tmpout.sh/bggp/3/ реших да потърся добра мишена, за да започна фъзинг.

Мишена

Първата ми цел беше tshark, прецених, че това би било добре да се насоча, тъй като наскоро бях свършил известна работа по модифицирането на изходния код на wireshark и имах някаква представа как работи. Реших също, че се извършва много анализиране поради големия брой протоколи и типове файлове, поддържани от wireshark. Честно казано, прекарах само един ден в работа по тази цел и не стигнах много далеч. Реших да потърся някои други цели, в идеалния случай включващи много анализиране от общ файлов тип, и тогава се натъкнах на munpack: https://linux.die.net/man/1/munpack.

Стартирайте Fuzzing

Munpack е програма, която чете имейл файлове, извлича всички прикачени файлове (добре технически само първия прикачен файл, който среща) и записва копие на прикачения файл на диска. За да започна да размивам munpack, трябваше да намеря примерни .eml файлове. Успях да намеря това git хранилище, което съдържа редица .eml проби: https://github.com/mikel/mail/blob/master/spec/fixtures/emails/

Клонирах git хранилището, копирах редица проби в нова директория и стартирах honggfuzz със следната команда:

honggfuzz -i образци -x — /usr/bin/munpack ___FILE___

За моя изненада, това предизвика срив за по-малко от 10 секунди време на работа. Оставих fuzzer да продължи още малко, сривовете се увеличаваха на всеки няколко секунди, но само един от тях беше уникален. Реших да спра да размивам на този етап и да погледна файла, който генерира срива.

Двоичният файл на munpack, предоставен от apt, беше премахнат, така че изтеглих изходния код и го компилирах ръчно и отново тествах срива, което доведе до същия резултат.

Катастрофа

След като разгледах изхода на GDB от този срив, видях, че е причинен от препълване на буфера при извикване на sprintf от функция с име os_newfiletype. Можех също да видя, че форматиращият низ, предаден на sprintf, беше „%s.%d“, което помогна да се стесни кое извикване на sprintf причинява това препълване (имаше няколко извиквания на sprintf и някои, които също бяха уязвими на подобни препълвания).

Търсейки този конкретен форматен низ във функцията os_newfiletype, намерих извикването на sprintf, което препълваше:

Разглеждайки тази част от кода, можем да видим, че това извикване на sprintf се случва само когато флагът overwrite_files не е зададен и ако извикването на fopen(fname, “r”) е успешно, което показва, че файлът, който се опитва да отвори, съществува . Ако и двата случая са изпълнени, тогава оригиналното име на файл, fname, се свързва с файлов суфикс.

Целта на този код е да предотврати презаписването на файлове със същото име по време на процеса на извличане на прикачени файлове към имейл, но проблемът с това конкретно извикване на sprintf е, че няма проверка, за да се види дали размерът на fname е по-голям от това, което buf може дръжка. По-рано в тази функция buf е деклариран със статичен размер от 128 байта.

Проверих какъв е максималният размер на името на файла в Linux и очевидно името на файл може да бъде с дължина до 255 знака (4096 знака, когато включва пътя), което е много знаци за препълване на 128-байтовия буфер.

Намалете размера на файла

Добре, в този момент имам файл, който причинява срив поради препълване на буфера в името на файла, извлечено от .eml файл, но самият файл е доста голям.

root@mern:~# ls -alh big.eml
-rw-r--r-- 1 root root 645 Jul 19 19:41 big.eml

root@mern:~# cat big.eml
Subject: this message JUST contains an attachment
From: Test Test <[email protected]>
To: [email protected]
Content-Disposition: attachment; filename=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"\
Content-Transfer-Encoding: base64
Content-Description: Attachment has identical content to above foo.gz
Message-Id: <blah@localhost>
Mime-Version: 1.0
Date: 23 Oct 2003 22:40:49 -0700
Content-Type: text/plain;name="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
blahblahblahblahblah

За да намаля размера на входния файл, реших да погледна отново кода и да видя как точно се идентифицират и извличат прикачените файлове.

Като погледна по-отблизо източника, видях, че входният файл се чете в структура, която се предава на функция с име handleMessage. Тази функция незабавно извиква друга функция, наречена ParseHeaders, която, както подсказва името, анализира файла, търсейки различни полета на заглавката, и задава съответните променливи, когато срещне тези полета.

Едно от полетата, които се анализират по време на този процес, е Content-Type, което, както виждаме от примера по-горе, е настроено на „text/plain“. Този тип съдържание се предава на функция с име saveToFile, за да се справи с извличането на прикачени файлове.

Така че сега, когато видях, че полето Content-Type е това, което задейства извикването на saveToFile, а дължината на името на файла е това, което задейства действителното препълване, реших да премахна целия друг текст от тестовия файл, освен content- тип и име на файл, което ме оставя с това:

Content-Type:text/plain;name="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

Пусках този оголен файл през munpack и за моя радост предизвика същия срив. Така че сега имам 158 байтов файл, който не е супер малък, но поне е нещо.

Отвличане на изпълнение

Сега, когато имам работещ срив и намален файл, се опитах да видя дали ще мога да открадна изпълнението.

Разглеждайки изхода, генериран от срива, можем да видим, че програмата е прекратена поради откриване на препълване на буфера.

root@mern:~/mpack-1.6# munpack ~/final.eml
*** buffer overflow detected ***: terminated
Aborted (core dumped)

Изглежда, че някакъв вид защита пречи на това препълване да бъде полезно и гледането на изхода на GDB от този срив ни подсказва.

Можем да видим в горния изход, че sprintf всъщност извиква друга функция __sprintf_chk, която в крайна сметка извиква _IO_str_chk_overflow. Реших да разгледам тези функции, за да видя дали мога да добия представа какво се случва тук.

Документацията показва следното за __sprintf_chk

"The interface __sprintf_chk() shall function in the same way as the interface sprintf(), except that __sprintf_chk() shall check for stack overflow before computing a result, depending on the value of the flag parameter. If an overflow is anticipated, the function shall abort and the program calling it shall exit."

Въз основа на това описание реших да разгледам този срив, след като деактивирах някои от защитите на стека. Прекомпилирах munpack, като добавих следните флагове на компилатора:

-fno-stack-protector -D_FORTIFY_SOURCE=0

Първоначално файлът, който използвах за срив на munpack, не предизвика срив след деактивирането на тези защити, но след като увеличих размера на името на файла във файла, успях да презапиша достатъчно от стека, за да презапиша множество регистри, включително $rip.

За съжаление, не успях напълно да открадна изпълнението чрез това препълване, тъй като munpack извършва известна обработка на името на файла, преди да се опита да го напише. Трябва да си представя, че има начини да се използва това преливане, за да се отвлече изпълнението, но не успях да го разбера сам.

Точки

Размер на файла: 4096–158 = 3938

Записване: 3938 + 1024 = 4962

Общо: 4962

Заключение

Въпреки че другите примери за записи, които съм виждал, са по-интересни от моите открития, все пак бях щастлив да участвам в това предизвикателство и определено научих няколко неща по пътя.

Вижте тези други свързани публикации:

https://ortiz.sh/identity/2022/07/17/BGGP3.html

https://remyhax.xyz/posts/bggp3-cob/