Суровый байт-одиночка 1dt.w0lf Хакер, номер #083, стр. 083-064-2 [работаем в gdb] Процесс перезаписи байта можно наблюдать в отладчике: $ gdb one_byte_over # ставим бряк на вызов функции strncat (gdb) b strncat (gdb) r CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Breakpoint 2, 0x280d2845 in strncat () from /lib/libc.so.5 # останавливаемся на вызове strncat (gdb) x/4 0xbfbfeb00 0xbfbfeb00: 0x00000282 0x00000000 0xbfbfeba8 0x080485f8 Здесь 0x080485f8 - это адрес возврата функции, 0x00000000 - место, выделенное для хранения строки, 0xbfbfeba8 - EBP, сохраненный в стеке. (gdb) n 0x0804861f in cat4 () (gdb) x/4 0xbfbfeb00 0xbfbfeb00: 0x00000282 0x41414141 0xbfbfeb00 0x080485f8 0x080485f8 - адрес возврата из функции (пока он нам неинтересен), 0x41414141 - наша строка, 0xbfbfeb00 - а здесь отчетливо виден символ NULL, который перезаписал часть адреса, сохраненного в стеке. Итак, один байт в сохраненном указателе кадра стека был благополучно нами перезаписан на 00. Теперь самое время разобраться, что происходит при выходе из функции. При выходе из функции происходит выполнение двух инструкций: 0x08048622 <cat4+34>: leave 0x08048623 <cat4+35>: ret [инструкция leave] Инструкция leave эквивалентна последовательности mov %ebp,%esp pop ebp Leave копирует регистр ebp в esp, после чего в регистр ebp значение извлекается из стека. Таким образом, стек после вызова leave должен возвращаться в такое же состояние, как и до пролога функции. Так как в регистр EBP значение извлекается из вершины стека, то есть восстанавливается из сохраненного указателя кадра стека, который мы уже изменили путем перезаписи одного байта, то и значение ebp будет изменено по отношению к ebp, который был при входе в функцию. Далее инструкция ret помещает значение с вершины стека в регистр eip и продолжает выполнение программы. В отладчике можно увидеть, как при выходе из функции cat4 восстанавливается из стека значение ebp: (gdb) n 0x080485f8 in main () (gdb) i r ebp 0xbfbfeb00 0xbfbfeb00 Итак, как видно, измененный нами адрес благополучно попадает в регистр ebp. Таким образом, функция main с нашей легкой руки будет работать с измененным указателем. Далее наша многострадальная функция main также завершает свою работу и действует по такому же принципу, то есть leave и ret. Но так как при возврате в функцию main мы уже изменили значение ebp, то и работа со стеком в этой функции будет идти в соответствии с измененным значением и данные из стека будут читаться не по тому адресу, по которому предполагалось. Смотрим, что находится по адресу, в котором мы изменили регистр ebp: (gdb) x/4 0xbfbfeb00 0xbfbfeb00: 0x00000282 0x41414141 0xbfbfeb00 0x080485f8 Значит, при выходе функция прочитает 0x00000282 в ebp при выполнении leave. После чего прочитает 0x41414141 в eip при выполнении ret и потом попытается прыгнуть на 0х41414141. Проверим это: |