Возвратно-ориентированное программирование

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Возвра́тно-ориенти́рованное программи́рование (англ. Return oriented programming) — метод эксплуатации уязвимостей в программном обеспечении, который позволяет атакующему выполнить необходимый код при наличии защитных технологий, например запрета исполнения определённых страниц памяти.[1] Атакующий получает контроль над стеком вызовов и выполняет тщательно подобранные последовательности инструкций, называемые «гаджетами».[2] «Гаджет» обычно заканчивается инструкцией возврата и располагается в существующем коде программы или разделяемой библиотеки. Связанные в цепочку инструкциями возврата, эти гаджеты позволяют атакующему выполнить произвольные операции, в том числе на машинах, которые предотвращают более простые атаки.

История[править | править вики-текст]

Атака на переполнение буфера[править | править вики-текст]

Возвратно-ориентированное программирование — это усовершенствованная версия атаки на переполнение буфера. При данной атаке атакующий использует ошибку в программе, когда функция не проверяет (или неправильно проверяет) границы при записи в буфер данных, полученных от пользователя. Если пользователь передаёт больше данных, чем размер буфера, то лишние данные попадают в область памяти, предназначенную для других локальных переменных, а также могут перезаписать адрес возврата. Если адрес возврата оказывается перезаписан, то при возврате из функции управление будет передано по вновь записанному адресу.

В самой простой версии атаки на переполнение буфера, атакующий помещает код(«полезную нагрузку») в стек, а затем перезаписывает адрес возврата адресом только что записанных им инструкций. До конца 90-х годов большинство операционных систем не предоставляло никакой защиты от этих атак. В системах Windows не было защиты от атак на переполнение буфера до 2004 года.[3] В конце концов, операционные системы стали бороться с эксплуатацией уязвимостей переполнения буфера, помечая определённые страницы памяти как неисполняемые (технология, называемая «Предотвращение выполнения данных»). При включённом предотвращении выполнения данных, машина откажется выполнять код на страницах памяти, помеченных «только для данных», включая страницы, содержащие стек. Это не позволяет поместить полезную нагрузку в стек, а затем перейти на неё, перезаписав адрес возврата. Позже для усиления защиты появилась аппаратная поддержка предотвращение выполнения данных.

Атака возврата в библиотеку[править | править вики-текст]

Предотвращение выполнения данных не позволяет произвести атаку методом, описанным выше. Атакующий ограничен кодом, уже имеющимся в атакуемой программе и разделяемых библиотеках. Однако разделяемые библиотеки, в частности libc, часто содержат функции для совершения системных вызовов и другие полезные для атакующего функции, что позволяет использовать эти функции при атаке.

При атаке возврата в библиотеку также эксплуатируется переполнение буфера. Адрес возврата перезаписывается точкой входа нужной библиотечной функции. Также перезаписываются ячейки над адресом возврата, чтобы передать функции параметры или связать в цепочку несколько вызовов. Эта техника была впервые представлена Александром Песляком (известным как Solar Designer) в 1997 году,[4] а затем была расширена, позволяя произвести неограниченную цепочку вызовов функций.[5]

Заимствование кусков кода[править | править вики-текст]

С распространением 64-разрядного оборудования и операционных систем стало сложнее производить атаку возврата в библиотеку: в используемых в 64-разрядных системах соглашениях о вызове первые параметры передаются функции не в стеке, а в регистрах. Это усложняет подготовку параметров для вызова в процессе атаки. Кроме того, разработчики разделяемых библиотек стали убирать из библиотек или ограничивать «опасные» функции, такие как обёртки системных вызовов.

Следующим витком развития атаки стало использование частей библиотечных функций вместо функций целиком.[6] В этой технике ищутся части функций, которые передают данные из стека в регистры. Тщательный подбор этих частей позволяет подготовить нужные параметры в регистрах для вызова функции по новому соглашению. Дальше атака производится так же, как атака возврата в библиотеку.

Атака методом возвратно-ориентированного программирования[править | править вики-текст]

Возвратно-ориентированное программирование расширяет подход заимствования кусков кода, предоставляя атакующему полную по Тьюрингу функциональность, включая циклы и ветвления.[7] Другими словами, возвратно-ориентированное программирование предоставляет атакующему возможность выполнить любую операцию. Ховав Шахам опубликовал описание метода в 2007 году[8] и продемонстрировал его на программе, использующей стандартную библиотеку языка Си и содержащую уязвимость переполнения буфера. Возвратно-ориентированное программирование превосходит другие описанные выше типы атак и по выразительной мощности, и по устойчивости к защитным мерам. Ни один из вышеописанных методов противодействия атакам, включая удаление опасных функций из разделяемых библиотек, не является эффективным против возвратно-ориентированного программирования.

В отличие от атаки возврата в библиотеку, в которой используются функции целиком, в возвратно-ориентированном программировании используются небольшие последовательности инструкций, заканчивающиеся инструкцией возврата, так называемые «гаджеты». Гаджетами являются, например, окончания существующих функций. Однако на некоторых платформах, в частности x86, гаджеты могут возникать «между строк», то есть при декодировании с середины существующей инструкции. Например, следующая последовательность инструкций:[8]

test edi, 7        ; f7 c7 07 00 00 00
setnz byte[ebp-61] ; 0f 95 45 c3

при начале декодирования на один байт позже, даёт

