0

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

  1. 在堆中分配一堆内存
  2. 在其中写入机器代码
  3. 执行那些机器指令

当然,我必须将堆栈的状态恢复到手动执行这些机器指令之前的状态,但我想知道这是否可行。

4

4 回答 4

7

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

不过,基础非常简单:您构建一段代码,然后手动或通过编译器4 组装它。然后你需要一段代码空间,所以你将代码插入到你的程序中

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 指令。但是怎么做呢?

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

    | 子程序返回地址 |
    | 参数... |
    | 自动变量 |

这里的基本技巧是偷偷地将你的代码地址放入返回地址;当一个例程返回时,它基本上会跳转到那个返回地址。如果您可以伪造它,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 移动到其中。出于安全目的,这很有用,因为我们也不会让您完全控制。

于 2009-01-03T05:10:13.700 回答
3

这与这个问题非常相似:)

于 2009-01-03T04:26:27.597 回答
2

从 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。

所以更好,首先检查您的操作系统是否有效。

于 2009-01-03T04:28:10.353 回答
0

RE:手动恢复堆栈

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

*pfunc(参数)

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

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

于 2009-01-03T04:34:57.580 回答