Friday, December 15, 2006

A Kernel Extension... by hand

I generally like using Xcode. It typically does the job, and usually even makes the job easier. It shields the user from having to worry about mundane details that are all too common when building software. However, for the same reason it can increase a developer's productivity, it can also make it more difficult to understand what's actually going on. For example, Xcode has a template to create a Generic Kernel Extension. The template compiles with no modifications. The resulting kernel extension (KEXT) can then be loaded and unloaded without having to write one line of code. This is super cool, but it also hides some of the details of what a KEXT really is. So, today we'll write a "Hello, World" KEXT from scratch without the help of Xcode

(note the issues above do not apply to Xcode alone, rather they apply to almost all IDEs)

Well, let's just jump right in. Basically, a KEXT is a bundle on Mac OS X (it's also a "package"), which means it is a directory structure with some predefined form and an Info.plist file. Our sample KEXT will be named "MyKext.kext", and it will be in a directory structure that looks like this:

$ find MyKext.kext
MyKext.kext
MyKext.kext/Contents
MyKext.kext/Contents/Info.plist
MyKext.kext/Contents/MacOS
MyKext.kext/Contents/MacOS/MyKext


To start off, we need to make the basic directory structure.
$ cd
$ mkdir -p MyKext.kext/Contents/MacOS
$ cd MyKext.kext/Contents/MacOS


Now we can start writing our code. Our code will contain two main routines: MyKextStart() and MyKextStop(), which are called when the KEXT is loaded and unloaded respectively. It will also contain some required bookkeeping code that's needed in order to make our compiled binary proper. Our start and stop routines look like:
// File: mykext.c
#include <libkern/libkern.h>
#include <mach/mach_types.h>

kern_return_t MyKextStart(kmod_info_t *ki, void *d) {
printf("Hello, World!\n");
return KERN_SUCCESS;
}

kern_return_t MyKextStop(kmod_info_t *ki, void *d) {
printf("Goodbye, World!\n");
return KERN_SUCCESS;
}

... more to come in a minute


After these two methods (in the same mykext.c file) we need to put the required bookkeeping stuff.
extern kern_return_t _start(kmod_info_t *ki, void *data);
extern kern_return_t _stop(kmod_info_t *ki, void *data);

KMOD_EXPLICIT_DECL(net.unixjunkie.kext.MyKext, "1.0.0d1", _start, _stop)
__private_extern__ kmod_start_func_t *_realmain = MyKextStart;
__private_extern__ kmod_stop_func_t *_antimain = MyKextStop;
__private_extern__ int _kext_apple_cc = __APPLE_CC__;


This stuff basically declares some needed structures, and it also sets up our routines (MyKextStart() and MyKextStop()) so that they're called on load and unload, by assigning them to the _realmain and _antimain symbols respectively.

OK, now comes the tricky part: the compile. KEXTs are compiled statically, they can only use certain headers that are available in the kernel, and they can't link with the standard C library. These requirements basically translate into a gcc command like the following:

$ gcc -static mykext.c -o MyKext -fno-builtin -nostdlib -lkmod -r -mlong-branch -I/System/Library/Frameworks/Kernel.framework/Headers -Wall


If the planets are properly aligned, you won't get any errors or warnings, and you'll end up with a Mach-O object file in the current directory named MyKext. This is your actual compiled KEXT. (At this point, it's fun to inspect this file using otool. For example, otool -hV MyKext, and otool -l MyKext. Read the man page for otool(1) for more details here.)

Now, the last thing we need to do (before we actually load this thing up) is to give our KEXT an Info.plist. The easiest way to do this is to copy another KEXT's Info.plist file, and change the names of a few things. For this example, I'm going to copy /System/Library/Extensions/webdav_fs.kext/Contents/Info.plist.
$ cd ..
$ pwd
/Users/jgm/MyKext.kext/Contents
$ cp /System/Library/Extensions/webdav_fs.kext/Contents/Info.plist .


Now, you'll need to edit the file and change the value of the "CFBundleExecutable" key to MyKext, and the value of "CFBundleIdentifier" to net.unixjunkie.kext.MyKext (or whatever you set that value to in your mykext.c file).

Okay, it's show time. To load any KEXT, all files in the KEXT must be owned by root and be in group wheel. The files must also have certain permissions in order to load. Here's the steps to load the KEXT.

$ cd /tmp
$ sudo -s
# cp -rp ~/MyKext.kext .
# chown -R root:wheel MyKext.kext
# chmod -R 0644 MyKext.kext
# kextload -v MyKext.kext
kextload: extension MyKext.kext appears to be valid
kextload: loading extension MyKext.kext
kextload: sending 1 personality to the kernel
kextload: MyKext.kext loaded successfully
# tail -1 /var/log/system.log
Dec 15 20:15:47 jgm-mac kernel[0]: Hello, World!


We can see that our MyKextStart() was called. Now let's unload it and see what happens.

# kextunload -v MyKext.kext
kextunload: unload kext MyKext.kext succeeded


Wahoo! It looks like we made a kernel extension with our own 10 fingers, and it worked! :-)

That was fun. Check out Amit Singh's Mac OS X Internals book for more cool bits about what that required "bookkeeping" stuff was in our source file, and why it's required.

1 comment:

Anonymous said...

An interesting kernel extension is Interceptor. It tries to block write & delete accesses to the filesystem. (Just wondering if this could be daemonized for selected files!)

http://www.stuffonfire.com/2005/12/12/mfp-interceptor-pre-release-2/

http://www.stuffonfire.com/P/Interceptor-2.src.tar.gz