;一个汇编版的NDIS驱动程序模板(by mgf @2007.02.23)
; 写驱动程序已经很久了,但我一直用汇编来写驱动程序,原因是我不喜欢DDK,因为DDK和汇编一样,不是象VC,C#那样的“可视化”的开发工具,
;DDK和汇编一样看不见结构或对象的成员,也看不到各种函数的原型,这些东西和各种常量定义还必须去查看相关的包含文件,不仅如此,DDK还会
;“自作聪明”地把很多垃圾代码加到我的驱动里,虽然没有影响驱动程序的功能,但是还是不爽,不如直接用汇编,生成的驱动程序是什么样的自己
;心里有数。其实这也怪不得MS,它不想大家了解它的操作系统核心技术,但是又不得不开放一些技术细节给大家写驱动,所以只有提高门槛,搞出这
;么一个变态的DDK来吓走很多人,它的目的也就达到了,就是让尽可能少的人了解它的核心技术。能够跨过DDK这道槛的,必定不是一般的高手。呵呵!
; 很多书都建议写驱动最好不用汇编,因为写出来的程序不可移植,但是我不这么认为,因为现在90%的人写出的驱动都是在PC上运行的,没有必要
;为了这10%而放弃这么好的汇编语言。而且计算机都是相通的,如果你会写PC下的驱动了,再写MAC下的驱动不就容易多了吗?
; 这是一个标准的legacy驱动程序,虽然编译时用WDM的方法编译,它有Unload,Read,Write例程,而且还用NDIS HOOK来部分实现了防火墙的功能。
;NDIS HOOK技术被广泛应用于瑞星、江民、天网等众多流行的防火墙里,它的原理是使用NdisRegisterProtocol()注册假协议来获取系统的协议链地址,
;再搜索这个单向协议链,找到TCPIP协议的NDIS_OPEN_BLOCK(NDIS_BINDING_HANDLE)结构,里面就有TCPIP协议的各种派发函数地址,比如SEND在结构
;偏移30H的地方,RECV在结构偏移40H的地方,还有其他很多重要的函数地址都在这个表里。把表里的地址替换成我们的函数地址,就可以拦截操作系统
;收发的所有数据包了。本驱动只是实现了嗅探数据包的功能,还没有实现拦截恶意数据包的功能,如果想实现这个功能,在_Write函数、_mySend函数、
;_myRecv函数里加些代码,使它们配合完成拦截数据包的功能。本驱动如果IoAttachDevice其他驱动,可以改成过滤器;做些修改,还可以实现更多功能,
;可以用来过滤缓冲区溢出攻击包,也可以用来定制自己的SQL INJECTION防护网。
; 本驱动嗅探数据包的流程是这样的:当系统收发数据包时,被_mySend或_myRecv拦截,这两个函数就用NdisSetEvent()置位一个RING3事件(这个事
;件在DriverEntry里建立,在RING3里打开),这样在RING3应用层的WaitForSingleObject()因为事件被置位而返回,于是RING3代码用ReadFile()读取数
;据包的内容,判断是放行还是拦截,再用WriteFile来通知驱动做相应的操作。当然为了提高效率,不是每个数据包都提示RING3应用程序该不该放行,
;有些可以直接在RING0驱动里判断的就不必传到RING3去判断了。处于RING3的代码大概是这样的:
;#include "stdafx.h"
;#include "windows.h"
;#include "stdlib.h"
;int _tmain(int argc, _TCHAR* argv[])
;{
; HANDLE hdrv=CreateFile("////.//NDISDRV",0xc0000000,0,0,3,0,0);
; HANDLE hEvent=OpenEvent(0x100000,0,"Send3Event"); //注意Send3Event必须和驱动里的名字对应
; //HANDLE hEvent=OpenEvent(0x100000,0,"Recv3Event"); //注意Recv3Event必须和驱动里的名字对应
;
; int dwTempVar=1;char szTempBuffer[0x800];
; while(dwTempVar)
; {
; WaitForSingleObject(hEvent,-1);
; ReadFile(hdrv,szTempBuffer,0x800,(LPDWORD)&dwTempVar,0);
; }
;
; return 0;
;}
;把下面的代码保存到ndisdrv.asm里,在MASM32 6.14及以上的编译器里编译:
;ml /c /coff /Cp ndisdrv.asm
;link /subsystem:native /driver:wdm /release /align:16 /base:0x10000 /out:ndisdrv.sys ndisdrv.obj
;由于驱动程序涉及的知识太广而深奥了,本文难免有错漏的地方,希望大家指出,以免一错再错,误导别人。
;下面是驱动的代码:
.586p
.model flat,stdcall
option casemap:none
assume fs:nothing,gs:nothing
includelib ntoskrnl.lib
includelib hal.lib
includelib ndis.lib
includelib tdi.lib
includelib wdm.lib
;以上LIB文件要从DDK里复制到MASM32的LIB目录里
NdisQueryBufferSafe proto :dword,:dword,:dword,:dword
NdisInitializeEvent proto :dword
NdisSetEvent proto :dword
NdisResetEvent proto :dword
NdisWaitEvent proto :dword,:dword
NdisRegisterProtocol proto :dword,:dword,:dword,:dword
NdisDeregisterProtocol proto :dword,:dword
IoCreateDevice proto :dword,:dword,:dword,:dword,:dword,:dword,:dword
IoDeleteDevice proto :dword
IoCreateSymbolicLink proto :dword,:dword
IoDeleteSymbolicLink proto :dword
IoCompleteRequest proto :dword,:dword
IoCreateNotificationEvent proto :dword,:dword
PsGetVersion proto :dword,:dword,:dword,:dword
RtlCompareUnicodeString proto :dword,:dword,:dword
ZwClose proto :dword
;由于MASM32没有相关的INC文件,所以要使用的API要自己定义原型
.data
lpProtocolHandle dd 0 ;协议句柄的指针
lpDeviceObject dd 0 ;设备对象的指针
lpOldSend dd 0 ;保存TCPIP协议驱动的OPEN_BLOCK(BINDING HANDLE)里的SEND派发函数地址
lpOldRecv dd 0 ;保存TCPIP协议驱动的OPEN_BLOCK(BINDING HANDLE)里的RECV派发函数地址
lpSend3Event dd 0 ;RING3发送事件在本驱动的指针
hSend3Event dd 0 ;RING3发送事件在本驱动的句柄
lpRecv3Event dd 0 ;RING3接收事件在本驱动的指针
hRecv3Event dd 0 ;RING3接收事件在本驱动的句柄
dwStatus dd 0
dwTempVar dd 0
obSendEvent db 16 dup(0) ;RING0 SEND对象
obRecvEvent db 16 dup(0) ;RING0 RECV对象
stProtocolChar db 70h dup(0) ;NdisRegisterProtocol()要使用的NDIS_PROTOCOL_CHARACTERISTIC结构
szSendBuffer db 800h dup(0) ;系统将要发送的数据包的副本
szRecvBuffer db 800h dup(0) ;系统将要接收的数据包的副本
dwSendSize dd 0 ;发送副本大小
dwRecvSize dd 0 ;接收副本大小
.const
stTcpip dw 5*2,6*2
dd offset szTcpip
szTcpip dw 'T','c','p','i','p',0
stProtName dw 7*2,8*2
dd offset szProtName
szProtName dw 'N','d','i','s','D','r','v',0
stDeviceName dw 15*2,16*2
dd offset szDeviceName
szDeviceName dw '/','D','e','v','i','c','e','/','N','d','i','s','D','r','v',0
stSymbolicLinkName dw 19*2,20*2
dd offset szSymbolicLinkName
szSymbolicLinkName dw '/','D','o','s','D','e','v','i','c','e','s','/','N','D','I','S','D','R','V',0
stSend3Event dw 28*2,29*2
dd offset szSend3Event
szSend3Event dw '/','B','a','s','e','N','a','m','e','d','O','b','j','e','c','t','s','/','S','e','n','d','3','E','v','e','n','t',0
stRecv3Event dw 28*2,29*2
dd offset szRecv3Event
szRecv3Event dw '/','B','a','s','e','N','a','m','e','d','O','b','j','e','c','t','s','/','R','e','c','v','3','E','v','e','n','t',0
.code
start proc DriverObject,RegisterPath
pushad
invoke PsGetVersion,offset dwTempVar,0,0,0 ;获取操作系统版本,如果不是5.0(2000,XP,2003)就退出,因为不能使用NDIS5.0
.if dwTempVar!=5
jmp _exit
.endif
mov byte ptr stProtocolChar,5
mov byte ptr stProtocolChar+1,0
mov eax,dword ptr stProtName
mov dword ptr stProtocolChar+30h,eax
mov eax,dword ptr stProtName+4
mov dword ptr stProtocolChar+34h,eax
mov dword ptr stProtocolChar+08h,offset PtOpenAdapterComplete
mov dword ptr stProtocolChar+0ch,offset PtCloseAdapterComplete
mov dword ptr stProtocolChar+10h,offset PtSendComplete
mov dword ptr stProtocolChar+14h,offset PtTransferDataComplete
mov dword ptr stProtocolChar+18h,offset PtResetComplete
mov dword ptr stProtocolChar+1ch,offset PtRequestComplete
mov dword ptr stProtocolChar+20h,offset PtReceive
mov dword ptr stProtocolChar+24h,offset PtReceiveComplete
mov dword ptr stProtocolChar+28h,offset PtStatus
mov dword ptr stProtocolChar+2ch,offset PtStatusComplete
mov dword ptr stProtocolChar+3ch,offset PtBindAdapter
mov dword ptr stProtocolChar+40h,offset PtUnbindAdapter
mov dword ptr stProtocolChar+38h,offset PtReceivePacket
mov dword ptr stProtocolChar+44h,offset PtPNPHandler
;以上是填充NDIS_PROTOCOL_CHARACTERISTIC结构
invoke NdisRegisterProtocol,offset dwStatus,offset lpProtocolHandle,offset stProtocolChar,6ch ;获取系统协议链表的地址,前面做了这么多工作都是为了这个地址
.if dwStatus
jmp _exit
.endif
mov ebx,lpProtocolHandle
mov ebx,[ebx+10h] ;跳过我的假协议,保存协议链里下一项的地址
invoke NdisDeregisterProtocol,offset dwStatus,lpProtocolHandle ;我注册的假协议已经没有用了,注销
mov lpProtocolHandle,ebx ;真正的系统协议链的地址
mov esi,offset stTcpip
.repeat
lea edi,[ebx+44h]
invoke RtlCompareUnicodeString,edi,esi,1
.break .if eax==0
mov ebx,[ebx+10h]
.until ebx==0 ;这个循环是搜索TCPIP协议的PROTOCOL_HANDLE
.if eax
jmp _exit
.endif
mov ebx,[ebx] ;取出PROTOCOL_HANDLE里的OPEN_BLOCK指针,每个版本的NDIS的PROTOCOL_HANDLE结构都可能不同,要注意
mov lpProtocolHandle,ebx ;保存TCPIP协议的OPEN_BLOCK(BINDING_HANDLE)
mov eax,[ebx+30h]
mov lpOldSend,eax ;保存TCPIP协议的OPEN_BLOCK(BINDING_HANDLE)里的SEND派发函数地址
mov eax,[ebx+40h]
mov lpOldRecv,eax ;保存TCPIP协议的OPEN_BLOCK(BINDING_HANDLE)里的RECV派发函数地址
invoke IoCreateDevice,DriverObject,18h,offset stDeviceName,21h,0,0,offset lpDeviceObject ;建立设备,DeviceExtension size=18h, type= device_transfer
invoke IoCreateSymbolicLink,offset stSymbolicLinkName,offset stDeviceName
mov eax,lpDeviceObject
or dword ptr [eax+1ch],10h ;把device.flag设置为DO_DIRECT_IO,使驱动程序的READ、WRITE例程直接映射用户缓冲区到本驱动
mov edi,DriverObject
add edi,38h
mov ecx,1ch
mov eax,offset _CommonIoControl
rep stosd ;填充共用例程,必须,否则CreateFile()不能打开本驱动
mov eax,DriverObject
mov dword ptr [eax+34h],offset _Unload
mov dword ptr [eax+44h],offset _Read
mov dword ptr [eax+48h],offset _Write ;注册驱动例程
mov byte ptr [eax+8],2 ;强行修改Driver.flag为legacy driver,否则DriverEntry返回时系统就会卸载本驱动(因为本驱动默认编译为WDM drvier)
invoke NdisInitializeEvent,offset obSendEvent
invoke NdisInitializeEvent,offset obRecvEvent
invoke NdisResetEvent,offset obSendEvent
invoke NdisResetEvent,offset obRecvEvent
;建立RING0事件
invoke IoCreateNotificationEvent,offset stSend3Event,offset hSend3Event
mov lpSend3Event,eax
invoke NdisResetEvent,eax
invoke IoCreateNotificationEvent,offset stRecv3Event,offset hRecv3Event
mov lpRecv3Event,eax
invoke NdisResetEvent,eax
;建立RING3事件,在RING3用OpenEvent()打开
mov ebx,lpProtocolHandle
mov dword ptr [ebx+30h],offset _mySend ;HOOK TCPIP协议的SEND
mov dword ptr [ebx+40h],offset _myRecv ;HOOK TCPIP协议的RECV
_exit:
popad
xor eax,eax
ret
start endp
_Unload proc DriverObject
mov edx,lpProtocolHandle
mov eax,lpOldSend
mov [edx+30h],eax
mov eax,lpOldRecv
mov [edx+40h],eax
;恢复TCPIP协议的OPEN_BLOCK里原来的SEND/RECV派发例程
invoke ZwClose,hSend3Event
invoke ZwClose,hRecv3Event
invoke NdisSetEvent,offset obSendEvent
invoke NdisSetEvent,offset obRecvEvent
invoke IoDeleteSymbolicLink,offset stSymbolicLinkName
invoke IoDeleteDevice,lpDeviceObject
xor eax,eax
ret
_Unload endp
_CommonIoControl proc DeviceObject,pIrp
mov eax,pIrp
mov dword ptr [eax+18h],0
mov dword ptr [eax+1ch],0
invoke IoCompleteRequest,pIrp,0
xor eax,eax
ret
_CommonIoControl endp
_Read proc DeviceObject,pIrp
pushad
mov ebx,pIrp
mov edi,[ebx+4]
mov ecx,[edi+18h]
mov edi,[edi+10h]
add edi,ecx ;EDI=用户缓冲区
mov ecx,lpSend3Event
mov edx,lpRecv3Event
.if dword ptr [ecx+4]
invoke NdisResetEvent,lpSend3Event ;RING3事件复位,防止再次放行
mov esi,offset szSendBuffer
mov ecx,dwSendSize
mov eax,[ebx+60h]
.if ecx>[eax+4]
mov ecx,[eax+4]
.endif
.else
invoke NdisResetEvent,lpRecv3Event
mov esi,offset szRecvBuffer
mov ecx,dwRecvSize
mov eax,[ebx+60h]
.if ecx>[eax+4]
mov ecx,[eax+4]
.endif
.endif
mov dword ptr [ebx+18h],0
mov dword ptr [ebx+1ch],ecx ;设置ReadFile()的读取字节数
rep movsb ;把数据包复制到ReadFile()提供的缓冲区
invoke IoCompleteRequest,pIrp,0
popad
xor eax,eax
ret
_Read endp
_Write proc DeviceObject,pIrp
mov eax,pIrp
mov dword ptr [eax+18h],0
mov dword ptr [eax+1ch],0
mov edx,[eax+4]
mov ecx,[eax+60h]
mov ecx,[ecx+4]
mov eax,[edx+18h]
mov edx,[edx+10h]
add edx,eax
;EDX=用户缓冲区
invoke IoCompleteRequest,pIrp,0
xor eax,eax
ret
_Write endp
_mySend proc _lpAdapt,_lpPacket
local @va
local @size
pushad
mov ebx,_lpPacket
mov ebx,[ebx+8]
mov edi,offset szSendBuffer
.repeat
invoke NdisQueryBufferSafe,ebx,addr @va,addr @size,20h
mov esi,@va
mov ecx,@size
rep movsb
mov ebx,[ebx]
.until ebx==0
sub edi,offset szSendBuffer
mov dwSendSize,edi
;以上循环复制将要发送的数据包到本驱动
invoke NdisSetEvent,lpSend3Event ;放行RING3的WaitForSingleObject(),通知RING3用ReadFile来读数据包内容(重要)
popad
leave
jmp lpOldSend ;转到系统原来的Send例程执行
ret
_mySend endp
_myRecv proc _lpAdapt,_lpPacket
local @va
local @size
pushad
mov ebx,_lpPacket
mov ebx,[ebx+8]
mov edi,offset szRecvBuffer
.repeat
invoke NdisQueryBufferSafe,ebx,addr @va,addr @size,20h
mov esi,@va
mov ecx,@size
rep movsb
mov ebx,[ebx]
.until ebx==0
sub edi,offset szRecvBuffer
mov dwRecvSize,edi
;以上循环复制将要接收的数据包到本驱动
invoke NdisSetEvent,lpRecv3Event ;放行RING3的WaitForSingleObject(),通知RING3用ReadFile来读数据包内容(重要)
popad
leave
jmp lpOldRecv ;转到系统原来的Recv例程执行
ret
_myRecv endp
;以下空函数是为了填充NDIS_PROTOCOL_CHARACTERISTIC结构而设置的,实际下基本不会被系统调用,没有又不行。
PtOpenAdapterComplete proc pAdapt,Status,OpenErrorStatus
xor eax,eax
ret
PtOpenAdapterComplete endp
PtCloseAdapterComplete proc pAdapt,Status
xor eax,eax
ret
PtCloseAdapterComplete endp
PtSendComplete proc pAdapt,Packet,Status
xor eax,eax
ret
PtSendComplete endp
PtTransferDataComplete proc pAdapt,Packet,Status,BytesTransferred
xor eax,eax
ret
PtTransferDataComplete endp
PtResetComplete proc pAdapt,Status
xor eax,eax
ret
PtResetComplete endp
PtRequestComplete proc pAdapt,_NdisRequest,Status
xor eax,eax
ret
PtRequestComplete endp
PtReceive proc pAdapt,MacReceiveContext,HeaderBuffer,HeaderBufferSize,LookAheadBuffer,LookAheadBufferSize,PacketSize
xor eax,eax
ret
PtReceive endp
PtReceiveComplete proc pAdapt
xor eax,eax
ret
PtReceiveComplete endp
PtStatus proc pAdapt,GeneralStatus,StatusBuffer,StatusBufferSize
xor eax,eax
ret
PtStatus endp
PtStatusComplete proc pAdapt
xor eax,eax
ret
PtStatusComplete endp
PtBindAdapter proc Status,BindContext,DeviceName,SystemSpecific1,SystemSpecific2
xor eax,eax
ret
PtBindAdapter endp
PtUnbindAdapter proc Status,pAdapt,UnbindContext
xor eax,eax
ret
PtUnbindAdapter endp
PtReceivePacket proc pAdapt,Packet
xor eax,eax
ret
PtReceivePacket endp
PtPNPHandler proc pAdapt,pNetPnPEvent
xor eax,eax
ret
PtPNPHandler endp
end start