Суровый байт-одиночка 1dt.w0lf Хакер, номер #083, стр. 083-064-1 Однобайтовое переполнение буфера для взлома программ и сетевых демонов Если ты следишь за багтрак-лентами, то наверняка сталкивался с упоминаниями об «однобайтовом переполнении буфера» в различных сетевых сервисах. На первый взгляд, может показаться, что это что-то несущественное: ну подумаешь, удастся перезаписать один байт памяти, ерунда какая. Однако интуитивное предположение, что ничего ценного из этого извлечь невозможно, - неверное. Сейчас я расскажу тебе, каким образом возможно наиболее эффективно использовать эту лазейку. [начало] Прежде всего следует разобраться с тем, откуда возникает такая проблема. В некоторых строковых функциях завершающий символ всегда размещается в конце строки и, при недостаточном знании программистом особенностей таких функций, может привести к размещению данного символа за концом буфера, выделенного для хранения строки. В этом случае происходит ситуация, при которой перезаписывается один байт памяти. Эта, на первый взгляд, незначительная проблема может привести к полному захвату контроля над исполняемой программой. Чтобы не быть голословным, рассмотрю конкретный пример. Предположим, что какой-то программист написал следующий код (только не спрашивай меня, для чего он это сделал :)): Пример уязвимого кода #include <stdio.h> void cat4(char *); int main(int argc, char **argv) { char a[100] = ""; strncpy(a,argv[1],99); cat4(argv[2]); } void cat4(char * str) { char b[4] = ""; strncat(b,str,4); } На первый взгляд, все нормально и никаких ошибок в коде нет. Однако опытный человек сразу заметит: программист забыл о том, что функция strncat всегда добавляет символ NULL в конец, строки и, таким образом, при передаче этой функции строки из 4 символов, реально записано в память будет 5. То есть 4 байта строки + NULL. Этот символ NULL будет записан вне области памяти, выделенной для хранения строки b, и, соответственно, перезапишет находящиеся там данные. Чтобы определить, что именно будет перезаписываться, необходимо разобраться с тем, что происходит при вызове функции. [что происходит?] При вызове cat4 из main выполняется команда ассемблера call, при этом в стек проталкивается адрес возврата, это значение будет адресом команды, следующей за текущим eip. Далее за записью адреса возврата следует то, что называется прологом процедуры. На этом этапе в стек проталкивается текущее значение EBP, оно называется сохраненным указателем кадра и позднее применяется для восстановления исходного состояния EBP при выходе из функции. Далее текущее значение ESP копируется в EBP и устанавливает новый кадр стека. Если дизассемблировать приведенную выше программу, то будет видно, как это происходит: 0x080485f3 <main+95>: call 0x8048600 <cat4>; вызов функции // пролог функции 0x08048600 <cat4+0>: push %ebp; сохранение ebp в стеке 0x08048601 <cat4+1>: mov %esp,%ebp После этого в стеке отводится память для локальных переменных функции, в нашей программе это будет строка b. Так как в функции cat4 не выделяется место для каких-либо еще данных, то выделенное место в памяти для этой строки будет граничить с местом, где находится сохраненный адрес EBP и, соответственно, символ NULL, который вышел за границы памяти выделенной для переменной b будет перезаписывать один байт в этом адресе. |