34

I have been learning about rootkits recently and have noticed this hooking techniques that kernel-land rootkits use in order perform malicious actions.

Where a typical hooking operation would be to hook on to a legitimate system call, and then replace the legitimate action with the malicious action first, before actually calling the legitimate action.

But if that is the case, why not make the system call table to be unmodifiable from the start?

  • 26
    "make the system call table to be unmodifiable from the start" How do you propose doing that (in a way that wouldn't be trivial for a kernel rootkit to undo)? – Joseph Sible-Reinstate Monica May 28 '19 at 0:51
  • 3
    Oh okay, I see where you are coming from. That made a lot of sense! Thanks :) – meoware May 28 '19 at 1:18
  • 2
    I guess on x86, taking advantage of all four instead of only two privilege rings would allow some possibilities here :) – rackandboneman May 28 '19 at 22:56
  • 1
    @JosephSible There are a few ways to do that on a modern CPU (esp. with EPT and VT-x), but there are still a thousand other ways to hook syscalls that don't involve modifying the syscall table. – forest May 29 '19 at 3:57
58

The syscall table is read-only, and has been since kernel 2.6.16. However, a kernel rootkit has the ability to make it writable again. All it needs to do is execute a function like this* with the table as the argument:

static void set_addr_rw(const unsigned long addr)
{
    unsigned int level;
    pte_t *pte;

    pte = lookup_address(addr, &level);
    if (pte->pte &~ _PAGE_RW)
        pte->pte |= _PAGE_RW;

    local_flush_tlb();
}

This changes the permissions of the syscall table and makes it possible to edit it. If this doesn't work for whatever reason, then write protection in the kernel can be globally disabled with the following ASM:

cli
mov %cr0, %eax
and $~0x10000, %eax
mov %eax, %cr0
sti

This disables interrupts, disables the WP (Write-Protect) bit in CR0, and re-enables interrupts.

So why is it marked as read-only if it's so easy to disable? One reason is that vulnerabilities exist which allow modifying kernel memory but not necessarily directly executing code. By marking critical areas of the kernel as read-only, it becomes more difficult to exploit them without finding an additional vulnerability to mark the pages as writable (or disable write-protection altogether). This doesn't provide very strong security, so the main reason that it is marked as read-only is to make it easier to catch any accidental overwrites caused by bugs from causing a catastrophic and unrecoverable system crash.

* The kernel's internal API changes all the time, so this exact function may not work on older kernels or newer kernels. Globally disabling CR0.WP in ASM however is guaranteed to work on all x86 systems regardless of the kernel version.

22

As noted by forest, modern Linux does not allow this, but it's easy to override.

However, historically it was useful (and maybe still is) for security purposes: hot-patching against vulnerabilities. Back in the 1990s and early 2000s, whenever a new vulnerability was announced for a syscall I didn't absolutely need (ptrace was a really common one back then), I'd write a kernel module to overwrite the function address in the syscall table with the address of a function that just performed return -ENOSYS;. This eliminated the attack surface until an upgraded kernel was available. For some dubious syscalls I didn't need that repeatedly had vulnerabilities, I just preemptively did this for them and left the module enabled all the time.

  • 4
    Heh, I did the same thing. It's really handy as a hacky fix. – forest May 29 '19 at 2:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.