前文提到 VxD 提供四種介面,前三種 (Services, V86, PM) 均為 16-bit 環境 (V86/PM) 或 Ring 0 內部 (Services) 使用。**Win32 應用 (Ring 3, 32-bit) 無法直接使用。** 微軟在 Windows 95 引入 DeviceIoControl API,專門讓 Win32 應用與動態 VxD 通訊。
關鍵區別:DeviceIoControl 介面的功能碼 (dwIoControlCode) 與 VxD 服務編號**完全無關**,是獨立定義的介面契約。
; 1. 開啟/載入 VxD (檔名格式固定:\\.\VxDName.vxd)
invoke CreateFile, addr VxDName, 0, 0, 0, 0, FILE_FLAG_DELETE_ON_CLOSE, 0
; FILE_FLAG_DELETE_ON_CLOSE: 關閉代碼時自動卸載 VxD
.if eax != INVALID_HANDLE_VALUE
mov hVxD, eax
; 2. 呼叫 VxD 內部功能
invoke DeviceIoControl, hVxD, 1, addr InBuffer, 8, NULL, NULL, NULL, NULL
; hDevice=VxD代碼, dwIoControlCode=1 (自定義), lpInBuffer=輸入緩衝區, nInBufferSize=8
; 3. 關閉代碼 -> 觸發 VxD 卸載
invoke CloseHandle, hVxD
.endif
動態 VxD 必須在控制分派表註冊處理 w32_DeviceIoControl 訊息的回呼。
.386p
include vmm.inc
include vwin32.inc
DECLARE_VIRTUAL_DEVICE DYNAVXD, 1, 0, DYNAVXD_Control, \
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Begin_control_dispatch DYNAVXD
Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl
End_control_dispatch DYNAVXD
VxD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode == DIOC_Open
xor eax, eax ; 返回 0 = 成功,CreateFile 才會成功
.endif
ret
EndProc OnDeviceIoControl
VxD_PAGEABLE_CODE_ENDS
end
VXD DYNAVXD DYNAMIC
SEGMENTS
_LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE
CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE
_BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_ITEXT CLASS 'ICODE' DISCARDABLE
_IDATA CLASS 'ICODE' DISCARDABLE
_PTEXT CLASS 'PCODE' NONDISCARDABLE
_STEXT CLASS 'SCODE' RESIDENT
_SDATA CLASS 'SCODE' RESIDENT
; ... (標準 VxD 區段定義)
EXPORTS
DYNAVXD_DDB @1 ; 匯出裝置描述區塊 (DDB) 符號,VMM 載入器需求
.386
.model flat, stdcall
include windows.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
AppName db "DeviceIoControl",0
VxDName db "\\.\shellmsg.vxd",0 ; 必須 \\.\ 前綴
Success db "The VxD is successfully loaded!",0
Failure db "The VxD is not loaded!",0
Unload db "The VxD is now unloaded!",0
MsgTitle db "DeviceIoControl Example",0
MsgText db "I'm called from a VxD!",0
InBuffer dd offset MsgTitle ; 輸入緩衝區: 兩個字串指標 (8 bytes)
dd offset MsgText
.data?
hVxD dd ?
.code
start:
invoke CreateFile, addr VxDName, 0, 0, 0, 0, FILE_FLAG_DELETE_ON_CLOSE, 0
.if eax != INVALID_HANDLE_VALUE
mov hVxD, eax
invoke MessageBox, NULL, addr Success, addr AppName, MB_OK+MB_ICONINFORMATION
invoke DeviceIoControl, hVxD, 1, addr InBuffer, 8, NULL, NULL, NULL, NULL
invoke CloseHandle, hVxD
invoke MessageBox, NULL, addr Unload, addr AppName, MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox, NULL, addr Failure, NULL, MB_OK+MB_ICONERROR
.endif
invoke ExitProcess, NULL
end start
.386p
include vmm.inc
include vwin32.inc
include shell.inc ; SHELL_sysmodal_Message 定義
DECLARE_VIRTUAL_DEVICE SHELLMSG, 1, 0, SHELLMSG_Control, \
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Begin_control_dispatch SHELLMSG
Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl
End_control_dispatch SHELLMSG
VxD_PAGEABLE_DATA_SEG
pTitle dd ?
pMessage dd ?
VxD_PAGEABLE_DATA_ENDS
VxD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode == DIOC_Open
xor eax, eax
.elseif [esi].dwIoControlCode == 1 ; 我們自定義的功能碼
mov edi, [esi].lpvInBuffer ; 取得輸入緩衝區指標 (指向兩個 DWORD 字串指標)
; --- 複製標題字串到 VxD 可存取記憶體 (Ring 0 無法直接存取 Ring 3 位址) ---
VMMCall _lstrlen, <[edi]> ; 計算長度
inc eax ; +1 for null
push eax
VMMCall _HeapAllocate, ; 配置記憶體
mov pTitle, eax
pop eax
VMMCall _lstrcpyn, ; 複製字串
; --- 複製訊息字串 ---
VMMCall _lstrlen, <[edi+4]>
inc eax
push eax
VMMCall _HeapAllocate,
mov pMessage, eax
pop eax
VMMCall _lstrcpyn,
; --- 彈出系統模態訊息框 (凍結整個系統直到用戶回應) ---
mov edi, pTitle
mov ecx, pMessage
mov eax, MB_OK
VMMCall Get_Sys_VM_Handle ; 取得 System VM 代碼
VxDCall SHELL_sysmodal_Message ; 彈出訊息框
; --- 釋放記憶體 ---
VMMCall _HeapFree, pTitle, 0
VMMCall _HeapFree, pMessage, 0
xor eax, eax
.endif
ret
EndProc OnDeviceIoControl
VxD_PAGEABLE_CODE_ENDS
end
當 Win32 呼叫 DeviceIoControl,VWIN32 (負責 Win32/VxD 橋接的系統 VxD) 封裝參數並發送 w32_DeviceIoControl 給目標 VxD。ESI 指向 DIOCParams:
DIOCParams STRUC
Internal1 DD ? ; 指向 Win32 用戶端寄存器結構
VMHandle DD ? ; 呼叫者 VM 代碼
Internal2 DD ? ; 指向 DDB (裝置描述區塊)
dwIoControlCode DD ? ; 功能碼 (Win32 傳入)
lpvInBuffer DD ? ; 輸入緩衝區指標 (Ring 3 位址)
cbInBuffer DD ? ; 輸入緩衝區大小
lpvOutBuffer DD ? ; 輸出緩衝區指標
cbOutBuffer DD ? ; 輸出緩衝區大小
lpcbBytesReturned DD ? ; 實際返回位元組數指標
lpoOverlapped DD ? ; OVERLAPPED 結構指標
hDevice DD ? ; Ring 3 設備代碼
tagProcess DD ? ; 進程標籤
DIOCParams ENDS
核心難點:lpvInBuffer / lpvOutBuffer 是 **Ring 3 線性位址**。VxD 運行在 Ring 0,**不能直接解引用**。必須:
VMMCall _HeapAllocate 在系統記憶體配置緩衝區。VMMCall _lstrcpyn (或 CopyMemory) 從 Ring 3 複製資料到 Ring 0 緩衝區 (VMM 會處理跨環複製)。_HeapFree 釋放。VMMCall ServiceName, :呼叫 **VMM 導出服務** (如 _HeapAllocate, _lstrlen, Get_Sys_VM_Handle)。宏展開為 INT 20h + VMM Device ID + 服務索引 (經抓取優化後為直接 Call)。VxDCall ServiceName, :呼叫 **其他 VxD 導出服務** (如 SHELL_sysmodal_Message 屬於 SHELL VxD)。SHELL_sysmodal_Message 參數:EDI=標題指標, ECX=訊息指標, EAX=旗標 (MB_OK等)。它會凍結**整個系統** (所有 VM 暫停),直到用戶點擊確定。這在除錯時極有用,但生產環境慎用。
DECLARE_VIRTUAL_DEVICE,註冊 w32_DeviceIoControl 分派。OnDeviceIoControl,處理 DIOC_Open (返回 0) 與自定義功能碼。DDB 符號。CreateFile("\\.\name.vxd") -> DeviceIoControl -> CloseHandle (配合 FILE_FLAG_DELETE_ON_CLOSE 自動卸載)。現代替代方案:Windows NT 系 (2000+) 使用 **WDM/KMDF 驅動 + DeviceIoControl (IRP_MJ_DEVICE_CONTROL)**,架構相似但支援 PnP、電源管理、簽名強制、安全模型完整。VxD 僅限 Win9x 歷史研究或極舊系統維護。