系统段描述符表IDTR
中断门和陷阱门
不同描述符表中的值代表不同类型的门,放在gdtr表里面的门叫做调用门,放在idtr表里面的门叫做中断门和陷阱门
中断门和陷阱门描述中断/异常处理程序的入口点。中断门和陷阱门内的选择子必须指向代码段描述符,门内的偏移就是对应代码段的入口点偏移。中断门和陷阱门只有在描述符表IDT中才有效。
中断
对80386而言,中断是由异步的外部事件引起的,也就是cpu外部中断。在汇编中,可以用int
指令进入中断门,int 0x1
代表调用IDTR指向的第一个门,依次类推,int 0x20
就是指向IDTR + 0x20 * 8地址的描述符。读取idt中的8个字节,就获取系统函数入口。
异常
异常时80386在执行指令期间检测到不正常的或非法的条件引起的,也就是cpu内部中断。当异常发生后,处理器就像响应中断那样处理异常。也就是根据中断向量号,跳转到IDT对应的地址。
故障
故障是在引起异常之前,把异常情况通知给系统的一种异常。故障往往是可修复的,在故障处理程序把故障排除后,执行IRET返回到故障程序继续执行,但不能保证继续执行的程序可以正确执行。
陷阱
陷阱是在引起异常指令后,把异常情况通知给系统的一种异常(如软中断指令、单步异常)。通常用于调试目的,如INT3和溢出检测指令INTO。在转入陷阱处理程序时,引起陷阱的指令应正常完成,他有可能改变了寄存器或储存单元。
终止
中止时在系统出现严重情况时,通知系统的一种异常。产生中止时正执行的程序不能被恢复执行。系统接受中止后处理程序要重新建立各种系统表格,并可能需要重新启动操作系统,也可能会蓝屏。
异常类型
其中0x2
的向量号为外部设备导致错误(cpu断电等)
在执行int 0x8双重错误时发生错误则变成三重错误,这时系统重启或卡死
任务段出错率较低,所有寄存器都换了一遍后,可以把寄存器恢复到出错之前
故障类异常
堆栈段故障
异常0CH
当处理器检测到用SS进行寻址的与段有关的某种问题时,就发生堆栈段故障。堆栈段故障提供一个出错码,格式如下:
在出现如下三种情况将引起堆栈段故障:
1、偏移超出段界限所规定的范围,出错码为0。如PUSH时堆栈溢出
2、在又特权级变换所引起的对内层堆栈的操作时,偏移超出段界限对规定的范围,出错码包含又内层堆栈的选择子。
3、装入到SS寄存器的描述符中存在位为0,出错码包含又对应的选择子。
通用保护故障
异常0DH
除了明确列出的段异常外,其他段异常均称为通用保护异常。在进入故障处理程序时,保存的CS和EIP指向发生故障的指令,或该故障作为任务切换的一部分发生时指向任务的第一条指令。通用保护故障通常分为两类:
1、违反保护方式,但程序无需中止的异常,出错码为0
2、外翻保护方式,导致程序中止异常,出错码不定
WinDBG测试
使用命令dq idtr l70
在idt中找到一块空地址,计算一个描述符
此处选择0x8003f500地址,根据(当前地址-基址)/8
可以算出中断偏移量为0x20
根据下表进一步构造中断门这8字符的值,这里取0x60000000为基址,计算出来则为0x6000ee00`00080000
编写代码
#include "StdAfx.h"
#include <Windows.h>
WORD w_cs;
WORD w_ss;
BYTE GdtBuffer[0x4000];
DWORD GetFunAddress(void* Fun){
BYTE* p;
p = (BYTE*)Fun;
if (*p == 0xe9){
p = p+5+ *(DWORD*)&p[1];
}
return (DWORD)p;
}
_declspec(naked)void fun(){
_asm {
push ebp
mov ebp,esp
sub esp,0x44
push ebx
push esi
push edi
}
_asm {
mov ax,cs
mov cx,ss
mov w_cs,ax
mov w_ss,cx
}
BYTE* pGdtAddress;
int i;
pGdtAddress = (BYTE*)0x8003f400;
for (i = 0; i < 0x400; i++ ){
GdtBuffer[i] = pGdtAddress[i];
}
_asm{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
iretd
}
}
int main(int argc, char* argv[]){
LPVOID pAddress;
DWORD dwFunAddress;
pAddress = VirtualAlloc((void*)0x60000000,0x4000, MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if ( pAddress != NULL ){
dwFunAddress = GetFunAddress(fun);
memcpy(pAddress, (void*)dwFunAddress,0x200);
}
memset(GdtBuffer,0x0,0x4000);
int i;
_asm{
int 0x20
}
printf("cs = %x ss = %x\n", w_cs,w_ss);
for ( i = 0; i < 0x100; i+= 2){
printf(" 0x%08x`0x%08x\n", *(DWORD*)&GdtBuffer[i*4+4], *(DWORD*)&GdtBuffer[i*4]);
}
printf("Hello World!\n");
return 0;
}
成功打印
IDT中不为空的地址代表系统函数的入口地址
取第一个地址的EIP u 0x805431a0
,可以看到系统函数名和反汇编
依次尝试接下来的几个,可以发现均为系统函数的入口