Переполнение буфера в стеке. Часть 1

kas1e

Xakep, номер #050, стр. 050-062-3


Стек и инструкции

Слово stack было заимствовано из скандинавских языков (помогли викинги, покорившие Англию в 900-ых годах). Слово означало "стог, куча". Кстати, что интересно, и такие слова как root, window - тоже от них. Стоит также заметить, что напрашивающиеся слово стек (в смысле стекаться), это не то же самое, что "куча" – но, в общем, близко. О том, что под словом "куча" теперь чаще всего подразумевают понятие "heap" – надо иметь в виду, но запариваться этим не стоит. В различных текстах/книгах/документациях можно встретить разные способы визуального представления стека - от стопки листов, до перевернутых стаканов. Лично мне проще всего представлять его таким, какой он есть, т.е. участком памяти, начало которого на потолке и имеет смещение 0xffffffff, и все, что мы будем добавлять к нему, будет добавляться снизу. Вот самое удачное, на мой взгляд, схематическое представление стека:

0xffffffff Начало стека

0xfffffffc Занесли некоторые данные

0xfffffff8 Занесли некоторые данные

0xfffffff4 Занесли некоторые данные

0xfffffff0 Занесли некоторые данные

|

|| Стек растет к меньшему адресу

\/

И вот так стек растет от большего адреса к меньшему. Итак, у нас получается, что стек - это та же обычная память, только организованная задом наперед. Это сделано для облегчения и ускорения работы процессора и инструкций, которые он выполняет. Инструкции - это своего рода команды процессора (то есть всякие pop, push, ret, call, mov, xor и так далее). У каждого семейства процессоров инструкции свои. У x86 они постоянно накапливаются (сначала было немного, потом добавился защищенный режим, появились новые, после этого добавился сопроцессор - еще, потом mmx и т.д. - т.е. как правило, с появлением каждого аппаратного новшества внутри процессора появляются новые инструкции). На самом же деле процессор "видит" лишь биты, которые собраны в байты. И, скажем, инструкция RET для x86 процессора - это C3 (1100 0011). Чтобы стало понятней, я приведу хронологическую цепь развития событий.

Сначала люди сделали простенький процессор, он работал с битами и выполнял некие действия. Чтобы им управлять, люди давали/писали ему эти биты. Т.е. первые программисты писали именно так: 011010101 11101110101 10110110 1101001 1101, что, конечно, с развитием процессоров стало устаревать. Тогда люди придумали шестнадцатеричную систему счисления (так сказать, промежуточное звено понимания между тем, что близко процессору – биты, и тем, что близко человеку - десятеричная система счисления). И стали работать в этих hex'ах, что, разумеется, проще. А процессоры все развивались, людям стало мало и hex'ов. И придумали они компиляторы. Это программы, которые читают человеческие слова (mov, push и т.д.) и переводят их в удобоваримый для процессора вид (биты). Это были первые компиляторы ассемблера. Потом для производительности и упрощения стали появляться языки высокого уровня - c, basic, pascal и прочие. И мы пришли к тому, что имеем сейчас.

Назад на стр. 050-062-2  Содержание  Вперед на стр. 050-062-4