elfvirtual-memorymprotect

Make all pages readable/writable/executable


I would like to grant full permissions (read, write, and execute) to all memory pages in an ELF binary. Ideally, I'd like to be able to do this as a transformation on a binary or object file, in the same way that symbols can be changed with objcopy. I have not yet found a good way to do this. I would also be okay with a solution that involves running code at startup that calls mprotect on every page with the flags PROT_READ | PROT_WRITE | PROT_EXEC. I've tried this briefly, but I haven't found a good way to know which pages are mapped, and therefore which pages need to be mprotected.

It isn't required that dynamically allocated pages have all permissions, only the pages mapped at program startup.


Solution

  • The following script implements Employed Russian's answer in code:

    It depends on pyelftools for python3, which can be installed with pip3 install pyelftools.

    #!/usr/bin/env python3
    
    import sys
    
    from elftools.elf.elffile import ELFFile
    from elftools.elf.descriptions import describe_p_type
    
    if len(sys.argv) != 2:
        print("Usage: elf_rwe <elf>")
    
    name = sys.argv[1]
    with open(name, "rb") as f:
        elf = ELFFile(f)
    
        rwe_offsets = []
        relro_offsets = []
        for i in range(elf['e_phnum']):
            program_offset = elf['e_phoff'] + i * elf['e_phentsize']
            f.seek(program_offset)
            program_header = elf.structs.Elf_Phdr.parse_stream(f)
    
            if program_header['p_type'] == "PT_LOAD":
                rwe_offsets.append(program_offset)
            if program_header['p_type'] == "PT_GNU_RELRO":
                relro_offsets.append(program_offset)
    
        f.seek(0)
        b = list(f.read())
    
        # Zap RELRO
        pt_null = 0
        for off in relro_offsets:
            b[off] = pt_null
    
        # Fix permissions
        p_flags_offset = 4
        for off in rwe_offsets:
            b[off + p_flags_offset] = 0x7 # PF_X|PF_W|PF_R
    
    with open(name, "wb") as f:
        f.write(bytes(b))