中斷描述符表(IDT)是 x86 處理器處理中斷與異常的核心資料結構。本文深入剖析 Windows 2000 下的 IDT 架構,示範如何透過修改 IDT 實作軟體中斷鉤子(Hook int 0x2e 截獲系統服務呼叫)與新增自訂中斷門,並提供完整組合語言與 C 語言混合程式碼,為核心層級開發與逆向分析奠定基礎。
當硬體中斷、軟體中斷(int n)或異常發生時,處理器依 IDT 中對應的「門」描述符定位中斷服務例程(ISR)。IDT 最多支援 8192 個條目,每個 8 位元組,包含程式碼段選擇子、位移位址、類型(中斷門/陷阱門/任務門)與特權等級(DPL)。sidt 指令可讀取 IDTR 暫存器,取得 IDT 基址與界限,是所有 IDT 操作的起點。
typedef struct _idtentry {
unsigned short OffsetLow; // 位移低 16 位
unsigned short Selector; // 程式碼段選擇子
unsigned char Reserved; // 保留,必為 0
unsigned char Type:4; // 門類型:0xE=中斷門, 0xF=陷阱門
unsigned char SegmentFlag:1;
unsigned char DPL:2; // 特權等級:0=核心, 3=使用者
unsigned char Present:1; // 存在位
unsigned short OffsetHigh; // 位移高 16 位
} IDTENTRY, *PIDTENTRY;
sidt 取得 IDT 基址。IDT[0x2e],備份原始 OffsetLow/OffsetHigh 至全域變數 OldISR。cli 關中斷,將 IDT[0x2e] 位移指向自訂 NewISR;sti 開中斷。NewISR 中保存現場、執行監控邏輯(如依 EAX 區分系統服務號)、還原現場、jmp OldISR 執行原系統服務。OldISR 寫回 IDT。VOID HookInt(VOID) {
IDTR idtr; PIDTENTRY IdtEntry;
__asm sidt idtr;
IdtEntry = (PIDTENTRY)idtr.IDTBase;
OldISR = ((ULONG)IdtEntry[HOOKINTID].OffsetHigh << 16) | IdtEntry[HOOKINTID].OffsetLow;
__asm cli;
IdtEntry[HOOKINTID].OffsetLow = (USHORT)NewISR;
IdtEntry[HOOKINTID].OffsetHigh = (USHORT)((ULONG)NewISR >> 16);
__asm sti;
}
VOID __fastcall Monitor() {
__asm mov dwServiceId, eax;
dwProcessId = (ULONG)PsGetCurrentProcessId();
KeRaiseIrql(HIGH_LEVEL, &OldIrql);
switch(dwServiceId) {
case 0x23: DbgPrint("ProcessId: %d ZwCreateKey\n", dwProcessId); break;
// ... 監控其他登錄表相關系統服務
}
KeLowerIrql(OldIrql);
}
IDT 中仍有未使用的條目(如 0xF0 以上)。將目標條目 Type=0xE、DPL=3(允許 Ring 3 呼叫)、Selector=8(核心程式碼段)、Present=1、位移指向自訂 ISR。使用者模式即可透過 int 0xF0 呼叫核心功能,實現系統服務擴充。
NTSTATUS InstallIG() {
// 檢查目標中斷門是否空閒
if (IdtEntry[ADDINTID].OffsetLow || IdtEntry[ADDINTID].OffsetHigh)
return STATUS_UNSUCCESSFUL;
RtlCopyMemory(&OldIdtEntry, &IdtEntry[ADDINTID], sizeof(IDTENTRY));
__asm cli;
IdtEntry[ADDINTID].OffsetLow = (USHORT)InterruptServiceRoutine;
IdtEntry[ADDINTID].Selector = 8;
IdtEntry[ADDINTID].Type = 0xE;
IdtEntry[ADDINTID].DPL = 3;
IdtEntry[ADDINTID].Present = 1;
IdtEntry[ADDINTID].OffsetHigh = (USHORT)((ULONG)InterruptServiceRoutine >> 16);
__asm sti;
return STATUS_SUCCESS;
}
void _cdecl InterruptServiceRoutine() {
__asm mov Command, eax; // 取得使用者傳入參數
DbgPrint("NtBuildNumber == %d\n", (USHORT)NtBuildNumber);
__asm iretd;
}
IoConnectInterrupt)。sysenter 取代 int 0x2e,本文技術於新版 Windows 失效。