Встроенная сборка и перезапись функций, приводящая к сбою сегментирования

Кто-то в SO разместил вопрос, спрашивая, как он может «скрыть» функцию. Это был мой ответ:

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

int encrypt(void)
{
  char *text="Hello World";
  asm("push text");
  asm("call printf");
  return 0;
}

int main(int argc, char *argv[])
{
  volatile unsigned char *i=encrypt;
  while(*i!=0x00)
    *i++^=0xBE;
  return EXIT_SUCCESS;
}

но есть проблемы:

encode.c: In function `main':
encode.c:13: warning: initialization from incompatible pointer type
C:\DOCUME~1\Aviral\LOCALS~1\Temp/ccYaOZhn.o:encode.c:(.text+0xf): undefined reference to `text'
C:\DOCUME~1\Aviral\LOCALS~1\Temp/ccYaOZhn.o:encode.c:(.text+0x14): undefined reference to `printf'
collect2: ld returned 1 exit status

Мой первый вопрос: почему не работает встроенная сборка ... как это сделать правильно? Другое дело - код для "ret" или "retn" равен 0x00, верно ... мой код xor-материал, пока он не достигнет возврата ... так почему это SEGFAULTing?


person aviraldg    schedule 03.10.2009    source источник


Ответы (3)


Как точка высокого уровня, я не совсем уверен, почему вы пытаетесь использовать встроенную сборку для выполнения простого вызова printf, поскольку все, что вы сделали, это создали неправильную версию вызова функции (ваша встроенная версия что-то подталкивает к стек, но никогда не вынимайте его, что, скорее всего, вызывает проблемы, потому что GCC не знает, что вы изменили указатель стека в середине функции. Это нормально в тривиальном примере, но может привести к неочевидным ошибкам в более сложной функции)

Вот правильная реализация вашей главной функции:

int encrypt(void)
{
  char *text="Hello World";
  char *formatString = "%s\n";
  // volatile really isn't necessary but I just use it by habit
  asm volatile("pushl %0;\n\t"
               "pushl %1;\n\t"
            "call printf;\n\t"
               "addl $0x8, %%esp\n\t"          
               : 
               : "r"(text), "r"(formatString)
               );

  return 0;
}

Что касается вашего последнего вопроса, обычный код операции для RET - "C3", но есть много вариантов, посмотрите http://pdos.csail.mit.edu/6.828/2009/readings/i386/RET.htm Ваша идея поиска RET также ошибочна. к тому факту, что когда вы видите байт 0xC3 в случайном наборе инструкций, это НЕ означает, что вы столкнулись с ret. Поскольку 0xC3 может быть просто данными / атрибутами другой инструкции (в качестве побочного примечания, особенно сложно пытаться анализировать инструкции x86, как вы это делаете, потому что x86 - это архитектура CISC с длиной инструкций от 1 до 16 байтов. )

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

person Falaina    schedule 03.10.2009

Встроенный asm GCC использует синтаксис AT&T (если не выбраны специальные параметры для использования Intel).

Вот пример:

 int a=10, b;
 asm ("movl %1, %%eax; 
       movl %%eax, %0;"
      :"=r"(b)        /* output */
      :"r"(a)         /* input */
      :"%eax"         /* clobbered register */
      );       

Таким образом, ваша проблема в том, что «текст» не идентифицируется по вашему звонку (и по следующей инструкции).

См. здесь для справки.

Более того, ваш код не переносится между 32- и 64-битными средами. Скомпилируйте его с флагом -m32, чтобы обеспечить правильный анализ (GCC все равно пожалуется, если вы ошибетесь).

Полное решение вашей проблемы можно найти в этом сообщении в списке рассылки GCC. Вот отрывок:

for ( i = method->args_size - 1; i >= 0; i-- ) {
    asm( "pushl %0": /* no outputs */: \
         "g" (stack_frame->op_stack[i]) );
}

asm( "call *%0" : /* no outputs */ : "g" (fp) :
     "%eax", "%ecx", "%edx", "%cc", "memory" );

asm ( "movl %%eax, %0" : "=g" (ret_value) : /* No inputs */ );

В системах Windows нужно сделать еще asm ( "addl %0, %%esp" : /* No outputs */ : "g" (method->args_size * 4) );. Google для получения более подробной информации.

person ZZambia    schedule 03.10.2009
comment
Этот пример является огромным взломом и зависит от поведения оптимизатора (не ссылаясь на какие-либо локальные переменные относительно ESP), и даже не может использовать один и тот же оператор asm для call и получения возвращаемого значения. Он должен просто использовать вывод "=a" (ret_value) `в операторе asm, выполняющем вызов. (И тогда, конечно, это должно быть asm volatile, потому что только операторы asm без выходных данных неявно изменчивы). При этом также не удается исправить ESP после вызова, поэтому он полностью не работает, если только вызываемый не является Windows stdcall или каким-либо другим соглашением о вызываемых вызовах. - person Peter Cordes; 10.02.2020

Это не printf, а _printf

person Mandrake    schedule 21.10.2009