驱动开发中的seh

2012-06-27 19:52:03 cosmoslife 阅读数 706

  

   进程的空间空际上被分成了两部分。一部分供进程独立使用,称为用户空间:另一部分容纳操作系统的内核,称为内核空间,或称为系统空间。具体到可以容纳4GB内存空间的32位Windows系统上,低2GB是用户空间,高2GB是内核空间。用户空间是各个进程隔离的,但是内核空间是共享的。也就是说,每个进程看见的高2GB空间范围内的数据,都应该是一样的。这是一个非常重要的概念。


  内核空间是受到硬件保护的,比如X86加构下R0层(Ring 0)的代码才可以访问内核空间。普通应用程序编译出来之后都运行在R3层,R3层的代码要调用R0层的功能时 ,一般通过操作系统提供的一个入口(该入口中调用sysenter指令)来实现。


  内核模块位于内核空间,而内核空间又被所有的进程共享。因此,内核模块实际上位于任何一个进程空间中。但是任意一段代码的任意一次执行, 一定是位于某个进程空间中的。这个进程是那一个?这取决于请求的来源、处理的过程等。


  Windows的所谓系统进程是一个名为"system"的进程,是Windows自身生成的一个特殊进程,这个进程在Windows XP下PID始终为4。读者只要调用PsGetCurrentProcessId就会发现内核模块中分发函数调用时,当前进程一般都不是system进程。但是DriverEntry函数被调用时,一般都位于系统进程中这是因为Windows一般都用系统进程来加载内核模块,并不说明内核代码始终运行在system进程里。


   一个驱动对象(DRIVER_OBJECT)代表一个驱动程序或者说是一个内核模块。内核模块并不生成一个进程,只是填写一组回调函数让Windows来调用,而且这组回调函数必须符合Windows内核规定


  设备对象是内核中的重要对象,其重要性不亚于Windows GUI编程中的窗口(Windows).进行过Windows窗口应用程序开发的读者都知道,窗口是唯一可以接收消息的东西,任何消息都是发送给一个窗口的。而在内核世界里,大部分“消息”都可以请求(IRP)的方式传递。而设备对象(DEVICE_OBJECT)是唯一可以接收请求的实体,任何一个“请求”(IRP)都是发送给某个设备对象的。


  大部分内核API都有前缀,主要的函数以Io-、Ex-、Rtl-、Ke-、Zw-、Nt-、和Ps-开头。些外,与NDIS网络驱动开发相关的函数几乎都是以Ndis- 开头的,与开发WDF驱动相关的函数都是以Wdf-开头的。


  IO管理器就是将用户调用的API函数翻译成IRP或者将等价请求发送到内核各个不同的设备的关键组件


  在Windows系统上与安全软件相关的驱动开发过程中,“过滤”是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真实驱动程序,就加入新的功能


  进行过滤的最重要的方法是对一个设备对象(Device Object)进行绑定。


  通过编程可以生成一个虚拟的设备对象,并“绑定”在一个真实的设备上。一旦绑定,则本来操作系统发送给真实设备的请求,就会首先发送到这个虚拟设备


驱动与应用层交互
Create
Read
Write
DeviceIoControl
Close


驱动服务安装

  安装:
OpenSCManager()
CreateService()/OpenService()
StartService()
  卸载:
OpenSCManager()
ControlService() -- SERVICE_CONTROL_STOP
DeleteService()



内核模块并不生成一个进程,只是填写一组回调了函数让Windows来调用,而且这组回调函数必须符合Windows内核规定。

这一组回调函数包括上面的“普通分发函数”(MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1])和“快速IO分发函数”。这些函数用来处理发送给这个内核模块的请求,一个内核模块所有的功能都由它们提供给Windows.





Windbg中的调试命令分为3种:基本命令元命令扩展命令


基本命令和元命令是调试器自带的


元命令是以"."点开头;而扩展命令是外部加入的,总是以感叹号"!"开头。



有效的内核同步方法有3种:内核事件同步、IRP同步和WMI同步


关于文件过滤的学习:

文件系统的设备对象


文件系统过滤虽然复杂,但是基本方法还是一样的,就是生成过滤设备对象来绑定真实的设备对象


