Hijack the divide_error (Interrupt 0) exception handler

My technique for hijacking the divide_error interrupt 0 follows these steps:

1. Obtain original IDT pointer from a specific register to retrieve the address and size of original IDT.
2. Create new IDT by allocating one page and memory copy from original IDT.
3. In new IDT, modify the divide_error entry with the new address that point to my assembly handler.
4. My assembly handler would call my C handler first and then just simply jump to the original assembly handler.
5. The address of original assembly handler is obtained in System.map and is hardcoded in the source code.
6. Create new IDT pointer based on new IDT.
7. Active new IDT by loading new IDT pointer to that specific register.
8. Recover by loading original IDT pointer to that specific register.

All these steps are implemented in my module called hook. Loading my module would hijack the IDT and removing my module would recover the IDT and report the total number of divide error interrupts handled during hijacking. And my C handler would just simply maintain and print out a counter each time it is invoked. I write a C program called float to generate divide error interrupt. It looks like:

int a,b;
a = 1;
b = 0;
printf(“%d\n”,a/b);

All source code could be found in Appendix at last. Moreover, the following snapshot would demonstrate my work, which is tested in Linux kernel 3.12.6.

hijacking

Appendix:

File: hook.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/desc.h>

#define DIVIDE_ERROR 0x00

/* global varaible */
char msg[200];
char *str = "test";
struct desc_ptr newidtr, oldidtr,tempidtr;
gate_desc *newidt, *oldidt, *tempidt;
int counter = 0;
unsigned long old_stub = 0xc15d9c64;
struct tty_struct *my_tty;

/* global function */
extern asmlinkage void new_stub(void);

/* print message to console */
int write_console (char *str)
{
         struct tty_struct *my_tty;
         if((my_tty=current->signal->tty) != NULL)
         {
                ((my_tty->driver->ops->write) (my_tty,str,strlen(str)));
                return 0;
         }
         else return -1;
}

/* active idt_table by loading new idt pointer to the register */
static void load_IDTR(void *addr)
{
	asm volatile("lidt %0"::"m"(*(unsigned short *)addr));
}

/* my C handler */
void my_func(void)
{
	/* add the counter and send messge to console */
	sprintf(msg, "Counter = %d \r\n", ++counter);
     ((my_tty->driver->ops->write)(my_tty,msg,strlen(msg)));
}

/* my Assembly handler */
void my_dummy(void)
{
        __asm__ (
        ".globl new_stub    \n\t"
        ".align 4, 0x90     \n\t"
        "new_stub:	    \n\t"
        "pushfl	            \n\t"
        "pushal	            \n\t"
        "call my_func 	    \n\t"
        "popal	            \n\t"
        "popfl	            \n\t"
        "jmp *old_stub      \n\t"
         ::);
}
 
int __init hook_init(void){

	/* message */
	write_console("Jianchen hijacked interrupt_0\r\n");

	/* initialize tty for console print */
	my_tty = current->signal->tty;

	/* create new idt_table copied from old one */
	store_idt(&oldidtr);
	oldidt = (gate_desc *)oldidtr.address;
	newidtr.address = __get_free_page(GFP_KERNEL);
	if(!newidtr.address)
		return -1;
	newidtr.size = oldidtr.size;
	newidt = (gate_desc *)newidtr.address; 
	memcpy(newidt, oldidt, oldidtr.size);

	/* modify the divide_error entry to point to my assembly handler */ 
	pack_gate(&newidt[DIVIDE_ERROR], GATE_INTERRUPT, (unsigned long)new_stub, 0, 0, __KERNEL_CS);

	/* active the new idt_table */
	load_IDTR((void *)&newidtr);

	/* for smp architecture */
     //smp_call_function(load_IDTR,(void *)&newidtr, 0);

   	return 0; 
} 
void __exit hook_exit(void){

	/* message */
	write_console("Jianchen recovered interrupt_0 \r\n");
	sprintf(msg, "Interrupt_0 handled during hijacking = %d \r\n", counter);
	write_console(msg);
	
	/* active old idt_table */
	load_IDTR(&oldidtr);

	/* for smp architecture */
     //smp_call_function(load_IDTR, (void *)&oldidtr, 0);

	/* free the allocated page for new idt_table */
	if(newidtr.address)
		free_page(newidtr.address);	
}
 
module_init(hook_init);
module_exit(hook_exit);
MODULE_LICENSE("GPL");

File: Makefile

obj-m += hook.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

File: float.c

#include <stdio.h>
#include <iostream>

using namespace std;

int main()
{
   	int a,b;
   	a = 1;
   	b = 0;
   	printf("%d\n",a/b);
   	return 0; 
}

references:
http://mammon.github.io/Text/linux_hooker.txt
https://ruinedsec.wordpress.com/2013/04/04/modifying-system-calls-dispatching-linux/
http://phrack.org/issues/59/4.html
http://stackoverflow.com/questions/2497919/changing-the-interrupt-descriptor-table
http://www.makelinux.net/books/lkd2/ch11lev1sec3
http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
http://stackoverflow.com/questions/5302392/idt-table-undefined-warning-when-compiling-kernel-module

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: