#include <sys/mman.h>
#include <stdint.h>
int main() {
char function[2] = { 0xEB, 0xFE };
mprotect(((intptr_t)function | 0xFFF) - 0xFFF, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE);
((void(*)())function)();
}
Now the thing you will notice is
mprotect
. This is a function to tell your operating system that a given range of memory should have its properties modified. In this case, the function
array is on the stack and sane operating systems map that as non-executable, to prevent you from executing your stack contents. The mprotect call circumvents this by explicitly asking it to be executable, and my OS is nice enough to honor such a request. You can try this and you will see it crashes if you omit it.So then the big question comes with this bit of code
const char function[] = { 0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3 };
int main() {
return ((int(*)())function)();
}
As you can see, no mprotect call. The array is not modified in any evil way (such as "__attribute__((section(".text")))" to force it to be executable, and it is not. Yet, this runs just fine!
So what's going on? Let's investigate the binary to see what's up.
$ objdump -d test | grep 2[aA]
$
There's definitely no 2A byte in the code. This would show it if it were code - so it is definitely in the data section. In fact, it'll be in the .rodata section:
$ objdump -h test
test: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
12 .text 000001c2 0000000000000530 0000000000000530 00000530 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .rodata 0000000a 0000000000000700 0000000000000700 00000700 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame 000000f4 0000000000000740 0000000000000740 00000740 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .init_array 00000008 0000000000200de0 0000000000200de0 00000de0 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .data 00000010 0000000000201000 0000000000201000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
I've omitted the irrelevant sections for readability. To note is that all READONLY sections are grouped together and mapped to the first page of memory, and the writable sections are grouped up and mapped to the second (2MB) page of memory. Look at the VMA column for the target Virtual Memory Address.
The .text section requires it to be executable - so the page that contains it will be executable. That page runs until beyond the end of .rodata though - so by extension, all of .rodata will be executable! The runtime doesn't even notice that we have data there, as the page granularity of the protection does not allow it to tell the OS. You can remove the "const" from the array; that will move it into .data and by extension a different page, causing it to be guarded by the non-executability of the .data section.
Note that this assumes you have a processor with NX (No-Execute) or XD (eXecute-Disable) bit. If you do not, the code will work regardless as your processor has no way to stop it.