mov dword[edi], 0f000000h ; c7 07 00 00 00 0f
xchg ebp, eax             ; 95
inc ebp                   ; 45
ret                       ; c3

Также гаджеты могут находиться в данных, по тем или иным причинам располагающихся в секции кода. Это связано с тем, что набор инструкций архитектуры x86 достаточно плотен, то есть велика вероятность того, что произвольный поток байтов будет интерпретирован как поток действительных инструкций. В архитектуре MIPS, с другой стороны, все инструкции имеют длину 4 байта, а также адрес любой инструкции в коде должен быть кратен 4 байтам. Поэтому там нельзя получить новую последовательность «чтением между строк».

При атаке эксплуатируется уязвимость переполнения буфера. Адрес возврата из текущей функции перезаписывается адресом первого гаджета. На последующие позиции в стеке записываются адреса следующих гаджетов и данные, используемые гаджетами.

Примеры[править | править вики-текст]

Расширения[править | править вики-текст]

В своём изначальном варианте для платформы x86, гаджеты представляют собой цепочки последовательно расположенных инструкций без переходов, завершающиеся инструкцией ближнего возврата. В расширенных вариантах атаки инструкции цепочки могут не обязательно быть последовательно расположены, а связаны инструкциями прямого перехода. Также роль завершающей инструкции может выполнять другая инструкция возврата (в x86 есть ещё инструкция дальнего возврата, инструкции ближнего и дальнего возврата с очисткой стека), инструкция косвенного перехода или даже косвенного вызова. Это усложняет борьбу с данным методом атаки.

Автоматическая генерация[править | править вики-текст]

Существуют инструменты для автоматического нахождения гаджетов и конструирования атаки. Примером такого инструмента может служить ROPgadget.[9]

Защита от возвратно-ориентированного программирования[править | править вики-текст]

Существует несколько методов защиты от возвратно-ориентированного программирования.[10] Большинство полагаются на расположение кода программы и библиотек по относительно произвольному адресу, так что атакующий не может точно предсказать расположение инструкций, могущих быть полезными в гаджетах, а значит не может построить цепочку гаджетов для атаки. Одна из реализаций этого метода, ASLR, загружает разделяемые библиотеки по адресу, различному при каждом запуске программы. Однако, хотя эта технология широко применяется в современных операционных системах, она является уязвимой к атакам утечки информации и другим атакам, позволяющим определить положение известной библиотечной функции. Если атакующий может определить расположение одной функции, он может определить расположение всех инструкций библиотеки и произвести атаку методом возвратно-ориентированного программирования.

Можно переставлять не только библиотеки целиком, но и отдельные инструкции программ и библиотек.[11] Однако это требует обширной поддержки во время исполнения, например, динамической трансляции, чтобы вернуть переставленные инструкции в правильный порядок для их исполнения. Этот метод усложняет нахождение и использование гаджетов, однако имеет большие накладные расходы.

Подход, применяемый в kBouncer[12], заключается в проверке того, что инструкция возврата передаёт управление на инструкцию, непосредственно следующую за инструкцией вызова. Это сильно сокращает множество возможных гаджетов, но также вызывает значительное снижение производительности.[12] Кроме того в расширенном варианте возвратно-ориентированного программирования гаджеты можно связывать не только инструкцией возврата, но и инструкцией непрямого перехода или вызова. Против такой расширенной атаки kBouncer будет неэффективен.

Примечания[править | править вики-текст]

  1. Return-Oriented Programming: Exploits Without Code Injection. Проверено 12 августа 2009.
  2. Erik Buchanan, Ryan Roemer, Hovav Shacham and Stefan Savage; When Good Instructions Go Bad: Generalizing Return-Oriented Programming to RISC, in Proceedings of CCS 2008, ACM Press, October 2008.
  3. Microsoft Windows XP SP2 Data Execution Prevention
  4. Solar Designer, Return-into-lib(c) exploits, Bugtraq
  5. Nergal, Phrack 58 Article 4, return-into-lib(c) exploits
  6. Sebastian Krahmer, x86-64 buffer overflow exploits and the borrowed code chunks exploitation technique, September 28, 2005
  7. Martín Abadi, Mihai Budiu, Úlfar Erlingsson, and Jay Ligatti. Control-Flow Integrity: Principles, Implementations, and Applications. In Catherine Meadows and Ari Juels, editors, Proceedings of CCS 2005. ACM. November 2005
  8. 1 2 Hovav Shacham. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86). In Proceedings of the 14th ACM conference on Computer and communications security (CCS '07). ACM 2007
  9. Jonathan Salwan and Allan Wirth, ROPgadget — Gadgets finder and auto-roper
  10. Richard Skowyra, Kelly Casteel, Hamed Okhravi and William Streilein; Systematic Analysis of Defenses Against Return-Oriented Programming, In Proceedings of RAID 2013, Lecture Notes in Computer Science (LNCS), Vol. 8145, pp. 82-102, 2013.
  11. Jason Hiser, Anh Nguyen-Tuong, Michele Co, Matthew Hall, JackW. Davidson, ILR: Where’d My Gadgets Go? Proceedings of the IEEE Symposium on Security and Privacy, May 2012, San Francisco, CA
  12. 1 2 Vasilis Pappas. kBouncer: Efficient and Transparent ROP Mitigation. April 2012.