Win64 驱动内核编程-4.内核里操作字符串
內核里操作字符串
? 字符串本質上就是一段內存,之所以和內存使用分開講,是因為內核里的字符串太有花
樣了,細數下來竟然有?4?種字符串!這四種字符串,分別是:CHAR*、WCHAR*、ANSI_STRING、UNICODE_STRING。當然,內核里使用頻率最多的是?UNICODE_STRING,其次是?WCHAR*,再次是?CHAR*,而?ANSI_STRING,則幾乎沒見過有什么內核函數使用。
? 但其實這四種字符串也不是完全獨立的,ANSI_STRING可以看成是CHAR*的安全性擴展,UNICODE_STRING?可以看成是?WCHAR*的安全性擴展。CHAR*?,?可以?理解成?成?CHAR??數組,?本質上來說?是一個地址,?它?的有效長度是多少?不知道?。?字符串?有多長?不清楚?,?遇到\0??就當成是?字符串?的?結尾。綜上所述,CHAR*的安全性是非常糟糕的,如果內核使用?CHAR*作為主要字符串,那么會非常不穩定。WCHAR*和?和?CHAR*?類似?,和?和?CHAR*?的唯一不同在于?一個WCHAR?占?占?2??個字節,而一個?CHAR?只占?1?個字節。所以,WCHAR*?的安全性也是非常糟糕的。微軟為了安全性著想,推出了這兩種字符串的擴展集,ANSI_STRING和UNICODE_STRING。ANSI_STRING?和?UNICODE_STRING?都是結構體,定義如下:
? 可以看到,ANSI_STRING?和?UNICODE_STRING?的結構體大小是一樣的,唯一不同在于第三個成員,他們分別對應?CHAR*和?WCHAR*。它們的第一和第二個成員分別代表字符串的長度和內存的有效長度。比如?BUFFER?的長度是?260?字節,而?BUFFER?只是一個單詞”WERT”,則?ANSI_STRING?的?Length=4、MaximumLength=260;UNICODE_STRING?的?Length=8、MaximumLength=260。另外需要注意的是,CHAR*和?和?WCHAR*?都是以?0??結尾的?(CHAR*以?以?1個\0??結尾?,WCHAR*以?以?2??個\0??結尾?),但?但?ANSI_STRING?和?和?UNICODE_STRING??不一定以?以?0??結尾。?因為?有了長度的說明,就不需要用特殊標識符來表示結尾了?.?有些自以為是?的人直接把ANSI_STRING?或?或?UNICODE_STRING?的?的?BUFFER??當作字符串用,是極端錯誤的。
? 在內核里,大部分的?C?語言字符串函數都是可以用的,無論是寬版的還是窄版的。比如內核里可以照樣使用?strcpy?和?wcscpy,但某些字符串函數簽名可能要加上一個下劃線。比如?strlwr?要寫成_strlwr。ANSI_STRING?和?UNICODE_STRING?有一套專門的字符串函數(http://msdn.microsoft.com/en-us/library/windows/hardware/ff563638(v=vs.85).aspx),如果需要查詢怎么使用,就用瀏覽器打開,搜索?AnsiString?或者?UnicodeString,并點擊進去查看說明即可。關于?CHAR*和?WCHAR*的字符串操作就不講了,不懂的請看任意的?C?語言教程。下面舉幾個關于?UNICODE_STRING?的例子(以下代碼借用了張帆寫的示例,特此聲明感謝)。1.?字符串初始化、2.?字符串拷貝?、3.?字符串比較?、4.?字符串變大寫?、5.?字符串與整型相互轉化?、6.?ANSI_STRING??字符串與?UNICODE_STRING??字符串相互轉換。
//1.字符串初始化 VOID StringInitTest() { //(1)用 RtlInitAnsiString 初始化字符串 ANSI_STRING AnsiString1; CHAR string1[] = "hello";//此處注意,原版的這里面寫的是CHAR *,這樣的話會導致改值失敗。 //初始化 ANSI_STRING 字符串 RtlInitAnsiString(&AnsiString1, string1); DbgPrint("AnsiString1:%Z\n", &AnsiString1);//打印 hello string1[0] = 'H'; string1[1] = 'E'; string1[2] = 'L'; string1[3] = 'L'; string1[4] = 'O'; //改變 string1,AnsiString1 同樣會導致變化 DbgPrint("AnsiString1:%Z\n", &AnsiString1);//打印 HELLO //(2)程序員自己初始化字符串 #define BUFFER_SIZE 1024 UNICODE_STRING UnicodeString1 = { 0 }; //設置緩沖區大小 UnicodeString1.MaximumLength = BUFFER_SIZE; //分配內存 UnicodeString1.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); WCHAR wideString[] = L"hello"; //設置字符長度,因為是寬字符,所以是字符長度的 2 倍 UnicodeString1.Length = 2 * wcslen(wideString); //保證緩沖區足夠大,否則程序終止 ASSERT(UnicodeString1.MaximumLength >= UnicodeString1.Length); //內存拷貝, RtlCopyMemory(UnicodeString1.Buffer, wideString, UnicodeString1.Length); //設置字符長度 UnicodeString1.Length = 2 * wcslen(wideString); DbgPrint("UnicodeString:%wZ\n", &UnicodeString1); //清理內存 ExFreePool(UnicodeString1.Buffer); UnicodeString1.Buffer = NULL; UnicodeString1.Length = UnicodeString1.MaximumLength = 0; } //2.字符串拷貝 VOID StringCopyTest() { //初始化 UnicodeString1 UNICODE_STRING UnicodeString1; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); //初始化 UnicodeString2 UNICODE_STRING UnicodeString2 = { 0 }; UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); UnicodeString2.MaximumLength = BUFFER_SIZE; //將初始化 UnicodeString2 拷貝到 UnicodeString1 RtlCopyUnicodeString(&UnicodeString2, &UnicodeString1); //分別顯示 UnicodeString1 和 UnicodeString2 DbgPrint("UnicodeString1:%wZ\n", &UnicodeString1); DbgPrint("UnicodeString2:%wZ\n", &UnicodeString2); //銷毀 UnicodeString2 //注意!!UnicodeString1 不用銷毀 RtlFreeUnicodeString(&UnicodeString2); }//3.字符串比較 VOID StringCompareTest() { //初始化 UnicodeString1 UNICODE_STRING UnicodeString1; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); //初始化 UnicodeString2 UNICODE_STRING UnicodeString2; RtlInitUnicodeString(&UnicodeString1, L"Hello"); if (RtlEqualUnicodeString(&UnicodeString1, &UnicodeString2, TRUE)) DbgPrint("UnicodeString1 and UnicodeString2 are equal\n"); else DbgPrint("UnicodeString1 and UnicodeString2 are NOT equal\n"); }//4.字符串變大寫 VOID StringToUpperTest() { //初始化 UnicodeString1 UNICODE_STRING UnicodeString1; UNICODE_STRING UnicodeString2; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); //變化前 DbgPrint("UnicodeString1:%wZ\n", &UnicodeString1); //變大寫 RtlUpcaseUnicodeString(&UnicodeString2, &UnicodeString1, TRUE); //變化后 DbgPrint("UnicodeString2:%wZ\n", &UnicodeString2); //銷毀 UnicodeString2(UnicodeString1 不用銷毀) RtlFreeUnicodeString(&UnicodeString2); } //5.字符串與整型相互轉化 VOID StringToIntegerTest() { //(1)字符串轉換成數字 //初始化 UnicodeString1 UNICODE_STRING UnicodeString1; RtlInitUnicodeString(&UnicodeString1, L"-100"); ULONG lNumber; NTSTATUS nStatus = RtlUnicodeStringToInteger(&UnicodeString1, 10, &lNumber); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver to integer succussfully!\n"); DbgPrint("Result:%d\n", lNumber); } else { DbgPrint("Conver to integer unsuccessfully!\n"); } //(2)數字轉換成字符串 //初始化 UnicodeString2 UNICODE_STRING UnicodeString2 = { 0 }; UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); UnicodeString2.MaximumLength = BUFFER_SIZE; nStatus = RtlIntegerToUnicodeString(200, 10, &UnicodeString2); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver to string succussfully!\n"); DbgPrint("Result:%wZ\n", &UnicodeString2); } else { DbgPrint("Conver to string unsuccessfully!\n"); } //銷毀 UnicodeString2 //注意!!UnicodeString1 不用銷毀 RtlFreeUnicodeString(&UnicodeString2); } //6. ANSI_STRING 字符串與 UNICODE_STRING 字符串相互轉換 VOID StringConverTest() { //(1)將 UNICODE_STRING 字符串轉換成 ANSI_STRING 字符串 //初始化 UnicodeString1 UNICODE_STRING UnicodeString1; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); ANSI_STRING AnsiString1; NTSTATUS nStatus = RtlUnicodeStringToAnsiString(&AnsiString1, &UnicodeString1, TRUE); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver succussfully!\n"); DbgPrint("Result:%Z\n", &AnsiString1); } else { DbgPrint("Conver unsuccessfully!\n"); } //銷毀 AnsiString1 RtlFreeAnsiString(&AnsiString1); //(2)將 ANSI_STRING 字符串轉換成 UNICODE_STRING 字符串 //初始化 AnsiString2 ANSI_STRING AnsiString2; RtlInitString(&AnsiString2, "Hello World"); UNICODE_STRING UnicodeString2; nStatus = RtlAnsiStringToUnicodeString(&UnicodeString2, &AnsiString2, TRUE); if (NT_SUCCESS(nStatus)) { KdPrint("Conver succussfully!\n"); KdPrint("Result:%wZ\n", &UnicodeString2); } else { KdPrint("Conver unsuccessfully!\n"); } //銷毀 UnicodeString2 RtlFreeUnicodeString(&UnicodeString2); }測試結果:
? 以上示例是我在很多書本的示例中精心挑選出來的,簡單明了,當然能保證能測試成功。不過如果在實戰環境下,因為操作字符串而導致藍屏的還是非常多見的。根本原因只有兩個:1.緩沖區長度溢出;2.操作的指針無效。所以大家以后在做項目時,遇到需要操作字符串的場景還是要格外當心。
最后再加上看的資料的那個作者自己寫的三個函數,留著筆記:
//UNICODE_STRINGz?轉換為?CHAR*
//輸入?UNICODE_STRING?的指針,輸出窄字符串,BUFFER?需要已經分配好空間
VOID UnicodeToChar(PUNICODE_STRING dst, char *src) { ANSI_STRING string; RtlUnicodeStringToAnsiString(&string,dst, TRUE); strcpy(src,string.Buffer); RtlFreeAnsiString(&string); }//WCHAR*轉換為 CHAR* //輸入寬字符串首地址,輸出窄字符串,BUFFER 需要已經分配好空間 VOID WcharToChar(PWCHAR src, PCHAR dst) { UNICODE_STRING uString; ANSI_STRING aString; RtlInitUnicodeString(&uString,src); RtlUnicodeStringToAnsiString(&aString,&uString,TRUE); strcpy(dst,aString.Buffer); RtlFreeAnsiString(&aString); } //CHAR*轉 WCHAR* //輸入窄字符串首地址,輸出寬字符串,BUFFER 需要已經分配好空間 VOID CharToWchar(PCHAR src, PWCHAR dst) { UNICODE_STRING uString; ANSI_STRING aString; RtlInitAnsiString(&aString,src); RtlAnsiStringToUnicodeString(&uString,&aString,TRUE); wcscpy(dst,uString.Buffer); RtlFreeUnicodeString(&uString); }總結
以上是生活随笔為你收集整理的Win64 驱动内核编程-4.内核里操作字符串的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win64 驱动内核编程-3.内核里使用
- 下一篇: Win64 驱动内核编程-5.内核里操作