玩转IDT(PHRACK59-0x04) 原作: <<Handling Interrupt Descriptor Table for fun and profit >>
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
6348|47 40|3932
+------------------------------------------------------------
| | |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) |
| |
------------------------------------------------------------+
3116|15 0
- bits0 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
trapgate : 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_VECTOR0x20
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,&am
p;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_