The library code is shared. I can trivially search memory for a pattern
that matches the routine I want to call. The memory can be read. The
pattern is known beforehand since the binaries are widely distributed.
The pattern match is a small loop.
The typical buffer overflow, especially inline buffers, simply overwrites
inline instructions. I've seen a buffer overflow in the several hundred K
range of code. You can statically link the library code in the exploit given
that much code in the overflow. Forcing code areas to be read-only would
disallow inline buffers and require malloced buffers.
I also understand the stack-execute issue. I can think of ways to exploit
the system by stomping on return addresses in stack frames and even playing
spaghetti-stack continuation games to leave my exploit around. All of these
I can explain to my students.
These are all good changes. I've just drawn a complete blank about how
to explain fragmenting memory, especially after I've explained how and why
segment registers exist.