Бинарные уязвимости/ROP
Возвратно-ориентированное программирование (return-oriented programming, ROP) — техника эксплуатации перезаписи стека вызовов.
Описание техники
В ситуации, когда присутствует NX (защита от выполнения данных как кода), одним из выходом является переииспользование существующих фрагментов кода, чтобы вызвать какие-то библиотечные функции или системные вызовы.
Но сперва нужно помнить, что, в соотвествии с System V AMD64 ABI, для передачи аргументов функций используются регистры. Значит, аргументы нельзя просто так положить на стек, их нужно как-то поместить в регистры.
Мы рассматриваем, в первую очердь, подобные фрагменты кода (x86_64):
pop rdi ret pop rsi pop r15 ret
Например, если в стеке на место адреса возврата положить последовательно адрес гаджета pop rdi; ret
, число 4, и адрес функции f
, то это будет иметь такой же эффект, как будто функция f
была вызвана с аргументом 4.
Из гаджетов можно составлять длинные цепочки с последовательным заданием нескольких регистров, вызовами разных функций, и т.д.
Где искать гаджеты
Гаджеты pop rdi; ret
с самыми интересными нам регистрами не встречаются естественным образом в типичной программе. Однако они встречаются, если рассматривать не только начала легитимных инструкций, но и середины. Для автоматического поиска гаджетов можно использовать, например, ROPgadget или ropper.
Есть известный гаджет "ret2csu", который не находится автоматически, но присутствует почти всегда, и позволяет вызвать функцию с тремя аргументами.
Средства защиты
Во-первых, stack canary. Функция перед выходом перепроверяет, что секретное значение перед адресом возврата не было испорчено. Чтобы это обойти, нужно или как-то писать сразу за "канарейку", или предварительно узнать значение "канарейки", чтобы при перезаписи её оставить прежним.
Во-вторых, ASLR. Сама программа, библиотеки, и стек будут находиться на случайных смещениях в памяти, изменяющихся при каждом запуске. Если не обойти это, узнав каким-нибудь образом фактические адреса загрузки, составить ROP-цепочку не получится, поскольку туда нужно записывать абсолютные адреса функций/строк/etc..