Угрозы безопасности при использовании bar() и bar(void)

Для HW в классе безопасности я хочу продемонстрировать атаку, основанную на разнице между bar() (сообщает компилятору, что количество и тип аргументов неизвестны) и bar(void) (сообщает компилятору, что функция не принимает аргументы).

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

Мой код:

#include <stdio.h>

void bar(){
    int a = 4;
    printf("%d", a);
}


int main() {
    bar("hhhhhhhhhhhhhhhhhhhhhhhh");

    return 0;
}

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

Большинство реализаций просто игнорируют дополнительные аргументы по историческим причинам.

Barmar 06.06.2024 23:28

В вашем посте нет вопросов. Stack Overflow предназначен для того, чтобы задавать конкретные вопросы.

Eric Postpischil 06.06.2024 23:29

Я не уверен, как программа C, которая «атакует» другую функцию, работающую в том же пространстве памяти, делает что-то ценное, учитывая, что (как программа C) вы имеете полный доступ к той же памяти, что и любая вызываемая вами функция ( а функции OS/system-lib — это просто оболочки над системными вызовами, которые невосприимчивы к такого рода «угрозам»).

Dai 06.06.2024 23:40

Хе, я всегда думал, что декларация определения — это всегда прототип, но, видимо, нет.

Kerrek SB 06.06.2024 23:44

Возможно, у какого-нибудь K&R C старой школы были такие проблемы... но в наши дни будет очень сложно найти компилятор, который не ставит барьеров в отношении этих древних проблем. Вам больше повезет при использовании функции с проблемой переполнения буфера или функции с переменным аргументом, в которой отсутствует ожидаемый переменный параметр. например printf("%s%s", p);

selbie 07.06.2024 01:38

По крайней мере, начиная с C89, определение функции с пустым списком аргументов эквивалентно списку аргументов void; в противном случае количество и типы аргументов не указаны (6.7.6.3/14).

John Bode 08.06.2024 00:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
6
127
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Если вызываемая подпрограмма не имеет такого параметра, она не получит значение из назначенного места. Тогда ничего больше не произойдет в отношении спора. Вызывающая подпрограмма может поместить значение в указанное место, но вызываемая подпрограмма не будет его использовать. Ничего не будет перегружено. Аргумент просто игнорируется и не имеет никакого эффекта.

В этой ситуации атака невозможна.

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

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

0___________ 07.06.2024 00:38

Аргументы передаются куда-то (регистр/стек/куча) в зависимости от ABI, но если вызываемый объект для примеров никогда не извлекает стек, то к аргументам не будет доступа. Переменная a выделяется при входе в функцию, поэтому после того, как аргументы были записаны в вызывающей стороне, перед вызовом, ее нельзя перезаписать.

Я не эксперт, но, возможно, вы сможете перезаписать обратный адрес, который используется вызываемым абонентом, с помощью вашего подхода. Но я бы не стал считать это проблемой безопасности, потому что как вызывающий абонент вы несете ответственность за написание обратного адреса, поэтому вы все равно можете туда что-то написать.

Нет, вы ничего не перезапишете.

0___________ 07.06.2024 00:46

@0___________ Но разве адрес возврата не записывается перед аргументами, так что вызываемый должен знать аргументы, чтобы правильно получить доступ к адресу возврата?

Caulder 07.06.2024 10:09

@0___________ Разве тогда нельзя было бы позволить вызывающему абоненту указать другой (потенциально неправильный) обратный адрес с помощью подхода OP?

Caulder 07.06.2024 10:15

нет, это невозможно

0___________ 07.06.2024 10:52

@0___________ Извините, но почему бы и нет?

Caulder 09.06.2024 22:43
Ответ принят как подходящий

Как ни странно, при проверке стека с помощью GDB (используя 'info Frame'), это не говорит о том, что строка была передана.

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

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

bar((struct {char a[30];}){"hhhhhhhhhhhhhhhhhhhhhhhh"});

Он будет помещен в стек (с отключенной оптимизацией).

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        movabs  rax, 7523377975159973992
        mov     rdx, rax
        mov     QWORD PTR [rbp-32], rax
        mov     QWORD PTR [rbp-24], rdx
        movabs  rax, 7523377975159973992
        mov     edx, 26728
        mov     QWORD PTR [rbp-18], rax
        mov     QWORD PTR [rbp-10], rdx
        sub     rsp, 32
        mov     rcx, rsp
        mov     rax, QWORD PTR [rbp-32]
        mov     rdx, QWORD PTR [rbp-24]
        mov     QWORD PTR [rcx], rax
        mov     QWORD PTR [rcx+8], rdx
        mov     rax, QWORD PTR [rbp-18]
        mov     rdx, QWORD PTR [rbp-10]
        mov     QWORD PTR [rcx+14], rax
        mov     QWORD PTR [rcx+22], rdx
        mov     eax, 0
        call    bar
        add     rsp, 32
        mov     eax, 0
        leave
        ret

Но вызываемая функция создаст свой собственный фрейм стека - отдельный от "hhhhhhhhhhhhhhhhhhhhhhhh" функции.

bar:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 4
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        nop
        leave
        ret

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

Давайте рассуждать об этом логически. Если у вас есть функция foo(), которая принимает любое количество параметров, есть два случая:

  1. Реализация функции производилась с учетом этого факта и сознательного обращения к получаемым параметрам.
  2. Реализация функции производилась без знания этого факта и, следовательно, без ссылки на возможно полученные параметры.

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

Таким образом, внутри функции нет реальной опасности.

За пределами функции существует небольшая «опасность», поскольку, таким образом, если вы скомпилируете два разных кода с определением foo() в обоих, а затем проведете их реверс-инжиниринг и соедините их вместе, вы можете получить противоречивые определения. Но если это произойдет, такую ​​проблему можно исправить, и хакерам не стоит утруждать себя созданием ошибки, которую можно легко исправить. Никакой кражи данных или даже повреждения данных не происходит.

Тем не менее, реальная опасность такой функции, как foo(), заключается в том, что она устарела, и некоторые компиляторы с какого-то момента могут отказаться ее разрешать.

РЕДАКТИРОВАТЬ

@Caulder сделал полезное замечание в разделе комментариев, заявив:

Я думаю, что реальная опасность - это вариативная функция, ожидающая аргументов, которые не заданы, тем самым разрушая остальную память.

Я думаю, что реальная опасность - это вариативная функция, ожидающая аргументов, которые не заданы, тем самым разрушая остальную память.

Caulder 07.06.2024 10:21

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