Почему приведение "extern put" к указателю на функцию "(void (*) (char *)) & put"? - PullRequest
7 голосов
/ 19 февраля 2011

Я смотрю на пример abo3.c из Небезопасное программирование , и в приведенном ниже примере я не прикидываюсь кастом.Может ли кто-нибудь просветить меня?

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);  
}

Итак, что с кастингом на систему и путы?Они оба возвращают int, так почему бы отказаться от него?

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

[РЕДАКТИРОВАТЬ]
Спасибо вам обоим за ваш вклад!

Джонатан Леффлер , на самом деле есть причина, по которой код должен быть "плохим".Предполагается, что он может использоваться, переполнять буферы и указатели функций и т. Д. mishou.org имеет сообщение в блоге о том, как использовать приведенный выше код.Многое все еще над моей головой.

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

Одна вещь, которая не сразу понятна, это то, что система и адреса адресов записываются в одно и то же место, я думаю, что это может быть тем, о чем говорит gera «, так что компоновщик не удаляет его”.

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

#include <stdio.h>

char shellcode[] = "some shellcode";

int main(void)
{
    fprintf(stdout,"Length: %d\n",strlen(shellcode));
    (*(void(*)()) shellcode)();
}

Итак, массив приводится к функции, возвращающей void, на которую ссылаются и вызывают?Это выглядит просто противно - так какова цель приведенного выше кода?

[/ EDIT]

Ответы [ 2 ]

10 голосов
/ 19 февраля 2011

Оригинальный вопрос

Пользователь bta дал правильное объяснение актерского состава и прокомментировал неверность каста system.

Я собираюсь добавить:

Строка extern в лучшем случае странная. Он является ошибочным при строгом C99, потому что нет типа, что делает его недействительным; в C89 тип будет считаться int. Строка говорит: «существует внешне определенное целое число, называемое системой, и другое, называемое put», что неверно - есть пара функций с этими именами. Код может на самом деле «работать», потому что компоновщик может связать функции с предполагаемыми целыми числами. Но это небезопасно для 64-битной машины, где указатели имеют разный размер с int. Конечно, код должен содержать правильные заголовки (<stdio.h> для puts() и <stdlib.h> для system() и exit(), и <string.h> для strcpy()).

exit(1); плохо по двум отдельным пунктам.

  • Указывает на сбой - безоговорочно. Вы выходите с 0 или EXIT_SUCCESS, чтобы указать успех.

  • На мой взгляд, лучше использовать return в конце main(), чем exit(). Не все обязательно соглашаются со мной, но я не хотел бы видеть exit() последней строкой main(). Единственное оправдание этому - избегать проблем из-за других плохих практик, таких как функции, зарегистрированные с atexit(), которые зависят от продолжающегося существования локальных переменных, определенных в main().


/usr/bin/gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -c nasty.c
nasty.c: In function ‘main’:
nasty.c:3: warning: type defaults to ‘int’ in declaration of ‘system’
nasty.c:3: warning: type defaults to ‘int’ in declaration of ‘puts’
nasty.c:3: warning: built-in function ‘puts’ declared as non-function
nasty.c:8: warning: implicit declaration of function ‘strcpy’
nasty.c:8: warning: incompatible implicit declaration of built-in function ‘strcpy’
nasty.c:10: warning: implicit declaration of function ‘exit’
nasty.c:10: warning: incompatible implicit declaration of built-in function ‘exit’
nasty.c: At top level:
nasty.c:1: warning: unused parameter ‘argv’

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


В коде есть еще одна странность:

int main(int argv,char **argc)   

Это «правильно» (это будет работать), но на 100% условно. Обычное объявление:

int main(int argc, char **argv)

Имена являются короткими для «количества аргументов» и «вектора аргументов», и использование argc в качестве имени для вектора (массива) строк является ненормальным и совершенно запутанным.


