2015-03-29 1 views
3

Я наткнулся на этот фрагмент кода (для всей программы см. Страницу this, см. Программу под названием «srop.c»).C литье функции ассемблера

Мой вопрос касается того, как func используется в методе main. Я только сохранил код, который, как я думал, может быть связан.

Это строка *ret = (int)func +4;, которая меня смущает.

Есть три вопроса, я по поводу этого:

  1. func(void) является функцией, если она не будет вызвана с func() (обратите внимание на скобки)
  2. Признавая, что это может быть какой-то мне неизвестно способ вызов функции, как ее можно отличить до int, когда он должен вернуть void?
  3. Я понимаю, что автор не хочет сохранять указатель кадра и не обновлять его (пролог), как указывает его комментарий. Как это пропуская две линии вперед достигнуты с литой функции до int и добавлением четырех?

.

(gdb) disassemble func 
Dump of assembler code for function func: 
0x000000000040069b <+0>:  push %rbp 
0x000000000040069c <+1>:  mov %rsp,%rbp 
0x000000000040069f <+4>:  mov $0xf,%rax 
0x00000000004006a6 <+11>: retq 
0x00000000004006a7 <+12>: pop %rbp 
0x00000000004006a8 <+13>: retq 
End of assembler dump. 

Возможно отношение в том, что при компиляции GCC говорит мне следующее:
warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]

Пожалуйста, смотрите ниже код.

void func(void) 
{ 
    asm("mov $0xf,%rax\n\t"); 
    asm("retq\n\t"); 
} 

int main(void) 
{ 
    unsigned long *ret; 

    /*...*/ 

    /* overflowing */ 
    ret = (unsigned long *)&ret + 2; 
    *ret = (int)func +4; //skip gadget's function prologue 

    /*...*/ 

    return 0; 
} 

[Редактировать] После очень полезные советы, вот некоторые дополнительные данные:

calling func returns a pointer to the start of the function: 0x400530 
    casting this to an int is dangerous (in hex) 400530 
    casting this to an int in decimal 4195632 
    safe cast to unsigned long 4195632 

    size of void pointer: 8 
    size of int: 4 
    size of unsigned long: 8 

[Edit 2:] @cmaster: Не могли бы вы указать мне еще немного информации о том, как поставить функция ассемблера в отдельном файле и ссылка на него? Исходная программа не будет компилироваться, потому что она не знает, что функция prog (если она помещена в файл ассемблера), поэтому она должна быть добавлена ​​либо до, либо во время компиляции?

Кроме того, gcc -S при запуске файла C, включая только команды сборки, похоже, добавляет много дополнительной информации, не может func(void) быть представлен следующим кодом ассемблера?

func: 
mov $0xf,%rax 
retq 

ответ

1

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

  • func сам по себе производит указатель на функцию.

    Успение 1:
    Указатель фактически указывает на начало ассемблерного кода для func. Это предположение не обязательно правильно, существуют архитектуры, где указатель на функцию является указателем на пару указателей, один из которых указывает на код, другой - на сегмент данных.

  • func + 4 увеличивает этот указатель, указывая на первую инструкцию тела функции.

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

    Успение 3: Пролог, вставленный компилятором, составляет в точности четыре байта. Абсолютно ничего, что диктует, какой пролог должен испускать компилятор, существует множество вариантов, имеющих очень разную длину. Код, который вы указали, пытается контролировать длину пролога, не передавая/не возвращая какие-либо параметры, но все же могут быть компиляторы, которые производят другой пролог. Хуже того, размер пролога может зависеть от уровня оптимизации.

  • Результирующий указатель преобразуется в int.

    Успение 4:
    sizeof(void (*)(void)) == sizeof(int). Это неверно для большинства 64-разрядных систем: в этих системах int обычно остается четыре байта, в то время как указатель занимает восемь байтов. В такой системе значение указателя будет усечено. Когда int будет возвращен в указатель функции и вызван, это, скорее всего, приведет к сбою программы.


Мой совет:
Если вы действительно хотите программировать на ассемблере, скомпилировать файл только пустой функции с gcc -S. Это даст вам исходный файл ассемблера со всем трещиной, который необходим для ассемблера для создания действительного объектного файла, и покажет вам, где вы можете добавить код для своей собственной функции. Измените этот файл любым способом, а затем скомпилируйте его вместе с некоторым кодом C, как обычно. Таким образом, вы избегаете всех этих опасных небольших предположений.

+0

Спасибо, отличный ответ. Проверка предположения №4, и вы были полностью правы, однако адреса памяти настолько низки, что они работают даже при усечении. Не могли бы вы связать мне дополнительную информацию о том, как связать две программы, как при компиляции, так и как вызвать функцию сборки из программы C? –

+0

Поскольку вы изменяете сгенерированный файл ассемблера, у вас уже есть необходимые точки входа (они определяются директивами ассемблера, которые обычно начинаются с точки '.'). То есть, связь точно такая же, как если бы вы компилировались из файла .c, который использовался для создания вашего шаблона.Чтобы вызвать функцию ассемблера, вам нужно только «указать» правильный прототип функции в файле заголовка C. Я говорю «только», потому что ваш компилятор не будет проверять его правильность, и вам нужно будет подчиняться соглашениям о вызовах, которые использует ваш компилятор. – cmaster

+0

Что касается дальнейшего чтения: google для «ABI <ваше системное имя><ваша архитектура процессора><ваш компилятор>", и вы должны найти некоторые спецификации со всеми соответствующими данными (ABI означает «абстрактный двоичный интерфейс»). Для менее формальной информации повторите попытку с «вызовами конвенций». Обратите внимание, что существует несколько радикально отличающихся ABI для процессоров X86, вам нужно найти тот, который используется в вашей системе. Один из этих ABI передает все аргументы в стек, например, в то время как другой использует регистры для первых нескольких аргументов. Хорошим началом может быть: http://en.wikipedia.org/wiki/X86_calling_conventions – cmaster

0
  1. Имя функции является указателем на начало функции. Таким образом, автор не вызывает функцию в этой точке. Просто сохраните ссылку на ее начало.

  2. Это не пустота. Это указатель на функцию. Точнее, в этом случае он имеет тип: void (*) (void). Указатель - это просто адрес, поэтому его можно передать в int (но адрес может быть усечен, если скомпилирован для 64-битной системы, так как ints в этом случае 32 бита).

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