文件系统驱动(c:\windows\system32\drivers目录下两相fastfat.sys与ntfs.sys)


在文件系统驱动程序会生成两类设备:控制设备CDO与文件系统的卷设备


文件系统驱动会生成哪些设备对象?
像FAT32、NTFS这样的文件系统,主要生成两类设备。
首先文件系统驱动本身往往生成一个控制设备CDO,这个设备的主要任务是修改
整个驱动的内部配置。因此,一般的说,一个文件系统只对应一个CDO


另一种设备是这个文件系统的卷设备。一般一个卷对应一个逻辑盘。


说明:请注意这个卷设备本身并不是文件系统驱动生成的,而是卷管理
器生成的
。但是当有一个卷使用了某种文件系统时,则该文件系统会对应
地为该设备生成一个没有名字的设备对象





驱动对象结构DRIVER_OBJECT中既然有快速IO分发函数的设备接口,那么理
论上讲,所有的驱动对象都可以有快速IO分发函数---只是它们可能根本没
有被设置。但是文件系统不行,如果不设置,上层依然会调用,而且会导致
蓝屏。



快速IO分发函数是独立于普通的处理IRP的分发函数之外的另一组接口。但是
它们的作用是一样的,就是由驱动处理外部给予的请求,而且所处理的请求
也基本相同,只是根本没有IRP,本来应该写在IRP中的参数,被直接通过函数
的参数传递进来了。这可以减少分配IRP的效率消耗。



请注意,如果一个调用可以在高级处运行,那么它就能在低处运行,反过来则不行。


Windows本身有一个系统线程负责处理一些日常工作,也可以把自己的工作任务
插入其中,免除需要多生成一个线程的开销。



LARGE_INTEGER这是一个在Windows驱动开发中常用的64位整数数据的共用体。




















  

2014-04-17 23:26:33 cherish_2012 阅读数 469
SEH 的工作原理。
         Windows 程序设计中最重要的理念就是消息传递,事件驱动。当GUI应用程序触发一个消息时,系统将把该消息放入消息队列,然后去查找并调用窗体的消息处理函数(CALLBACK),传递的参数当然就是这个消息。我们同样可以把异常也当作是一种消息,应用程序发生异常时就触发了该消息并告知系统。系统接收后同样会找它的“回调函数”,也就是我们的异常处理例程。当然,如果我们在程序中没有做异常处理的话,系统也不会置之不理,它将弹出我们常见的应用程序错误框,然后结束该程序。所以,当我们改变思维方式,以CALLBACK 的思想来看待 SEH,SEH 将不再神秘。

SEH 是 Windows 系统提供的功能,跟开发工具无关。值得一提的是,VC 将 SEH 进行了封装 try  catch  finally,c++中也可以用c的封装 __try{}__except(){} 和 __try{}__finally{}. 所以当你建立一个C++ try块时,编译器就生成一个S E H_ _t r y块。一个C + +c a t c h测试变成一个S E H异常过滤器,并且c a t c h中的代码变成S E H_ _e x c e p t块中的代码。实际上,当你写一条C++ throw语句时,编译器就生成一个对Wi n d o w s的R a i s e E x c e p t i o n函数的调用。用于t h r o w语句的变量传递给R a i s e E x c e p t i o n作为附加的参数。

r_no157.gif
一个简单的使用SEH的例子

假如要实现一个完全强壮的应用程序,该程序需要每周7天,每天2 4小时运行。在今天的世界里,软件变得这么复杂,有那么多的变量和因子来影响程序的性能,笔者认为如果不用S E H,要实现完全强壮的应用程序简直是不可能的。我们先来看一个样板程序,即C的运行时函数s t r c p y:

char *  strcpy(
   
char *  strDestination,
   
const   char *  strSource);


这是一个相当简单的函数,它怎么会引起一个进程结束呢?如果调用者对这些参数中的某一个传递N U L L(或任何无效的地址),s t r c p y就引起一个存取异常,并且导致整个进程结束。
使用S E H,就可以建立一个完全强壮的s t r c p y函数:

char *  RobustStrCpy( char *  strDestination,  const   char *  strSource)
{
   __try 
   
{
      strcpy(strDestination, strSource);
   }

   __except(EXCEPTION_EXECUTE_HANDLER)
   
{
      
//  Nothing to do here
   }


   
return (strDestination);
}



