Используя ftrace в простой программе, Inline Assembly __asm ​​__ ("уход") приводит к ошибке seg - PullRequest
0 голосов
/ 27 июня 2019

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

При запуске ftrace на нем ничего не происходит.Если я запускаю исполняемый файл самостоятельно, я просто получаю ошибку сегмента.Я компилирую его так: gcc -nostdlib test.c -o test

Вот код, который у меня есть:

int foo(void) {  
}

_start()
{
    foo();
    __asm__("leave");
}

Ожидаемые результаты демонстрируют трассировку ftraceфункция вызывает через выполнение.

Вот изображение из текста того, из чего я ухожу:

simple ftrace

Вот ftrace, который я использую:

https://github.com/elfmaster/ftrace

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

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

pid_read() failed: Input/output error <0x1>

1 Ответ

3 голосов
/ 27 июня 2019

Звоните _exit(0); или exit_group(0) в конце _start. (Связывайте с gcc -static -nostartfiles вместо -nostdlib, чтобы вы могли вызывать функции-оболочки системных вызовов libc; они должны работать, даже если функции инициализации glibc не выполнялись , поэтому malloc или printf вылетали бы ).

Или сделать системный вызов exit_group(0) вручную с помощью встроенного asm. В x86-64 Linux:
asm("mov $231, %eax; xor %edi,%edi; syscall");

См. Также Как получить значение аргументов, используя встроенную сборку в C без Glibc? для получения дополнительной информации о написании хакерского x86-64 _start для запуска собственной функции C в качестве первой вещи в вашем процессе. (Но большая часть этого ответа касается взлома соглашения о вызовах для доступа к argc / argv, что является неприятным, и я не рекомендую его.) Ответ Маттео на этот вопрос имеет целый минимум _start, записанный в asm, который вызывает нормальный C main функция.


Код книги просто сломан по двум причинам . (Я не знаю, как это могло сработать на i386 или x86-64. Мне это кажется очень странным. Вы уверены, что это не должно было произойти сбой, но вы смотрите на то, что он делает до того, как это произойдет?)

  1. _start не является функцией в Linux; вы (или сгенерированный компилятором код) не можете ret из него . Вам нужно сделать системный вызов _exit. В стеке нет обратного адреса 1 .

    Если функция имеет адрес возврата, точка входа ELF _start имеет argc, как указано в документах ABI. (x86-64 System V или i386 System V в зависимости от того, создаете ли вы 64-битный или gcc -m32 32-битный исполняемый файл.)

  2. Вставка leave (что соответствует mov %ebp, %esp / pop %ebp или эквиваленту RBP / RSP) в сгенерированный компилятором код здесь не имеет смысла. Это что-то вроде дополнительного pop, но нарушает EBP / RBP компилятора, поэтому, если он выберет leave вместо pop %rbp для своего собственного пролога, сгенерированный компилятором код даст сбой. (RBP при входе в _start равен 0 в статически связанном исполняемом файле. Или удерживает любой динамический компоновщик, оставленный в RBP, прежде чем перейти к _start в исполняемом файле PIE.)

    Но, в конечном итоге, GCC скомпилирует _start как обычную функцию, таким образом, в конечном итоге выполняя инструкцию ret. Нигде нет действительного / полезного обратного адреса, поэтому ret вообще не может работать.

    Если вы компилируете без оптимизации (по умолчанию), gcc по умолчанию будет -fno-omit-frame-pointer, поэтому его пролог функции настроит EBP или RBP в качестве указателя кадра, что позволит самой leave не вызывать ошибку. Если вы скомпилировали с оптимизацией (-O1 и выше включает -fomit-frame-pointer), gcc не будет связываться с RBP, и будет равен нулю, когда вы запустите leave, таким образом, непосредственно вызывая segfault. (Поскольку он выполняет RSP = RBP, а затем использует новый RSP в качестве указателя стека для pop %rbp.)

В любом случае, если не произойдет сбой, указатель стека снова будет указывать на argc, прежде чем сгенерированный компилятором pop %rbp как часть нормального эпилога функции. Таким образом, сгенерированный компилятором ret попытается вернуться к argv[0]. Так как стек по умолчанию не исполняемый, это приведет к ошибке. (и он указывает на символы ASCII, которые, вероятно, не будут декодироваться как полезный машинный код x86-64.)

Вы могли бы выяснить это самостоятельно, пошагово выполняя задание с помощью GDB. (layout reg и используйте stepi aka si).

В общем случае, если вы возитесь с указателем стека и другими регистрами за спиной компилятора, это обычно приводит к сбою. И если бы в стеке был адрес возврата выше, pop %rcx был бы намного более разумным, чем leave.


Сноска 1:

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

Вы связались с -nostdlib, поэтому libc не связан. Если вы связали libc динамически, но по-прежнему записали свой собственный _start (например, с gcc -nostartfiles вместо полного -nostdlib), ASLR означало бы, что функция libc _exit была по некоторому адресу переменной времени выполнения.

Если вы статически связали libc (gcc -nostartfiles -static), код для _exit() не будет скопирован в ваш исполняемый файл, если вы на самом деле не ссылаетесь на него, чего нет в этом коде. Но вам все равно нужно как-то его вызвать; обратного адреса на него нет.

...