程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 解析Windows2000的IDT擴展機制

解析Windows2000的IDT擴展機制

編輯:關於VC++

前言

今天我們談談Windows 2000下中斷機制的擴展,首先申明本文提到的技術並非本人發現的,只不過是我在學習Windows內核過程中的一點心得罷了,目的在於為和我一樣剛剛步入Windows底層學習的朋友提供一點實用的資料,同時也順帶記錄下自己的學習過程。如果您是Windows Kernel高手,還望有時間能多多指點一下我們這些晚輩;如果您也是初學者,同樣歡迎到我們FZ5FZ網站來交流探討!那好吧,我們就直接進入正題,如果您對中斷還不怎麼了解,那眼前將是一次激動人心的旅程。

1、Windows陷阱機制簡介

陷阱(Trap)是Windows系統中一種不可缺少的系統機制。當系統中發生中斷(硬件中斷或軟件中斷),異常時,處理器會捕捉這個動作,並將系統的控制轉移到一個固定的處理程序處,進行相應的操作處理。在處理器開始處理發生的中斷或異常前,必須保存一些處理器環境參數到堆棧中以備系統還原時使用。系統是通過一種稱為陷阱幀(Trap Frame)的方式來實現的,它將系統中全部線程的環境數據保存到內核堆棧(Kernel Stack)中,在執行完後通過堆棧的出棧機制來恢復系統控制流程中的執行點。內核中的陷阱機制分為中斷和異常。中斷是系統中隨即發生的異步事件,與當前系統的處理器狀態無關。同時系統中的中斷可分為可屏蔽中斷和不可屏蔽中斷。而異常則是一種同步事件,在特定情況下異常可以重現,而中斷不可以。中斷又可以分為硬件中斷和軟件中斷。很明顯硬件中斷是與硬件相關的,比如I/O設備執行的某些操作,處理器時鐘或硬件端口上的處理等。軟件中斷則是通過中斷指令int xx引入的,它往往是應用程序在用戶模式執行後進入操作系統的代碼,這時系統為用戶提供了各種各樣的系統服務。比如我們上次提到的系統服務調用(System Service Call),在Windows NT/2000下就是通過軟件中斷int 0x2e(System Service Interrupt)來實現的,雖然在Windows XP/2003下微軟使用了一種稱為“快速系統調用接口”來為用戶提供系統服務,不過大量的中斷服務仍然存在與系統之中的。

2、中斷處理及其相關流程

此處我們討論的是與特定處理器相關的數據結構,所以會有一些移植方面的問題,本文僅針對Intel的x86 Family處理器,並且本文附帶的程序也只支持在Intel x86處理器上正常執行。何為IDT?IDT(Interrupt Descriptor Table)稱為中斷描述符表。它是可容納8192個單元的數組,數組中的每個成員是稱之為“門”的長度為8字節的段描述符。在IDT中門可分為三種:中斷門(Interrrupt Gate),陷阱門(Trap Gate)和任務門(Task Gate),但主要的是中斷門和陷阱門。而它們兩者之間也只有少許差別,我們在此只關心IDT中的中斷門,如果您對這方面比較感興趣,請查閱Intel處理器的相關文檔《Intel Architecture Software Developer''s Manual,Volume 3》。同時,在系統中存在一個中斷描述符表寄存器(IDTR),它包含了系統中斷描述符表的基地址和IDT的限制信息,它於一條匯編指令sidt息息相關。在下文中我們將看到它是我們實現各種中斷描述符表擴展的基礎和關鍵!還有一點是需要注意的,在Windows系統中引入了分頁,分段和虛擬存儲機制後,就存在這一種調度機制,將需要執行的代碼和數據調入內存,將不需要的數據調到外存(輔助存儲器,如硬盤等)。如果我們在執行某些代碼時發現了我們需要的數據不在內存中時,就會發出一個“缺頁中斷”,這時系統就會在IDT中搜尋這個中斷的ISR(Interrupt Service Routine,中斷服務例程),執行相應的調入工作。大家可以想象如果我們的中斷描述符表被調出到外存後會是什麼樣的結果?那時系統將無法定位“缺頁中斷”的服務例程,至此系統將會崩潰掉!

在中斷描述符表中,我們剛才提到了一個感興趣的寄存器IDTR,當然我們更關心對我們來說更直接的數據:IDT中的代碼段選擇器(Code Segment Selector),中斷執行代碼的偏移量(Offset)和中斷描述符的權限等級(Descriptor Privilege Level)參數。下面我們看看中斷指令的執行流程,我們應該知道應用程序執行在用戶模式(Ring 3)下,而中斷描述符表則是存在於內核模式(Ring 0)才可以訪問的系統地址空間內的。在軟件中斷發生後,也就是應用程序調用了某條軟件中斷指令後,處理器首先在IDT中檢索傳入的中斷號參數,找到響應的入口單元後就檢查中斷門的權限等級參數,看是否允許Ring 3下的應用程序調用,這樣操作系統就為我們保留了對軟件中斷調用控制的權力,然而硬件中斷和異常是不會關注權限方面的信息。如果當前權限等級(Current Privilge Level,CPL)數值大於中斷門描述符需要的權限(Descriptor Privilege Level),也就是權限不夠時會引發一個通用保護故障(General Protection Fault),反之則進行處理器的切換從用戶堆棧到內核堆棧。現在是保存線程環境的時候了,處理器將在用戶模式下的堆棧指針(SS:ESP)和標准的中斷幀(EFLAGS和CS:EIP)壓入堆棧。之後處理器進入我們的中斷服務例程,執行相關的代碼處理後通過匯編指令iretd返回到調用的應用程序。在指令iretd執行時,系統將存儲在堆棧中的線程環境數據出棧還原,待系統恢復中斷指令執行前的環境後就接著執行應用程序的後續代碼。

3、中斷相關數據結構

首先我們介紹一下前面我們提到的一條關鍵匯編指令sidt的相關數據結構。在執行指令sidt後,系統將會把中斷描述符表的基地址和限制(總共長六字節)保存在指令中指向的變量指針中,這就是我們進行IDT操作的入門口。typedef struct _idtr
{
  //定義中斷描述符表的限制,長度兩字節;
  short    IDTLimit;
  //定義中斷描述服表的基址,長度四字節;
  unsigned int  IDTBase;
}IDTR,*PIDTR;
當我們獲得了IDT的入口後,就會在中斷描述符表中檢索我們需要處理的中斷號對應的IDT單元,單元中包含了很多我們需要注意的數據結構,其中我們最為關心的是代碼段選擇器,中斷代碼執行的偏移量和特權等級等,那好我們先給出它的定義,在下文中我們將詳細討論它們的具體應用。typedef struct _idtentry
{
  //中斷執行代碼偏移量的底16位;
  unsigned short  OffsetLow;
  //選擇器,也就是寄存器;
  unsigned short  Selector;
  //保留位,始終為零;
  unsigned char    Reserved;
  //IDT中的門的類型:包括中斷門,陷阱門和任務門;
  unsigned char    Type:4;
  //段標識位;
  unsigned char    SegmentFlag:1;
  //中斷門的權限等級,0表示內核級,3表示用戶級;
  unsigned char    DPL:2;
  //呈現標志位;
  unsigned char    Present:1;
  //中斷執行代碼偏移量的高16位;
  unsigned short  OffsetHigh;
}IDTENTRY,*PIDTENTRY;
4、創建軟件中斷鉤子的作用

作為普通的Windows程序員,或許您需要的是熟悉對系統基本功能的操作,以及對通用程序開發的熟練掌握。但對於一個有想法的Windows內核級分析開發人員來說,對系統底層的深入了解是非常必要的,同時也是非常重要的。Hook為我們創造了一個絕好的機會,它使我們了解系統內部運行機制的想法成為了一種可能。同時,書寫一個系統相關的監視程序可以自動的對系統內部操作進行記錄與分析。當然我們不能局限於對系統的了解,我們更渴望實施對系統的修改與擴展,改變系統原有的操作特性,注入我們需要的功能組件,讓系統做更適合我們自己,也是我們最希望看到的操作。前面我們曾經談到了創建系統服務調用的鉤子來截獲系統服務調用,同樣在Windows2000下,系統服務是通過系統服務中斷(System Service Interrupt,int 0x2e)來實現的,通過截獲軟件中斷同樣可以達到監視並修改系統服務調用的功能。在此我們主要討論的是為軟件中斷創建鉤子,不過對於硬件中斷和異常也同樣不例外,我們同樣可以將本文提到的方法應用於硬件中斷和異常。比如我們也可以通過截獲鍵盤驅動的中斷調用來書寫內核級的鍵盤記錄器,它可以直接對每次擊鍵和釋放進行操作,效果是非常的明顯,不過這還需要使用到一些微軟為我們提供的與硬件中斷鉤子相關的函數。

