Разбить стек, пример 3 в стиле Aleph One

Я воспроизвел пример 3 из раздела Разрушение стека для развлечения и получения прибыли в Linux x86_64. Однако у меня возникли проблемы с пониманием того, какое правильное количество байтов должно быть увеличено до адреса возврата, чтобы пропустить инструкцию:

0x0000000000400595 <+35>:   movl   $0x1,-0x4(%rbp)

именно здесь, я думаю, находится инструкция x = 1. Я написал следующее:

#include <stdio.h>

void fn(int a, int b, int c) {
  char buf1[5];
  char buf2[10];
  int *ret;

  ret = buf1 + 24;
  (*ret) += 7;
}

int main() {
  int x;

  x = 0;
  fn(1, 2, 3);
  x = 1;
  printf("%d\n", x);
}

и разобрал его в gdb. Я отключил рандомизацию адресов и скомпилировал программу с опцией -fno-stack-protector.

Вопрос 1

Из вывода дизассемблера ниже видно, что я хочу пропустить инструкцию по адресу 0x0000000000400595: и адрес возврата из callq <fn>, и адрес инструкции movl. Следовательно, если адрес возврата 0x0000000000400595, а следующая инструкция 0x000000000040059c, мне нужно добавить 7 байт к адресу возврата?

0x0000000000400572 <+0>:    push   %rbp
0x0000000000400573 <+1>:    mov    %rsp,%rbp
0x0000000000400576 <+4>:    sub    $0x10,%rsp
0x000000000040057a <+8>:    movl   $0x0,-0x4(%rbp)
0x0000000000400581 <+15>:   mov    $0x3,%edx
0x0000000000400586 <+20>:   mov    $0x2,%esi
0x000000000040058b <+25>:   mov    $0x1,%edi
0x0000000000400590 <+30>:   callq  0x40052d <fn>
0x0000000000400595 <+35>:   movl   $0x1,-0x4(%rbp)
0x000000000040059c <+42>:   mov    -0x4(%rbp),%eax
0x000000000040059f <+45>:   mov    %eax,%esi
0x00000000004005a1 <+47>:   mov    $0x40064a,%edi
0x00000000004005a6 <+52>:   mov    $0x0,%eax
0x00000000004005ab <+57>:   callq  0x400410 <printf@plt>
0x00000000004005b0 <+62>:   leaveq 
0x00000000004005b1 <+63>:   retq 

вопрос 2

Я заметил, что могу добавить 5 байтов к адресу возврата вместо 7 и добиться того же результата. Когда я это делаю, не перепрыгиваю ли я в середину инструкции 0x0000000000400595 <+35>: movl $0x1,-0x4(%rbp)? В таком случае, почему это не приводит к сбою программы, например, когда я добавляю 6 байтов к адресу возврата вместо 5 байтов или 7 байтов.

Вопрос 3

Непосредственно перед buffer1[] в стеке находится SFP, а перед ним адрес возврата. То есть 4 байта проходят через конец buffer1[]. Но помните, что buffer1[] на самом деле состоит из 2 слов, поэтому его длина составляет 8 байт. Таким образом, адрес возврата находится в 12 байтах от начала buffer1[].

В примере Алеф 1 вычисляет смещение адреса возврата как 12 байт от начала буфера1[]. Так как я на x86_64, а не на x86_32, мне нужно пересчитать смещение на обратный адрес. На x86_64 буфер1[] по-прежнему состоит из 2 слов, что составляет 16 байтов; и SFP, и адрес возврата составляют 8 байтов каждый (поскольку мы используем 64-разрядную систему), и, следовательно, адрес возврата находится по адресу: buf1 + (8 * 2) + 8, что эквивалентно buf1 + 24?


person Community    schedule 02.06.2015    source источник
comment
Относительно выравнивание памяти сегодня и 20 лет назад может помочь понять, что изменилось с тех пор.   -  person Shafik Yaghmour    schedule 02.06.2015


Ответы (1)


Первое и очень важное замечание: все числа и смещения очень зависят от компилятора. Разные компиляторы и даже один и тот же компилятор с разными настройками могут создавать совершенно разные сборки. Например, многие компиляторы могут (и будут) удалять buf2, потому что он не используется. Они также могут удалить x = 0, так как его эффект не используется и позже перезаписывается. Они также могут удалить x = 1 и заменить все вхождения x на константу 1 и т. д. и т. д.

Тем не менее, вам абсолютно необходимо сделать числа для конкретной сборки, которую вы получаете для своего конкретного компилятора и его настроек.

Вопрос 1 Поскольку вы предоставили сборку для main(), я могу подтвердить, что вам нужно добавить 7 байтов к адресу возврата, который обычно равен 0x0000000000400595, чтобы пропустить x=1 и перейти к 0x000000000040059c, который загружает x в зарегистрируйтесь для последующего использования. 0x000000000040059c - 0x0000000000400595 = 7.

Вопрос 2 Добавление всего 5 байтов вместо 7 действительно приведет к переходу в середину инструкции. Однако этот 2-байтовый хвост инструкции оказывается (по чистой случайности) другим допустимым кодом инструкции. Вот почему он не падает.

Вопрос 3 Это снова очень зависит от компилятора и настроек. Там вообще все может случиться. Поскольку вы не предоставили разборку, я могу только строить догадки. Предположение было бы следующим: buf и buf2 округляются до границы следующего блока стека (8 байт на x64). buf становится 8 байтами, а buf2 становится 16 байтами. Указатели кадров не сохраняются в стеке на x64, поэтому нет «SFP». Всего 24 байта.

person Codeguard    schedule 08.06.2015