Посетив упомянутый сайт, вы можете увидеть, что он проходит через множество градуированных примеров. Я не уверен, имеет ли автор просто слепое пятно в вопросе argc / argv или намеренно возится ( 'abo1' предполагает, что он играет, но, на мой взгляд, это не помогает). Предполагается, что примеры питают ваш разум, но мало что объясняют, что они делают. Не думаю, что смогу порекомендовать сайт.


Дополнительный вопрос

Что делает приведение в этом коде?

#include <stdio.h>

char shellcode[] = "some shellcode";

int main(void)
{
    fprintf(stdout,"Length: %d\n",strlen(shellcode));
    (*(void(*)()) shellcode)();
}

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

Просмотр анализа

Анализ на сайте Mishou не настолько авторитетен, как хотелось бы:

Во-первых, этот код использует ключевое слово extern на языке C, чтобы сделать систему и предоставляет функции доступными. То, что это делает (я думаю), в основном ссылается непосредственно на расположение функции, определенной в (подразумеваемых) заголовочных файлах… У меня создается впечатление, что GDB автоматически волшебно включает в себя заголовочные файлы stdlib.h для system и stdio.h для put , Одна вещь, которая не сразу понятна, - это то, что система и адреса адресов записаны в одном и том же месте, я думаю, это может быть тем, о чем говорит gera «так что компоновщик не удаляет его».

Рассечение комментария:

  1. ThПервое предложение не очень точное;он сообщает компилятору, что символы system и puts определены (как целые числа) где-то еще.Когда код связан, адрес puts() -функции известен;код будет обрабатывать его как целочисленную переменную, но адрес целочисленной переменной, по сути, является адресом функции - так что приведение приводит к тому, что компилятор все равно обрабатывает его как указатель на функцию.
  2. Второе предложение не совсем точно;компоновщик разрешает адреса внешних «переменных» с помощью функциональных символов system() и puts() в библиотеке C.
  3. GDB не имеет никакого отношения к процессу компиляции или компоновки.
  4. Последнее предложение не имеет никакого смысла вообще.Адреса записываются только в одно и то же место, потому что у вас есть инициализация и присвоение одной и той же переменной.

Это не побудило меня прочитать всю статью, это должно быть сказано.Должная осмотрительность заставляет меня двигаться вперед;объяснение после этого лучше, хотя все еще не так ясно, как мне кажется.Но операция переполнения буфера слишком длинной, но тщательно созданной строкой аргумента является ядром операции.В коде упоминаются как puts(), так и system(), так что при запуске в неэксплуатационном режиме функция puts() является известным символом (в противном случае вам придется использовать dlopen(), чтобы найти его адрес), и поэтомучто при запуске в режиме эксплойта код имеет символ system(), доступный для непосредственного использования.Неиспользуемые внешние ссылки не становятся доступными в исполняемом файле - хорошо, когда вы понимаете, сколько символов содержится в типичном системном заголовке по сравнению с числом, используемым программой, которая включает заголовок.

Есть некоторые аккуратныепоказанные приемы - хотя реализация этих приемов не показана на конкретной странице;Я предполагаю (не проверив это), что информация для программы getenvaddr доступна.

Код abo3.c можно записать так:

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

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

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

Теперь он компилируется только с однимпредупреждение с опциональными опциями компиляции, которые я использовал изначально - и это точное предупреждение о том, что argc не используется.Он так же пригоден для использования, как и оригинал;это «лучший» код, потому что он компилируется чисто.Направления были неоправданной загадкой, а не решающей частью обеспечения возможности использования кода.

4 голосов
/ 19 февраля 2011

Оба system и puts обычно возвращают int.Код приводит их к указателю, который возвращает void, вероятно, потому что они хотят игнорировать любое возвращаемое значение.Это должно быть эквивалентно использованию (void)fn(argc[2]); в качестве предпоследней строки, если приведение не изменило тип возврата.Изъятие типа возврата иногда выполняется для функций обратного вызова, и этот фрагмент кода кажется упрощенным примером обратного вызова.

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

...