Quantcast
Channel: 看雪安全论坛
Viewing all articles
Browse latest Browse all 9556

技术专题 【原创】Win32Asm 驱动学习笔记 最简单的驱动程序

$
0
0
第四章 最简单的驱动程序
本章目录

4.1 关于NT式驱动
4.2 驱动的头文件
4.3 驱动入口
4.4Hello world
4.5 驱动程序的运行
4.6 简单的例子
4.6.1 Beeper源代码
4.6.2 控制系统定时器
正文

从今天开始,我们一起来学习驱动的编写。

4.1 认识NT式驱动
Windows驱动从加载方式来说,可以分为两类,一类是不支持即插即用功能的"NT式驱动程序",另一类是支持即插即用的"WDM式驱动程序"。
NT式驱动的安装是基于服务的,可以通过修改注册表进行,也可以直接通过服务函数,如 CreateService 进行安装;但 WDM 式驱动不同,它安装的时候需要通过编写一个 inf 文件进行控制,除此之外,它们所使用的头文件也不大相同;
我们这里讲的主要是NT式驱动,WDM 式驱动不在本文学习的范围。

4.2 驱动的头文件
在RadASM的默认驱动模板中,只用到了下面的头文件:
include w2k\ntstatus.inc
include w2k\ntddk.inc

重要的头文件是ntddk.inc,它是驱动开发包所需要的头文件,ntstatus.inc主要是驱动返回码的头文件。

另外还有几个需要用到的:

include w2k\ntoskrnl.inc
includelib \RadASM\masm32\lib\w2k\ntoskrnl.lib
include \RadASM\masm32\Macros\Strings.mac
ntoskrnl.inc和ntoskrnl.lib是内核ntoskrnl.exe的导出函数相关文件,Strings.mac是文本宏,打印调试信息就会用到。

4.3 驱动入口

和其它的可执行程序一样,每个驱动程序也有一个入口点,这是当驱动被装载到内存中时首先被调用的,下面是一个最简单的驱动框架:

引用:

.386
.model flat, stdcall
option casemap:none
include w2k\ntstatus.inc
include w2k\ntddk.inc
.code
DriverEntry proc pDriverObject:PDRIVER_OBJECT,pusRegistryPath:PUNICODE_STRING
mov eax,STATUS_DEVICE_CONFIGURATION_ERROR
ret

DriverEntry endp

end DriverEntry

代码中DriverEntry过程即是驱动的入口点,用来对驱动程序的一些数据结构进行初始化,它的函数原型定义如下:

DriverEntry proto DriverObject:PDRIVER_OBJECT, RegistryPath:PUNICODE_STRING


