libcrack.so – non-sleep thinking

bypassing devmem_is_allowed with kernel probes

02.09.2012 (5:09 am) – Filed under: debugging,hacking,linux,programming ::

In this article I’m going to illustrate how to read the full content of /dev/mem on linux 3.x machines. I will bypass the function devmem_is_allowed with a kernel return probe.

The kernel probes is a kernel component designed for kernel developers to debug the system internals.It can dynamically break into any kernel routine and modify the function’s behavour. This proves had been heavily since yeah by kernel developers. RedHat has build an user interface to kprobes called SystemTap

You can find kprobes’ documentation in Documentation/kprobes.txt. You should also download the article example files kprobe.tgz


The /dev/mem restriction

As far as you know, nobody can access beyond > 1024M using /dev/mem. This restriction was imposed since kernel ~v2.6.
If you try to read /dev/mem beyond that point, the follwing occurrs:

arch ~ # dd if=/dev/mem of=yeah.img bs=4K count=4096
dd: leyendo «/dev/mem»: Operación no permitida
257+0 registros leídos
257+0 registros escritos
1052672 bytes (1,1 MB) copiados, 0,192253 s, 5,5 MB/s
arch ~ # 

And this messages is shown in /var/log/kernel.log

Sep  2 02:16:30 arch kernel: [ 6446.740508] Program dd tried to access /dev/mem between 100000->200000.

I’m going to ilustrate how to bypass the restricction imposed by devmem_is_allowed(int) with kprobes.
The devmem_is_allowed code is located at /usr/src/linux/arch/x86/mm/init.c.

Looking for this funcion in /usr/src/linux/arch/x86/mm/init.c , we find:

/*
 * devmem_is_allowed() checks to see if /dev/mem access to a certain address
 * is valid. The argument is a physical page number.
 *
 *
 * On x86, access has to be given to the first megabyte of ram because that area
 * contains bios code and data regions used by X and dosemu and similar apps.
 * Access has to be given to non-kernel-ram areas as well, these contain the PCI
 * mmio resources as well as potential bios/acpi data regions.
 */
int devmem_is_allowed(unsigned long pagenr)
{
        if (pagenr <= 256)
                return 1;
        if (iomem_is_exclusive(pagenr << PAGE_SHIFT))
                return 0;
        if (!page_is_ram(pagenr))
                return 1;
        return 0;
}


Coding a kernel module

Currently, I’m using the kernel 3.5.3-1 on x86_64:

Linux arch 3.5.3-1-ARCH #1 SMP PREEMPT Sun Aug 26 09:14:51 CEST 2012 x86_64 GNU/Linux

It is needed to code a kernel return probe to overwrite the return value of devmem_is_allowed(int).So, the goal is to program a function which checks the return code of devmem_is_allowed (saved in the %eax register on i386/x86_64), and change It to 1 when this value equals 0.

For building a kernel module, 2 files are needed:

  1. Makefile
  2. Source.c

Here is the Makefile for building the kernel module kprobe.c

obj-m += kprobe.o
all: 
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

The kernel module source kprobe.c:

/*
 * root@libcrack.so
 * kprobe to bypass > 1024M read from /dev/mem
 * From  /usr/src/linux-3.6-rc3/arch/x86/mm/init.c:
 *     devmem_is_allowed() checks to see if /dev/mem access to a certain address
 *     is valid. The argument is a physical page number.
 *     On x86, access has to be given to the first megabyte of ram because that area
 *     contains bios code and data regions used by X and dosemu and similar apps.
 *     Access has to be given to non-kernel-ram areas as well, these contain the PCI
 *     mmio resources as well as potential bios/acpi data regions.
 *
 *     int devmem_is_allowed(unsigned long pagenr)
 *     {
 *             if (pagenr <= 256)
 *                     return 1;
 *             if (iomem_is_exclusive(pagenr << PAGE_SHIFT))
 *                     return 0;
 *             if (!page_is_ram(pagenr))
 *                     return 1;
 *             return 0;
 *     }
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ptrace.h>
#include <linux/kprobes.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("root@libcrack.so");
MODULE_DESCRIPTION("kprobe to bypass >1024M read from /dev/mem");

/**************** CONSTANTS ***************/
static const char *func_name = "devmem_is_allowed";

/**************** FUNC HOOKS ***************/
static int ret_handler (struct kretprobe_instance *rp, struct pt_regs *regs)
{
    /* denied access bypass  */
    if (regs->ax == 0) {
        //printk("Intercepted %s returns (%%eax) 0 => setting %%eax to 0x01\n", func_name);
        regs->ax = 0x1;
    }
    return 0;   /* not reached */
}

/**************** KRETPROBE ***************/
static struct kretprobe krp = {
    .handler = ret_handler,
    .maxactive = 20 /* Probe up to 20 instances concurrently. */
};

/**************** INIT MODULE ***************/
int init_module(void)
{
    int ret;
    printk("Activating %s kreprobe \n", func_name);

    krp.kp.symbol_name = func_name;

    if ((ret=register_kretprobe(&krp)) < 0) {
        printk("register_kprobe for %s failed!\n", func_name);
        } else {
            printk("Kprobed %s :-D \n", func_name);
            }
    return 0;
}

/**************** CLEANUP MODULE ***************/
void cleanup_module(void)
{
    printk("Unregistering %s kprobe\n", func_name);
    unregister_kretprobe(&krp);
    printk("%s kprobe unregistered\n", func_name);
}


Using the kretprobe module

First, It is needed to compile the kernel module with “make”

