接上篇续,我们继续谈谈386保护模式中的门,今天一起学习下中断门。
386实模式下的中断和异常的转移方法与8086相同。这里介绍的中断和异常的转移方法是指 80386在保护模式下响应中断和处理异常时所采用的转移方法。
1.中断描述符表IDT
象全局描述符表GDT一样,在整个系统中,中断描述符表IDT只有一个。中断描述符表寄存器IDTR指示IDT在内存中的位置。由于80386只识别256个中断向量号,所以IDT最大长度是2K。
中断描述符表IDT所含的描述符只能是中断门、陷阱门和任务门。也就是说,在保护模式下,80386只有通过中断门、陷阱门或任务门才能转移到对应的中断或异常处理程序。
我们直观的看看IDT的内存位置,在本机 IDT: 8003f400
lkd> !pcr
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: b2c3fc7c
NtTib.StackBase: b2c3fdf0
NtTib.StackLimit: b2c3c000
NtTib.SubSystemTib: 00000000
NtTib.Version: 00000000
NtTib.UserPointer: 00000000
NtTib.SelfTib: 7ffde000
SelfPcr: ffdff000
Prcb: ffdff120
Irql: 00000000
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8003f400
GDT: 8003f000
TSS: 80042000
CurrentThread: 873dd1e8
NextThread: 00000000
IdleThread: 80552d20
前面我们讲过80386只识别256个中断向量号,每个中断向量号占8个字节,对应着一个描述符。因此,中断描述表IDT共占 256 * 8 = 2048字节。
下面是中断描述符的结构。
在IDT中,并不是这256个中断向量都被系统占用了,系统紧紧占用了一小部分,大部分是空闲的,因此用户可以在其中添加自己的中断描述符,即中断门。
2.中断响应和异常处理的步骤
由硬件自动实现的中断响应和异常处理的步骤如下:
首先,判断中断向量号要索引的门描述符是否超出IDT的界限。若超出界限,就引起通用保护故障,出错码为中断向量号乘8再加2。
其次,从IDT中取得对应的门描述符,分解出选择子、偏移量和描述符属性类型,并进 行有关检查。描述符只能是任务门、286中断门、286陷阱门、386中断门或386陷阱门,否则就引起通用保护故障,出错码是中断向量号乘8再加2。如果是由INT n指令或INTO指令引起转移,还要检查中断门、陷阱门或任务门描述符中的DPL是否满足CPL<=DPL(对于其它的异常或中断,门中的DPL被 忽略)。这种检查可以避免应用程序执行INT n指令时,使用分配给各种设备用的中断向量号。如果检查不通过,就引起通用保护故障,出错码是中断向量号乘8再加2。门描述符中的P位必须是1,表示门描述符是一个有效项,否则就引起段不存在故障,出错码是中断向量号乘8再加2。
最后,根据门描述符类型,分情况转入中断或异常处理程序。 如下图:
3.通过中断门的转移
如果中断向量号所指示的门描述符是386中断门,那么控制转移到当前任务的一个处理程序过程,并且可以变换特权级。与其它调用门的CALL指令一样,从中断门中获取指向处理程序的48位全指针。其中16位 选择子是对应处理程序或代码段的选择子,它指示全局描述符表GDT或局部描述符表LDT中的代码段描述符;32位偏移指示处理程序入口点在代码段内的偏移量。
通过中断门的转移过程如下所示,该过程由硬件自动进行,简单了解下。
(1)若选择子为空,则产生通用保护故障;
(2)取对应的描述符;
(3)若非存储段描述符,则产生通用保护故障;
(4)若非一致代码段且DPL且段存在,则切换到内层堆栈;
(5)调整RPL=0;
(6)把描述符装入CS;
(7)若入口偏移越界,则产生通用保护故障;
(8)EFLAGS压入堆栈;
(9)CS压入堆栈;
(10)EIP压入堆栈;
(11)使TF=0,NT=0;
(12)若为中断门,则使IF=0;
(13)若有出错码,则把出错码压入堆栈;
(14)转入处理程序。
由上述转移过程可见,中断门中指示处理程序的选择子必须指向描述一个可执行的代码段的描述符。如果选择子为空,就引起通用保护故障,出错码是0。如果描述符不是代码段描述符,就引起通用保护故障,出错码含选择子。
中断或异常可以转移到同一特权级或内层特权级。上述指定处理程序代码段的描述符中的类型及DPL字段,决定了这种同一任务内的转移是否要发生特权级变换。如果是一个非一致代码段,并且DPL<CPL则产生通用保护异常。
上述转移过程中的第六步,也就是“把描述符装入CS”,是指把上述指定处理 程序段的描述符装入CS的高速缓冲寄存器中,在这一步骤中要对描述符进行类似通过调用门进行转移的其它检查,包括是否代码段描述符和代码段描述符是否存在 等,因此可能再发生异常。在对该描述符进行检查时,通过调整门中选择子的RPL=0(在处理器内部调整,而不影响存储器中的选择子的RPL字段)的方法, 实现只考虑代码段的DPL,而不考虑门中选择子的RPL。把描述符装入CS之后,还要检查门描述符中给出的表示处理程序代码段入口的偏移是否越界,即是否 超出段界限。如果越界,就引起出错码为0的通用保护故障。
把TF置成0,表示不允许处理程序单步执行。把NT置成0,表示处理程序在利用中断返回指令IRET返回时,返回到同一任务而不是一个嵌套任务。
通过中断门的转移和通过陷阱门的转移之间的差别只是对IF标志的处理。对于中断门,在转移过程中把IF置为0,使得在处理程序执行期间屏蔽掉INTR中断。
4.中断或异常处理后的返回
中断返回指令IRET用于从中断处理程序的返回。该指令的执行根据任务嵌套标志NT位是否为1分为两种情形。 该过程由硬件自动进行,简单了解下。
NT位为1,表示是嵌套任务的返回。
NT位为0,表示当前任务内的返回。
后面跟上我们的代码:
.386
.model flat, stdcall
option casemap:none
include myIntGate.inc
.const
CCOUNTED_UNICODE_STRING "\\Device\\MyIntGate",g_usDeviceName,4
CCOUNTED_UNICODE_STRING "\\??\\MyIntGate",g_usSymbolicLinkName,4
IDT_LIMIT = 256 * 8
GATE_TYPE = 0eeh
.data?
g_myInt dd ?
g_myIntoffset dd ?
g_IsAddMyGate dd ?
.code
;中断执行函数
MyIntGateFunction proc
invoke DbgPrint,$CTA0("MyIntGate calling...\n")
iretd
MyIntGateFunction endp
;在IDT中添加中断门,返回中断号
AddMyIntGate proc FuncAddr:DWORD
push ebx
push ecx
sidt [esp-2]
pop ecx
mov eax,0
.while eax < IDT_LIMIT
lea edx,[ecx+eax]
assume edx:ptr GATE
test [edx].GTYPE,80h
.if ZERO?
cli
mov ebx,FuncAddr
mov [edx].OFFSETL,bx
mov [edx].SELECTOR,08h
mov [edx].DCOUNT,0
mov [edx].GTYPE,GATE_TYPE
shr ebx,16
mov [edx].OFFSETH,bx
sti
.break
.endif
assume edx:nothing
add eax,8
.endw
pop ebx
mov g_myInt,eax
ret
AddMyIntGate endp
;去掉添加的中断门
RemoveMyIntGate proc
push ebx
push ecx
sidt [esp-2]
pop ecx
mov eax,g_myIntoffset
lea edx,[ecx+eax]
assume edx:ptr GATE
cli
mov [edx].OFFSETL,0
mov [edx].SELECTOR,08h
mov [edx].DCOUNT,0
mov [edx].GTYPE,0
mov [edx].OFFSETH,0
sti
assume edx:nothing
pop ebx
ret
RemoveMyIntGate endp
DispatchControl proc uses esi edi pDeviceObject:PDEVICE_OBJECT,pIrp:PIRP
mov esi,pIrp
assume esi:ptr _IRP
mov [esi].IoStatus.Status,STATUS_UNSUCCESSFUL
and [esi].IoStatus.Information,0
IoGetCurrentIrpStackLocation esi
mov edi,eax
assume edi:ptr IO_STACK_LOCATION
.if [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_MYINTGATE && !g_IsAddMyGate
invoke AddMyIntGate,offset MyIntGateFunction
mov g_myIntoffset,eax
xor edx,edx
cdq
mov ecx,8
div ecx
mov g_myInt,eax
mov edx,[esi].AssociatedIrp.SystemBuffer
mov [edx],eax
mov [esi].IoStatus.Status, STATUS_SUCCESS
mov [esi].IoStatus.Information,4
mov g_IsAddMyGate,1
.else
mov [esi].IoStatus.Status,STATUS_INVALID_DEVICE_REQUEST
.endif
push [esi].IoStatus.Status
assume edi:nothing
assume esi:nothing
invoke IoCompleteRequest,esi,IO_NO_INCREMENT
pop eax
ret
DispatchControl endp
DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT,pIrp:PIRP
mov eax,pIrp
assume eax:ptr _IRP
mov [eax].IoStatus.Status,STATUS_SUCCESS
and [eax].IoStatus.Information,0
fastcall IofCompleteRequest,pIrp,IO_NO_INCREMENT
assume eax:nothing
mov eax,STATUS_SUCCESS
ret
DispatchCreateClose endp
DriverUnload proc pDriverObject:PDRIVER_OBJECT
.if g_IsAddMyGate
invoke RemoveMyIntGate
.endif
invoke IoDeleteSymbolicLink,addr g_usSymbolicLinkName
mov eax,pDriverObject
invoke IoDeleteDevice,(DRIVER_OBJECT PTR[eax]).DeviceObject
ret
DriverUnload endp DriverEntry proc pDriverObject:PDRIVER_OBJECT,pusRegistryPath:PUNICODE_STRING
LOCAL pDeviceObject:PDEVICE_OBJECT
LOCAL status:NTSTATUS
mov status,STATUS_DEVICE_CONFIGURATION_ERROR
invoke IoCreateDevice,pDriverObject,0,addr g_usDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,addr pDeviceObject
.if eax == STATUS_SUCCESS
invoke IoCreateSymbolicLink,addr g_usSymbolicLinkName,addr g_usDeviceName
.if eax == STATUS_SUCCESS
mov eax,pDriverObject
assume eax:ptr DRIVER_OBJECT
mov [eax].MajorFunction[IRP_MJ_CREATE * (sizeof PVOID)],offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_CLOSE * (sizeof PVOID)],offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL *(sizeof PVOID)],offset DispatchControl
mov [eax].DriverUnload,offset DriverUnload
assume eax:nothing
mov g_IsAddMyGate,0
mov status,STATUS_SUCCESS
.else
invoke IoDeleteDevice,pDeviceObject
.endif
.endif
mov eax,status
ret
DriverEntry endp
end DriverEntry
最后友情提示:
在我的本机返回添加的中断向量是0x20, 调用方法是:
__asm int 0x20;
当然,如果你有参数传递的话,可以使用寄存器进行传递,在MyIntGateFunction中断响应函数中可以得到这个参数。例如:
_asm
{
mov eax, 100
int 0x20
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: