4.7. Уязвимость, связанная с двойным освобождением памяти 191
вполне возможно, так как глобальная таблица смещений (GOT) доступна для записи. Одна
ко в случае бинарного формата Windows эта информация не может быть перезаписана.
Адрес - 1 2 включен во вредоносный аргумент, так что метод unl i nk ( ) перезаписывает
адрес библиотечного вызова free ( ) адресом шеллкода . После этого шеллкод выполняется
как результат вызова f ree ( ) (строка 1 0 атакуемой программы) . Шеллкод осуществляет
безусловный переход через первые 1 2 байтов, поскольку часть его памяти перезаписыва
ется макросом unl ink ( ) при присваивании BK-> fd = FD. Значение (lvalue) BK->fd ука
зывает на адрес шеллкода плюс 8; следовательно, байты шеллкода с 9 по 12 оказываются
перез а п и с а н н ы м и .
И спользование переполнения буфера в куче оказывается не особенно сложным. Наибо
лее сложная часть этого эксплойта состоит в определении размера первого блока, чтобы
точно перезаписать дескриптор границы второго аргумента. Для этого злоумышленник
может скопировать макрос request2 s i ze ( req, nb ) из dlmalloc в свой эксплойт и исполь
зовать его для вычисления размера блока.
• 4.7. Уязвимость, связанная с двойным
освобождением памяти
mal l oc Дуга Ли также восприимчив к уязвимостям, связанным с двойным освобожде
нием памяти. Этот тип уязвимости возникает из-за освобождения одного и того же блока
памяти дважды без перераспределения между этими вызовами f ree ( ) .
Чтобы эксплойт на основе двойного освобождения памяти был успешен, должны
быть выполнены два условия. Освобождаемый блок должен быть изолирован в памяти
(т.е. смежные с ним блоки не должны быть свободными, чтобы не происходило объеди
нение блоков памяти) , а список, в который должен быть помещен данный блок, должен
быть пустым.
На рис. 4. 1 1 показаны пустой список и выделенный блок памяти. Поскольку список
пуст, указатели указывают на заголовок списка. Нет никакой связи между списком и бло
ком памяти, потому что этот блок выделен.
Список -> Указатель на первый блок в cn с е
Указа ель на последний бло s списке
first -> Раа ер nредыдУщеrо бло ·а (если он свободен)
Поскольку сnисо пуст. Размер блокв в байтах р
указател у азываю
Пользовательс е данн е
на заголово сnи
.
.
Рис. 4. 1 1 . Пустой сп исок и Выделен ный блок
1192 Управление динамической памятью
На рис. 4. 1 2 показаны эти структуры данных после освобождения блока памяти, на ко
торый указывает Р. Функция free ( ) добавляет свободный блок в список с помощью кода
frontlink, приведенного в примере 4. 1 5.
Список -> Указатель на первый блок в списке
Указатель на последний блок в списке
first -> Размер предыдущего блока {если он свободен)
Ожидаемое поведение; Размер блока в байтах р
дважды связанный Указатель на следующий блок в списке
список содержит
свободный блок Указатель на предыдущий блок в списке
Неиспользуемая память (может быть О байт)
Размер блока
Рис. 4. 12. Список с одн им с8ободным блоком
П ример 4.1 5. Код добавления блока в список
0 1 ВК = Ьin ;
02 F D = BK->fd;
03 if ( FD ! = ВК)
04 whi le ( FD ! = ВК && S < chun ksize ( FD ) ) {
05 FD = FD->fd;
06
07 ВК = FD->bk;
08
0 9 P->bk = ВК ;
10 P-> fd = FD;
1 1 FD->bk = BK->fd = Р;
Когда освобождается блок памяти, он должен быть внесен в соответствующий дважды
связанный список. В некоторых версиях dlmalloc это осуществляется показанным выше
кодом. Этот код выполняется после слияния смежных пустых блоков. Блоки хранятся в
дважды связанном списке в порядке убывания их размеров.
Злоумышленник предоставляет адрес блока памяти и организует первые 4 байта этого
блока так, чтобы они содержали выполняемый код (т.е. команду безусловного перехода
к шеллкоду ) . Это осуществляется путем записи требуемых ком анд в последние 4 байта
предыдущего блока памяти. ( Вспомните, что, как показано на рис. 4.3, последние 4 бай
та данных предыдущего блока (если он выделен ) перекрываются с текущим блоком.)
4.7. Уязвимость, связанная с двойным освобождением памяти 193
После выполнения кода связывания указатели из заголовка списка указывают на ос
вобожденный блок, а указатели блока - на заголовок списка. Это ожидаемое поведение,
поскольку теперь двойной связанный список содержит свободный блок памяти.
Однако, если блок памяти, на который указывает Р, освобождается второй раз, струк
тура данных повреждается. Как показано на рис. 4. 1 3, указатели списка все еще указывают
на бпок, а вот указатели блока теперь указывают на сам блок.
Основной Указатель на первый бло в сnиске
список -> Указатель на последний блок в спис е
Размер предыдущего блока {если он свободен)
Раз ер блока в байтах р
Указатель на следую й бло в списке
Указатель на предыдущий блок в списке
Неиспользуе ая память ( ожет быть О байт)
Размер бло а
Кеш-
список -> Указатель на еш роаанный блок
Рис. 4. 1 3. Поврежден ные структуры дан ных после по8торного 8ызо8а free ()
Если пользователь запрашивает память того же размера, которую имеет указывающий
на самого себя блок, распределитель памяти попытается выполнить запрос, выделяя блок
из этого списка. Поскольку указатель списка указывает на блок, этот блок будет найден и
возвращен пользователю . Однако вызов макроса unl ink ( ) для удаления блока из списка
оставляет указатели не измененными. Вместо того чтобы удалить блок из списка, структу
ры данных останутся в точности такими же, как и до выполнения запроса. В результате,
если будут делаться дополнительные запросы на выделение блока того же размера, поль
зователь будет вновь и вновь получать один и тот же блок памяти. После такого повреж
дения структур данных функцию mal loc ( ) можно использовать для выполнения произ
вольного кода.
В примере 4 . 1 6 приведен упрощенный код, использующий уязвимость, связанную с
двойным освобождением памяти. Целью этого эксплойта является блок, выделенный в
строке 1 2. Однако, прежде чем эксплойт сможет увенчаться успехом, память должна быть
использована специальным образом, ведущим к уязвимости. Злоумышленнику необходи
мо убедиться, что первый блок при освобождении не будет объединен с другими свобод
ными блоками.
1194 Управление динамической памятью
Пример 4. 1 6. Код эксплойта, использующего двойное освобожден ие памяти
0 1 s t a t i c char * GOT_LOCATI ON = ( cha r * ) Ох 0 8 0 4 с 9 8 с ;
02 stati c char she l l code [ ] =
0 3 " \ xeb \ x 0 c j ump 1 2 chars_" / * j ump * /
04 " \х90\х90\х90\х90\х90\х90\х90\х90" ;
0 5 int main (void) {
0 6 int s i ze = si zeof ( shell code ) ;
0 7 char * shell c ode l o ca t i on ;
08 char * firs t , *second , * third, * fourth ;
0 9 char * f i f th , * s i xth , * s eventh ;
1 0 shell code_location = ma l l o c ( s i ze ) ;
1 1 s t rcpy ( s he l l code_loca t i on , s he l l code ) ;
1 2 first = mal l oc ( 2 5 6 ) ;
1 3 second = ma l l oc ( 2 5 6 ) ;
1 4 third = mal l oc ( 2 5 6 ) ;
1 5 fourth = ma l l oc ( 2 5 6 ) ;
1 6 free ( f i r s t ) ;
1 7 free ( third) ;
1 8 fi fth = mal loc ( 1 2 8 ) ;
1 9 free ( fi rs t ) ; / / Повторное освобождение
2 0 s i xth = mal l oc ( 2 5 6 ) ;
2 1 * ( ( char * * ) ( s ixth+O ) ) = GOT LOCAT I ON- 1 2 ;
22 * ( ( char * * ) ( s i xth+ 4 ) ) = shell code_lo cati on ;
2 3 seventh = ma l l oc ( 2 5 6 ) ;
2 4 s t rcpy ( f i fth , " s ome thing " ) ;
2 5 re turn О ;
26
Когда блок, на который указывает f irst, освобождается впервые ( в строке 1 6), он по
мещается не в основной, а в кеш-список. Освобождение третьего блока ( thi rd) перено
сит первый блок в основной список. Выделение второго и четвертого ( second и fourth)
блоков предохраняет третий блок от объединения при освобождении. Выделение пятого
блока (указатель f i fth) в строке 1 8 приводит к отделению памяти от третьего блока и,
как побочный эффект, к гарантированному перемещению первого блока в основной спи
сок. Теперь память сконфигурирована таким образом , что повторное освобождение пер
вого блока в строке 19 приводит к уязвимости. Когда в строке 20 выделяется шестой блок,
malloc ( ) возвращает указатель на тот же блок, на который указывает fi r st. Адрес фун
кции s t rcpy ( ) в GOT (минус 1 2) и местоположение шеллкода копируются в эту память
(строки 2 1-22); затем тот же блок памяти выделяется еще раз как седьмой блок в строке
23. В этот раз при выделении блока памяти макрос unl i n k ( ) копирует адрес шеллкода
в адрес функции s t rcpy ( ) в глобальной таблице смещений (и переписывает несколько
байтов около начала шеллкода ). Когда после этого в строке 24 вызывается s t rcpy ( ) , уп
равление передается шеллкоду.
Эти уязвимости трудно использовать из-за строгих требований к конфигурации па
мяти, а также потому, что детали эксплойта варьируются от одной реализации кучи к
другой. Хотя пример 4. 1 6 сочетает в себе элементы уязвимого кода и кода эксплойта и
хотя адреса жестко кодированы в коде, существуют реальные примеры уязвимого кода,
который был успешно взломан. Н апример, использованию описанных уязвимостей под
вержены серверы, которые остаются резидентными в памяти и могут управляться после
дующими вызовами.
4.7. Уязвимость, связанная с двойным освобождением памяти 195
В настоящее время большинство современных диспетчеров кучи реализует безопасное
удаление блоков из списков (что косвенно решает проблему уязвимости из-за двойного
освобождения) путем добавления проверок инвариантов дважды связанных списков. Про
верка этих инвариантов перед удалением из списка дает возможность раннего выявления
повреждения структур данных [ 1 48 ] . Тем не менее при программировании следует в лю
бом случае избегать повторного освобождения памяти.
Запись в освобожденную память
Еще одним распространенным дефектом безопасности является запись в память, кото
рая уже была освобождена. В примере 4. 1 7 в программе, практически идентичной програм
ме из примера 4. 1 6, показано, как запись в освобожденную память может вести к уязви
мости. Однако здесь вместо повторного освобождения блока памяти программа выполняет
запись в первый блок в строках 1 8 и 1 9 после того, как этот блок был освобожден в строке
1 5. Все происходит практически так же, как и в эксплойте для двойного освобождения.
Вызов rnal l oc ( ) в строке 20 подменяет адрес s t rcpy ( ) адресом шеллкода, так что вызов
s trcpy ( ) в строке 2 1 вызывает шеллкод.
Пример 4.1 7. Эксплойт для записи в освобожденную память
0 1 s ta t i c char * GOT_LOCAT ION = ( cha r * ) О х 0 8 0 4 с 9 8 с ;
02 s tatic char s he l lcode [ ] =
0 3 " \ xeb \ x 0 cj umpl 2 ch a r s_" / * j ump * /
04 "\х90\х90\х90\х90\х90\х90\х90\х90" ;
0 5 i nt main ( vo i d ) {
0 6 i n t s i ze = s i zeof ( s he l lcode ) ;
0 7 char * shel l code l ocat ion ;
0 8 char *firs t , * second, * third, * fourth , * fi fth , *s ixth ;
0 9 she l l code_locat i on = mal l oc { s i ze ) ;
1 0 s trcpy ( shel l code_l oca t i on , s he l l code ) ;
1 1 first = mal loc ( 2 5 6 ) ;
12 second = ma l lo c ( 2 5 6 ) ;
1 3 thi rd = malloc ( 2 5 6 ) ;
1 4 fourth = mal loc ( 2 5 6 ) ;
15 free ( first ) ;
1 6 free ( third) ;
1 7 fi fth = ma l l oc ( l 2 8 ) ; / / На стройка начальных условий
1 8 * ( ( char * * ) ( fi r s t +O ) ) GOT_LOCAT ION - 1 2 ;
1 9 * ( ( char * * ) ( fi rs t+ 4 ) ) = shel l code l o ca t i on ;
2 0 s ix th = ma l l oc ( 2 5 6 ) ;
2 1 s t rcpy ( f i fth , " s ome th i ng " ) ;
2 2 return О ;
23
RtlHeap
Уязвимостям, связанным с кучей , подвержены не только приложения, разработанные с
использованием dlmalloc. Точно так же уязвимости могут быть и в приложениях, разрабо
танных с помощью Microsoft Rtl Heap, при неправильном использовании API управления
памятью.
На рис. 4. 14 показаны пять наборов API управления памятью в Win32. Каждый из них
разработан для использования независимо от других.