Title : Games with kernel Memory...FreeBSD Style
Author : jkong
==Phrack Inc.==
Volume 0x0b, Issue 0x3f, Phile #0x07 of 0x14
|=-------=[ Playing Games With Kernel Memory ... FreeBSD Style ]=--------=|
|=-----------------------------------------------------------------------=|
|=-----------------=[ Joseph Kong <[email protected]> ]=-----------------=|
|=--------------------------=[ July 8, 2005 ]=---------------------------=|
--[ Contents
1.0 - Introduction
2.0 - Finding System Calls
3.0 - Understanding Call Statements And Bytecode Injection
4.0 - Allocating Kernel Memory
5.0 - Putting It All Together
6.0 - Concluding Remarks
7.0 - References
--[ 1.0 - Introduction
The kernel memory interface or kvm interface was first introduced in
SunOS. Although it has been around for quite some time, many people still
consider it to be rather obscure. This article documents the basic usage
of the Kernel Data Access Library (libkvm), and will explore some ways to
use libkvm (/dev/kmem) in order to alter the behavior of a running FreeBSD
system.
FreeBSD kernel hacking skills of a moderate level (i.e. you know how to
use ddb), as well as a decent understanding of C and x86 Assembly (AT&T
Syntax) are required in order to understand the contents of this article.
This article was written from the perspective of a FreeBSD 5.4 Stable
System.
Note: Although the techniques described in this article have been explored
in other articles (see References), they are always from a Linux or Windows
perspective. I personally only know of one other text that touches on the
information contained herein. That text entitled "Fun and Games with
FreeBSD Kernel Modules" by Stephanie Wehner explained some of the things
one can do with libkvm. Considering the fact that one can do much more,
and that documentation regarding libkvm is scarce (man pages and source
code aside), I decided to write this article.
--[ 2.0 - Finding System Calls
Note: This section is extremely basic, if you have a good grasp of the
libkvm functions read the next paragraph and skip to the next section.
Stephanie Wehner wrote a program called checkcall, which would check if
sysent[CALL] had been tampered with, and if so would change it back to the
original function. In order to help with the debugging during the latter
sections of this article, we are going to make use of checkcall's find
system call functionality. Following is a stripped down version of
checkcall, with just the find system call function. It is also a good
example to learn the basics of libkvm from. A line by line explanation of
the libkvm functions appears after the source code listing.
find_syscall.c:
/*
* Takes two arguments: the name of a syscall and corresponding number,
* and reports the location in memory where the syscall is located.
*
* If you enter the name of a syscall with an incorrect syscall number,
* the output will be fubar. Too lazy to implement a check
*
* Based off of Stephanie Wehner's checkcall.c,v 1.1.1.1
*
* find_syscall.c,v 1.0 2005/05/20
*/
#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/sysent.h>
#include <sys/syscall.h>
int main(int argc, char *argv[]) {
char errbuf[_POSIX2_LINE_MAX];
kvm_t *kd;
u_int32_t addr;
int callnum;
struct sysent call;
struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };
/* Check for the correct number of arguments */
if(argc != 3) {
printf("Usage:\n%s <name of system call> <syscall number>"
" \n\n", argv[0]);
printf("See /usr/src/sys/sys/syscall.h for syscall numbers"
" \n");
exit(0);
}
/* Find the syscall */
nl[0].n_name = "sysent";
nl[1].n_name = argv[1];
callnum = atoi(argv[2]);
printf("Finding syscall %d: %s\n\n", callnum, argv[1]);
/* Initialize kernel virtual memory access */
kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
if(kd == NULL) {
fprintf(stderr, "ERROR: %s\n", errbuf);
exit(-1);
}
/* Find the addresses */
if(kvm_nlist(kd, nl) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
if(!nl[0].n_value) {
fprintf(stderr, "ERROR: %s not found (fubar?)\n"
, nl[0].n_name);
exit(-1);
}
else {
printf("%s is 0x%x at 0x%x\n", nl[0].n_name, nl[0].n_type
, nl[0].n_value);
}
if(!nl[1].n_value) {
fprintf(stderr, "ERROR: %s not found\n", nl[1].n_name);
exit(-1);
}
/* Calculate the address */
addr = nl[0].n_value + callnum * sizeof(struct sysent);
/* Print out location */
if(kvm_read(kd, addr, &call, sizeof(struct sysent)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
else {
printf("sysent[%d] is at 0x%x and will execute function"
" located at 0x%x\n", callnum, addr, call.sy_call);
}
if(kvm_close(kd) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
exit(0);
}
There are five functions from libkvm that are included in the above
program; they are:
kvm_openfiles
kvm_nlist
kvm_geterr
kvm_read
kvm_close
kvm_openfiles:
Basically kvm_openfiles initializes kernel virtual memory access, and
returns a descriptor to be used in subsequent kvm library calls. In
find_syscall the syntax was as follows:
kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
kd is used to store the returned descriptor, if after the call kd
equals NULL then an error has occurred.
The first three arguments correspond to const char *execfile, const
char *corefile, and const char *swapfiles respectively. However for our
purposes they are unnecessary, hence NULL. The fourth argument indicates
that we want read/write access. The fifth argument indicates which buffer
to place any error messages, more on that later.
kvm_nlist:
The man page states that kvm_nlist retrieves the symbol table entries
indicated by the name list argument (struct nlist). The members of struct
nlist that interest us are as follows:
char *n_name; /* symbol name (in memory) */
unsigned long n_value; /* address of the symbol */
Prior to calling kvm_nlist in find_syscall a struct nlist array was
setup as follows:
struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };
nl[0].n_name = "sysent";
nl[1].n_name = argv[1];
The syntax for calling kvm_nlist is as follows:
kvm_nlist(kd, nl)
What this did was fill out the n_value member of each element in the
array nl with the starting address in memory corresponding to the value in
n_name. In other words we now know the location in memory of sysent and the
user supplied syscall (argv[1]). nl was initialized with three elements
because kvm_nlist expects as its second argument a NULL terminated array of
nlist structures.
kvm_geterr:
As stated in the man page this function returns a string describing the
most recent error condition. If you look through the above source code
listing you will see kvm_geterr gets called after every libkvm function,
except kvm_openfiles. kvm_openfiles uses its own unique form of error
reporting, because kvm_geterr requires a descriptor as an argument, which
would not exist if kvm_openfiles has not been called yet. An example usage
of kvm_geterr follows:
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
kvm_read:
This function is used to read kernel virtual memory. In find_syscall
the syntax was as follows:
kvm_read(kd, addr, &call, sizeof(struct sysent))
The first argument is the descriptor. The second is the address to
begin reading from. The third argument is the user-space location to store
the data read. The fourth argument is the number of bytes to read.
kvm_close:
This function breaks the connection between the pointer and the kernel
virtual memory established with kvm_openfiles. In find_syscall this
function was called as follows:
kvm_close(kd)
The following is an algorithmic explanation of find_syscall.c:
1. Check to make sure the user has supplied a syscall name and
number. (No error checking, just checks for two arguments)
2. Setup the array of nlist structures appropriately.
3. Initialize kernel virtual memory access. (kvm_openfiles)
4. Find the address of sysent and the user supplied syscall.
(kvm_nlist)
5. Calculate the location of the syscall in sysent.
6. Copy the syscall's sysent structure from kernel-space to
user-space. (kvm_read)
7. Print out the location of the syscall in the sysent structure
and the location of the executed function.
8. Close the descriptor (kvm_close)
In order to verify that the output of find_syscall is accurate, one can
make use of ddb as follows:
Note: The output below was modified in order to meet the 75 character per
line requirement.
[---------------------------------------------------------]
ghost@slavetwo:~#ls
find_syscall.c
ghost@slavetwo:~#gcc -o find_syscall find_syscall.c -lkvm
ghost@slavetwo:~#ls
find_syscall find_syscall.c
ghost@slavetwo:~#sudo ./find_syscall
Password:
Usage:
./find_syscall <name of system call> <syscall number>
See /usr/src/sys/sys/syscall.h for syscall numbers
ghost@slavetwo:~#sudo ./find_syscall mkdir 136
Finding syscall 136: mkdir
sysent is 0x4 at 0xc06dc840
sysent[136] is at 0xc06dcc80 and will execute function located at
0xc0541900
ghost@slavetwo:~#KDB: enter: manual escape to debugger
[thread pid 12 tid 100004 ]
Stopped at kdb_enter+0x32: leave
db> examine/i 0xc0541900
mkdir: pushl %ebp
db>
mkdir+0x1: movl %esp,%ebp
db> c
ghost@slavetwo:~#
[---------------------------------------------------------]
--[ 3.0 - Understanding Call Statements And Bytecode Injection
In x86 Assembly a Call statement is a control transfer instruction,
used to call a procedure. There are two types of Call statements Near and
Far, for the purposes of this article one only needs to understand a Near
Call. The following code illustrates the details of a Near Call statement
(in Intel Syntax):
0200 BB1295 MOV BX,9512
0203 E8FA00 CALL 0300
0206 B82F14 MOV AX,142F
In the above code snippet, when the IP (Instruction Pointer) gets to
0203 it will jump to 0300. The hexadecimal representation for CALL is E8,
however FA00 is not 0300. 0x300 - 0x206 = 0xFA. In a near call the IP
address of the instruction after the Call is saved on the stack, so the
called procedure knows where to return to. This explains why the operand
for Call in this example is 0xFA00 and not 0x300. This is an important
point and will come into play later.
One of the more entertaining things one can do with the libkvm
functions is patch kernel virtual memory. As always we start with a very
simple example ... Hello World! The following is a kld which adds a
syscall that functions as a Hello World! program.
hello.c:
/*
* Prints "FreeBSD Rox!" 10 times
*
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
/*
* The function for implementing the syscall.
*/
static int
hello (struct thread *td, void *arg)
{
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
printf ("FreeBSD Rox!\n");
return 0;
}
/*
* The `sysent' for the new syscall
*/
static struct sysent hello_sysent = {
0, /* sy_narg */
hello /* sy_call */
};
/*
* The offset in sysent where the syscall is allocated.
*/
static int offset = 210;
/*
* The function called at load/unload.
*/
static int
load (struct module *module, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_LOAD :
printf ("syscall loaded at %d\n", offset);
break;
case MOD_UNLOAD :
printf ("syscall unloaded from %d\n", offset);
break;
default :
error = EOPNOTSUPP;
break;
}
return error;
}
SYSCALL_MODULE(hello, &offset, &hello_sysent, load, NULL);
The following is the user-space program for the above kld:
interface.c:
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>
int main(int argc, char **argv) {
return syscall(210);
}
If we compile the above kld using a standard Makefile, load it, and
then run the user-space program, we get some very annoying output. In order
to make this syscall less annoying we can use the following program. As
before an explanation of any new functions and concepts appears after the
source code listing.
test_call.c:
/*
* Test understanding of call statement:
* Operand for call statement is the difference between the called function
* and the address of the instruction following the call statement.
*
* Tested on syscall hello. Normally prints out "FreeBSD Rox!" 10 times,
* after patching only prints it out once.
*
* test_call.c,v 2.1 2005/06/15
*/
#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
/*
* Offset of string to be printed
* Starting at the beginning of the syscall hello
*/
#define OFFSET_1 0xed
/*
* Offset of instruction following call statement
*/
#define OFFSET_2 0x12
/*
* Replacement code
*/
unsigned char code[] =
"\x55" /* push %ebp */
"\x89\xe5" /* mov %esp,%ebp */
"\x83\xec\x04" /* sub $0x4,%esp */
"\xc7\x04\x24\x00\x00\x00\x00" /* movl $0,(%esp) */
"\xe8\x00\x00\x00\x00" /* call printf */
"\xc9" /* leave */
"\x31\xc0" /* xor %eax,%eax */
"\xc3" /* ret */
"\x8d\xb4\x26\x00\x00\x00\x00" /* lea 0x0(%esi),%esi */
"\x8d\xbc\x27\x00\x00\x00\x00"; /* lea 0x0(%edi),%edi */
int main(int argc, char *argv[]) {
char errbuf[_POSIX2_LINE_MAX];
kvm_t *kd;
u_int32_t offset_1;
u_int32_t offset_2;
struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };
/* Initialize kernel virtual memory access */
kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
if(kd == NULL) {
fprintf(stderr, "ERROR: %s\n", errbuf);
exit(-1);
}
/* Find the address of hello and printf */
nl[0].n_name = "hello";
nl[1].n_name = "printf";
if(kvm_nlist(kd, nl) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
if(!nl[0].n_value) {
fprintf(stderr, "ERROR: Symbol %s not found\n"
, nl[0].n_name);
exit(-1);
}
if(!nl[1].n_value) {
fprintf(stderr, "ERROR: Symbol %s not found\n"
, nl[1].n_name);
exit(-1);
}
/* Calculate the correct offsets */
offset_1 = nl[0].n_value + OFFSET_1;
offset_2 = nl[0].n_value + OFFSET_2;
/* Set the code to contain the correct addresses */
*(unsigned long *)&code[9] = offset_1;
*(unsigned long *)&code[14] = nl[1].n_value - offset_2;
/* Patch hello */
if(kvm_write(kd, nl[0].n_value, code, sizeof(code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
printf("Luke, I am your father!\n");
/* Close kd */
if(kvm_close(kd) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
exit(0);
}
The only libkvm function that is included in the above program that
hasn't been discussed before is kvm_write.
kvm_write:
This function is used to write to kernel virtual memory. In test_call
the syntax was as follows:
kvm_write(kd, nl[0].n_value, code, sizeof(code))
The first argument is the descriptor. The second is the address to
begin writing to. The third argument is the user-space location to read
from. The fourth argument is the number of bytes to read.
The replacement code (bytecode) in test_call was generated with help of
objdump.
[---------------------------------------------------------]
ghost@slavetwo:~#objdump -DR hello.ko | less
hello.ko: file format elf32-i386-freebsd
Disassembly of section .hash:
00000094 <.hash>:
94: 11 00 adc %eax,(%eax)
96: 00 00 add %al,(%eax)
OUTPUT SNIPPED
Disassembly of section .text:
00000500 <hello>:
500: 55 push %ebp
501: 89 e5 mov %esp,%ebp
503: 83 ec 04 sub $0x4,%esp
506: c7 04 24 ed 05 00 00 movl $0x5ed,(%esp)
509: R_386_RELATIVE *ABS*
50d: e8 fc ff ff ff call 50e <hello+0xe>
50e: R_386_PC32 printf
512: c7 04 24 ed 05 00 00 movl $0x5ed,(%esp)
515: R_386_RELATIVE *ABS*
519: e8 fc ff ff ff call 51a <hello+0x1a>
51a: R_386_PC32 printf
51e: c7 04 24 ed 05 00 00 movl $0x5ed,(%esp)
521: R_386_RELATIVE *ABS*
525: e8 fc ff ff ff call 526 <hello+0x26>
526: R_386_PC32 printf
OUTPUT SNIPPED
57e: c9 leave
57f: 31 c0 xor %eax,%eax
581: c3 ret
582: 8d b4 26 00 00 00 00 lea 0x0(%esi),%esi
589: 8d bc 27 00 00 00 00 lea 0x0(%edi),%edi
[---------------------------------------------------------]
Note: Your output may vary depending on your compiler version and flags.
Comparing the output of the text section with the bytecode in test_call
one can see that they are essentially the same, minus setting up nine more
calls to printf. An important item to take note of is when objdump reports
something as being relative. In this case two items are; movl $0x5ed,(%esp)
(sets up the string to be printed) and call printf. Which brings us to ...
In test_call there are two #define statements, they are:
#define OFFSET_1 0xed
#define OFFSET_2 0x12
The first represents the address of the string to be printed relative
to the beginning of syscall hello (the number is derived from the output of
objdump). While the second represents the offset of the instruction
following the call to printf in the bytecode. Later on in test_call there
are these four statements:
/* Calculate the correct offsets */
offset_1 = nl[0].n_value + OFFSET_1;
offset_2 = nl[0].n_value + OFFSET_2;
/* Set the code to contain the correct addresses */
*(unsigned long *)&code[9] = offset_1;
*(unsigned long *)&code[14] = nl[1].n_value - offset_2;
From the comments it should be obvious what these four statements do.
code[9] is the section in bytecode where the address of the string to be
printed is stored. code[14] is the operand for the call statement; address
of printf - address of the next statement.
The following is the output before and after running test_call:
[---------------------------------------------------------]
ghost@slavetwo:~#ls
Makefile hello.c interface.c test_call.c
ghost@slavetwo:~#make
Warning: Object directory not changed from original /usr/home/ghost
@ -> /usr/src/sys
machine -> /usr/src/sys/i386/include
OUTPUT SNIPPED
J% objcopy % hello.kld
ld -Bshareable -d -warn-common -o hello.ko hello.kld
objcopy --strip-debug hello.ko
ghost@slavetwo:~#sudo kldload ./hello.ko
Password:
syscall loaded at 210
ghost@slavetwo:~#gcc -o interface interface.c
ghost@slavetwo:~#./interface
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
ghost@slavetwo:~#gcc -o test_call test_call.c -lkvm
ghost@slavetwo:~#sudo ./test_call
Luke, I am your father!
ghost@slavetwo:~#./interface
FreeBSD Rox!
ghost@slavetwo:~#
[---------------------------------------------------------]
--[ 4.0 - Allocating Kernel Memory
Being able to just patch kernel memory has its limitations since you
don't have much room to play with. Being able to allocate kernel memory
alleviates this problem. The following is a kld which does just that.
kmalloc.c:
/*
* Module to allow a non-privileged user to allocate kernel memory
*
* kmalloc.c,v 2.0 2005/06/01
* Date Modified 2005/06/14
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
/*
* Arguments for kmalloc
*/
struct kma_struct {
unsigned long size;
unsigned long *addr;
};
struct kmalloc_args { struct kma_struct *kma; };
/*
* The function for implementing kmalloc.
*/
static int
kmalloc (struct thread *td, struct kmalloc_args *uap) {
int error = 1;
struct kma_struct kts;
if(uap->kma) {
MALLOC(kts.addr, unsigned long*, uap->kma->size
, M_TEMP, M_NOWAIT);
error = copyout(&kts, uap->kma, sizeof(kts));
}
return (error);
}
/*
* The `sysent' for kmalloc
*/
static struct sysent kmalloc_sysent = {
1, /* sy_narg */
kmalloc /* sy_call */
};
/*
* The offset in sysent where the syscall is allocated.
*/
static int offset = 210;
/*
* The function called at load/unload.
*/
static int
load (struct module *module, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_LOAD :
uprintf ("syscall loaded at %d\n", offset);
break;
case MOD_UNLOAD :
uprintf ("syscall unloaded from %d\n", offset);
break;
default :
error = EOPNOTSUPP;
break;
}
return error;
}
SYSCALL_MODULE(kmalloc, &offset, &kmalloc_sysent, load, NULL);
The following is the user-space program for the above kld:
interface.c:
/*
* User Program To Interact With kmalloc module
*/
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>
struct kma_struct {
unsigned long size;
unsigned long *addr;
};
int main(int argc, char **argv) {
struct kma_struct kma;
if(argc != 2) {
printf("Usage:\n%s <size>\n", argv[0]);
exit(0);
}
kma.size = (unsigned long)atoi(argv[1]);
return syscall(210, &kma);
}
Using the techniques/functions described in the previous two sections
and the following algorithm coined by Silvio Cesare one can allocate kernel
memory without the use of a kld.
Silvio Cesare's kmalloc from user-space algorithm:
1. Get the address of some syscall
2. Write a function which will allocate kernel memory
3. Save sizeof(our_function) bytes of some syscall
4. Overwrite some syscall with our_function
5. Call newly overwritten syscall
6. Restore syscall
test_kmalloc.c:
/*
* Allocate kernel memory from user-space
*
* Algorithm to allocate kernel memory is as follows:
*
* 1. Get address of mkdir
* 2. Overwrite mkdir with function that calls man 9 malloc()
* 3. Call mkdir through int $0x80
* This will cause the kernel to run the new "mkdir" syscall, which will
* call man 9 malloc() and pass out the address of the newly allocated
* kernel memory
* 4. Restore mkdir syscall
*
* test_kmalloc.c,v 2.0 2005/06/24
*/
#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>
/*
* Offset of instruction following call statements
* Starting at the beginning of the function kmalloc
*/
#define OFFSET_1 0x3a
#define OFFSET_2 0x56
/*
* kmalloc function code
*/
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 used to store kernel address
*/
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);
}
/* Initialize kernel virtual memory access */
kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
if(kd == NULL) {
fprintf(stderr, "ERROR: %s\n", errbuf);
exit(-1);
}
/* Find the address of mkdir, M_TEMP, malloc, and copyout */
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);
}
}
/* Calculate the correct offsets */
offset_1 = nl[0].n_value + OFFSET_1;
offset_2 = nl[0].n_value + OFFSET_2;
/* Set the code to contain the correct addresses */
*(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;
/* Save mkdir syscall */
if(kvm_read(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Patch mkdir */
if(kvm_write(kd, nl[0].n_value, code, sizeof(code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Allocate kernel memory */
kma.size = (unsigned long)atoi(argv[1]);
syscall(136, &kma);
printf("Address of kernel memory: 0x%x\n", kma.addr);
/* Restore mkdir */
if(kvm_write(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Close kd */
if(kvm_close(kd) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
exit(0);
}
Using ddb one can verify the results of the above program as follows:
[---------------------------------------------------------]
ghost@slavetwo:~#ls
test_kmalloc.c
ghost@slavetwo:~#gcc -o test_kmalloc test_kmalloc.c -lkvm
ghost@slavetwo:~#sudo ./test_kmalloc
Usage:
./test_kmalloc <size>
ghost@slavetwo:~#sudo ./test_kmalloc 10
Address of kernel memory: 0xc2580870
ghost@slavetwo:~#KDB: enter: manual escape to debugger
[thread pid 12 tid 100004 ]
Stopped at kdb_enter+0x32: leave
db> examine/x 0xc2580870
0xc2580870: 70707070
db>
0xc2580874: 70707070
db>
0xc2580878: dead7070
db> c
ghost@slavetwo:~#
[---------------------------------------------------------]
--[ 5.0 - Putting It All Together
Knowing how to patch and allocate kernel memory gives one a lot of
freedom. This last section will demonstrate how to apply a call hook using
the techniques described in the previous sections. Typically call hooks on
FreeBSD are done by changing the sysent and having it point to another
function, we will not be doing this. Instead we will be using the following
algorithm (with a few minor twists, shown later):
1. Copy syscall we want to hook
2. Allocate kernel memory (use technique described in previous
section)
3. Place new routine in newly allocated address space
4. Overwrite first 7 bytes of syscall with an instruction to jump
to new routine
5. Execute new routine, plus the first x bytes of syscall (this
step will become clearer later)
6. Jump back to syscall + offset
Where offset is equal to x
Stealing an idea from pragmatic of THC we will hook mkdir to print out
a debug message. The following is the kld used in conjunction with objdump
in order to extract the bytecode required for the call hook.
hacked_mkdir.c:
/*
* mkdir call hook
*
* Prints a simple debugging message
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/linker.h>
#include <sys/sysproto.h>
#include <sys/syscall.h>
/* The hacked system call */
static int
hacked_mkdir (struct proc *p, struct mkdir_args *uap) {
uprintf ("MKDIR SYSCALL : %s\n", uap->path);
return 0;
}
/* The sysent for the hacked system call */
static struct sysent
hacked_mkdir_sysent = {
1, /* sy_narg */
hacked_mkdir /* sy_call */
};
/* The offset in sysent where the syscall is allocated */
static int offset = NO_SYSCALL;
/* The function called at load/unload */
static int
load (struct module *module, int cmd, void *arg) {
int error = 0;
switch (cmd) {
case MOD_LOAD :
uprintf ("syscall loaded at %d\n", offset);
break;
case MOD_UNLOAD :
uprintf ("syscall unloaded from %d\n", offset);
break;
default :
error = EINVAL;
break;
}
return error;
}
SYSCALL_MODULE(hacked_mkdir, &offset, &hacked_mkdir_sysent, load, NULL);
The following is an example program which hooks mkdir to print out a
simple debug message. As always an explanation of any new concepts appears
after the source code listing.
test_hook.c:
/*
* Intercept mkdir system call, printing out a debug message before
* executing mkdir.
*
* Algorithm is as follows:
* 1. Copy mkdir syscall upto but not including \xe8.
* 2. Allocate kernel memory.
* 3. Place new routine in newly allocated address space.
* 4. Overwrite first 7 bytes of mkdir syscall with an instruction to jump
* to new routine.
* 5. Execute new routine, plus the first x bytes of mkdir syscall.
* Where x is equal to the number of bytes copied from step 1.
* 6. Jump back to mkdir syscall + offset.
* Where offset is equal to the location of \xe8.
*
* test_hook.c,v 3.0 2005/07/02
*/
#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>
/*
* Offset of instruction following call statements
* Starting at the beginning of the function kmalloc
*/
#define KM_OFFSET_1 0x3a
#define KM_OFFSET_2 0x56
/*
* kmalloc function code
*/
unsigned char km_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 */
/*
* Offset of instruction following call statements
* Starting at the beginning of the function hacked_mkdir
*/
#define HA_OFFSET_1 0x2f
/*
* hacked_mkdir function code
*/
unsigned char ha_code[] =
"\x4d" /* M */
"\x4b" /* K */
"\x44" /* D */
"\x49" /* I */
"\x52" /* R */
"\x20" /* sp */
"\x53" /* S */
"\x59" /* Y */
"\x53" /* S */
"\x43" /* C */
"\x41" /* A */
"\x4c" /* L */
"\x4c" /* L */
"\x20" /* sp */
"\x3a" /* : */
"\x20" /* sp */
"\x25" /* % */
"\x73" /* s */
"\x0a" /* nl */
"\x00" /* null */
"\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) */
"\x89\x44\x24\x04" /* mov %eax,0x4(%esp) */
"\xe8\xfc\xff\xff\xff" /* call 17 <hacked_mkdir+0x17>*/
"\x31\xc0" /* xor %eax,%eax */
"\x83\xc4\x08" /* add $0x8,%esp */
"\x5d"; /* pop %ebp */
/*
* jump code
*/
unsigned char jp_code[] =
"\xb8\x00\x00\x00\x00" /* movl $0,%eax */
"\xff\xe0"; /* jmp *%eax */
/*
* struct used to store kernel address
*/
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 km_offset_1;
u_int32_t km_offset_2;
u_int32_t ha_offset_1;
struct nlist nl[] =
{ { NULL },{ NULL },{ NULL },{ NULL },{ NULL },{ NULL},{ NULL }, };
unsigned long diff;
int position;
unsigned char orig_code[sizeof(km_code)];
struct kma_struct kma;
/* Initialize kernel virtual memory access */
kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
if(kd == NULL) {
fprintf(stderr, "ERROR: %s\n", errbuf);
exit(-1);
}
/* Find the address of mkdir, M_TEMP, malloc, copyout,
uprintf, and kern_rmdir */
nl[0].n_name = "mkdir";
nl[1].n_name = "M_TEMP";
nl[2].n_name = "malloc";
nl[3].n_name = "copyout";
nl[4].n_name = "uprintf";
nl[5].n_name = "kern_rmdir";
if(kvm_nlist(kd, nl) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
for(i = 0; i <= 5; i++) {
if(!nl[i].n_value) {
fprintf(stderr, "ERROR: Symbol %s not found\n"
, nl[i].n_name);
exit(-1);
}
}
/* Determine size of mkdir syscall */
diff = nl[5].n_value - nl[0].n_value;
unsigned char mk_code[diff];
/* Save a copy of mkdir syscall */
if(kvm_read(kd, nl[0].n_value, mk_code, diff) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Determine position of 0xe8 */
for(i = 0; i < (int)diff; i++) {
if(mk_code[i] == 0xe8) {
position = i;
}
}
/* Calculate the correct offsets for kmalloc */
km_offset_1 = nl[0].n_value + KM_OFFSET_1;
km_offset_2 = nl[0].n_value + KM_OFFSET_2;
/* Set the km_code to contain the correct addresses */
*(unsigned long *)&km_code[44] = nl[1].n_value;
*(unsigned long *)&km_code[54] = nl[2].n_value - km_offset_1;
*(unsigned long *)&km_code[82] = nl[3].n_value - km_offset_2;
/* Save mkdir syscall */
if(kvm_read(kd, nl[0].n_value, orig_code, sizeof(km_code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Replace mkdir with kmalloc */
if(kvm_write(kd, nl[0].n_value, km_code, sizeof(km_code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Allocate kernel memory */
kma.size = (unsigned long)sizeof(ha_code) + (unsigned long)position
+ (unsigned long)sizeof(jp_code);
syscall(136, &kma);
/* Restore mkdir */
if(kvm_write(kd, nl[0].n_value, orig_code, sizeof(km_code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Calculate the correct offsets for hacked_mkdir */
ha_offset_1 = (unsigned long)kma.addr + HA_OFFSET_1;
/* Set the ha_code to contain the correct addresses */
*(unsigned long *)&ha_code[34] = (unsigned long)kma.addr;
*(unsigned long *)&ha_code[43] = nl[4].n_value - ha_offset_1;
/* Place hacked_mkdir routine into kernel memory */
if(kvm_write(kd, (unsigned long)kma.addr, ha_code, sizeof(ha_code))
< 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Place mk_code into kernel memory */
if(kvm_write(kd, (unsigned long)kma.addr +
(unsigned long)sizeof(ha_code) - 1, mk_code, position) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Set the jp_code to contain the correct address */
*(unsigned long *)&jp_code[1] = nl[0].n_value +
(unsigned long)position;
/* Place jump code into kernel memory */
if(kvm_write(kd, (unsigned long)kma.addr +
(unsigned long)sizeof(ha_code) - 1 +
(unsigned long)position
, jp_code, sizeof(jp_code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
/* Set the jp_code to contain the correct address */
*(unsigned long *)&jp_code[1] = (unsigned long)kma.addr + 0x14;
if(kvm_write(kd, nl[0].n_value, jp_code, sizeof(jp_code)) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
printf("I love the PowerGlove. It's so bad!\n");
/* Close kd */
if(kvm_close(kd) < 0) {
fprintf(stderr, "ERROR: %s\n", kvm_geterr(kd));
exit(-1);
}
exit(0);
}
The comments state that the algorithm for this program is as follows:
1. Copy mkdir syscall upto but not including \xe8.
2. Allocate kernel memory.
3. Place new routine in newly allocated address space.
4. Overwrite first 7 bytes of mkdir syscall with an instruction to jump
to new routine.
5. Execute new routine, plus the first x bytes of mkdir syscall.
Where x is equal to the number of bytes copied from step 1.
6. Jump back to mkdir syscall + offset.
Where offset is equal to the location of \xe8.
The reason behind copying mkdir upto but not including \xe8 is because
on different builds of FreeBSD the disassembly of the mkdir syscall is
different. Therefore one cannot determine a static location to jump back
to. However, on all builds of FreeBSD mkdir makes a call to kern_mkdir,
thus we choose to jump back to that point. The following illustrates this.
[---------------------------------------------------------]
ghost@slavezero:~#nm /boot/kernel/kernel | grep mkdir
c047c560 T devfs_vmkdir
c0620e40 t handle_written_mkdir
c0556ca0 T kern_mkdir
c0557030 T mkdir
c071d57c B mkdirlisthd
c048a3e0 t msdosfs_mkdir
c05e2ed0 t nfs4_mkdir
c05d8710 t nfs_mkdir
c05f9140 T nfsrv_mkdir
c06b4856 r nfsv3err_mkdir
c063a670 t ufs_mkdir
c0702f40 D vop_mkdir_desc
c0702f64 d vop_mkdir_vp_offsets
ghost@slavezero:~#nm /boot/kernel/kernel | grep kern_rmdir
c0557060 T kern_rmdir
ghost@slavezero:~#objdump -d --start-address=0xc0557030
--stop-address=0xc0557060 /boot/kernel/kernel | less
/boot/kernel/kernel: file format elf32-i386-freebsd
Disassembly of section .text:
c0557030 <mkdir>:
c0557030: 55 push %ebp
c0557031: 31 c9 xor %ecx,%ecx
c0557033: 89 e5 mov %esp,%ebp
c0557035: 83 ec 10 sub $0x10,%esp
c0557038: 8b 55 0c mov 0xc(%ebp),%edx
c055703b: 8b 42 04 mov 0x4(%edx),%eax
c055703e: 89 4c 24 08 mov %ecx,0x8(%esp)
c0557042: 89 44 24 0c mov %eax,0xc(%esp)
c0557046: 8b 02 mov (%edx),%eax
c0557048: 89 44 24 04 mov %eax,0x4(%esp)
c055704c: 8b 45 08 mov 0x8(%ebp),%eax
c055704f: 89 04 24 mov %eax,(%esp)
c0557052: e8 49 fc ff ff call c0556ca0 <kern_mkdir>
c0557057: c9 leave
c0557058: c3 ret
c0557059: 8d b4 26 00 00 00 00 lea 0x0(%esi),%esi
ghost@slavezero:~#
[---------------------------------------------------------]
[---------------------------------------------------------]
ghost@slavetwo:~#nm /boot/kernel/kernel | grep mkdir
c046f680 T devfs_vmkdir
c0608fd0 t handle_written_mkdir
c05415d0 T kern_mkdir
c0541900 T mkdir
c074a9bc B mkdirlisthd
c047d270 t msdosfs_mkdir
c05c7160 t nfs4_mkdir
c05bcfd0 t nfs_mkdir
c05db750 T nfsrv_mkdir
c06a2676 r nfsv3err_mkdir
c06216a0 t ufs_mkdir
c06fef40 D vop_mkdir_desc
c06fef64 d vop_mkdir_vp_offsets
ghost@slavetwo:~#nm /boot/kernel/kernel | grep kern_rmdir
c0541930 T kern_rmdir
ghost@slavetwo:~#objdump -dR --start-address=0xc0541900
--stop-address=0xc0541930 /boot/kernel/kernel | less
/boot/kernel/kernel: file format elf32-i386-freebsd
Disassembly of section .text:
c0541900 <mkdir>:
c0541900: 55 push %ebp
c0541901: 89 e5 mov %esp,%ebp
c0541903: 83 ec 10 sub $0x10,%esp
c0541906: 8b 55 0c mov 0xc(%ebp),%edx
c0541909: 8b 42 04 mov 0x4(%edx),%eax
c054190c: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
c0541913: 00
c0541914: 89 44 24 0c mov %eax,0xc(%esp)
c0541918: 8b 02 mov (%edx),%eax
c054191a: 89 44 24 04 mov %eax,0x4(%esp)
c054191e: 8b 45 08 mov 0x8(%ebp),%eax
c0541921: 89 04 24 mov %eax,(%esp)
c0541924: e8 a7 fc ff ff call c05415d0 <kern_mkdir>
c0541929: c9 leave
c054192a: c3 ret
c054192b: 90 nop
c054192c: 8d 74 26 00 lea 0x0(%esi),%esi
ghost@slavetwo:~#
[---------------------------------------------------------]
The above output was generated from two different FreeBSD 5.4 builds.
As one can clearly see the dissassembly dump of mkdir is different for each
one.
In test_hook the address of kern_rmdir is sought after, this is because
in memory kern_rmdir comes right after mkdir, thus its address is the end
boundary for mkdir.
The bytecode for the call hook is as follows:
unsigned char ha_code[] =
"\x4d" /* M */
"\x4b" /* K */
"\x44" /* D */
"\x49" /* I */
"\x52" /* R */
"\x20" /* sp */
"\x53" /* S */
"\x59" /* Y */
"\x53" /* S */
"\x43" /* C */
"\x41" /* A */
"\x4c" /* L */
"\x4c" /* L */
"\x20" /* sp */
"\x3a" /* : */
"\x20" /* sp */
"\x25" /* % */
"\x73" /* s */
"\x0a" /* nl */
"\x00" /* null */
"\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) */
"\x89\x44\x24\x04" /* mov %eax,0x4(%esp) */
"\xe8\xfc\xff\xff\xff" /* call 17 <hacked_mkdir+0x17>*/
"\x31\xc0" /* xor %eax,%eax */
"\x83\xc4\x08" /* add $0x8,%esp */
"\x5d"; /* pop %ebp */
The first 20 bytes is for the string to be printed, because of this
when we jump to this function we have to start at an offset of 0x14, as
illustrated from this line of code:
*(unsigned long *)&jp_code[1] = (unsigned long)kma.addr + 0x14;
The last three statements in the hacked_mkdir bytecode zeros out the
eax register, cleans up the stack, and restores the ebp register. This is
done so that when mkdir actually executes its as if nothing has already
occurred.
One thing to remember about character arrays in C is that they are all
null terminated. For example if we declare the following variable,
unsigned char example[] = "\x41";
sizeof(example) will return 2. This is the reason why in test_hook we
subtract 1 from sizeof(ha_code), otherwise we would be writing to the
wrong spot.
The following is the output before and after running test_hook:
[---------------------------------------------------------]
ghost@slavetwo:~#ls
test_hook.c
ghost@slavetwo:~#gcc -o test_hook test_hook.c -lkvm
ghost@slavetwo:~#mkdir before
ghost@slavetwo:~#ls -F
before/ test_hook* test_hook.c
ghost@slavetwo:~#sudo ./test_hook
Password:
I love the PowerGlove. It's so bad!
ghost@slavetwo:~#mkdir after
MKDIR SYSCALL : after
ghost@slavetwo:~#ls -F
after/ before/ test_hook* test_hook.c
ghost@slavetwo:~#
[---------------------------------------------------------]
One could also use find_syscall and ddb to verify the results of test_hook
--[ 6.0 - Concluding Remarks
Being able to patch and allocate kernel memory gives one a lot of power
over a system. All the examples in this article are trivial as it was my
intention to show the how not the what. Other authors have better ideas
than me anyways on what to do (see References).
I would like to take this space to apologize if any of my explanations
are unclear, hopefully reading over the source code and looking at the
output makes up for it.
Finally, I would like to thank Silvio Cesare, pragmatic, and Stephanie
Wehner, for the inspiration/ideas.
--[ 7.0 - References
[ Internet ]
[1] Silvio Cesare, "Runtime Kernel Kmem Patching"
http://reactor-core.org/runtime-kernel-patching.html
[2] devik & sd, "Linux on-th-fly kernel patching without LKM"
http://www.phrack.org/show.php?p=58&a=7
[3] pragmatic, "Attacking FreeBSD with Kernel Modules"
http://www.thc.org/papers/bsdkern.html
[4] Andrew Reiter, "Dynamic Kernel Linker (KLD) Facility Programming
Tutorial"
http://ezine.daemonnews.org/200010/blueprints.html
[5] Stephanie Wehner, "Fun and Games with FreeBSD Kernel Modules"
http://www.r4k.net/mod/fbsdfun.html
[ Books ]
[6] Muhammad Ali Mazidi & Janice Gillispie Mazidi, "The 80x86 IBM PC And
Compatible Computers: Assembly Language, Design, And Interfacing"
(Prentice Hall)
|=[ EOF ]=---------------------------------------------------------------=|