这个函数所做的一切就是将对s t r c p y的调用置于一个结构化的异常处理框架中。如果s t r c p y执行成功,函数就返回。如果s t r c p y引起一个存取异常,异常过滤器返回E X C E P T I O N _E X E C U T E _ H A N D L E R,导致该线程执行异常处理程序代码。在这个函数中,处理程序代码什么也不做,R o b u s t S t r C p y只是返回到它的调用者,根本不会造成进程结束。

另一个使用:

#include  " stdio.h "

void  test()
{
int *  p  =   0x00000000 //  pointer to NULL

__try
{
puts(
" in try " );
__try
{
puts(
" in try " );

//  causes an access violation exception;
//  导致一个存储异常
* =   13

//  呵呵,注意这条语句
puts( " 这里不会被执行到 " );
}

__finally
{
puts(
" in finally " );
}


//  呵呵,注意这条语句
puts( " 这里也不会被执行到 " );
}

__except(puts(
" in filter 1 " ),  0 )
{
puts(
" in except 1 " );
}

}


void  main()
{
puts(
" hello " );
__try
{
test();
}

__except(puts(
" in filter 2 " ),  1 )
{
puts(
" in except 2 " );
}

puts(
" world " );
}

 

上面的程序运行结果如下:
hello
in try
in try
in filter 1
in filter 2
in finally
in except 2
world
Press any key to continue

另一个混合c++的异常处理使用:

// 注意,这是 C++ 程序,文件名为: SEH-test.cpp

#include  " stdio.h "  

class  A 



public

void  f1()  {}  

//  抛出 C++ 异常 

void  f2()  throw   888 ;}  

}


//  这个函数中使用了 try-catch 处理异常,也即 C++ 异常处理 

void  test1() 



A a1; 

A a2,a3; 

try  



a2.f1(); 

a3.f2(); 

}
 

catch ( int  errorcode) 



printf(
" catch exception,error code:%d\n " , errorcode); 

}
 

}
 

//  这个函数没什么改变,仍然采用 try-except 异常机制,也即 SEH 机制 

void  test() 



int *  p  =   0x00000000 //  pointer to NULL 

__try 



//  这里调用 test1 函数 

test1(); 

puts(
" in try " ); 

__try 



puts(
" in try " ); 

//  causes an access violation exception; 

//  导致一个存储异常 

* =   13

puts(
"  这里不会被执行到  " ); 

}
 

__finally 



puts(
" in finally " ); 

}
 

puts(
"  这里也不会被执行到  " ); 

}
 

__except(puts(
" in filter 1 " ),  0



puts(
" in except 1 " ); 

}
 

}
 

void  main() 



puts(
" hello " ); 

__try 



test(); 

}
 

__except(puts(
" in filter 2 " ),  1



puts(
" in except 2 " ); 

}
 

puts(
" world " ); 

}
 


上面程序不仅能够被编译通过,而且运行结果也是正确的(和预期的一样,同样符合 C++ 异常处理模型的规则,和 SEH 异常模型的处理规则)。其结果如下:

hello

catch exception,error code:888

in try

in try

in filter 1

in filter 2

in finally

in except 2

world

Press any key to continue

2011-01-20 10:56:00 hzpeixun 阅读数 607

Windows 驱动程序 开发 (本课程已经全部录制完成)

 

 

课程描述:
    Windows驱动和内核程序开发自从Windows诞生的时刻起,就保持着神秘的面纱,一直被认为是开发领域中比较高深的技术。它需要程序员对 Windows操作系统底层有很深刻的理解,同时又需要有丰富的调试经验。但同时,计算机很多领域又需要大量的驱动和内核开发人员,如PCI、USB 设备的驱动设计。同时,随着计算机病毒和木马的 广泛传播,大量的安全方面软件需要内核开发这方面的技术,如进程和线程的监视、文件和注册表的过滤,以及诸如此类的防范Rootkit技术。由于驱动和内 核方面人才的短缺,它也一直被别人看成是待遇不错的黄金职业。

购买地址:http://www.china-pub.com/3500895  

培训特色:
1.结合ReactOS和WRK源码进行学习驱动和内核

2.注重细节分析,使学员能掌握内核学习分析

3.结合逆向工程(反汇编)技术分析内核

4.大量rootkit技巧

5.重在调试技巧


                  《
Windows 驱动程序开发》

 

序号

 
 

培训内容

 
 

课时

 
 

1

 

C 语言补遗和提


运行时函数


函数与堆栈的结构 ( 对比 C 语言函数和汇编代码,分析 ESP EBP EIP 等寄存器 ) 、缓冲区溢出和防护


编译常见错误分析、连接常 见错误分析


VC 编译器详解


异常处理 (C/C++ 语言 Windows SEH ,结合汇编代 )


调用习俗 (C 语言、 STDCALL)


逆向工程初

 

3

 
 

2

 

Windows 内核框架驱动程序框架

课程目标:

编译环境设置

Windows操作系统概述

KMD驱动程序的基本框架

WDM驱动程序框架

驱动的安装、调试信息、注 册表的变化

IRQL DPC APC、分页内存

驱动的水平和垂直层次结构

 

4

 
 

3

 

内核常用运行时函数

链表相关操作

文件操作

注册表操作

错误处理

内存操作

字符串处理

C++在驱动开发中注意的事项

 

3

 
 

4

 

派遣函数

读写操作

DeiviceIOControl操作

缓冲方式读写操作

直接方式读写操作

其他方式读写操作

 

2

 
 

5

 

驱动程序的同步处理

同步事件

互斥体

信号灯

内核中使用线程

快速互斥体

内核与应用程序共享同步对象

自旋锁

定时器和等待方法

 

3

 
 

6

 

驱动程序的分层

IO定时器

DPC定时器

其他定时器和等待方法

 

2

 
 

7

 

驱动程序的分层

PCI驱动编写

USB驱动编写

 

2

 

2009-08-02 15:33:00 xxagri 阅读数 652

摘自《Windows驱动开发技术详解》
1.
机构化异常处理(try-except块)

      结构化异常处理(SHE, Structured Exception Handling)是微软编译器提供的独特处理机制,这种处理方式能在一定程度上在出现错误的情况下,避免程序崩溃。先说明两个概念。

(1) 异常:异常的概念类似于中断的概念,当程序中某中错误触发一个异常,操作系统会寻找处理这个异常的处理函数。如果程序提供异常处理函数,则进入该函数,否则由操作系统提供的默认异常处理函数处理。在内核模式下,操作系统默认处理错误的方法是直接让系统蓝屏,并在蓝屏上简单描述出错的信息。

(2) 回卷:程序执行到某个地方出现异常错误时,系统会寻找出错点是否处于一个try{}块中,并进入try块提供的异常处理代码。如果当前try块没有提供异常处理,则会向更外一层的try块寻找异常处理代码,直到最外层try块也没有提供异常处理代码,则交由操作系统处理。这种向更外一层寻找异常处理的机制,被称为回卷。

一般处理异常,是通过try-except块来处理的。

__try

{

//your normal code

}

__except(filter_value)

{

       //your operate code

}

在被__try{}包围的块中,如果出现异常,会根据filter_value的数值,判断是否需要在__except{}块中处理。filter_value的数组会有三种可能。

(1)                EXCEPTION_EXECUTE_HANDLE,该数值为1。进入到__except进行错误处理,处理完后不再回到__try{}块中,转而继续执行下面的代码。

(2)                EXCEPTION_CONTINUE_SEARCH,该数值为0。不进入__except块中的异常处理,而是向上一层回卷。如果已经是最外层,则向操作系统请求异常处理函数。

(3)                EXCEPTION_CONTINUE_EXECUTION,该数值为-1。重复先去错误的指令,这个在驱动程序中很少用到。

下面一段代码是用来检测某段内存是否可读写,这段代码通过try-except来探测指针的地址是否可写。

VOID ProbeTest()

