網絡安全編程:內核驅動進程遍歷
內核驅動在安全方面占據重要的地位。本文實現一個枚舉進程的函數。枚舉進程不能在用戶態(tài)下進行,需要到內核態(tài)下進行,這樣就必須使用驅動程序來完成。先用WinDbg完成一次手動的枚舉過程,再通過代碼來完成。
1. 配置VMware和WinDbg進行驅動調試
使用WinDbg調試驅動程序或內核,需要雙機進行調試。所謂雙機,就是兩臺電腦。通常情況下,大部分人往往只有一臺電腦。那么,解決的方法就是安裝虛擬機,然后對虛擬機進行一些設置,也是可以通過WinDbg進行調試的。虛擬機選擇使用VMware,下面介紹如何對虛擬機進行配置。
安裝好VMware,并在VMware中安裝好操作系統(tǒng),然后對安裝好的虛擬機進行一些設置。通過此設置可以達到調試器與虛擬機的連接。單擊菜單“VM”→“Settings”命令,彈出“Virtual Machine Settings”對話框,如圖1所示。
圖1 “Virtual Machine Settings”對話框
單擊“Add”按鈕,打開“Add Hardware Wizard”(添加硬件向導)對話框,如圖2所示。
圖2 “Add Hardware Wizard”對話框1
在該對話框中選擇“Serial Port”選項,也就是串口,然后單擊“Next”按鈕,彈出“Add Hardware Wizard”對話框的第二個界面,如圖3所示。
圖3 “Add Hardware Wizard”對話框2
在該界面中選擇“Output to named pipe”單選按鈕,也就是命名管道。命名管道是Windows下進程通信的一種方法。選中該項后繼續(xù)單擊“Next”按鈕,進入下一個界面,也是設置的最后一個界面,如圖4所示。
圖4 “Add Hardware Wizard”對話框3
在這個界面中對命名管道進行設置,然后單擊“Finish”按鈕即可。至此,已經完成了一半的設置。接著,啟動虛擬機配置Windows的Boot.ini文件。Boot.ini文件原內容如下:
- [boot loader]
- timeout=30
- default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
- [operating systems]
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fa
- stdetect /NoExecute=AlwaysOff
將最后一行復制,然后放到最后面,并進行修改。修改后的內容如下:
- [boot loader]
- timeout=30
- default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
- [operating systems]
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fa
- stdetect /NoExecute=AlwaysOff
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fa
- stdetect /NoExecute=optin /debug /debugport=com1 /baudrate=115200
去掉Boot.ini文件的只讀屬性,然后保存Boot.ini文件。在下次需要對驅動進行調試,或者對內核進行調試時,選擇啟動Debug模式的Windows。
這里只介紹了針對Windows XP系統(tǒng)的配置方法。關于其他版本系統(tǒng)的配置方法,請自行參考相關內容。
至此,所有的配置工作都做好了,但是使用WinDbg進行連接時,還是要有連接參數的。先在桌面上創(chuàng)建一個WinDbg的快捷方式,然后在WinDbg快捷方式上單擊右鍵,在彈出的快捷菜單中選擇“屬性”命令,彈出“屬性”對話框,將“目標”位置改為:
- F:\WinDDK\7600.16385.0\Debuggers\windbg.exe -b -k com:port=\\.\pipe\com_1,baud= 115200,pipe
這樣就可以用WinDbg連接虛擬機中調試狀態(tài)下的Windows XP了。
2. EPROCESS和手動遍歷進程
Windows中有一個非常大的與進程有關的結構體——EPROCESS。每個進程對應一個EPROCESS結構,但EPROCESS是一個系統(tǒng)未公開的結構體,在WDK中只能找到說明,而找不到其結構體的具體定義,因此需要通過WinDbg來查看。這次使用WinDbg和VMware進行調試。按照前面的方法,使WinDbg和VMware可以連接。當WinDbg出現調試界面時,在其命令處輸入dt _eprocess命令來查看該結構體,如圖5所示。
圖5 WinDbg顯示的部分EPROCESS結構體
從圖中可以看出,EPROCESS結構體顯示出非常多的內容,從WinDbg調試界面只能看到部分成員變量,而且偏移已經到了0x258,非常多??匆幌耊inDbg的全部內容。
- kd> dt _eprocess
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS // 進程控制塊
- +0x06c ProcessLock : _EX_PUSH_LOCK
- +0x070 CreateTime : _LARGE_INTEGER
- +0x078 ExitTime : _LARGE_INTEGER
- +0x080 RundownProtect : _EX_RUNDOWN_REF
- +0x084 UniqueProcessId : Ptr32 Void // 進程 ID
- +0x088 ActiveProcessLinks : _LIST_ENTRY // 活動進程鏈表
- +0x090 QuotaUsage : [3] Uint4B
- +0x09c QuotaPeak : [3] Uint4B
- +0x0a8 CommitCharge : Uint4B
- +0x0ac PeakVirtualSize : Uint4B
- +0x0b0 VirtualSize : Uint4B
- +0x0b4 SessionProcessLinks : _LIST_ENTRY
- +0x0bc DebugPort : Ptr32 Void
- +0x0c0 ExceptionPort : Ptr32 Void
- +0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
- +0x0c8 Token : _EX_FAST_REF
- +0x0cc WorkingSetLock : _FAST_MUTEX
- +0x0ec WorkingSetPage : Uint4B
- +0x0f0 AddressCreationLock : _FAST_MUTEX
- +0x110 HyperSpaceLock : Uint4B
- +0x114 ForkInProgress : Ptr32 _ETHREAD
- +0x118 HardwareTrigger : Uint4B
- +0x11c VadRoot : Ptr32 Void
- +0x120 VadHint : Ptr32 Void
- +0x124 CloneRoot : Ptr32 Void
- +0x128 NumberOfPrivatePages : Uint4B
- +0x12c NumberOfLockedPages : Uint4B
- +0x130 Win32Process : Ptr32 Void
- +0x134 Job : Ptr32 _EJOB
- +0x138 SectionObject : Ptr32 Void
- +0x13c SectionBaseAddress : Ptr32 Void
- +0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
- +0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
- +0x148 Win32WindowStation : Ptr32 Void
- +0x14c InheritedFromUniqueProcessId : Ptr32 Void
- +0x150 LdtInformation : Ptr32 Void
- +0x154 VadFreeHint : Ptr32 Void
- +0x158 VdmObjects : Ptr32 Void
- +0x15c DeviceMap : Ptr32 Void
- +0x160 PhysicalVadList : _LIST_ENTRY
- +0x168 PageDirectoryPte : _HARDWARE_PTE
- +0x168 Filler : Uint8B
- +0x170 Session : Ptr32 Void
- +0x174 ImageFileName : [16] UChar // 進程名
- +0x184 JobLinks : _LIST_ENTRY
- +0x18c LockedPagesList : Ptr32 Void
- +0x190 ThreadListHead : _LIST_ENTRY
- +0x198 SecurityPort : Ptr32 Void
- +0x19c PaeTop : Ptr32 Void
- +0x1a0 ActiveThreads : Uint4B
- +0x1a4 GrantedAccess : Uint4B
- +0x1a8 DefaultHardErrorProcessing : Uint4B
- +0x1ac LastThreadExitStatus : Int4B
- +0x1b0 Peb : Ptr32 _PEB // 進程環(huán)境塊
- +0x1b4 PrefetchTrace : _EX_FAST_REF
- +0x1b8 ReadOperationCount : _LARGE_INTEGER
- +0x1c0 WriteOperationCount : _LARGE_INTEGER
- +0x1c8 OtherOperationCount : _LARGE_INTEGER
- +0x1d0 ReadTransferCount : _LARGE_INTEGER
- +0x1d8 WriteTransferCount : _LARGE_INTEGER
- +0x1e0 OtherTransferCount : _LARGE_INTEGER
- +0x1e8 CommitChargeLimit : Uint4B
- +0x1ec CommitChargePeak : Uint4B
- +0x1f0 AweInfo : Ptr32 Void
- +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
- +0x1f8 Vm : _MMSUPPORT
- +0x238 LastFaultCount : Uint4B
- +0x23c ModifiedPageCount : Uint4B
- +0x240 NumberOfVads : Uint4B
- +0x244 JobStatus : Uint4B
- +0x248 Flags : Uint4B
- +0x248 CreateReported : Pos 0, 1 Bit
- +0x248 NoDebugInherit : Pos 1, 1 Bit
- +0x248 ProcessExiting : Pos 2, 1 Bit
- +0x248 ProcessDelete : Pos 3, 1 Bit
- +0x248 Wow64SplitPages : Pos 4, 1 Bit
- +0x248 VmDeleted : Pos 5, 1 Bit
- +0x248 OutswapEnabled : Pos 6, 1 Bit
- +0x248 Outswapped : Pos 7, 1 Bit
- +0x248 ForkFailed : Pos 8, 1 Bit
- +0x248 HasPhysicalVad : Pos 9, 1 Bit
- +0x248 AddressSpaceInitialized : Pos 10, 2 Bits
- +0x248 SetTimerResolution : Pos 12, 1 Bit
- +0x248 BreakOnTermination : Pos 13, 1 Bit
- +0x248 SessionCreationUnderway : Pos 14, 1 Bit
- +0x248 WriteWatch : Pos 15, 1 Bit
- +0x248 ProcessInSession : Pos 16, 1 Bit
- +0x248 OverrideAddressSpace : Pos 17, 1 Bit
- +0x248 HasAddressSpace : Pos 18, 1 Bit
- +0x248 LaunchPrefetched : Pos 19, 1 Bit
- +0x248 InjectInpageErrors : Pos 20, 1 Bit
- +0x248 VmTopDown : Pos 21, 1 Bit
- +0x248 Unused3 : Pos 22, 1 Bit
- +0x248 Unused4 : Pos 23, 1 Bit
- +0x248 VdmAllowed : Pos 24, 1 Bit
- +0x248 Unused : Pos 25, 5 Bits
- +0x248 Unused1 : Pos 30, 1 Bit
- +0x248 Unused2 : Pos 31, 1 Bit
- +0x24c ExitStatus : Int4B
- +0x250 NextPageColor : Uint2B
- +0x252 SubSystemMinorVersion : UChar
- +0x253 SubSystemMajorVersion : UChar
- +0x252 SubSystemVersion : Uint2B
- +0x254 PriorityClass : UChar
- +0x255 WorkingSetAcquiredUnsafe : UChar
- +0x258 Cookie : Uint4B
上面就是EPROCESS結構體的全部。對于遍歷進程列表來說,有用的只有幾個內容,首先是偏移0x84處的進程ID,然后是偏移0x88處的進程鏈表,最后一個是偏移0x174的進程名。下面手動進行一次遍歷。
在WinDbg的命令輸入提示處輸入! Process 0 0命令,得到進程的列表,如圖6所示。
圖6 進程信息
PROCESS后面給出的值就是當前進程中EPROCESS的地址,選擇explorer.exe進程給出的地址0xff364708來解析EPROCESS。輸入命令dt _eprocess ff364708,輸出如下:
- kd> dt _eprocess ff364708
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS
- +0x06c ProcessLock : _EX_PUSH_LOCK
- +0x070 CreateTime : _LARGE_INTEGER 0x1cb6af5`91d56cea
- +0x078 ExitTime : _LARGE_INTEGER 0x0
- +0x080 RundownProtect : _EX_RUNDOWN_REF
- +0x084 UniqueProcessId : 0x00000600
- +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0xff2b44b0 - 0xff3640a8 ]
- <部分省略>
- +0x174 ImageFileName : [16] "explorer.exe"
- <部分省略>
- +0x1b0 Peb : 0x7ffde000 _PEB
- <后面省略>
可以看到,按照EPROCESS結構體解析ff364708地址,輸出了需要的內容。接著,通過ActiveProcessLinks獲取下一個進程的信息。輸入命令dd ff364708 + 0x88,輸出如下:
- kd> dd ff364708 + 0x88
- ff364790 ff2b44b0 ff3640a8 00002940 00021944
- ff3647a0 00000a92 00003940 00024cb4 00000bf8
- ff3647b0 00000a92 05e04000 0563a000 ff2b44dc
- ff3647c0 ff3640d4 00000000 e15b6eb8 e1ce2640
- ff3647d0 e166f389 00000001 f39a5440 00000000
- ff3647e0 00040001 00000000 ff3647e8 ff3647e8
- ff3647f0 0000003d 000059ca 00000001 f39a5440
- ff364800 00000000 00040001 00000000 ff36480c
ff364790地址處保存了下一個EPROCESS結構體ActiveProcessLinks的地址。要得到下一個EPROCESS的地址,必須減去0x88才行。輸入命令dt _eprocess (ff2b44b0 – 0x88),輸出如下:
- kd> dt _eprocess (ff2b44b0 - 0x88)
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS
- +0x06c ProcessLock : _EX_PUSH_LOCK
- +0x070 CreateTime : _LARGE_INTEGER 0x1cb6af5`95026ecc
- +0x078 ExitTime : _LARGE_INTEGER 0x0
- +0x080 RundownProtect : _EX_RUNDOWN_REF
- +0x084 UniqueProcessId : 0x000006b8
- +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0xff2b7580 - 0xff364790 ]
- <后面省略>
- +0x174 ImageFileName : [16] "VMwareTray.exe"
- <后面省略>
將輸出結果和圖6中的結果對比,explorer.exe的下一個進程為VMwareTray.exe??梢姳闅v方法是正確的。
3. 編程實現進程遍歷
上面介紹的手動遍歷過程就是指導用戶如何編寫代碼的,只要能夠掌握上面的手動遍歷過程,那么代碼的編寫也就不是問題了。下面直接看代碼:
- NTSTATUS DriverEntry(
- PDRIVER_OBJECT pDriverObject,
- PUNICODE_STRING pRegistryPath)
- {
- PEPROCESS pEprocess = NULL;
- PEPROCESS pFirstEprocess = NULL;
- ULONG ulProcessName = 0;
- ULONG ulProcessId = 0;
- pDriverObject->DriverUnloadDriverUnload = DriverUnload;
- pEprocess = PsGetCurrentProcess();
- if ( pEprocess == 0 )
- {
- KdPrint(("PsGetcurrentProcess Error ! \r\n"));
- return STATUS_SUCCESS;
- }
- pFirstEprocess = pEprocess;
- while ( pEprocess != NULL )
- {
- ulProcessName = (ULONG)pEprocess + 0x174;
- ulProcessId = *(ULONG *)((ULONG)pEprocess + 0x84);
- KdPrint(("ProcessName = %s, ProcessId = %d \r\n", ulProcessName, ulProcessId));
- pEprocess = (ULONG)( *(ULONG *)((ULONG)pEprocess + 0x88) - 0x88);
- if ( pEprocess == pFirstEprocess || (*(LONG *)((LONG)pEprocess + 0x84)) < 0 )
- {
- break ;
- }
- }
- return STATUS_SUCCESS;
- }
代碼中用到了一個函數,就是PsGetCurrentProcess()。這個函數是用來獲取當前進程的EPROCESS指針的,其定義如下:
- PEPROCESS PsGetCurrentProcess(VOID);
通過PsGetCurrentProcess()函數獲得的是system進程的EPROCESS,大多數內核模式系統(tǒng)線程都在system進程中。除了這個函數沒有接觸過以外,剩下的部分就是對EPROCESS結構體的操作,這里不做過多的介紹。實現進程內DLL文件的隱藏方法是將指定DLL在DLL鏈表中“脫鏈”。為了隱藏進程,同樣可以將指定進程的EPROCESS結構體在進程鏈表中“脫鏈”,以達到隱藏的目的。