This is the third part of the WRK series. For your convenience you can find other parts in the table of contents in Part 1 — Compiling and debugging

Today we are going to write a very simple hello world in the kernel space. Let’s go.

Kernel

First, we need to add new syscall to the table of services. Go to the file base\ntos\ke\i386\systable.asm. In line 392 add the following:

TABLE_ENTRY  HelloKernel, 1, 1

This specifies that at least one argument must be passed (first 1) and at most one as well (second 1).
Next, replace TABLE_END 295 with TABLE_END 295.

Now we need to add a stub. Go to base\ntos\ke\i386\sysstubs.asm and add the following in line 2485:

SYSSTUBS_ENTRY1  296, HelloKernel, 1
SYSSTUBS_ENTRY2  296, HelloKernel, 1
SYSSTUBS_ENTRY3  296, HelloKernel, 1
SYSSTUBS_ENTRY4  296, HelloKernel, 1
SYSSTUBS_ENTRY5  296, HelloKernel, 1
SYSSTUBS_ENTRY6  296, HelloKernel, 1
SYSSTUBS_ENTRY7  296, HelloKernel, 1 
SYSSTUBS_ENTRY8  296, HelloKernel, 1

Now the linker expects a method with one int parameter. We need to implement it, so let’s go to base\ntos\ps\psquery.c and add the following in line 4220:

NTSTATUS
NtHelloKernel(
    int count
    )
{
    int i;
    PAGED_CODE();
    for (i=0; i < count; ++i) {
        DbgPrint("Hello world: [%d]",i);
    }

    return STATUS_SUCCESS;
} 

Recompile the kernel and reboot the os.

User mode

Now we need to execute the method from the user space. In theory we should implement a wrapper for the syscall in a DLL. Next, we would just write an application calling the method via the DLL. But, we are going to do it a little differently:

void
CallNtHelloKernel(
	int count
	)
{
	void** stackFrame = (void*)(&count);

	__asm {
		mov eax, 0x0128
		mov edx, stackFrame
		int 0x2E
	}
}

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

	printf("calling HelloKernel\n");

	// use new system service call
	CallNtHelloKernel(5);
}

We first define a method with exactly the same signature as the method in the kernel. Next, we define a stack frame for the parameter. Finally, we switch to the kernel space with the interrupt. eax register holds the ordinal of the syscall (remember what number we put in sysstubs.asm?) and edx holds a stack frame.

Finally, we just call this method from the C code.

Compile the code to a binary. This binary is completely unrelated to the WRK, it is just an application like every other. Copy it to your VM and run in your modified kernel. As a bonus, try to run it with the unmodified WRK kernel and see what happens.