Подтвердить что ты не робот

Разбивание примера стека3 ala Aleph One

Я воспроизвел Пример 3 из Smashing the Stack for Fun and Profit в 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 <[email protected]>
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 байтов начало буфера1 [].

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

4b9b3361

Ответ 1

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

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

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

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

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