Кажется, что в этом коде происходит много ненужных манипуляций с указателем стека, но все, что действительно важно, это то, что переменная buf
имеет значение ebp-4
.Вы можете видеть это из последовательности:
leal 0xfffffffc(%ebp),%eax
pushl %eax
call 80483c4 <strcpy>
0xfffffffc
равно -4, поэтому leal 0xfffffffc(%ebp),%eax
устанавливает eax в адрес ячейки памяти ebp-4
.Это значение затем помещается в стек в качестве первого аргумента strcpy
.Поскольку первый аргумент, переданный strcpy
, равен buf
, мы знаем, что адрес buf
находится в ebp-4
.
Теперь рассмотрим, как вызывается стек, как foo
.
Сначала в строку адреса вставляется инструкция pushl $0x804859c
.
0804859c # string pointer
Затем, когда вызывается функция foo
, адрес инструкции после вызова (08048523
) помещается в стек как адрес возврата.
08048523 # return address
Затем внутри foo ebp сохраняется в стеке.В этот момент это может быть что угодно.
???????? # saved ebp
Тогда ebp устанавливается на esp, поэтому теперь он указывает на место, где был сохранен предыдущий ebp.
Теперь, когда мы знаем, что buf
находится на уровне ebp-4, это означает, что следующий элемент в стеке будет buf
.В стеке выделено намного больше места, чем необходимо для инструкций subl
и addl
, но все, что нас волнует, это то, что buf
находится на ebp-4
.Итак, часть стека, о которой мы заботимся, выглядит следующим образом:
0804859c # string pointer
08048523 # return address
???????? # saved ebp <- ebp points here
???????? # buff[0] <- ebp-4 points here
Итак, что теперь происходит, когда вы копируете "abcdefghi" в buff?Поскольку машина не имеет порядка байтов, эти мечи будут заполняться справа налево.У вас есть 9 символов плюс нулевой терминатор в этой строке, поэтому вы собираетесь перезаписать все четыре байта buff[0]
, все четыре байта сохраненного ebp, а затем два байта адреса возврата.
Итак, ваш стек теперь выглядит следующим образом:
0804859c # string pointer
08040069 # return address
68676665 # saved ebp <- ebp points here
64636261 # buff[0] <- ebp-4 points here
Исходя из этого, должно быть совершенно очевидно, каковы ответы на различные вопросы.
Поскольку стек строится в памяти вниз, buff[1]
и buff[2]
расположены непосредственно над buff[0]
в представлении стека, как я это показал.Таким образом, вы можете видеть, что различные значения баффов просто:
buff[0] = 0x64636261
buff[1] = 0x68676665
buff[2] = 0x08040069
Затем, непосредственно перед инструкцией ret
, у нас есть следующие две инструкции:
movl %ebp,%esp
popl ebp
Первая устанавливает esp втекущее значение ebp, поэтому оно будет указывать на позицию в стеке, где был сохранен предыдущий ebp.Однако, глядя на представление стека, вы можете видеть, что это значение теперь перезаписано с 68676665
.Поэтому, когда вы вставляете ebp, это значение, которое вы получите.
%ebp = 0x68676665
Аналогично, когда функция вернется, она попытается вытолкнуть адрес возврата из стека, но снова вы можете увидетьиз представления стека, что исходный адрес возврата был частично перезаписан.Так что сразу после того, как ret
инструкция eip появится, 08040069
.
$eip = 0x08040069
И это, я думаю, ответит на все ваши вопросы.
Я понимаю, что этот вопрос уже несколько летстарый, но он не был закрыт и нет принятого ответа, поэтому, возможно, это объяснение все еще может быть кому-то полезно.