{

       PVOID pBad = NULL;

       KdPrint((“Enter ProbeTest/n”));

       __try

{

       KdPrint((“Enter __try block/n”));

       //判断空指针是否可写,显然会导致异常

       ProbeForWrite(pBad, 100, 4);

       //由于在上面引发异常,所以下面语句不会被执行

       KdPrint((“Leave __try block/n”));

}

__except(EXCEPTION_EXCUTE_HANDLE)

{

       KdPrint((“Catch the exception/n”));

       KdPrint((“The program will keep going/n”));

}

//该语句会被执行

KdPrint((“Leave ProbeTest/n”));

}

      除了处理异常之外,DDK还提供了一些函数用来触发异常。如表1所示:

1 触发异常函数

函数

描述

ExRaiseStatus

用指定状态代码触发异常

ExRaiseAccessViolatioin

触发STATUS_ACCESS_VILOATION异常

ExRaiseDatatypeMisalignment

触发STATUS_DATATYPE_MISALIGNMENT异常

 

2. 结构化异常处理(try-finally块)

      结构化异常处理还有另外一种使用方法,就是利用try-finally块,强迫函数在退出前执行一段代码。

NTSTATUS TryFinallyTest()

{

NTSTATUS status  = STATUS_SECCESS;

__try

{

       //your normal code

       return status;

}

__finally

{

       //程序退出前必然运行到此

       KdPrint((“Enter finally block/n”));

}

}

      上面代码的__try{}块中,无论运行什么代码(即使是return语句或者触发异常),在程序退出前都会运行__finally{}块中的代码。这样的目的是,在退出前需要运行一些资源回收的工作,而资源回收代码的最佳位置就是放在这个块中。

      此外,使用try-finally块还可以在某种程度上简化代码。比较下面两段代码,其中地一段是没有使用try-finally块的代码,而第二段是使用了try-finally。可以看出,第二段代码比第一段代码清晰明了。

第一段代码:

VOID FooTest()

{

       NTSTATUS status = STATUS_SUCCESS;

       //执行操作1

       status = Foo1();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              //回收资源

              return status;

}

 

//执行操作2

       status = Foo2();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              //回收资源

              return status;

}

 

//执行操作n

       status = FooN();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              //回收资源

              return status;

}

 

return status;

}

第二段代码:

VOID FooTest()

{

       NTSTATUS status = STATUS_SUCCESS;

       __try

{

       //执行操作1

       status = Foo1();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              return status;

}

 

//执行操作1

       status = Foo2();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              return status;

}

 

//执行操作n

       status = Foo1();

       //判断操作是否成功

       if (!NT_SUCCESS(status))

       {

              return status;

}

}

__finally

{

       //回收资源

}

return status;

}

2013-12-18 09:55:14 FrankieWang008 阅读数 1824

SEH 的工作原理。
         Windows 程序设计中最重要的理念就是消息传递,事件驱动。当GUI应用程序触发一个消息时,系统将把该消息放入消息队列,然后去查找并调用窗体的消息处理函数(CALLBACK),传递的参数当然就是这个消息。我们同样可以把异常也当作是一种消息,应用程序发生异常时就触发了该消息并告知系统。系统接收后同样会找它的“回调函数”,也就是我们的异常处理例程。当然,如果我们在程序中没有做异常处理的话,系统也不会置之不理,它将弹出我们常见的应用程序错误框,然后结束该程序。所以,当我们改变思维方式,以CALLBACK 的思想来看待 SEH,SEH 将不再神秘。

SEH 是 Windows 系统提供的功能,跟开发工具无关。值得一提的是,VC 将 SEH 进行了封装 try  catch  finally,c++中也可以用c的封装 __try{}__except(){} 和 __try{}__finally{}. 所以当你建立一个C++ try块时,编译器就生成一个S E H_ _t r y块。一个C + +c a t c h测试变成一个S E H异常过滤器,并且c a t c h中的代码变成S E H_ _e x c e p t块中的代码。实际上,当你写一条C++ throw语句时,编译器就生成一个对Wi n d o w s的R a i s e E x c e p t i o n函数的调用。用于t h r o w语句的变量传递给R a i s e E x c e p t i o n作为附加的参数。

r_no157.gif 
一个简单的使用SEH的例子

