如何调用存储在char数组中的机器码?

我试图调用本地机器语言代码。 这是我到目前为止(它得到一个总线错误):

char prog[] = {'\xc3'}; // x86 ret instruction int main() { typedef double (*dfunc)(); dfunc d = (dfunc)(&prog[0]); (*d)(); return 0; } 

它确实调用了该函数,并且它到达了ret指令。 但是当它试图执行ret指令时,它有一个SIGBUS错误。 是因为我在一个没有被执行的页面上执行代码或类似的东西?

那么我在这里做错了什么?

第一个问题可能是编程数据存储的位置不可执行。

至less在Linux上,生成的二进制文件将把全局variables的内容放在“数据”段或这里 ,这在大多数情况下是不可执行的。

第二个问题可能是你调用的代码在某些方面是无效的。 有一个特定的过程来调用C中的方法,称为调用约定 (例如,您可能使用“cdecl”)。 被调用函数只是“ret”可能是不够的。 它可能还需要做一些堆栈清理等,否则程序会出现意外的行为。 一旦你解决了第一个问题,这可能会成为一个问题。

您需要调用memprotect才能使页面可执行的页面。 下面的代码确实调用了这个函数,并且可以在prog中执行文本。

 #include <unistd.h> #include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h> char prog[] = { 0x55, // push %rbp 0x48, 0x89, 0xe5, // mov %rsp,%rbp 0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00, //movsd 0x0(%rip),%xmm0 # c <x+0xc> 0x00, 0x5d, // pop %rbp 0xc3, // retq }; int main() { long pagesize = sysconf(_SC_PAGE_SIZE); long page_no = (long)prog/pagesize; int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE); if(res) { fprintf(stderr, "mprotect error:%d\n", res); return 1; } typedef double (*dfunc)(void); dfunc d = (dfunc)(&prog[0]); double x = (*d)(); printf("x=%f\n", x); fflush(stdout); return 0; } 

正如所有人都已经说过的,你必须确保prog[]是可执行的,但是除非你正在编写JIT编译器,否则正确的方法是将符号放在可执行区域,或者使用链接器脚本或者指定如果编译器允许,在C代码中的部分,例如:

 const char prog[] __attribute__((section(".text"))) = {...} 

几乎所有的C编译器都可以让你在代码中embedded常规的汇编语言。 当然,这对C来说是一个非标准的扩展,但是编译器编写者认识到它经常是必要的。 作为一个非标准的扩展,你将不得不阅读你的编译器手册,并检查如何做,但GCC“asm”扩展是一个相当标准的方法。

  void DoCheck(uint32_t dwSomeValue) { uint32_t dwRes; // Assumes dwSomeValue is not zero. asm ("bsfl %1,%0" : "=r" (dwRes) : "r" (dwSomeValue) : "cc"); assert(dwRes > 3); } 

由于在汇编程序中很容易垃圾堆栈,编译器通常也允许你识别你将用作汇编程序一部分的寄存器。 然后编译器可以确保该函数的其余部分避开这些寄存器。

如果您自己编写汇编代码,那么将汇编程序设置为字节数组没有任何好处。 这不仅仅是一种代码异味 – 我想说这是一个真正的错误,只有在不了解“汇编”扩展时才会发生,这是将汇编器embedded到C中的正确方法。

从本质上来说,这是受到严厉打击的,因为这是对病毒作者的公开邀请。 但是你可以直接使用本地机器代码进行分配和缓冲,并设置它 – 这是没有问题的。 问题在于调用它。 虽然你可以试着用缓冲区的地址来设置一个函数指针并调用它,但是这样做很不可能,而且很有可能在下一个版本的编译器中崩溃,如果你设法让它变成你想做的事情的话。 所以最好的办法是简单地使用一些内联汇编来设置返回并跳转到自动生成的代码。 但是如果系统防止这种情况的发生,你就必须find规避这种保护的方法,正如Rudi在他的回答中所描述的(但是对于一个特定的系统非常具体)。

一个明显的错误是\xc3没有返回你声称它正在返回的double

您可以通过允许编译器将数组存储在进程内存的只读部分(如果在编译时已知)来消除崩溃。 例如通过声明数组const

例:

 const char prog[] = {'\xc3'}; // x86 ret instruction int main() { typedef double (*dfunc)(); dfunc d = (dfunc)(&prog[0]); (*d)(); return 0; } 

或者,您可以使用禁用的堆栈保护gcc -z execstack来编译代码。

相关问题:

  • 字节编码函数时出现分段错误?