Windows内核面试题(持续更新,目前完成度30%约1.8万字)
WINDOWS內核編程問題與答案
1.WDK和SDK的區別是什么
2.WDK全稱叫做
3.如何創建WDK程序
4.WinDbg如何連接虛擬機
5.Windows內核符號表的作用
6.如何設置內核符號表與源文件
7.如何設置斷點與源碼調試
8.什么時候共享內核空間
9.內核模塊與驅動程序的區別是什么
10.內核模塊運行在什么空間
11.PsGetCurrentProcessId函數的作用是什么
12.System進程的作用是什么
13.x64和x86操作系統的用戶空間和內核空間的范圍分別是多少
14.WDK基本數據類型有幾種分別是什么
15.內核返回狀態類型是什么NT_SUCCESS函數的作用是什么
16.內核字符串使用了什么結構如何創建一個雙字符字符串
17.驅動對象的用途是什么它使用了什么結構
18.內核回調函數分為幾種他們的作用是什么
19.簡單講講如何使用內核實現病毒掃描、文件加密
20.內核設備對象的作用是什么它與內核驅動對象是什么關系
21.請寫出設備對象是如何進行請求處理的
22.請寫出IRP內核請求的結構名和作用
23.什么是IRP棧空間他的作用是什么
24.請寫出常見的內核API函數以及Ex、Zw、Nt、Rtl、Io系列函數是做什么的
25.內核編程中主要調用源的作用是什么
26.如何判斷內核函數的多線程安全性是否需要,如何做內核多線程的安全性
27.內核中斷級共有幾級分別叫做什么
28.請寫出指定函數位置的預編譯指令以及作用
29._UNICODE_STIRNG與_STRING結構的作用是什么
30.如何合法的初始化一個內核字符串
31.如何拷貝一個UNICODE_STRING字符串
32.RTL_CONSTANT_STRING的作用是什么
33.RTLInitEmptyUnicodeString函數的作用是什么
34.RTLCopyUnicodeString()函數的作用是什么
35.NTSTATUS成功與失敗分別返回什么
36.內核入口函數和內核卸載函數分別叫做什么
37.RtlAppendUnicodeToString函數的作用是什么
38.ExAllocatePoolWithTag函數和ExFreePool函數的作用是什么他們是什么關系
39.什么是內存分配標識符
40.ExFreePool函數是否可以隨意釋放棧空間
41.KdPrint和Dbgprintf的區別是什么
42.LARGE_INTEGER結構的作用是什么
43.什么是自旋鎖,如何使用自旋鎖,使用自旋鎖需要注意什么
44.什么是隊列自旋鎖,它與自旋鎖的區別是什么
45.OBJECT_ATTRIBUTES結構的作用是什么
46.VXD、KDM、WDM、WDF分別是什么意思
47.初始化OBJECT_ATTRIBUTES結構的函數是什么
48.內核打開一個和關閉一個文件的函數分別是什么
49.內核文件讀/寫的函數分別是什么
50.SDK編程與WDK編程中注冊表鍵的區別是什么
51.內核中如何打開或新建注冊表鍵的函數是
52.內核中注冊表鍵的讀取使用的函數是
53.內核中注冊表鍵的讀使用的3種結構和范圍
54.內核如何獲取系統自啟后滴答數
55.內核如何獲得當前時間
56.內核定時器函數是
57.KeInitializeTimer()函數的作用是什么
58.請簡單講講什么是系統線程
59.內核系統線程使用的函數是
60.內核系統線程中睡眠的函數是
61.內核系統線程如何實現同步事件
62.KeWaiteForSingleObject函數的作用是什么
63.簡單講講應用程序和內核通信的原理
64.生成設備使用的函數是
65.內核基本框架是由什么組成的
66.設備對象可以沒有名字嗎?如果沒有名字如何與應用層通信
67.符號鏈接是什么,使用什么函數生成
68.控制設備如何刪除函數是
69.設備對象、控制設備、符號鏈接它們三者的關系試什么
70.什么是GUID生成的函數是什么
71.分發函數的作用是什么,請寫出標準分發函數原型。
72.應用層程序如何打開對應內核驅動
73.DeviceIoControl函數的作用是什么
74.內核如何捕獲客戶端數據
75.客戶端如何獲取內核的數據
76.如何獲得R3級發送給內核的緩沖區、輸入緩沖區長度、輸出緩沖區長度
77.CTL_CODE宏的作用是什么
78.什么是WOW64子系統
79.PatchGuard技術是什么
80.什么是驅動簽名它的作用是什么
81.Win64位和Win32位內核嵌入匯編有什么變化
82.如何使用宏判斷當前系統是Win64位還是Win32位
83.NTLDR從注冊表什么位置以此加載驅動
答案在下面=.=
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
1.SDK是應用程序開發WDK是內核開發
2.WDK全稱:Windows Driver Kit
3.下載Windows WDK包,創建C語言編寫的文件使用x86 Checked Build Environment程序cd進入對應的文件夾(只能在WDK安裝目錄)之后使用bulid生成sys驅動程序文件,之后使用SRVINSTW.EXE文件安裝驅動程序,使用net start 驅動名或net stop 驅動名啟動或關閉服務
4.Windbg使用串行端口連接并且需要boot.ini文件之后重啟前打開Windbg設置Kernel dbg為COM 端口號與\. \pipe\com_1 的pipe模式連接
5.Windows把內核作為一個整體,我們要告訴他代碼的路徑和符號表路徑就可以了,Windwos內核符號表是提供了對應函數名和棧調用的查看,如果調試的時候重視看不見可懂的函數名,這就是符號表的設置問題。
6.符號表設置winDbg在連接內核成功在File下選擇Symbol File Path功能目錄為build生產的i386文件夾,源碼設置選擇File下的Soucres File Path功能將包含源碼的目錄寫上即可
7.首先在驅動中入口函數DriverEntry中寫入
#if DBG
__asm int 3
#endif
編寫成sys驅動文件到虛擬機中安裝服務之后Windbg下File->Open soucres File選擇源碼文件打開,之后net start 驅動名啟動服務這個時候就會有提示點擊確定源文件會更新現在現在的斷點位置,需要注意的是在之前一定要先設置好Soucres File Path功能,F9取消斷點、F10單步執行,F11單步進入
8.每個進程的內存空間都是互相獨立的,雖然地址的數值看起來都是0~N的一個線性空間,但是大多數進程都要調用操作系統提供的功能,才能成為一個完整的應用程序,因此進程空間被劃分為兩部分,一部分供進程獨立使用,被稱為用戶空間,另一部分容納操作系統的內核,稱為內核空間或者稱為系統空間,比如Windows32位操作系統低2G是操作系統空間站,高2G是內核空間用戶空間是各個進程隔離的,但是內核空間是共享的嗎,也就是說每個進程看見的高2G空間范圍內的數據,絕大部分是一樣的,內核空間是受到硬件保護的,比如X86架構下的Ring 0的代碼才可以訪問內核空間,普通應用程序編譯出來之后都是運行在Ring 3層,Ring 3調用Ring 0一般都是通過操作系統提供的一個入口,(該入口調用sysenter指令)來實現,這樣應用程序就是可以調用操作系統內核提供的功能,又不至于隨意修改內核的權利,內核模塊并非和普通程序一樣作為一個進程執行,而是運行在內核空間,成為操作系統的一個模塊,最終被需要該模塊提供功能的應用程序調用(也有可能被操作系統本身調用)。
9.內核模塊(kernel Module)實際上可以被稱為驅動程序但是并不是太嚴謹因為內核模塊并沒有,驅動任何硬件有人稱之為軟驅動或者稱之為內核模塊。
10.內核模塊位于內核空間,而內核空間又被所有進程共享,因此實際上內核模塊位于任意一個進程空間中。
11.PsGetCurrentProcessId函數是獲得當前進程的進程號,因為任意一段代碼的任意一次執行都是一定位于某個進程空間中的,所以如果知道是哪一個進程的調用就需要使用此函數。
12.System進程是Windwows的一個特殊進程,這個進程的PID始終為4,在調用PsGetCurrentProcessId函數時,就會發現內核模塊分發函數調用時,當前進程一般都不是System進程,但是DriverEntry(驅動入口)函數被調用時,一般都是在系統進程中的,這是因為Windwos一般都是用操作系統來加載內核模塊的,但并不是說所有內核代碼始終運行在System進程里。
13.X86用戶空間0x00000000~0x7FFFFFFF
X86內核空間0x80000000~0xFFFFFFFF
X64用戶空間0x0000000000000000~0x7FFFFFFFFFFFFFFF
X64內核空間0x8000000000000000~0xFFFFFFFFFFFFFFFF
14.WDK基本數據結構類型,在內核編程時應該遵守WDK編碼習慣,雖然并不是必須的但是不同C語言編譯器或者不同平臺的編譯有可能會表示的字節長度不一樣,如果長度不一樣的話使用重新定義過的類型是有好處的,遇到了是沒問題重新定義一下就行了,不至于產生不可控的問題.
(1)unsigned long ULONG
(2)unsigned char UCHAR
(3)unsigned int UINT
(4)void VOID
(5)unsgined long* PULONG
(6)unsigned int* PUINT
(7)void * PVOID
15.絕大部分內核API返回值是一個返回狀態也就是一個錯誤碼,這類型是NTSTATUS
NTSTATUS status;
If(!NT_SUCCESS(status)){錯誤捕獲}
NT_SUCCESS函數是用來檢查返回值是否成功。
16.驅動字符串一般是使用下面的結構
typedef struct UNICODE_STRING{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}UNICODE_STRING *PUNICODE_STRING;
使用例子:
UNICODE_STRING str=RTL_CONSTANT_STRING(L”first:Hello word system”);
DbgPrint(“%wz”,&str);
UNICODE_STRING的指針使用%wZ打印,int使用%d打印,char用%c打印,
UNICODE_STRING除了初始化和打印之外,還可以像普通字符串一樣做很多操作,比如
拷貝、連接、轉換等。
17.驅動對象是Windows內核采用面向對象的編程方式,雖然使用的是C語言但是仍然模擬C++面向對象編程,Windows內核認為許多東西都是對象,比如一個驅動,一個設備、一個文件、甚至其他的一些東西,對象相當于一個基類。
在Windows啟動之后,這些內核對象都在內存中,比如我們在內核中寫代碼,則可以隨意訪問他們,每個種類的內核對象都用一個結構體來表示。
一個驅動對象代表了一個驅動程序,或者說一個內核模塊。驅動對象使用的結構是DRIVER_OBJECT,可以到WDK中的wdm.h查看,寫一個驅動程序或者你內核模塊要在Windows中加載。則必須填寫這樣的一個結構,來告訴Windows提供哪些功能…
18
內核回調函數分為兩種,一種是普通分發函數,另一種是快速IO分發函數,相比SDK編程開始執行來生成一個進程就不同了,內核模塊并不生成一個進程,只是填寫一組回調函數讓Windows來調用,而且這組回調函數必須符合Windows內核規定,上述的2中回調函數(分發函數)是用來處理發送給這個內核模塊的請求,一個內核模塊的所有功能都由它們提供給Windows。
19.Windows中很多組件都有自己的DRIVER_OBJECT比如所有的硬件驅動程序、所有的類驅動(Disk Cdron)文件系統(NTFS、FastFat)都有各自的DRIVER_OBJCET以及其他的內核組件,可以通過編寫內核程序,能找到這些關鍵的驅動對象結構(比如NTFS文件操作系統),然后改寫下面的分發函數,替換成我們自己的函數,可能就能捕獲Windows文件的功能,比如掃描病毒、文件加密、這是就是分發函數Hook技術。
20.設備對象結構是DEVICE_OBJECT被常常簡稱為DO,它的作用類似于Windows GUI編程中的窗口,窗口是唯一可以接受消息的東西,任何消息都是發送給一個窗口的,而內核世界中,大部分”消息”都是請求(IRP)的方式傳遞的,而內核設備對象DEVICE_OBJECT是唯一可以接受請求的實體,任何一個請求(IRP)都是發送給某個設備對象的,比如一個DO(DEVICE_OBJECT)可以代表一個硬盤因為硬盤可以讀或者寫,一個DO是由內核程序生成的,而一個內核程序是一個驅動對象表示的,所以一個設備對象總是屬于一個驅動對象,驅動對象可以生成多個設備對象。
21.Windows向設備對象發送請求,這些請求是被驅動對象的分發函數捕獲的,當Windows內核向一個設備發送一個請求時,驅動對象的分發函數中的某一個會被調用。
分發函數例子:
參數device是請求的目標設備、irp是請求指針
NTSTATUS MyDispatch(PDEVICE_OBJECT device,PIRP irp);
處理過程由內核模塊的開發者在這函數中編寫
22.IRP內核請求類似于一個動作比如網卡發送一個數據包、或者想網卡請求把已經緩存在緩存區收到的包讀出來,這就是一個請求,又比如在硬盤64MB地方寫入一個512字節的數據這也是一個請求,IRP請求的結構是DECLSPEC_ALIGN、IRP對象、*PIRP指針,IRP結構相對來說比較復雜以為他要傳遞n個設備才能完成。
23.IRP棧空間是因為IRP往往要傳遞N個設備才能得以完成,在傳遞過程中,有可能會有一些中間變換,導致請求參數變化,為了保護這種變化,我們每次中轉都留一個棧空間,用來保存中間參數,請求并非簡單的一個輸入,并等待一個輸出,而是經過許多中轉才得以完成,而且在中轉過程的每個不中輸入都是可以被改變的,所以可變部分的輸入信息保存在一個類似于棧的機構中,每中轉一次,都使用其中的一個位置,域CurrentLoaction表示當前使用了那個一個。
24.
常見Windows內核API
Ex系列函數(常用于內核分配,獲取互斥體等)
ExAllocatPool:內存分配,相當于C RunTime庫里的malloc函數
ExFreePool:內存釋放,相當于 C RunTime庫里的free函數
ExAcquireFastMutex:獲取一個快速互斥體,互斥體用于多線程環境中的同步
ExReleaseFastMutex:釋放一個快速互斥體。
ExRaiseStatus:拋出一個異常,帶有錯誤status值,這個函數用于從代碼很深的地方直接報錯
Zw系列函數與Nt系列函數(讀寫、打開、請求)
Zw函數和同名的Nt函數有相同的作用實際上就是簡單的跳轉關系,被稱為Native API
ZwCreateFile、NtCreateFile:打開文件(實際上也可以用于打開一個設備)
ZwWrite、NtWirte:寫入文件(實際上也可以用于發送請求給設備)
ZwReadFile、NtReadFile:讀同上
ZwQueryDirectoryFile、NtQueryDirectoryFile:目錄查詢
ZwDeviceIoControFile、NtDeviceIoControFile:發出設備控制請求
ZwCreateKey、NtCreayeKey:打開一個注冊表鍵
ZwQueryValueKey、NtQueryValueKey讀取一個注冊表中的值
Rtl系列函數(字符串操作、內存操作)
RtInitUincodeString:初始化一個字符串
RtlCopyUnicodeString:拷貝字符串
RtlAppendUnicodeToString:將一個字符串追加到另一個字符串后
RtlStringCbPrintf:將字符串打印到一個字符串中,相當于sprintf
RtlCopyMemory:內存數據塊的拷貝
RtlMoveMemory:內存數據塊移動
RtlZeroMemory:內存數據清空
RtlCompareMemroy:比較內存
RtlGetVersion:獲取當前Windows版本
Io系列函數(IO管理器、IRP、消息發送)
IoCreateFile:打開一個文件相比ZwCrateFile更加底層
IoCreateDevice:生成一個設備對象
IoCallDriver:發送請求,實際上這個函數可能是IoCallDriver的一個別名Windows管理器調用這個函數吧不同IRP發送不同的設備
IoComplateRequest:完成請求,這實際是通知IO管理器這個IRP已經完成了
IoCopyCurrentIrpSatckLocationToNext:獲取當前IRP棧空間拷貝到下一個棧空間
IoSkipCurrentIrpStackLocationToNext:跳過當前IRP棧空間
IoGetCurrentIrpStackLocation:獲得IRP當前棧空間指針
25.Windows內核編程主要調用源
調用源類似于SDK編程中main函數中調用了其他函數,比如調用了函數B而函數B調用了函數C,那么函數C的調用源就是main函數中,調用路徑中包含了函數B,在大部分情況下單線程函的用戶態應用程序只有一個調用源那就是主函數,內核編程的一個顯著特點是,任意一個函數往往可能有多個調用源,主要可以追溯的調用源如下:
(1)入口函數DriverEntry和卸載函數DriverUnload
(2)各種分發函數(包括普通分發函數和快速IO分發函數)
(3)處理請求時設置的完成函數,也就是說,該請求完成后會被系統調用的回調函數
(4)其他回調用函數(如各類NDIS驅動程序的特征函數)
還有可能包含其他調用源,在寫任意代碼時,都應該了解這段代碼可能是調用源應該在哪里,這對處理函數可重入性和考慮運行中斷級都有很大的好處。
26.
多線程安全就是指一個函數被調用過程中,還沒有返回時,又被其他線程調用的情況下,函數的執行結果的可靠性,則成為這個函數的多線程安全性,結果不可靠就是非安全的。
相較于SDK開發WDK開發的多線程安全沖突比較常見,但是嚴格的保證每個函數的多線程安全性是浪費和不可能的,需要通過下面幾個規則進行判斷是否需要多線程安全。
(規則1)可能運行于多線程環境的函數,必須是多線程安全的,只運行于單線程環境的函數,則不需要多線程安全性。
(規則2)如果函數A的所有調用源只運行于同一個單線程的環境下,則函數A也是只運行在單線程環境下。
(規則3)如果函數A的其中一個調用源可能是運行在多線程環境中,或者多個調用源可能運行于不同的可并發的多線程環境下,而且調用路徑上沒有采取多線程序列化成單線程的強制措施,則函數A也是可能運行在多線程環境下的。
(規則4)如果函數A所有的可能運行于多線程環境的調用路徑上,都有多線程序列化成單線程的強制措施,則函數A是運行在單線程環境下的。
多線程序列化成單線程的強制措施是指如互斥體、自旋鎖、等同步手段
內核代碼中主要調用源的運行環境
DriverEntry、DriverUnload 單線程 這兩個函數由系統進程的單一線程調用
各種分發函數 多線程 沒有任何文檔保證分發函數不會被多線程同時調用,此外分罰函數不會和DriverEntry并發、但是有可能和DriverUnload并發
完成函數 多線程 完成函數隨時可能被未知的線程調用
各種NDIS回調函數 多線程 和完成函數相同
保證多線程安全性規則
(規則1)只使用函數內部資源,完全不使用全局變量,靜態變量或者其他全局性資源函數是多線程安全的。
(規則2)如果對某個全局變量或者靜態變量的所有訪問都被強制的同步手段限制為同一時刻只有一個線程訪問,則即使使用了這些安全變量和靜態變量,對函數的多線程安全性也是沒用影響的,可以等同于內部函數。
27.內核中斷級有Dispatch級和Passive級
Dispathch級相比passive級要高
簡單函數在Dispatch級中執行,復雜的函數要在passive級中運行
內核代碼主要調用源中斷級
DriverEntry/DriverUnload Passive級
各種分發函數 Passive級
完成函數 Dispatch級
各種NDIS回調函數 Dispatch級
劃分規則
規則1:
如果在調用路徑上沒有特殊情況(導致中斷級的提高或降低)則一個函數執行時的中斷級和它的調用源的中斷級相同
規則2:
如果在調用路徑上有獲取自旋鎖,則中斷級隨之升高,如果在調用路徑上有釋放自旋鎖則中斷級隨之下降。
Dispacth級的代碼調用Passive級代碼不能強制降低當前中斷級,所以需要生成一個線程專門執行Passive級代碼。
28.
#pragma alloc_text宏用來指定某個函數可執行代碼在編譯出來之后在sys文件中的位置,
內核模塊編譯出來之后是一個PE格式的sys文件,這個文件的代碼段(text段)中有不同的節(Section),不同的節被加載到內存中之后處理情況不同,分別有三種節。
INT節:初始化完成之后就釋放,也就是說不再占用內存空間了。
PAGE節:可以進行分頁交換內存空間,這些內存空間在內存緊張時可以被交換到硬盤上以節省內存。
PAGELK節:是默認節,加載后位置不可分頁交換的內存地址中
(1)#pragma alloc_text(INIT,DriverEntry);
(2)#pragma alloc_text(PAGE,NdisProtUnload);
(3)#pragme alloc_text(PAGE,NdisProtOpen);
(4)#pragma alloc_text(PAGE,NdisProtClose);
需要注意的是PAGE節函數不可在Dispatch級中調用,因為這種函數調用可能誘發頁中斷,而缺頁中斷處理不能再Dispatch級完成,為此一般都用一個宏PAGED_CODE()進行測試,如果發現當前中斷級為Dispacth級,則程序直接報異常。
例子:
#pragma alloc_text(PAGE,SfAttachToMountedDevice)
NTSTATUS
SfAttachToMountedDevice(IN PDEVICE_OBJECT DeviceObject,IN PEDVICE_OBJECT SFilterDeviceObject){
PSFILTER_ERVICE_EXTENSION new DevExt=SfileterDeviceObject>DeviceExtension;
NTSTATUS status;
ULONG i;
PAGE_CODE();
29.
_UNICODE_STIRNG是內核寬字符串的結構體類似于SDK的string
_STRING是內核的窄字符結構體
30.RtlInitUnicodeString函數可以是初始化UNICODE_STRING結構變量
例子:
UNICODE_STRING str={0};
RtlInitUnicodeString(&str,L”my first string”);
31.例子:
UNICODE_STRING dst;//目標字符串
WCHAR dst_buf[256];//我們現在還不會分配內存,所以先預定緩沖區
UNICODE_STRING src=RTL_CONSTANT_STRING(L”My soucre string”);
//把目標字符串初始化為擁有緩沖區長度為0的UNICODE_STRING空串
RtInitEmptyUnicodeString(&dst,dst_buf,256sizeof(WCHAR));
RtlCopyUnicodeString(&dst,&src);
32.
RTL_CONSTANT_STRING是初始化一個常數字符串的常見宏
例子:UNICODE_STRING src=RTL_CONSTANT_STRING(L”My source srting !”);
33.初始化字符串長度
例子:
UNICODE_STRING dst;
WCHAR dst_buf[256];
RtlInitEmptyUnicodeString(&dsr,dsr_buf,256sizeof(WHCAR));
34.RtlCopyUnicodeString()函數是拷貝UNICODE_STRING結構字符串
例子:
UNICODE_STRING dst;
UNICODE_STRING dst2=RTL_CONSTANT_STRING(L”My source string”);
WCHAR dst_buf[256];
RtlInitEmptyUnicodeString(&dst,dst_buf,256*sizeof(WCHAR));
RtlCopyUnicodeString(&dst,&dst2);//拷貝
35.NTSTATUS 成功返回STATUS_SUCCESS錯誤則返回錯誤碼
36.NTSTATUS DriverEntry(PDRIVER_OBEJCT dirve,PUNICODE_STRING reg_path)//內核驅動入口
VOID DriveUnload(PDRIVER_OBJECT drive)//內核驅動卸載函數
37.如果希望兩個UNICODE_STRING連接成一個這種情況使用RtlAppendUnicodeToString函數
例子:
UNICODE_STRING str=RTL_CONSTANT_STRING(L”My str”);
UNICODE_STRING dsr=RTL_CONSTANT_STRING(L”My dsr”);
RtlAppendUnicodeToString(&str,&dsr);
38.
ExAllocatePoolWithTag函數類似于SDK的malloc分配內存,該函數分配的內存如果不釋放則永遠泄漏。
ExFreePool函數是釋放ExAllocatePoolWithTag函數分配的內存類似于SDK的freeMalloc
39.內存分配標識符是每個驅動程序都定義的一個主機的內存標識,也可以在每個模塊中定義單獨的內存標識,內存標識是隨意的32位數字,即使沖突也不會導致問題。
40.
ExFreePool不能夠隨意釋放棧空間,它只能釋放ExAllocatePoolWithTag分配的內存空間,它們需要成對出現,隨意釋放極容易導致系統崩潰藍屏。
41.KdPrint相較于DbgPrint來說它是一個宏定義,而且對于Free版本將被優化點,Check版本不會優化掉,在完成KernelModule的開發后手動去動DbgPrintf比較麻煩所以直接使用KdbPrint然后使用Free版本優化點即可.
42.LARGE_INTEGER這個結構體方便之處在于,既可以方便得到高32位和低32位,也可以方便地得到整個64位,使用union聯合體結構,在Windows內核中一個函數參數要使用64位數據,一般是PLARGE_INTEGER類型,VC中64位數據類型為_int64定義為_int64 file_offset.
43.(1)什么是自旋鎖
自旋鎖是用來解決多線程問題的,被聲明了自旋鎖的函數在同一時間內只有一個線程可以執行,其他線程則在KeAcquireSpinLock等候,用于防止多線程造成的數據異常。
(2)如何使用自旋鎖
KSPIN_LOCK my_spin_lock;
KeInitializeSpinLock(&my_spin_lock);
KIRQL irql;//舊的中斷級別被保存到這個參數中
KeAcquireSpinLock(&my_spin_lock,&irql);//單線程代碼段起始
//單線程運行的代碼段
KeReleaseSpinLock(&my_spin_lock,&irql);//單線程代碼段結束
(3)使用自旋鎖時需要注意什么
如果聲明了一個KSPIN_LOCK局部鎖變量在堆棧中,每個線程來執行的時候都會重新初始化一個鎖,只有所有的線程共用一個鎖,鎖才有意義,鎖一般不會定義成局部變量這個需要額外注意,可以使用靜態變量鎖、全局變量鎖。
44.隊列自旋鎖是在Windows Xp系統之后被引入的,和普通自旋鎖相比,隊列自旋鎖在多CPU平臺上具有更高的性能表現,并遵循first-come frist-served原則,即:隊列自旋鎖遵守誰先等待,誰先獲取自旋鎖的原則,隊列自旋鎖與普通自鎖的使用方法基本一樣,初始化自旋鎖也是使用KelInitializeSpinLock函數,唯一不同的地方是在獲取和釋放自旋鎖時需要使用新的函數。
例子:
隊列自旋鎖初始化
KSPIN_LOCK my_Queue_SpinLock={0};
KeInitializeSpinLock(&my_Queue_SpinLock);
隊列自旋鎖的獲取和釋放
KLOCK_QUEUE_HANDLE my_lock_queue_handle;
KeAcquireInStackQueueSpinLock(&my_Queue_SpinLock,&my_lock_queue_handle);
//鎖代碼段
KeReleaseInStackQueuedSpinLock(&my_lock_queue_handle);
從上面的代碼可以看出,隊列自旋鎖的使用增加了一個KLOCK_QUEUE_HANDLE數據結構,這個數據結構唯一的表示一個隊列自旋鎖。
45.OBJECT_ATTRIBUTES結構無論是打開文件、打開注冊表鍵、還是打開設備都需要使用到OBJECT_ATTRIBUTES結構
46.
Windows在不同版本上開發的驅動程序有過不同的名稱
VXD 是在Windows9X上的驅動程序
KDM 是在WindowsNT上的驅動程序
WDM 是在Windows98~Windows2000這個時期出現的驅動
WDF Windows2000~Windows8.1驅動
47.初始化OBJECT_ATTRIBUTES(無論是打開文件、打開注冊表、還是打開設備都需要該結構Windows不會開公布)
InitilaizeObjectAttributes()函數是初始化一個OBJECT_ATTRIBUTES結構
48.
ZwCreateFile()函數用于打開一個文件
ZwClose()函數用于關閉一個文件
49.
ZwReadFile()讀取文件
ZwWriteFile()寫文件
50.
應用程序編程與驅動編程注冊表鍵最大不同的一點是這個路徑的寫法不一樣,一般應用編程中需要提供一個根子健句柄,而驅動編程中則全部用路徑表示,實際上應用程序和驅動程序很大不同在于應用程序總是由某個當前用戶啟動的,因此可以直接讀取當前用戶的HKEY_CLASSE_ROOT和HKEY_CURRENT_USER。
對照
應用程序子健 驅動程序中路徑寫法
HEKY_LOCAL_MACHINE \Registry\Machine
HKEY_USERS \Registry\User
HKEY_CLASSES_ROOT 沒有對應路徑
HKEY_CURRENT_USER 沒有簡單的對應路徑,但是可以求得
51.
ZwOpenKey打開一個注冊表鍵
ZwCreateKey新建或打開一個注冊表鍵
在驅動程序中使用ZwOpenKey打開注冊表鍵的情況比較常見,和ZwCreateFile類似也需要一個OBJECT_ATTRIBUTES指針
52.注冊表鍵的讀取使用ZwQueryValueKey函數,需要注意的是注冊表中鍵的值可能有多種數據類型,而且長度也沒有定數,為此在讀取過程中,就要可能面對很多種可能的情況。
53.KeyValueBasicInformation:獲得基礎信息,包含值名和類型
KeyValueFullInformation:獲得完整信息,包含值名、類型和值的數據
KeyValuePartialInformation:獲得局部信息,包含類型和值數據。
54.
熟悉Win32應用開發的讀者會知道一個函數GetTickCount,這個函數返回系統自啟動后好的毫秒數,在驅動開發中有一個對應的函數KeQueryTickCount不過遺憾的是這是一個滴答數
55.獲取當前系統時間
KeQuerySystemTime得到當前時間,但是得到的不是當地時間,而是一個格林威爾時間,可以使用ExSystemTimeLocalTime轉換成當地時間
56.
KeSetTime函數是定時器函數
57.KeInitializeTimer()函數是初始化KTIMER結構,KTIMER結構是KetSetTime函數需要的
58.
系統線程類似于應用程序的線程不一樣的是,在驅動中做一些任務可能耗時過長容易使整個系統陷入停頓,一個單獨的線程長期等待或者執行一些耗時的任務還不至于對系統造成致命的影響,還為此啟動一個特殊線程來執行它們是最好的方法,在驅動中生成的線程一般是系統線程,系統線程所在的進程名是“System”。
59.系統線程函數是PsCreateSystemThread()
60.系統線程休眠函數KeDelayExecutionThread()
61.需要使用事件驅動技術,內核中的事件是一個數據結構,線程之間的同步,如一個線程需要等待另一個線程完成某些事后才能做某些事,則可以使用事件等待(類似于SDK互鎖函數、事件內核對象),需要使用KEVENT結構,KeInitizeEvent初始化KEVENT結構,設置事件使用KeSetEvent
62.KeWaitForSingleObject(與KEVENT結構一起使用的同步函數)函數是檢測事件是否被設置,如果沒有被設置,那么就會阻塞在這里繼續等待。
63.如果一個驅動需要和應用程序通信,那么首先要生成一個設備對象(Device Object),在Windows驅動開發體系中,設備對象是非常重要的元素。設備對象和分發函數構成了整個內核體系的基本框架。設備對象可以在內核中暴露出來給應用層,應用層可以像操作文件一樣操作它,用于和應用程序通信的設備往往用來控制這個內核驅動所以往往被稱為,
控制設備對象(Control Device Object CDO)
64.
IoCreateDevice生成設備函數該函數生成的設備默認具有安全屬性,必須具有管理員權限的進程才能打開它
IoCreateDeviceSecure對比IoCreateDvice來說可以強迫生成一個任何用戶都可以打開的設備,如果是商業軟件那么這顯然是不安全的。
65.內核基本框架是由設備對象和分發函數構成的,設備對象可以暴露給應用層,應用層可以像操作文件一樣操作設備對象
66.設備對象是可以沒有名字的,但是控制設備需要一個名字,這樣它才會暴露出來,供其他程序打開與通訊,設備的名字可以在調用IoCreateDevice或者IoCreateDeviceSecure時指定,此外應用層是無法直接通過設備的名字來打開對象的,為此必須要要建立給一個暴露給應用程序層的符號鏈接。
67.符號鏈接就是記錄一個字符串對應到另一個字符串的一種簡單結構,生成的函數是IoCreateSymbolicLink函數生成符號鏈接,需要注意的是符號鏈接名字如果在系統中已經存在(符號鏈接會在Windows全局存在)那么這個函數將返回是失敗.
68.如果在驅動中生成了控制設備與符號鏈接,那么在驅動卸載時就應該刪除它們,否則符號鏈接會一直存在,刪除符號表的函數是IoDeleteSymbolicLink;
69.設備對象可以沒有名字但是控制設備需要一個名字,這樣它才會被暴露出來,供應用程序通信,控制設備可以在設備對象創建的時候指定,應用層無法通過設備對象名與內核通信因此需要符號表綁定控制設備名來與內核通信。
70.GUID是全局唯一標識符,是一種由算法生成的二進制長度為128位的數字標識符,也可以手動生成格式為:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx例如:6F9619FF-8B86-D011-B42D-00C04FC964FF 即為有效的 GUID 值,內核生成GUID的函數是CoCreateGuid.
71. 分發函數是一組用來處理發送給設備對象(當然也包括控制設備)的請求的函數,這些函數由內核驅動開發者編寫,以便處理這些請求并返回給Windows,分發函數是設置在驅動對象(Driver Object)上的,也就是說每個驅動都有自己一組的分發函數,Windows的Io管理器在收到請求時,會根據請求發送的目標,也就是一個設備對象,來調用這個設備對象所從屬的驅動對象上對應的分發函數。
標準分發函數原型:
NTSTATUS cwDispatch(IN PDEVICE_OBJECT dev,IN PIRP irp)
其中dev就是請求要發送給的目標對象,irp則代表了請求內容的數據結構指針,無論如何,分發函數必須首先設置給驅動對象,這個工作一般在DriverEntry中完成
例子:
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path){
ULONG i;
for(i=0;i<=IRP_MJ_MAXIMUM_FUNCTION;i++){
driver->MajorFunction[i]=cwkDispatch;
}
注意:上面的片段中將所有的分發函數(實際上MajorFunction是一個函數指針數組,保存所有分發函數的指針)都設置成同一個函數,這是一種簡單的處理方案,讀者可以為每種請求設置不同的分發函數,如果有很多種類的請求要處理,而且每種請求的處理還都很復雜,那么放在同一個函數里做就會導致那個函數非常龐大、復雜。
72.應用層使用CreateFile函數打開內核驅動的符號鏈接并與內核的分發函數溝通
例子:
#define CW_DEV_SYM L"\\.\slbkcdo_3948d33e" //內核符號鏈接名
CreateFile(CW_DEV_SYM,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);
73.DeviceIoControl這個函數發送一個設備對象的設備控制請求到內核
74.內核分發函數檢查是否為IRP_MJ_DEVICD_CONTROL如果是則switch捕獲當前棧空間棧空間->Parameters.DeviceIoControl.IoControlCod,如果是對應的請求碼則進入
75.Cratefilemapping函數由R3創建一個共享內存,R3發送內核接受,內核往R3創建的共享內存返回數據。
76.irp->AssociatedIrp.SystemBuffer;//獲得緩沖區
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);//請求當前棧空間,棧空間結構是適用于內核的設備對象棧結構
ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;//獲得輸入緩沖區的長度
ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;//獲得輸出緩沖區的長度
77.CTL_CODE宏是SDK頭文件提供的,用來生成一個主機的設備控制請求功能號,CTL_CODE有四個參數,其中第一個參數是設備類型,第二參數是生成功能號的核心數字,這個數字用來和其他參數合成功能號,0x0~0x7ff被微軟預留所以必須使用大于數字,同時還這個數字不能大于0xfff,如果要定義超過一個的功能號,那么不同的功能號就靠著區分,第三個參數是緩沖方式,最后一個參數是操作需要的權限
例子:
#define CWK_DVC_SEND_STR
(ULONG)CTL_CODE(
FILE_DEVICE_UNKNOWN,
0x912,METHOD_BUFFERED,
FILE_READ_DATA)
FILE_DEVICE_UNKNOWN=沒有定義的類型
METHOD_BUFFERED=輸入\輸出緩沖區會在用戶和內核質之間拷貝
FILE_READ_DATA=寫入權限
78.
WOW64子系統是64位Windows系統為了兼容32位應用程序而新增的子系統,簡單來說,可以先把它理解成一個輕量級的兼容層,這個兼容層主要工作在應用層,主要由三個DLL實現,分別是Wow64.dll、Wow64Win.dll、Wow64Cpu.dll,當一個32位應用程序發起系統調試時,WOW64子系統攔截到這個系統調用,WoW64子系統會把這些指針長度轉換為合適的長度,然后把系統請求提交到內核,通常把這個攔截-轉換的過程稱為thunking.
WOW64子系統有兩個重要的模塊,分別為文件系統重定向器(File System Redirector)
和注冊表重定向器(Registry Redirector)模塊。
WOW64系統分別存在兩個System32目錄,分別為%windir%\System32和%windir%\SysWOW64,前者目錄下主要包含64位系統二進制文件,后者目錄下主要包含了32位系統二進制文件,一般來說,32位應用程序只能加載32位的DLL,64位應用程序只能加載64位的DLL,但是一個32位應用程序在32位系統下運行時,這個32位應用程序同樣會到System32目錄下加載所需的系統DLL,而64位系統的System32目錄下包含的是64位系統的DLL,這樣會導致32位應用程序無法啟動,為了解決這個問題32位系統文件訪問%windir%\System32目錄會被WOW64子系統中定向到%windir\SysWOW64目錄。
79.
簡單來說對于32位系統,驅動模塊可以在內核的數據結構或者關鍵函數進行掛鉤和修改,但這種技術不再適用于Windows64位系統,原因是微軟在64位系統中引入了PatchGuard機制,PatchGurad機制就是系統會定時檢查系統關鍵位置,比如SSDT(系統服務描述表),GDT(全局符號表)、IDT(中斷描述表)、系統模塊(如ntoskrnl.exe、hal.sys)等,一旦發現這些關鍵位置的數據或代碼被篡改,系統就會觸發藍屏(BSOD),正是因為如此,一些在32位系統上能使用的SSDT HOOK、INLINE HOOK、IDT HOOK等技術在64位系統下全部不在適用。
80.
與32位系統相比64位系統需要驅動簽名,驅動簽名就是要求驅動文件必須要經過微軟指定的證書頒發機構頒發的微軟代碼簽名證書簽名,只有被簽名的驅動文件才可以被加載運行,其目的有兩個一個是確保驅動代碼沒有被非法篡改,二是確保驅動代碼來源的可靠性,從而保護驅動代碼的完整性。
81.Win64位相比Win32位不也許內核嵌入匯編,如果需要使用匯編,需要把匯編diamante寫成函數放入到一個單獨的asm文件中,然后通過函數調用,WDK也提供了一些幫助的函數比如__asm int 3 常用的斷點調試,有DbgBreakPoint函數,如果使用IDA反匯編那么它和__asm int 3 功能類似。
82.在Windows驅動開發的過程中,有時候代碼的邏輯需要分不同的平臺編譯不同的代碼分支,比如需要開發一個注冊表監控與驅動,按照以往32位系統的常規做法,是通過掛鉤系統服務描述表(SSDT)中注冊表相關的API而實現的,但是在64位系統中,導致掛鉤SSDT方案不在適用,所以代碼必須針對不同系統平臺不同方案的代碼。
WDK中預置了一些宏來幫助開發者實現對不同系統平臺進行編譯
比如:
_M_AMD64:當_M_AMD64被定義時,表示當前編譯環境是AMD64。
_M_IX64:當_M_IX64被定義時,表示當前編譯環境是IA64。
_WIN64被定義說明是Win64系統
#ifdef _WIN64
//64位環境,使用其他方案監控注冊表
#else
//32位環境,使用SSDT HOOK 來監控注冊表
#endif
83.NTLDR從注冊表什么位置以此加載驅動
ntldr依次從HKEY_LOCAL_MACHINE\System\CurrentControlSet鍵下讀取機器安裝的驅動程序,然后依次加載驅動程序,初始化底層設備驅動,在注冊表HKEY_LACAL_MACHINE\System\CurrentControlSet\Service鍵下查詢Start鍵的值為0和1的設備驅動
“Start”鍵的值可以為0,1,2,3,4數值越小,啟動越早,SERVICE_BOOT_START(0)表示內核剛剛初始化,此時加載的都是與系統核心有關的重要驅動程序,例如磁盤驅動,SERVICE_SYSTEM_START(1)稍晚一些,SERVICE_AUTO_START(2)是從登陸界面出現的時候開始,如果登陸速度較快,很可能驅動還沒有加載就已經登陸了,SERVICE_DEMAN_START(3)表示在需要的時候手動加載,SERVICE_DISABLED(4)表示禁止加載。
總結
以上是生活随笔為你收集整理的Windows内核面试题(持续更新,目前完成度30%约1.8万字)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创建数独小游戏uniapp/vue
- 下一篇: 初中计算机位图和矢量图教案,浅析图形图像