Update: You seem to be expecting more concrete information, so I expanded my post a bit. As far as I know, however, no code besides the actual program, libraries and kernel code is usually mapped to memory when your program is run in user mode.
There are several places you could redirect the control flow to, in case the stack is not executable:
- ret2data: Place the exploit code into the data section and then point the return address there. Often not possible.
- ret2text: Jump to existing code in the
.text section of the exploited binary. This includes jumping to DLLs and other shared libraries (and theoretically even kernel code, without a mode switch).
- chained returns: A technique that can be used to combine several methods. Basically, you prepare the stack so that the returning instruction of the first function will jump to the second function, and so on.
- partial returns: Another technique. You don't have to jump to the beginning of a function, but can also jump in the middle or near the end.
- ret2syscall: A practical example what you can do. Basically, you have an
int 0x80 instruction (a linux system call that expects its arguments in registers) and any function that cleans up the stack, for instance
pop eax; pop ecx; pop edx; pop ebx; ret; You fill the stack with the address of the
pop eax instruction, then the values of the registers and then the address of the syscall.