Yes there is, the suckit rootkit as featured in Phrack is one such example, which modifies the linux kernel via
/dev/kmem. Essentially, the goals remain the same - replace entries in the system call table to do what we want them to do. The difference here being that the modification is done via
The grsecurity/PaX patches for the kernel include support/configuration for disabling access to
/dev/port - see the entry here.
The difference between
/dev/kmem is explained here.
The notable difference between modifying memory this way and loading in a kernel module is the exposure of symbols. Most kernel module rootkits are compiled with
static functions so these functions are not exported from the code; however, there are other techniques that might betray the existence of a kernel module (the init function would need to be exportable, for example). A rootkit modifying kernel memory directly would not be visible in a scan of the kernel address space except in what it had changed (i.e. if you know where the syscall table should point to and what it is pointing at, you might detect it, unless the author is sufficiently clever to ensure further reads of
/dev/kmem give out the correct addresses).
As to whether a bad guy can directly modify memory - a root process is technically still in userspace; that is to say it is still a part of ring 3 and does not have the ability to escape its virtual memory sandbox. The only memory any ring 3 process can modify is its own virtual address space, which as far as it is concerned is what memory looks like. A ring 3 process must abide by all the rules of marked memory; for example, if a page is marked read only it cannot be modified by a process (the CPU refuses and tells the OS). However, a ring 0 process (cpu supervisor mode) can modify any memory it likes and can go so far as to ask the CPU to ignore the status of memory pages (i.e. you can ignore the fact pages are read only).
So what I'm trying to get at is that a root process does not have implicit access to memory by way of being a root process - it must still ask and gain such access via the kernel. It's just that the kernel will obey blindly. So one solution is to use the PaX patches, making the kernel refuse such requests.