5、如何創建軟件中斷鉤子?

其實創建軟件中斷鉤子的過程應該是比較明顯了,下面我們將先簡要介紹一下創建Hook的過程,然後以實際代碼進行具體的講解。首先我們通過匯編指令sidt(sidt: Store Interrupt Descriptor Table Register;lidt: Load Interrupt Descriptor Table Register)來獲取IDT的基地址IDTBase,然後我們在中斷描述符表中搜尋我們需要HOOK的中斷號HOOKINTID,它應該是在0-255內的一個整數,雖然最新的Intel處理器聲稱支持8192個中斷描述符單元,但由於某些限制原因,仍然只能處理前256個中斷描述門。在找到我們需要Hook的中斷描述門後,將它原本的中斷執行代碼偏移量(32位)保存到一個全局變量OldISR中,以備我們在執行中斷處理或恢復IDT時使用。這樣新的IDT中對應中斷號的執行代碼偏移量就指向了我們自己的處理代碼了。在我們的處理代碼NewISR中,注意先要保存一些線程環境,在處理完我們額外添加的執行程序(Monitor,監視注冊表相關的16個系統服務調用)後,恢復現場並執行中斷門以前指向的程序代碼。這樣,對外就看不出我們對中斷門做了什麼額外的處理,感覺和以前沒什麼兩樣!如果我們只是處理了我們添加的代碼而沒有繼續執行中斷門對應的以前的程序代碼,那麼系統必將混亂甚至崩潰!同樣在我們卸載我們的軟件中斷鉤子時,就是進行了一個逆向工作。先獲取IDT的基地址,然後將保存在全局變量中的舊的執行代碼地址偏移量賦給對應中斷號的偏移量單元(OffsetLow/OffsetHigh)。大概過程講得差不多了,相關程序為T-HookInt,我們再看看代碼吧! VOID
HookInt(VOID)
{
  //保存IDT入口的基地址和限制信息的數據結構;
  IDTR    idtr;
  //記錄IDT數組的指針,通過它可以查找到我們需要Hook中斷號對應的中斷門;
  PIDTENTRY  IdtEntry;
  //匯編指令sidt,獲取IDT入口信息;
  __asm sidt  idtr;
  //賦予IDT基地址值;
  IdtEntry = (PIDTENTRY)idtr.IDTBase;
  //保存中斷號HOOKINTID對應中斷門所指向的執行代碼偏移量,以備執行中斷處理或恢復時使用;
  OldISR = ((unsigned int)IdtEntry[HOOKINTID].OffsetHigh << 16) │ (IdtEntry[HOOKINTID].OffsetLow);
  //關中斷
  __asm cli
  //更新執行代碼偏移量的底16位;
  IdtEntry[HOOKINTID].OffsetLow = (unsigned short)NewISR;
  //更新執行代碼偏移量的高16位;
  IdtEntry[HOOKINTID].OffsetHigh = (unsigned short)((unsigned int)NewISR >> 16);
  //開中斷
  __asm sti;
}
VOID
UnhookInt(VOID)
{
  IDTR    idtr;
  PIDTENTRY  IdtEntry;
  __asm sidt  idtr;
  IdtEntry = (PIDTENTRY)idtr.IDTBase;
  __asm cli
  //恢復中斷號HOOKINTID對應中斷門執行代碼偏移量的底16位;
  IdtEntry[HOOKINTID].OffsetLow = (unsigned short)OldISR;
  //恢復中斷號HOOKINTID對應中斷門執行代碼偏移量的高16位;
  IdtEntry[HOOKINTID].OffsetHigh = (unsigned short)((unsigned int)OldISR >> 16);
  __asm sti;
}
VOID
__fastcall
Monitor()
{
  ……
  //由於我們處理的中斷號為0x2e,
  //對應於系統服務中斷(System Service Interrupt),
  //通過獲取eax寄存器中的數值來區分系統服務調用;
  __asm  mov dwServiceId,eax;
  //執行內核函數獲取當前進程的ID號;
  dwProcessId = (unsigned int)PsGetCurrentProcessId();

  //提升當前IRQL,防止被中斷;
  KeRaiseIrql(HIGH_LEVEL,&OldIrql);
  switch(dwServiceId)
  {
  //如果eax對應的數值為0x23,
  //則對應於Windows2000的ZwCreateKey系統服務調用;
  case 0x23:
    DbgPrint("ProcessId: %d ZwCreateKey\n",dwProcessId);
    break;
  ……
  default:
    break;
  }
  //恢復原始IRQL;
  KeLowerIrql(OldIrql);
}