arch ~/kprobe # make
make -C /lib/modules/2.6.32-5-amd64/build M=/root/kprobe modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-5-amd64'
  CC [M]  /root/kprobe/kretprobe-bypass-devmem_is_allowed.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /root/kprobe/kretprobe-bypass-devmem_is_allowed.mod.o
  LD [M]  /root/kprobe/kretprobe-bypass-devmem_is_allowed.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-5-amd64'
arch ~/kprobe # 

Then, insert the kernel module with “insmod”

arch ~/kprobe # insmod kretprobe-bypass-devmem_is_allowed.ko

You must see the following loglines in /var/log/kernel.log

Sep  2 04:03:04 arch kernel: [1365788.135295] Activating devmem_is_allowed kreprobe 
Sep  2 04:03:04 arch kernel: [1365788.135730] Kprobed devmem_is_allowed :-D

Then, try to dump /dev/mem content into the filesystem:

arch ~/kprobe # dd if=/dev/mem of=/root/mem.img &
[1] 31661

You can check the increasing size of the dump file:

arch ~/kprobe # ls -la /root/mem.img 
-rw-r--r-- 1 root root 3517398528 Sep  2 04:11 /root/mem.img
arch ~/kprobe #  

To make “dd” binary print how much has been dumped, send a SIGUSR1 signal to the dd process

arch ~/kprobe # ps aux | grep if= | grep -v grep | awk '{print $2}' | xargs kill -SIGUSR1
6879303+0 records in
6879302+0 records out
3522202624 bytes (3.5 GB) copied, 435.751 s, 8.1 MB/s
arch ~/kprobe #  


Conclusions

It is clear that we can hook ANY kernel function with kprobes. An evil coder can hook determined
syscalls and take complete control over the system.

This tiny source code can help forensic investigators grab an actual RAM image via /dev/mem on Linux i386/x86_64 systems.

It is interesting that Debian stable comes with KPROBES active by default:

debian:~# 
debian:~# grep KPR /boot/config-2.6.32-5-amd64 
CONFIG_KPROBES=y
CONFIG_HAVE_KPROBES=y
debian:~# 
debian:~# 
debian:~# uname -a
Linux debian 2.6.32-5-amd64 #1 SMP Sun May 6 04:00:17 UTC 2012 x86_64 GNU/Linux
debian:~# 
debian:~# 
debian:~# cat /etc/debian_version 
6.0.5
debian:~# 

As said, kprobes gives great advantage to an evil coder. Why are they actives by default? There is really a need to have such debug feature enabled in debian stable kernels?

Android Devices

For the curious ones, android 4 looks like having KPROBES active by default

borja@arch:~/android-sdks/tools $ ./android list
Available Android targets:
.
.
.
----------
id: 2 or "android-15"
     Name: Android 4.0.3
     Type: Platform
     API level: 15
     Revision: 2
     Skins: WVGA800 (default), WQVGA432, WXGA800, WVGA854, WXGA720, WQVGA400, QVGA, WSVGA, HVGA
     ABIs : armeabi-v7a
Available Android Virtual Devices:
    Name: android4
    Path: /home/borja/.android/avd/android4.avd
  Target: Android 4.0.3 (API level 15)
     ABI: armeabi-v7a
    Skin: WVGA800
  Sdcard: 128M
borja@arch:~/android-sdks/tools $ cd ../platform-tools
borja@arch:~/android-sdks/platform-tools $ sudo ./adb root
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
adbd is already running as root
borja@arch:~/android-sdks/platform-tools $ 
borja@arch:~/android-sdks/platform-tools $ ./adb devices
List of devices attached 
emulator-5554	device

borja@arch:~/android-sdks/platform-tools $ 
borja@arch:~/android-sdks/platform-tools $ ./adb shell
# cat /proc/version
Linux version 2.6.29-g46b05b2 (vchtchetkine@vc-irv.irv.corp.google.com) (gcc version 4.4.3 (GCC) ) #28 Thu Nov 17 06:39:36 PST 2011
# 
# exit
borja@arch:~/android-sdks/platform-tools $
borja@arch:~/android-sdks/platform-tools $
borja@arch:~/android-sdks/platform-tools $ ./adb pull /proc/config.gz
85 KB/s (7170 bytes in 0.081s)
borja@arch:~/android-sdks/platform-tools $
borja@arch:~/android-sdks/platform-tools $
borja@arch:~/android-sdks/platform-tools $ zgrep KPR config.gz 
CONFIG_HAVE_KPROBES=y
borja@arch:~/android-sdks/platform-tools $
borja@arch:~/android-sdks/platform-tools $ # LOLLLLLLLLL 


Usefull links



Happy hacking!

2 Responses to “bypassing devmem_is_allowed with kernel probes”

  1. Frank Ch. Eigler Says:

    In systemtap, this would have been done as the one-liner

    # stap -g -e ‘probe kernel.function(“devmem_is_allowed”).return { $return = 1 }’

    It is no special risk to have the kprobes facility in the kernel, since it is accessible only to kernel modules. If one can load a kernel module, the security game is over anyway, never mind minor protections against /dev/mem reading. (Userspace perf probe can also attach, but in a strictly read-only way, and only if the invoking user is already root.)

  2. borja Says:

    Hi Frank!

    Yeah, you totally right! I know about RH’s SystemTap; indeed, I tought about writting a new post about SystemTap!
    But the point is that usually, Linux boxes do not ship SystemTap by default, but kprobes facility is active.

    So, if you manage to root remotely a box, SystemTap will not be there :-(

    An interesting technique is to patch range_is_allowed(), and then inject code in realtime via /dev/mem (think about IDT smashing, etc)
    You have a handfull of differents combinations :-)

    Cheers! ;-)