我尝试编写一个 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,如果相关的话。
我错过了什么,或者正在尝试做一些不可能的事情?谢谢。
首先,您需要系统以某种方式保证您可以从堆栈内存空间执行代码,这不是我所假设的。
如果你知道那部分是好的,那么非法指令意味着内存中包含乱码。这可能是由于在整个代码中使用了错误的类型造成的。
您需要应用各种错误修复并摆脱所有危险的 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 等。