假如要实现一个完全强壮的应用程序,该程序需要每周7天,每天2 4小时运行。在今天的世界里,软件变得这么复杂,有那么多的变量和因子来影响程序的性能,笔者认为如果不用S E H,要实现完全强壮的应用程序简直是不可能的。我们先来看一个样板程序,即C的运行时函数s t r c p y:

 char *  strcpy(
    char *  strDestination,
    const   char *  strSource);


这是一个相当简单的函数,它怎么会引起一个进程结束呢?如果调用者对这些参数中的某一个传递N U L L(或任何无效的地址),s t r c p y就引起一个存取异常,并且导致整个进程结束。
使用S E H,就可以建立一个完全强壮的s t r c p y函数:

 char *  RobustStrCpy( char *  strDestination,  const   char *  strSource)
 {
   __try 
    {
      strcpy(strDestination, strSource);
   } 

   __except(EXCEPTION_EXECUTE_HANDLER)
    {
       //  Nothing to do here 
 
   } 

 
    return (strDestination);

 


这个函数所做的一切就是将对s t r c p y的调用置于一个结构化的异常处理框架中。如果s t r c p y执行成功,函数就返回。如果s t r c p y引起一个存取异常,异常过滤器返回E X C E P T I O N _E X E C U T E _ H A N D L E R,导致该线程执行异常处理程序代码。在这个函数中,处理程序代码什么也不做,R o b u s t S t r C p y只是返回到它的调用者,根本不会造成进程结束。

另一个使用:

 #include  " stdio.h " 
 
 void  test()
 {
 int *  p  =   0x00000000 ;  //  pointer to NULL 
 

__try
 {
puts( " in try " );
__try
 {
puts( " in try " );

 //  causes an access violation exception;
 //  导致一个存储异常 
 
* p  =   13 ; 

 //  呵呵,注意这条语句 
 
puts( " 这里不会被执行到 " );

__finally
 {
puts( " in finally " );

 
 //  呵呵,注意这条语句 
 
puts( " 这里也不会被执行到 " );

__except(puts( " in filter 1 " ),  0 )
 {
puts( " in except 1 " );


 
 void  main()
 {
puts( " hello " );
__try
 {
test();

__except(puts( " in filter 2 " ),  1 )
 {
puts( " in except 2 " );

puts( " world " );

 

上面的程序运行结果如下:
hello
in try
in try
in filter 1
in filter 2
in finally
in except 2
world
Press any key to continue

另一个混合c++的异常处理使用:

// 注意,这是 C++ 程序,文件名为: SEH-test.cpp

 #include  " stdio.h "  

 class  A 

 

 public : 

 void  f1()  {}  

 //  抛出 C++ 异常  
 

 void  f2()  {  throw   888 ;}  



 //  这个函数中使用了 try-catch 处理异常,也即 C++ 异常处理  
 

 void  test1() 

 

A a1; 

A a2,a3; 

 try  

 

a2.f1(); 

a3.f2(); 

 

 catch ( int  errorcode) 

 

printf( " catch exception,error code:%d\n " , errorcode); 

 

 

 //  这个函数没什么改变,仍然采用 try-except 异常机制,也即 SEH 机制  
 

 void  test() 

 

 int *  p  =   0x00000000 ;  //  pointer to NULL  
 

__try 

 

 //  这里调用 test1 函数  
 

test1(); 

puts( " in try " ); 

__try 

 

puts( " in try " ); 

 //  causes an access violation exception; 

 //  导致一个存储异常  
 

 * p  =   13 ; 

puts( "  这里不会被执行到  " ); 

 

__finally 

 

puts( " in finally " ); 

 

puts( "  这里也不会被执行到  " ); 

 

__except(puts( " in filter 1 " ),  0 ) 

 

puts( " in except 1 " ); 

 

 

 void  main() 

 

puts( " hello " ); 

__try 

 

test(); 

 

__except(puts( " in filter 2 " ),  1 ) 

 

puts( " in except 2 " ); 

 

puts( " world " ); 

 


上面程序不仅能够被编译通过,而且运行结果也是正确的(和预期的一样,同样符合 C++ 异常处理模型的规则,和 SEH 异常模型的处理规则)。其结果如下:

hello

catch exception,error code:888

in try

in try

in filter 1

in filter 2

in finally

in except 2

world

Press any key to continue 

SEH 的工作原理

阅读数 386