当I/O管理器调用DriverEntry过程的时候,它会传过来两个指针类型的参数,说明如下:
◎ DriverObject--指向用于描述当前驱动的对象(所谓对象,在内存中也就表现为一个结构而已),这个对象刚被系统初始化。由于 Windows NT是一个面向对象的操作系统,因此,驱动也是被作为一个对象来描述的,当驱动被装载到内存中的时候,系统会创建一个对象来描述这个驱 动,对象在内存中的表示方式就是一个DRIVER_OBJECT结构(在\include\w2k\ntddk.inc中定 义),DriverObject参数指向这个对象,以便让驱动有存取它的机会,但我们现在还没必要用到它。
◎ RegistryPath--指向一个定长的Unicode字符串,内容是驱动的注册表键的路径(指向"HKLM\SYSTEM \CurrentControlSet\Services\驱动名字“)驱动程序可以用它来获取或者保存一些要用到的信息。如果在以后的执行中还要用到这 个字符串,驱动程序应该保留一份该Unicode字符串的拷贝而不是仅仅保存这个指针,因为指针指向的内存在DriverEntry过程返回后即被释放掉 了

定长的Unicode字符串是用UNICODE_STRING结构来表示的,和用户模式代码不同,内核模式的代码往往采用用UNICODE_STRING结构定义的字符串,该结构在\include\w2k\ntdef.inc中定义如下:

UNICODE_STRING STRUCT
_Length WORD ?
MaximumLength WORD ?
Buffer PWSTR ?
UNICODE_STRING ENDS


结构中的各字段含义如下:

◎ _Length--字符串的长度,以字节表示(而不是以字符数量表示),这个长度不包括末尾的0字符,由于Length是汇编的保留字,所以我不得不在前面加了一个下划线
◎ MaximumLength--字符串缓冲区的长度,也是以字节数表示
◎ Buffer--指向Unicode字符串,不要想当然地认为这个字符串就是以0结尾的,很多时候尾部并没有0

这种结构的优点在于它清楚地表现出了字符串的当前长度和最大的可能长度,这样就允许对它进行一些运算(比如在后面加上一些字符等)。

4.4 Hello world

上节举例的驱动程序是最最简单的,它仅仅可以被装载而已,但是即使被装载,它除了返回一个错误代码外什么都不干,这一节我们学点有用的,就让驱动程序打印个经典的“Hello world”,可能有人会说这个这么简单谁不会啊,那我们就从RadASM新建工程说起吧:
打开RadASM,新建一个工程,名字就叫"test",注释随便写,选择“Driver",选择模板"NtDriver",一步步确定后就来到了代码编辑页面,如下图:


附件 81607

"NtDriver"模板已经为我们搭建好了一个最简单的驱动框架,那么插入下面的代码即可:
invoke DbgPrint, $CTA0("Hello world! ")


另外模块建立的代码在驱动入口没有返回信息,还需要让它返回一个错误代码,不然可能无情的蓝屏:
mov eax,STATUS_DEVICE_CONFIGURATION_ERROR
那么完整代码应该是这样的:

代码:

    .386
.model flat, stdcall
option casemap:none
include test.inc
.code
DriverEntry proc pDriverObject:PDRIVER_OBJECT,pusRegistryPath:PUNICODE_STRING
    invoke DbgPrint, $CTA0("Hello world! ")
  mov eax,STATUS_DEVICE_CONFIGURATION_ERROR
    ret

DriverEntry endp

end DriverEntry

我们编译(构建)一下看看,提示错误: error A2006: undefined symbol : DbgPrint

这个是缺少头文件造成的,在右边的窗口双击“test.inc”,把下面几个添加进去就可以了:
include w2k\ntoskrnl.inc
includelib \RadASM\masm32\lib\w2k\ntoskrnl.lib
include \RadASM\masm32\Macros\Strings.mac

为什么要添加这几个?因为DbgPrint函数是由内核导出的,$CTA0宏也是在Strings.mac里定义的,为了避免麻烦,实际上我们可以自定义一个模板,这个我们以后会讲到,这里先跳过。

重新构建下,这下应该成功了吧,呵呵,来掌声献给最棒的自己吧。


4.5 驱动程序的运行
上一节,我们编写了一个最简单的驱动,但有一个问题,怎么运行我们的驱动呢?
这里我推荐大家使用驱动加载工具"DriverManager.exe"(这个软件的源码以后会提到),这个软件支持文件拖放,支持自动扫描同目录驱动,使用方法很简单,注意“注册—运行—停止—卸载”的顺序就可以了。
现在,我们运行上节的驱动测试下,先打开“Dbgview.exe”构选监控内核,然后回到RadAsm,菜单-工程-打开工程目录 ,把DriverManager.exe复制进去,运行点击注册-运行后,如下图:
附件 81606

注意:图中返回了信息“参数不正确”,是因为我们的驱动在入口地址返回了STATUS_DEVICE_CONFIGURATION_ERROR
完整的驱动应该是返回STATUS_SUCCESS,那么驱动会保留在内存中,但是这里你却无法卸载它,因为程序中缺少了负责卸载的DriverUnload过程


怎么样?感觉不错吧,我们的驱动工作了,恭喜你,你已经学会编写简单的驱动了。




4.6 一个简单的例子

下面的例子来自罗云彬翻译的KmdTut中文版,我感觉比较适合放在这里,这是个控制主板扬声器发声的简单驱动程序,我对它做了适量精简,请读者悉知。

4.6.1 Beeper源代码
这个驱动的源代码如下:
代码:



  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;  beeper - Kernel Mode Drive
;  Makes beep thorough computer speaker
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              I N C L U D E  F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


include w2k\ntstatus.inc
include w2k\ntddk.inc


;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                    E Q U A T E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
TIMER_FREQUENCY        equ 1193167                  ; 1,193,167 Hz
OCTAVE                equ 2                        ; octave multiplier
PITCH_C                equ 523                      ; C        -  523,25 Hz
PITCH_Cs              equ 554                      ; C#      -  554,37 Hz
PITCH_D                equ 587                      ; D        -  587,33 Hz
PITCH_Ds              equ 622                      ; D#      -  622,25 Hz
PITCH_E                equ 659                      ; E        -  659,25 Hz
PITCH_F                equ 698                      ; F        -  698,46 Hz
PITCH_Fs              equ 740                      ; F#      -  739,99 Hz
PITCH_G                equ 784                      ; G        -  783,99 Hz
PITCH_Gs              equ 831                      ; G#      -  830,61 Hz
PITCH_A                equ 880                      ; A        -  880,00 Hz
PITCH_As              equ 988                      ; B        -  987,77 Hz
PITCH_H                equ 1047                      ; H        - 1046,50 Hz
; We are going to play c-major chord


TONE_C                equ TIMER_FREQUENCY/(PITCH_C*OCTAVE)
TONE_E                equ TIMER_FREQUENCY/(PITCH_E*OCTAVE)
DELAY                  equ 1800000h          ; for my ~800mHz box


;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        M A C R O S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DO_DELAY MACRO
    mov eax, DELAY
    .while eax
        dec eax
    .endw
ENDM
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                            C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                            MakeBeep
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
MakeBeep  proc dwPitch:DWORD
    ; Direct hardware access
    ;设置定时器的控制寄存器
    cli
    mov al, 10110110y
    out 43h, al
   
    ;操作将初始值的低位字节和高位字节送到42h端口
    mov eax, dwPitch
    out 42h, al
    mov al, ah
    out 42h, al
    ; 打开扬声器
    in al, 61h
    or  al, 11y
    out 61h, al
    sti
    DO_DELAY
    cli
    ; 关闭扬声器
    in al, 61h
    and al, 11111100y
    out 61h, al


    sti
    ret


MakeBeep endp


;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING




  invoke MakeBeep, TONE_C
  DO_DELAY
  invoke MakeBeep, TONE_E
 
    mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
    ret


DriverEntry endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end DriverEntry

代码:


这个驱动程序会使用主板上的扬声器来播放C大调的几个音符,为了实现这个功能,程序使用了IN和OUT指令来访问I/O端口。众所周知的 是,Windows NT把I/O端口当作重要的资源来保护,任何用户模式的程序如果使用了IN或者OUT指令来访问端口的话,会被Windows立马枪 毙掉。但事实上还是有办法绕过这个限制使用户模式的程序直接访问I/O端口,我们以后会谈到这一点。

4.6.2 控制系统定时器

计 算机中有三个定时器,分别是定时器0、1和2,由可编程定时芯片(Programmable Interval Timer/PIT)实现,其中定时器2 用于发声,发声的频率取决于定时器计数器的初始设置值,定时器会将计数值从初始值开始递减到0,然后将计数值复原为初始值,以此循环。计数值的递减由频率 为1,193,180 Hz的系统振荡器控制着,该频率值在所有的PC家族的机器中是固定的。振荡器每产生一个脉冲,计数值就减一,为了发出不同频率的声 音,我们只需要设定不同的初始值即可,发声频率和初始值的关系是:声音频率=1193180/初始值。关于这方面更进一步的知识,读者可以在网上搜到更多 的内容。
好!现在我们用MakeBeep子程序来发第一个C大调音符。
mov al, 10110110y
out 43h, al

首先,我们要设置定时器的控制寄存器,也就是将2进制值的10110110送到43h端口。
mov eax, dwPitch
out 42h, al


mov al, ah
out 42h, al

然后,我们用两个连续的操作将初始值的低位字节和高位字节送到42h端口。
in al, 61h
or al, 11y
out 61h, al

现在要将扬声器打开,这可以通过将61h端口的位0和位1设置为1来完成,不出意外的话,现在应该能够听到声音了。
DO_DELAY MACRO
mov eax, DELAY
.while eax
dec eax
.endw
ENDM

为了让声音延续一段时间,我们用DO_DELAY宏来进行一些延时,虽然这种延时方法有点过时,但还是很有效的。
in al, 61h
and al, 11111100y
out 61h, al

现在可以关闭扬声器了,千万别忘了扬声器是整个系统的资源哦,这只要将端口61h的位0和位1清零就好了。在程序中我们用了cli指令清除中断允许标志来关闭中断,这在多处理器的机器上会对其他程序有所影响的。
源代码的前面部分,读者可以发现12个频率定义值,程序中只用到了2个,读者可以自行用剩余的定义去写一个合成器
Beeper驱动程序的DriverEntry过程返回一个错误值,所以系统直接就把它从系统中清除掉了。当然,在以后的全功能驱动程序里面,这里应该返回STATUS_SUCCESS值。


编译用”DriverManager.exe“加载运行这个驱动,你会会听到主板扬声器发出的尖锐声音。


好了,本章《最简单的驱动程序》的内容就到这里了,在多就超出本章的内容了,呵呵,附件里有本章使用到的工具和例子源程序,祝大家玩的愉快!

相关链接:

Win32Asm 驱动学习笔记 1-2 章
Win32Asm 驱动学习笔记<3>调试基础

上传的图像
文件类型: jpg 4.2.jpg (53.8 KB)
文件类型: gif 4.1.gif (74.8 KB)
上传的附件
文件类型: rar DriverDubeg Tool.rar (160.0 KB)
文件类型: rar Projects.rar (4.3 KB)

Viewing all articles
Browse latest Browse all 9556

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>