I don't think there is any way to do this using just standard C.
However you can use evil platform specific tricks to get an ides of how your memory mappings look. On Linux, the file /proc/(pid)/maps
will list the memory maps of process pid
, including read/write permission status. This is how it looks for a simple cat
process on my machine:
00400000-0040c000 r-xp 00000000 00:13 1237228 /usr/bin/cat
0060b000-0060c000 r--p 0000b000 00:13 1237228 /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 00:13 1237228 /usr/bin/cat
01864000-01885000 rw-p 00000000 00:00 0 [heap]
7fe7a5e0b000-7fe7a6121000 r--p 00000000 00:13 1487092 /usr/lib/locale/locale-archive
7fe7a6123000-7fe7a62ba000 r-xp 00000000 00:13 1486770 /usr/lib/libc-2.23.so
7fe7a62ba000-7fe7a64ba000 ---p 00197000 00:13 1486770 /usr/lib/libc-2.23.so
7fe7a64ba000-7fe7a64be000 r--p 00197000 00:13 1486770 /usr/lib/libc-2.23.so
7fe7a64be000-7fe7a64c0000 rw-p 0019b000 00:13 1486770 /usr/lib/libc-2.23.so
7fe7a64c0000-7fe7a64c4000 rw-p 00000000 00:00 0
7fe7a64cb000-7fe7a64ee000 r-xp 00000000 00:13 1486769 /usr/lib/ld-2.23.so
7fe7a66cc000-7fe7a66ee000 rw-p 00000000 00:00 0
7fe7a66ee000-7fe7a66ef000 r--p 00023000 00:13 1486769 /usr/lib/ld-2.23.so
7fe7a66ef000-7fe7a66f0000 rw-p 00024000 00:13 1486769 /usr/lib/ld-2.23.so
7fe7a66f0000-7fe7a66f1000 rw-p 00000000 00:00 0
7fe7a66f5000-7fe7a66f8000 rw-p 00000000 00:00 0
7ffe398e8000-7ffe39909000 rw-p 00000000 00:00 0 [stack]
7ffe3999b000-7ffe3999e000 r--p 00000000 00:00 0 [vvar]
7ffe3999e000-7ffe399a0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
So from this you can see that the program image itself is mapped near the beginning of virtual memory, the heap is slightly higher up, the stack is mapped to 7ffe398e8000-7ffe39909000
and the C library and dynamic linker are also loaded into memory.
Note that each file is mapped several times. For instance, /usr/bin/cat has both a read-only, executable and read-write segment. This is to prevent processes from writing to const
memory and from executing data.
From the mapping table you could get a fair idea of how your memory is laid out and what operations would be possible on these parts of memory.
Is this a good idea? NO.
Most likely not unless you are writing a debugger or similar development tool.
As an aside, the "shell" you are thinking about writing sounds very much like a debugger. Debuggers such as gdb can do the things you talk about, including evaluating C expressions and examining memory.
As a second aside, and because I find this very interesting, here is a small exercise:
As you can see there is some kernel memory mapped at ffffffffff600000
. If this theory is correct, we should be able to read that memory even though in general we can't access the kernel's memory. Let's try:
int main(void)
{
unsigned long *p = 0xffffffffff600000;
for (;;)
printf("0x%lx, ", *p++);
}
We get
0xf00000060c0c748, 0xccccccccccccc305, 0xcccccccccccccccc,
... Segmentation fault
If you wonder why this memory is readable to a user space process, it is to accelerate certain syscalls such as gettimeofday and allow them to work without having to switch to kernel mode as other syscalls have to. See e.g. this question.