2014-05-14 3 views
1

Мои главные выглядит следующим образом:Понимание ехЬегп и аннулируются указатели на функции

int main(int argv,char **argc) 
{ 
    extern system,puts; 
    void (*fn)(char*)=(void(*)(char*))&system; 
    char buf[256]; 

    fn=(void(*)(char*))&puts; 
    strcpy(buf,argc[1]); 
    fn(argc[2]); 
    exit(1); 
} 

У меня есть общее представление о том, что означает extern ключевое слово, и как она работает в этом коде. Я смущен объявлением функции fn. Объявлен ли указатель функции или функция? Кроме того, почему &system и &puts в конце определения fn.

+0

Еще один вопрос: & system & & puts - это адреса этих встроенных функций, когда они скомпилированы в программу правильно? Кроме того, в чем преимущество объявления хорошо известной функции extern и создания указателя функции? – skrillac

+3

Нет смысла писать такой код. Вы должны включить соответствующие заголовки, чтобы получить правильные прототипы. Этот код выглядит так, как будто он написан, чтобы продемонстрировать что-то конкретное. Где ты взял это? –

+1

В качестве аргумента аргументы 'main' отменяются:' int' должен быть 'argc' (аргумент count), а' char ** 'должен быть' argv' (vector vector) , –

ответ

3

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

Следующая строка объявляет указатель на функцию и инициализирует ее, указывая на функцию system.

void (*fn)(char*)=(void(*)(char*))&system; 

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

fn=(void(*)(char*))&puts; 

Уязвимость в программе находится в этой строке

strcpy(buf,argc[1]); 

Если strlen из argc[1] больше, чем размер буфера, то это возможно для переполнения буфера, чтобы изменить значение fn, чтобы указать некоторая произвольная функция, которая затем будет вызываться этой линией

fn(argc[2]); 

Сторона примечания: как указал кто-то в коллективе Должны быть переключены имена argc и argv.

2

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

Тем не менее, давайте посмотрим, как это работает:

extern system, puts; 

Это говорит о том, что system и puts определены где-то еще, а компоновщик позаботится дать вам свой адрес. Компилятор не знает, какой у них тип, поэтому gcc по крайней мере предполагает, что это int и выдает предупреждение. Я не могу найти предложение в стандарте, в котором говорится, является ли это четко определенным, определяемым реализацией или неопределенным поведением, но, безусловно, это плохо. Это особенно плохо, потому что эти символы не являются int. Я нашел это, хотя (C11, Приложение J.2 - Неопределенное поведение):

два заявления одного и того же объекта или функции указать типы, которые не совместимы (6.2.7).

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

Итак, давайте посмотрим, в памяти, как это выглядит:

system (as 4-byte int)   system (as function) 
    BYTE1        INSTRUCTION1 
    BYTE2        INSTRUCTION2 
    BYTE3        INSTRUCTION3 
    BYTE4        INSTRUCTION4 
            INSTRUCTION5 
            INSTRUCTION6 
            INSTRUCTION7 
            INSTRUCTION8 
            ... 

Так что линкер делает, чтобы сделать адрес system то же на каждом скомпилированный объекте (эффективно связывая их вместе). С другой стороны, содержимое system зависит от его типа, о котором линкер не знает и не заботится.

Так что в вашем файле, если сразу же после того, как:

extern system, puts; 

Вы пишете:

printf("%d\n", system); 

вы получите первые пару инструкции функции system, как если бы он был int и распечатать его , Что плохо, и не делайте этого.

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

&system 

и бросьте его правильный тип:

(void(*)(char*))&system 

вы получаете указатель на функцию, указывая на реальную system функции.Обратите внимание, что system просто теперь int, и мы собираемся назвать его как функцию, это само по себе неопределенное поведение:

C11 Приложение J-2 (неопределенное поведение):

Объект имеет его сохраненное значение доступно для доступа, отличное от lvalue допустимого типа (6.5).

С11 6.5 (курсив мой):

6. Эффективный тип объекта для доступа к его сохраненным значением является объявленный тип объекта, если таковые имеются. ...

7. Объект должен быть его сохраненное значение доступно только с помощью Lvalue выражения, имеющих один из следующих типов: 88)

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

Начало прошли все это, теперь у вас есть указатель на функцию, содержащую реальный адрес system. Затем вы называете это своим содержанием. То же самое делается с puts.


Это было плохо. Вот как это на самом деле должно быть сделано:

#include <stdlib.h> 
#include <stdio.h> 

int main(int argv,char **argc) 
{ 
    char buf[256]; 

    /* system was unused */ 

    strcpy(buf,argc[1]); 
    puts(argc[2]); 

    return 0; /* 0 means no error */ 
} 

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

Внутри файлов заголовков у вас много строк со многими объявлениями. Один для puts может выглядеть следующим образом:

int puts(const char *s); 

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

Во всяком случае, когда компилятор видит следующую строку:

int puts(const char *s); 
  1. он будет знать, что puts является внешнее определение:

    C11 6.2.2 (курсив мой):

    5. Если декларация идентификатора для функции не имеет спецификатора класса хранения, ее l Нейтралирование определяется точно так, как если бы оно было объявлено спецификатором класса хранения extern. Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь является внешней.

  2. он будет знать, что это функция, и какова ее подпись.

Итак, просто позвонив fputs(...), компилятор делает все правильно.

0
extern system, puts; 
// equivalent to 
extern int system, puts; 

Данное заявление объявляет две переменной system и puts типа int, который является типом по умолчанию, если не указано (НКУ выдает предупреждение в связи с этим). Декларация означает, что она вводит имя и тип переменной, но не выделяет для нее место.

Является ли указатель функции объявленным здесь или функцией?

void (*fn)(char*); 

определяет fn быть указателем на функцию, которая принимает char * и не возвращает значения. Выражение

(void(*)(char*))&system 

получает адрес переменной system и отбрасывает его в указатель типа void(*)(char *), то есть, указатель на функцию, которая принимает char * и не возвращает значений. Таким образом, утверждение

void (*fn)(char*)=(void(*)(char*))&system; 

присваивает адрес system после приведения его к соответствующему типу указателя fn.

почему & система и & ставит в конце определения фибронектина

Это потому, что fn является указателем на функцию. Во время фазы связывания компиляции идентификаторы system и puts связаны с библиотечными функциями. Это означает, что адрес переменных должен быть присвоен соответствующему типу перед назначением указателю функции fn.

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

0

Объявление для fn не объявляет функцию, оно объявляет указатель на функцию и инициализирует его. Эта линия:

void (*fn)(char*)=(void(*)(char*))&system; 

эквивалентно:

void (*fn)(char*); 
fn = (void(*)(char*)) &system; 

который объявляет fn быть указателем на функцию приема указатель на символ (и не возвращая ничего). Затем этому указателю присваивается адрес system(), присвоенный соответствующему типу, в соответствии с типом fn.

Смежные вопросы