内联汇编和函数覆盖导致段错误

时间:2009-10-03 07:48:42

标签: c obfuscation inline-assembly

有人在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?

3 个答案:

答案 0 :(得分:1)

GCC inline asm使用AT&amp; T语法(如果没有选择使用英特尔的特定选项)。

以下是一个例子:

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

因此,您的问题是您的电话无法识别“文字”(以及以下说明)。

请参阅here以供参考。

此外,您的代码在32位和64位环境之间无法移植。用-m32标志编译它以确保正确分析(如果你出错,GCC会抱怨)。

您的问题的完整解决方案是在this发布在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) );要做。谷歌提供更好的细节。

答案 1 :(得分:1)

作为一个高级别的问题,我不太清楚你为什么要尝试使用内联汇编来对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是一个指令长度在1-16字节之间的CISC架构,因此你正在努力尝试解析x86指令特别困难。 )

另外注意,并非所有操作系统都允许修改文本/代码段(存储可执行指令的位置),因此无论如何,您在main中的代码都可能无法正常工作。

答案 2 :(得分:0)

不是printf而是_printf