2

I tried to write a C program that would copy a function to other memory location, and then execute it as a function pointer. But I'm facing issues. This is my code:

#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;
}

I compile with executable stack:

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

But the program crashes on start:

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

With 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)

This system is arm64, if that's relevant.

What am I missing, or am trying to do something impossible? Thanks.

18
  • 3
    Disclaimer: I have no idea if that can ever work. Where does __builtin_alloca get the memory from? Will the memory be executable from? Did you look in the map file, where the section of your function is located? What are the involved addresses? While being undefined behaviour anyway, due to subtracting addresses of different memory object, I would assume __stop_myfnsection - __start_myfnsection results in the number of uint64_t elements, not in the size you need to copy. You probably should convert to intprt_t before you do the calculation. Commented Nov 14, 2024 at 9:58
  • 1
    Is there a non-zero offset between __start_myfnsection and myfn? Commented Nov 14, 2024 at 10:02
  • 2
    Cast both pointers to uintptr_t then subtract that to get the number of bytes. Commented Nov 14, 2024 at 10:17
  • 2
    Also, since 76 bytes is not divisible by 8, it would be better to change the type of __stop_myfnsection[] and __start_myfnsection[] to char. Commented Nov 14, 2024 at 10:18
  • 2
    @melonfsck-sheher try to step into the myfn_copy() at assembly instruction level and check if you get the same instructions than stepping into myfn(). And maybe execution is not allowed in stack memory. Commented Nov 14, 2024 at 10:26

1 Answer 1

4

First of all you need some manner of guarantee from the system that you can execute code from stack memory space, which is not something I'd assume.

If you know that part is OK, then illegal instruction means that the memory contains gibberish. Which is likely caused by using the wrong types all over the code.

You need apply various bug fixes and get rid of all dangerous gcc extensions... You didn't include string.h. You invoke UB with the pointer subtractions. You assign between void* and function pointers which is invalid C. And so on.

Less bad code would be something like this:

#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;
}

This would probably work on various low-end von Neumann microcontrollers but probably not on x86 Linux etc.

Sign up to request clarification or add additional context in comments.

5 Comments

Maybe someone who knows more about the Linux ABI than I do can comment regarding function alignment requirements, MMU and virtual addressing requirements etc. Even on a low end microcontroller system you wouldn't store this data in on the stack, that's plain crazy. You'd store it in a designated RAM memory space reserved for code execution, which in turn likely involves some linker script tweaking.
I also suspect that the memory area might not be correctly aligned for instructions, as the allocation function used is documented only to give memory aligned for largest requirement of data types.
Would it maybe work when linking with execstack, like -Wl,-z,execstack?
See my comments on the question - entry point isn't necessarily the first byte of the function, and we haven't flushed instruction caches.
I suggest copying the whole section, not just from &myfn onwards, and then apply the appropriate offset.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.