为什么我无法复制并执行C 中的函数?

问题描述 投票:0回答:1

我尝试编写一个 C 程序,将函数复制到其他内存位置,然后将其作为函数指针执行。 但我面临着问题。 这是我的代码:

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

extern uint64_t __start_myfnsection[];
extern uint64_t __stop_myfnsection[];

// I create a simple function, which will be placed in a separate binary section
__attribute__((noinline, section("myfnsection"))) void myfn() {
    static int i;
    i++;
    printf("I love lemons!^^.\n");
    printf("i=%d\n", i);
    return;
}

int main () {
    // Test the function.
    myfn();

    // Find out and print the size of my function.
    uint64_t myfn_length = (__stop_myfnsection - __start_myfnsection);
    printf("Length of myfn() function is: %lu\n", myfn_length);

    // Allocate on-stack memory, and copy my function to it
    void* memory = __builtin_alloca(myfn_length);
    memcpy(memory, &myfn, myfn_length);

    // Create a pointer to the copied function.
    void (*myfn_copy)() = memory;

    // Attempt to execute it.
    myfn_copy();

    return 0;
}

我使用可执行堆栈进行编译:

$ gcc -pie -z execstack -fno-stack-protector -g test.c

但是程序在启动时崩溃了:

$ ./a.out
I love lemons!^^.
i=1
Length of myfn() function is: 9
Illegal instruction

使用 gdb:

# gdb ./a.out
GNU gdb (GDB) 14.2
Reading symbols from ./a.out...
(gdb) run
Starting program: /home/mika/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so".
I love lemons!^^.
i=1
Length of myfn() function is: 9

Program received signal SIGILL, Illegal instruction.
0x0000007ffffffb10 in ?? ()
(gdb)
(gdb) bt
#0  0x0000007ffffffb10 in ?? ()
#1  0x0000005555556748 in _start_main ()
#2  0x0000000000000000 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb)

这个系统是arm64,如果相关的话。

我错过了什么,或者正在尝试做一些不可能的事情?谢谢。

c linux function gdb memcpy
1个回答
1
投票

首先,您需要系统以某种方式保证您可以从堆栈内存空间执行代码,这不是我所假设的。

如果你知道那部分是好的,那么非法指令意味着内存中包含乱码。这可能是由于在整个代码中使用了错误的类型造成的。

您需要应用各种错误修复并摆脱所有危险的 gcc 扩展...您没有包含 string.h。您可以通过指针减法来调用 UB。你在 void* 和函数指针之间赋值,这是无效的 C。等等。

不太糟糕的代码会是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>  // to print uint64_t
#include <string.h>    // to give memcpy a chance to succeed at all

// This is raw binary, not chunks of uint64_t:
extern uint8_t __start_myfnsection[];
extern uint8_t __stop_myfnsection[];

__attribute__((noinline, section("myfnsection"))) void myfn() {
    static int i;
    i++;
    printf("I love lemons!^^.\n");
    printf("i=%d\n", i);
    return;
}

// dirty type punning but better than wild & crazy pointer casts/gcc extensions:
typedef union
{
  void* vptr;
  void (*fptr)();
} ptr_t;

// some bare minimum of assumptions (we still know nothing about alignment though):
static_assert(sizeof(void*) == sizeof(void(*)(void)), "Pointers are too exotic...");

int main () {
    // Test the function.
    myfn();

    // Find out and print the size of my function.
    // use uintptr_t since pointer arithmetic on two unrelated sections is UB
    uint64_t myfn_length = (uintptr_t)__stop_myfnsection - (uintptr_t)__start_myfnsection;
    printf("Length of myfn() function is: %" PRIu64 "\n", myfn_length);

    ptr_t memory;
    ptr_t old_func = { .fptr = myfn };

    // Allocate on-stack memory, and copy my function to it
    memory.vptr = __builtin_alloca(myfn_length);
    memcpy(memory.vptr, old_func.vptr, myfn_length);

    // Create a pointer to the copied function.
    void (*myfn_copy)() = memory.fptr;

    // Attempt to execute it.
    myfn_copy(); // SIGSEGV here if you can't execute code from the stack

    return 0;
}

这可能适用于各种低端冯诺依曼微控制器,但可能不适用于 x86 Linux 等。

© www.soinside.com 2019 - 2024. All rights reserved.