6、添加軟件中斷的作用與原理

通過添加軟件中斷,我們可以擴展系統的功能,改變系統的很多操作行為。在前面我們介紹過為系統添加新的系統服務調用來擴展系統,通過添加新的軟件中斷同樣可以到達添加系統服務調用的目的,並且我們可以在新添的中斷處理程序中執行Ring 0級別的任意代碼,那是何等的讓人欣慰!

其實在IDT中,256個中斷門單元並不是被完全利用的,還剩下一些流給將來擴展使用的中斷門,我們可以自己給這些未使用的中斷門添加一些機制為我所用。其實添加軟件中斷的過程和前面我們詳細講解的添加軟件中斷鉤子有很多相似的地方,所以在此我就不做很詳細的介紹了。同樣是,首先獲得IDT的基地址,然後在中斷描述符表中查找我們將要添加的中斷號對應的中斷門描述符,之後給相關的參數賦值,使其成為名副其實的軟件中斷門。這時我們就可以在應用程序中使用中斷指令int xx來調用我們自己中斷門中的服務程序了。

7、添加軟件中斷的實現過程

相關程序為T-ADDIG(Add Interrupt Gate),我們來看看代碼哈~ NTSTATUS
InstallIG()
{
  ……
  //判斷我們想要添加的中斷是否已被占用;
  if(IdtEntry[ADDINTID].OffsetLow != 0
  ││ IdtEntry[ADDINTID].OffsetHigh != 0)
  {
    return STATUS_UNSUCCESSFUL;
  }
  //復制原始的中斷門描述信息;
  RtlCopyMemory(&OldIdtEntry,&IdtEntry[ADDINTID],sizeof(OldIdtEntry));
  //關中斷
  __asm cli

  //更新執行代碼偏移量的底16位;
  IdtEntry[ADDINTID].OffsetLow  = (unsigned short)InterruptServiceRoutine;
  //目的代碼段的段選擇器,CS為8;
  IdtEntry[ADDINTID].Selector    = 8;
  //保留位,始終為零;
  IdtEntry[ADDINTID].Reserved    = 0;
  //門類型,0xe代表中斷門;
  IdtEntry[ADDINTID].Type    = 0xe;
  //SegmentFlag設置0代碼為段;
  IdtEntry[ADDINTID].SegmentFlag  = 0;
  //描述符權限等級為3,允許用戶模式程序調用本中斷;
  IdtEntry[ADDINTID].DPL    = 3;
  //呈現標志位,設置為一;
  IdtEntry[ADDINTID].Present    = 1;
  //更新執行代碼偏移量的高16位;
  IdtEntry[ADDINTID].OffsetHigh  = (unsigned short)((unsigned int)InterruptServiceRoutine >> 16);
  //開中斷
  __asm sti
  return STATUS_SUCCESS;
}
VOID
RemoveIG()
{
  ……
  __asm cli
  //恢復我們修改過的中斷門描述符;
  RtlCopyMemory(&IdtEntry[ADDINTID],&OldIdtEntry,sizeof(OldIdtEntry));
  __asm sti
}
extern
void
_cdecl
InterruptServiceRoutine(VOID)
{
  unsigned int  Command;
  //獲取eax寄存器中的數值,接受從用戶模式傳入的命令參數;
  __asm mov Command,eax;
  //執行內核代碼,獲取操作系統版本號;
  DbgPrint("NtBuildNumber == %d\n",(unsigned short)NtBuildNumber);
  //中斷返回;
  __asm iretd;
}

後記

寫到這兒,我們只是介紹了擴展IDT的一些基本方法,當然還有很多更深入的,更值得我們研究的課題需要大家努力去探索。比如我們可以將T-HookInt擴展,不僅僅是監視系統注冊表操作相關的系統服務調用,不過在Windows XP/2003上由於其內在機制的一些變更,所以通過Hook int 0x2e來截獲系統服務調用就不這麼現實了。當然還有基於IDT的內核級後門,可以通過添加新的軟件中斷為任意用戶提供SYSTEM權限級別的Command等。總之,探究Windows內核奧秘的旅行還未結束,或許這只能算是一次起航罷了。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved