Search code examples
cshellcode

Using mprotect() to set main() as writeable


Using mprotect to set main() as writeable correctly works using this code.

https://godbolt.org/z/68vfrTq8z

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

int main()
{
    long page_size = sysconf(_SC_PAGE_SIZE);
    printf("page_size    = %li\n", page_size);

    void* main_address = (void*)&main;
    printf("main_address = %p\n",main_address);
    
    void* main_start = (void*)(((uintptr_t)&main) & ~(sysconf(_SC_PAGE_SIZE) - 1));
    printf("main_start   = %p\n", main_start);

    size_t main_size = (void*)&&main_end - (void*)&main;
    printf("main_size    = %d\n", main_size);

    mprotect(main_start, main_size, PROT_READ | PROT_WRITE | PROT_EXEC);

    // nop the breakpoint to allow printf() to run!
    *((char*)&&breakpoint) = 0x90;

breakpoint:
    __asm("int $3");

    printf("\nHello, World!\n");

    return 0;
    
main_end:
}

The program outputs

page_size    = 4096
main_address = 0x401156
main_start   = 0x401000
main_size    = 193

Hello, World!

Question

I initially coded it passing main_address to mprotect but it failed with an "Invalid argument" (22) error because it was not aligned to a page boundary! Passing main_start works because it's the aligned address of the program entry point, but what is main_address and why isn't it initially aligned too?

What's confusing me is that if mprotect(0x401000, 193, ...) is being write enabled, but the main_address is at 0x401156 which is 342 bytes further down in memory, how is the main() still writeable? Does mprotect() modify pages and not a specific byte range?

If pages are being write enabled, does that potentially imply memory portions past the end of main() are also writeable?


Solution

  • Per the POSIX documentation:

    The mprotect() function shall change the access protections to be that specified by prot for those whole pages containing any part of the address space of the process starting at address addr and continuing for len bytes.

    So by that, it will figure out which pages contain the specified bytes (effectively, rounding addr down to a page boundary and addr+len up to a page boundary) and change the permissions on those pages. So it may affect the permissions on bytes before and after the range requested.

    However, under errors it says:

    The mprotect() function may fail if:

    EINVAL The addr argument is not a multiple of the page size as returned by sysconf().

    So a non-page aligned addr may fail instead of rounding down, but a non-page aligned len will work. In practice, it depends on the actual OS -- some POSIX systems support unaligned addresses here and others do not.