C中的一种自修改程序

时间:2009-01-03 04:21:12

标签: c heap machine-code

是否可以编写执行以下操作的C函数?

  1. 在堆中分配一堆内存
  2. 在其中写入机器代码
  3. 执行这些机器说明
  4. 当然,我必须将堆栈状态恢复到手动执行这些机器指令之前的状态,但我想先知道这是否可行。

4 个答案:

答案 0 :(得分:7)

肯定是可能的。出于各种原因,我们在过去的30 - 40年中花了很多精力试图尽可能地让它变得困难,但这是可能的。现在,在大多数系统中,都有硬件和软件机制试图保护数据空间不被执行。

但是,基础知识非常简单:您构建一段代码,然后通过编译器手动或组装它。然后,您需要一段代码空间,因此您将代码插入到程序中

unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... };  // Random numbers, just as an example

因为你想使用,你需要malloc空间

void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
     memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}

现在,您需要的是一种让程序计数器指向同样是您的代码块的数据块的方法。这是你需要一点狡猾的地方。设置程序计数器没什么大不了的;这只是底层机器的JUMP指令。但是怎么做呢?

最简单的方法之一是有目的地搞乱堆栈。从概念上讲,堆栈看起来像这样(细节取决于您的操作系统和编译器对,以及您的硬件):

    | subroutine return addr |
    | parameters ...         |
    | automatic variables    |

这里的基本技巧是偷偷将代码的地址放入返回地址;当例程返回时,它基本上跳转到返回addrfess。如果你可以伪造它,PC将被设置到你喜欢的地方。

所以,你需要的是一个例程,我们称之为“goThere()”

void goThere(void * addr){
    int a ;     // observe above; this is the first space 
                // on the stack following the parameters
    int * pa;   // so we use it's address

    pa = (&a - (sizeof(int)+(2*sizeof(void*))) ;  // so use the address
                // but back up by the size of an int, the pointer on the
                // stack, and the return address
    // Now 'pa' points to the routine's return add on the stack.
    *pa = addr; // sneak the address of the new code into return addr
    return ;    // and return, tricking it into "returning"
                // to the address of your special code block
}

会起作用吗?好吧,也许,取决于硬件和操作系统。大多数现代操作系统将保护堆(通过内存映射或类似)从进入它的PC。出于安全目的,这是一个有用的东西,因为我们也让你采取那种完全控制。

答案 1 :(得分:3)

这与this question :)

非常相似

答案 2 :(得分:2)

阅读calling code stored in the heap from vc++。在posix上,mprotect似乎是合适的(查看man mprotect):

char *mem = malloc(sizeof(code));
mprotect(mem, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(mem, code, sizeof(code));
// now arrange some code to jump to mem. But read the notes here on casting 
// from void* to a function pointer:
// http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html

然而,它说:

  

PROT_EXEC是否与PROT_READ有任何不同,它取决于体系结构和内核版本。在某些硬件架构(例如,i386)上,PROT_WRITE意味着PROT_READ。

那么更好,先检查一下你的操作系统是否有效。

答案 3 :(得分:0)

RE:手动恢复堆栈

如果您遵循平台/编译器在您生成的机器代码中使用的调用约定,那么您不必进行任何手动堆栈恢复。当你做

时,编译器会为你做这件事

* pfunc(参数)

它应该添加必要的任何适当的前或后调用堆栈操作步骤。

但请确保在生成的代码中遵循正确的约定。