zoukankan      html  css  js  c++  java
  • 字符串的处理

    Windows 操作系统在Ring0 层用的都是Unicode 字符集,而且在Visual Studio的开发环境下也是默认的使用Unicode 字符集。今天就写写Unicode 字符集的操作的总结吧。

    Ring3

    在Visual Studio 的开发环境下是默认使用Unicode 字符集的。刚开始的时候不太习惯,毕竟自己写写C/C++都是ACSII 码。

    ASCII 的printf() 函数在 Unicode 中为

    int wprintf_s(
    const wchar_t *format [,
    argument]...
    );

    scanf()在Unicode 下

    int wscanf(
    const wchar_t *format [,
    argument]...
    );

    strcat

    wchar_t *wcscat(
    wchar_t *strDestination,
    const wchar_t *strSource
    );

    strcpy

    wchar_t *wcscpy(
    wchar_t *strDestination,
    const wchar_t *strSource
    );

    strcmp

    int wcscmp(
    const wchar_t *string1,
    const wchar_t *string2
    );

    strstr

    const wchar_t *wcsstr(   //目标元素第一次出现的位置
    const wchar_t *str,
    const wchar_t *strSearch

     

    Ring0

    在Ring0层,都是使用UNICODE_STRING

    typedef struct _UNICODE_STRING {
      USHORT  Length;     //UNICODE占用的内存字节数,个数*2;
      USHORT  MaximumLength;  //最大可容纳字节数
      PWSTR  Buffer;      //缓冲区
    } UNICODE_STRING ,*PUNICODE_STRING

    写到这,突然发现了一个更加完整的博客,转过来了。

    UNICODE_STRING:

    typedef struct _UNICODE_STRING {
      USHORT  Length;     //UNICODE占用的内存字节数,个数*2;
      USHORT  MaximumLength;
      PWSTR  Buffer;
    } UNICODE_STRING ,*PUNICODE_STRING;

    参数定义:
    Length-----buffer的字节长度,不包括终止符“NULL”
    MaximumLength---buffer的的总的字节大小。Up to MaximumLength bytes may be written into the                  buffer without trampling memory.
    Buffer---Pointer to a wide-character string指向宽字符串的指针

    ANSI_STRING

    typedef struct _STRING {
      USHORT  Length;
      USHORT  MaximumLength;
      PCHAR  Buffer;
    } STRING, *PANSI_STRING;

    例:

    WCAHR temp[] = L"Hello World!";   //定义宽字符串

    UNICODE_STRING str ;

    str.Buffer = temp;

    str.Length  = wcslen(temp)*sizeof(WCHAR)   //这个长度为UNICODE字符串所占用的字节数,即字符的个数乘以每个字符所占的字节数。通常为: 字符个数*2 ; 


        当使用UNICODE_STRING 时,一定要手动设置UNICODE_STRING 的Length和MaximumLength 成员,不要想当然的认为设置了Buffer后,Length和MaximumLength 成员就会根据Buffer被自动设置。由其是当自己写的函数用UNICODE_STRING作为参数返回时,一定要设置Length和MaximumLength 成员,不然很可能得到非预期结果。


     当应用程序与驱动通信时,一般应用程序传入的字符串为ANSI,所以在驱动中应先定义ANSI_STRING,然后再使用RtlAnsiStringToUnicodeString 将其转换成UNICODE_STRING,作为后用。例:

    ANSI_STRING  str_a;

    UNICODE_STRING  str_u;

    WCHAR buf_u[1024] = L"";

    str_a.Buffer = InputBuffer ;  //InputBuffer 为输入缓冲区地址

    str_a.Length = str_a.MaximumLength = strlen(InputBuffer);

    //开辟UNICODE内存空间

    str_u.Buffer = buf_u;

    str_u.Length = str_u.MaximumLength = strlen(InputBuffer) * sizeof(WCHAR);

    RtlAnsiStringToUnicodeString(&str_u, &str_a , TRUE);

    ........

    ........

    //当RtlAnsiStringToUnicodeString第三个参数为TRUE时,要用RtlFreeUnicodeString释放临时的UNICODE_STRING

    RtlFreeUnicodeString(&str_u);


    拼接UNICODE_STRING

      当拼接UNICODE_STRING 时,注意目标UNICODE的Length为当前UNICODE中存储字符的字节数。如:

    WCHAR str1[] = L"12345";

    UNICODE_STRING str2;

    str2.Buffer = ExAllocatePool(NonPagedPool, wcslen(str1)*sizeof(WCHAR))

    str2.Length = 0;

    str2.MaximumLength = wcslen(str1)*sizeof(WCHAR);

     RtlAppendUnicodeToString(&str2, str1);


     UNICODE  ExAllocatePool

       内核在UNICODE拼接或其他临时操作时,经常使用ExAllocatePool动态分配UNICODE的Buffer,简单情况:

    UNICODE_STRING str;

    str.Buffer = ExAllocatePool(NonPagedPool, 50*sizeof(WCHAR));

    str.Length = 0;

    str.MaximumLength = 50*sizeof(WCHAR);

    但若是定一个UNICODE的指如何初始化UNICODE  

    PUNICODE_STRING pStr;

    因为定义了一个指针,但指针目前并没有指向可用的内存地址,故先分配一块内存(NonPagedPool),让pStr指向这块内存。

    pStr = ExAllocatePool(NonPagedPool, 50*sizeof(WCHAR));

    接着初始化成

    pStr.Length = 0;

    pStr.MaximumLength = ???;

    如何初始化Buffer 

        因为UNICODE_STRING 是一个数据结构,我们申请一块内存来存储这个数据结构,所以这块内存不仅存储了Buffer这个我们最关心的字符串,而且还储存这个数据结构,即Length 、 MaximumLength 和 Buffer (指针)成员。因为pStr是这块内存的起始地址,所以:

    &pStr.Length = (USHORT*)pStr

    &pStr.MaximumLength = (USHORT*)pStr + 1

    &pStr.Buffer = (USHORT*)pStr + 2

    所以得:

    pStr.Buffer = (WCHAR*)((USHORT*)pStr + 2 + 2 )  因为一个USHORT占2个字节,一个指针占4个字节。这时得出,pStr.MaximumLength =  50*sizeof(WCHAR) - (2+2+4);

    (注意,以上是为了方便观察特意写成赋值形式,但在编译器中并不适用,因为=左边得为左值。)

     整体:

    PUNICODE_STRING pStr;

    pStr = ExAllocatePool(NonPagedPool, 50*sizeof(WCHAR));

    pStr.Length = 0;

    pStr.Buffer = (WCHAR*)( (USHORT*)pStr + 2 + 2 ) ; 

    总结:

        定义UNICODE_STRING 时,编译器在栈上自动分配了存储UNICODE_STRING这个数据结构的空间,我们唯一要做的就是给Buffer这个指针成员(指向)分配内存。

        而定义PUNICODE_STRING时,在堆上分配了一块内存,这块内存不仅存储了Buffer,而且还存储了UNICODE_STRING这个数据结构。所以定义为PUNICODE_STRING时,要比预期的字符串至少多8个字节,就因为此。

    pStr.MaximumLength = 50*sizeof(WCHAR) - (2+2+4);

    UNICODE_STRING在驱动应用比较多,其操作大致有如下几个:
    (1)初始化,常见的初始化有两种方式:
    1.调用RtlInitUnicodeString,该函数原型如下:

    程序代码

    VOID  RtlInitUnicodeString(
        IN OUT PUNICODE_STRING  DestinationString,
        IN PCWSTR  SourceString
    );
    该方法的实际原理是:将SourceString的宽字符串指针赋值给UNICODE_STRING的Buffer值,同时SourceString的长度值填入结构的Length域,默认的MaximumLength = Length + 2;
    如:RtlInitUnicodeString(&ustrRegString,L"Hello");
    2.采用动态分配内存的方式初始化,其调用方式如下:

    程序代码

    UNICODE_STRING ustr;
    ustr.Length= 0;
    ustr.MaximumLength = 256;
    ustr.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,256,MEM_TAG);  //MEM_TAG为自定义
    这样就动态分配了一个存储空间给ustr。
    (2)字符串连接
    连接常见的就是将两个UNICODE_STRING或者将WCHAR_T的字符串连接起来
    首先,将两个UNICODE_STRING连接起来,调用RtlAppendUnicodeStringToString函数实现,其原型如下:

    程序代码

    NTSTATUS  RtlAppendUnicodeStringToString(
        IN OUT PUNICODE_STRING  Destination,
        IN PUNICODE_STRING  Source
    );
    调用该函数的原理是将两者的字符串Buffer拼接起来,同时,更新对应的Length和MaxLength域。在调用的
    过程中要注意Destination的MaxLength域,若MaxLength小于Destination和Source的Length域的和的时候,该函数调用不成功,返回0xC0000023,即缓冲区溢出错误。所以在调用该函数的时候,一定要确定Destination的MaxLength 域。
    若将WCHAR_T字符串串接到UNICODE_STRING之后,则需要调用RtlAppendUnicodeToString,函数原型如下:

    程序代码

    NTSTATUS  RtlAppendUnicodeToString(
    IN OUT PUNICODE_STRING  Destination,
    IN PCWSTR  Source
    );
    该函数和上面函数并没有特别多的不一致,但该函数较上一个函数不同的地方就是不会出现缓冲区溢出的错误,也就是即使Destination的MaxLength域为0,也可以执行RtlAppendUnicodeToString的操作。
    还有另外一个需要注明的地方,如果UNICODE_STRING是通过RtlInitUnicodeString初始化,那么不管调用什么函数,修改UNICODE_STRING值的时候,初始化使用的PCWSTR数组的值也会发生改变,因为他们指向的是同一个Buffer。
    注上一例,备后用:

    程序代码

    UNICODE_STRING  ustrStr,ustrName;
    ustrStr.Length = wcslen(strKey) * 2;
    RtlAppendUnicodeToString(&ustrStr,strKey);
    ustrStr.MaximumLength = 256;
    RtlAppendUnicodeToString(&ustrStr,L"\");
    RtlAppendUnicodeStringToString(&ustrStr,&ustrName);
    (3)字符串转换
    转换目的可能涉及到中文显示的问题,如果简单的UNICODE_STRING转换为wchar_t或者char的形式,采用RtlCopyMemory的方式,因为UNICODE_STRING字符串并不一定以作为结束符,所以需要使用RtlCopyMemory,拷贝定长的字符串。
    对于中文显示的问题,采用ANSI_STRING的方式进行输出。从UNICODE_STRING到ANSI_STRING转换,可以通过RtlUnicodeStringToAnsiString实现,其原型如下:

    程序代码

    NTSTATUS  RtlUnicodeStringToAnsiString(
        IN OUT PANSI_STRING  DestinationString,
        IN PUNICODE_STRING  SourceString,
        IN BOOLEAN  AllocateDestinationString
    );
    典型例子如下:

    程序代码

    UNICODE_STRING src;
    ANSI_STRING dst;
    RtlInitUnicodeString(&src,L”打印汉字”);
    RtlUnicodeStringToAnsiString(&dst,&src,TRUE);
    DbgPrint(“%Z”,&dst);
    RtlFreeAnsiString(&dst);

    RtlInitUnicodeString与UNICODE_STRING的区别

    UNICODE_STRING是一个结构.当你声明一个UNICODE_STRING时它的成员未初始化.而RtlInitUnicodeString是一个函数用来初始化一个UNICODE_STRING.

    UNICODE_STRING
    The UNICODE_STRING structure is used to define Unicode strings.

    typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
    } UNICODE_STRING *PUNICODE_STRING;
    Members
    Length 
    The length in bytes of the string stored in Buffer. 
    MaximumLength 
    The maximum length in bytes of Buffer. 
    Buffer 
    Pointer to a buffer used to contain a string of wide characters. 
    Headers
    Defined in ntdef.h. Include wdm.h or ntddk.h. 

    Comments
    The UNICODE_STRING structure is used to pass Unicode strings. Use RtlInitUnicodeString to initialize a UNICODE_STRING.

    If the string is NULL-terminated, Length does not include the trailing NULL.

    The MaximumLength is used to indicate the length of Buffer so that if the string is passed to a conversion routine such as RtlAnsiStringToUnicodeString the returned string does not exceed the buffer size.

    我经常在网上遇到心如火燎的提问者。他们碰到很多工作中的技术问题,是关于驱动开发的。其实绝大部分他们碰到的“巨大困难”是被老牛们看成初级得不能再初级的问题。比如经常有人定义一个空的UNICODE_STRING,然后往里面拷贝字符串。结果无论如何都是蓝屏。也有人在堆栈中定义一个局部SPIN_LOCK,作为下面的同步用——这样用显然没有任何意义。我无法一一回答这些问题:因为往往要耐心的看他们的代码,才能很不容易的发现这些错误。而且我又不是总是空闲的,可以无休止的去帮网友阅读代码和查找初级错误。但是归根结底,这些问题的出现,是因为现在写驱动的同行越来越多,但是做驱动开发又没有比较基础的,容易读懂的资料。为此我决定从今天开始连载一篇超级入门级的教程,来解决那些最基本的开发问题。老牛们就请无视这篇教程,一笑而过了。

    Windows驱动编程基础教程(1.1-1.3)

    1.1 使用字符串结构

    常常使用传统C语言的程序员比较喜欢用如下的方法定义和使用字符串:

    char *str = { “my first string” }; // ansi字符串
    wchar_t *wstr = { L”my first string” }; // unicode字符串
    size_t len = strlen(str); // ansi字符串求长度
    size_t wlen = wcslen(wstr); // unicode字符串求长度
    printf(“%s %ws %d %d”,str,wstr,len,wlen); // 打印两种字符串

    但是实际上这种字符串相当的不安全。很容易导致缓冲溢出漏洞。这是因为没有任何地方确切的表明一个字符串的长度。仅仅用一个’’字符来标明这个字符串的结束。一旦碰到根本就没有空结束的字符串(可能是攻击者恶意的输入、或者是编程错误导致的意外),程序就可能陷入崩溃。
    使用高级C++特性的编码者则容易忽略这个问题。因为常常使用std::string和CString这样高级的类。不用去担忧字符串的安全性了。
    在驱动开发中,一般不再用空来表示一个字符串的结束。而是定义了如下的一个结构:

    typedef struct _UNICODE_STRING {
    USHORT Length; // 字符串的长度(字节数)
    USHORT MaximumLength; // 字符串缓冲区的长度(字节数)
    PWSTR Buffer; // 字符串缓冲区
    } UNICODE_STRING, *PUNICODE_STRING;

    以上是Unicode字符串,一个字符为双字节。与之对应的还有一个Ansi字符串。Ansi字符串就是C语言中常用的单字节表示一个字符的窄字符串。

    typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
    PSTR Buffer;
    } ANSI_STRING, *PANSI_STRING;

    在驱动开发中四处可见的是Unicode字符串。因此可以说:Windows的内核是使用Uincode编码的。ANSI_STRING仅仅在某些碰到窄字符的场合使用。而且这种场合非常罕见。
    UNICODE_STRING并不保证Buffer中的字符串是以空结束的。因此,类似下面的做法都是错误的,可能会会导致内核崩溃:

    UNICODE_STRING str;
    … 
    len = wcslen(str.Buffer); // 试图求长度。
    DbgPrint(“%ws”,str.Buffer); // 试图打印str.Buffer。

    如果要用以上的方法,必须在编码中保证Buffer始终是以空结束。但这又是一个麻烦的问题。所以,使用微软提供的Rtl系列函数来操作字符串,才是正确的方法。下文逐步的讲述这个系列的函数的使用。

    1.2 字符串的初始化

    请回顾之前的UNICODE_STRING结构。读者应该可以注意到,这个结构中并不含有字符串缓冲的空间。这是一个初学者常见的出问题的来源。以下的代码是完全错误的,内核会立刻崩溃:

    UNICODE_STRING str;
    wcscpy(str.Buffer,L”my first string!”);
    str.Length = str.MaximumLength = wcslen(L”my first string!”) * sizeof(WCHAR);

    以上的代码定义了一个字符串并试图初始化它的值。但是非常遗憾这样做是不对的。因为str.Buffer只是一个未初始化的指针。它并没有指向有意义的空间。相反以下的方法是正确的:

    // 先定义后,再定义空间
    UNICODE_STRING str;
    str.Buffer = L”my first string!”;
    str.Length = str.MaximumLength = wcslen(L”my first string!”) * sizeof(WCHAR);
    … …

    上面代码的第二行手写的常数字符串在代码中形成了“常数”内存空间。这个空间位于代码段。将被分配于可执行页面上。一般的情况下不可写。为此,要注意的是这个字符串空间一旦初始化就不要再更改。否则可能引发系统的保护异常。实际上更好的写法如下:

    //请分析一下为何这样写是对的:
    UNICODE_STRING str = { 
    sizeof(L”my first string!”) – sizeof((L”my first string!”)[0]), 
    sizeof(L”my first string!”),
    L”my first_string!” };

    但是这样定义一个字符串实在太繁琐了。但是在头文件ntdef.h中有一个宏方便这种定义。使用这个宏之后,我们就可以简单的定义一个常数字符串如下:

    #include <ntdef.h>
    UNICODE_STRING str = RTL_CONSTANT_STRING(L“my first string!”);

    这只能在定义这个字符串的时候使用。为了随时初始化一个字符串,可以使用RtlInitUnicodeString。示例如下:

    UNICODE_STRING str;
    RtlInitUnicodeString(&str,L”my first string!”);

    用本小节的方法初始化的字符串,不用担心内存释放方面的问题。因为我们并没有分配任何内存。

    1.3 字符串的拷贝

    因为字符串不再是空结束的,所以使用wcscpy来拷贝字符串是不行的。UNICODE_STRING可以用RtlCopyUnicodeString来进行拷贝。在进行这种拷贝的时候,最需要注意的一点是:拷贝目的字符串的Buffer必须有足够的空间。如果Buffer的空间不足,字符串会拷贝不完全。这是一个比较隐蔽的错误。
    下面举一个例子。

    UNICODE_STRING dst; // 目标字符串
    WCHAR dst_buf[256]; // 我们现在还不会分配内存,所以先定义缓冲区
    UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);

    // 把目标字符串初始化为拥有缓冲区长度为256的UNICODE_STRING空串。
    RtlInitEmptyString(dst,dst_buf,256*sizeof(WCHAR));
    RtlCopyUnicodeString(&dst,&src); // 字符串拷贝!

    以上这个拷贝之所以可以成功,是因为256比L” My source string!”的长度要大。如果小,则拷贝也不会出现任何明示的错误。但是拷贝结束之后,与使用者的目标不符,字符串实际上被截短了。
    我曾经犯过的一个错误是没有调用RtlInitEmptyString。结果dst字符串被初始化认为缓冲区长度为0。虽然程序没有崩溃,却实际上没有拷贝任何内容。
    在拷贝之前,最谨慎的方法是根据源字符串的长度动态分配空间。

  • 相关阅读:
    C# Redis实战(四)
    C# Redis实战(三)
    C# Redis实战(二)
    C# Redis实战(一)
    C#连接内存数据库redis【1、Redis存读取数据】
    C#连接内存数据库redis【1、安装配置】
    c#根据配置文件反射
    内存数据库:Redis与Memcached的区别
    内存数据库:memcached与redis技术的对比试验
    【转】【涨姿势】支付宝怎么做风险控制?
  • 原文地址:https://www.cnblogs.com/lanrenxinxin/p/4368729.html
Copyright © 2011-2022 走看看