天天看点

7、Windows驱动开发技术详解笔记(3) 基本语法回顾

3、文件读写

在ring3 我们可以使用CreateFile、ReadFile 、WriteFile 等API,在ring0 同样很相似,不过函数变成了ZwCreateFile、ZwReadFile、ZwWriteFile 等内核函数。

1)ZwCreateFile与ring3的CreateFile函数有所不同,它不能直接将需要打开或创建的文件路径传递过去,我们必须首先填写一个OBJECT_ATTRIBUTES结构。

UNICODE_STRING str;

OBJECT_ATTRIBUTES obj_attrib;

RtlInitUnicodeString(&str, L"\\??\\C:\\windows\\notepad.exe");

InitializeObjectAttributes(&obj_attrib,

&str, // 需要操作的对象、比如文件或注册表路径等

OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

NULL,

NULL);

第三个参数OBJ_CASE_INSENSITIVE 表示不区分大小写,OBJ_KERNEL_HANDLE

表示将要打开的句柄为内核句柄。内核句柄比起应用层句柄有很多的好处,例如它可以不受进程或线程的限制,而且在需要打开一个内核句柄时不需要考虑当前是否有权限访问该文件的问题。

2)创建、打开文件

创建和打开文件都可使用ZwCreateFile 函数,它的第一个参数将返回一个文件句柄,所有后续操作都可以通过这个句柄完成,在操作结束后,需要调用ZwClose 关闭句柄。

<a href="http://msdn.microsoft.com/en-us/library/ff566424%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff566424%28VS.85%29.aspx</a>

ZwCreateFile 函数的第三个参数就是使用我们此前填写的OBJECT_ATTRIBUTES 结构;它返回的信息通过第四个IO_STATUS_BLOCK 返回;第八、九个参数联合指明了如何打开或创建文件,其中IO_STATUS_BLOCK的定义如下所示:

typedef struct _IO_STATUS_BLOCK {

NTSTATUS Status;

ULONG Information;

}IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

http://msdn.microsoft.com/en-us/library/ff550671%28VS.85%29.aspx

其中Status指明了函数的执行结果,如果执行成功它的值将是STATUS_SUCCESS ,否

则它将会是一个形如STATUS_XXX 的错误提示。

此外,DDK 还提供了一个函数ZwOpenFile 用来简化打开文件的操作,它所需要的参数

比ZwCreateFile 更加简洁,使用更加简单。

3)打开文件

在内核中读写文件与用户模式下十分相似,它们分别使用ZwReadFile 和ZwWriteFile函数完成。

NTSTATUS

ZwReadFile(

IN HANDLE FileHandle,

IN HANDLE Event OPTIONAL,

IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,

IN PVOID ApcContext OPTIONAL,

OUT PIO_STATUS_BLOCK IoStatusBlock,

OUT PVOID Buffer,

IN ULONG Length,

IN PLARGE_INTEGER ByteOffset OPTIONAL,

IN PULONG Key OPTIONAL);

各参数的简要介绍如下所示:

FileHandle:函数ZwCreateFile 返回的句柄。如果它是一个内核句柄,则ZwReadFile 和

ZwCreateFile 并不需要在同一个进程中,因为内核句柄是各进程通用的。

Event :一个事件,用于异步完成读时;我们忽略这个参数。

ApcRoutine Apc:回调例程,用于异步完成读时;我们忽略这个参数。

IoStatusBlock:返回结果状态,与ZwCreateFile 中的同名参数相同。

Buffer:缓冲区,如果读取文件的内容成功,则内容将被读取到这里。

Length:描述缓冲区的长度,即试图读取文件的长度。

ByteOffset:要读取的文件的偏移量,也就是要读取的内容在文件中的位置。一般来说,不要将其设置为NULL,文件句柄不一定支持直接读取当前偏移。

Key:读取文件时用的一种附加信息,一般不使用。

当函数执行成功时返回STATUS_SUCCESS,实际上只要能够读取到任意字节的数据(不管它是否符合参数Length 的要求),都返回成功;但是,如果仅读取文件长度之外的部分,则返回STATUS_END_OF_FILE。ZwWriteFile 的参数与ZwReadFile 基本相同。

4)其它操作

ZwQueryInformationFile、ZwSetInformationFile可以分别用来获取和设置文件属性,包括文件大小、文件指针位置、文件属性(如只读、隐藏)、文件创建/修改日期等。

ZwSetInformationFile 函数:

ZwSetInformationFile(

IN PVOID FileInformation,

IN FILE_INFORMATION_CLASS FileInformationClass

);

FileInformationClass指定修改或查询的类别,它可能的值有很多种

<a href="http://msdn.microsoft.com/en-us/library/ff567096%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff567096%28VS.85%29.aspx</a>

在内核模式下操作文件的函数不像用户模式下那样丰富,想复制文件就调用CopyFile、想删除文件就调用DeleteFile等,在内核模式下除了读写文件的其他所有操作都是通过这两个ZwQueryInformation和ZwSetInformationFile 函数完成的,而如何使这两个函数精确完成我们需要的功能,就需要通过FileInformationClass参数来指定。

5)一个例子如下:

声明.h:

7、Windows驱动开发技术详解笔记(3) 基本语法回顾
7、Windows驱动开发技术详解笔记(3) 基本语法回顾

代码

1 BOOLEAN

2

3 MyCopyFile(IN PUNICODE_STRING ustrDestFile,

4

5 IN PUNICODE_STRING ustrSrcFile);

6

7 .cpp定义

8

9 DriverEntry函数中

10

11 UNICODE_STRING ustrSrcFile, ustrDestFile;

12

13 RtlInitUnicodeString(&amp;ustrSrcFile, L"\\??\\C:\\windows\\notepad.exe");

14

15 RtlInitUnicodeString(&amp;ustrDestFile, L"\\??\\C:\\notepad.exe");

16

17  if(MyCopyFile(&amp;ustrDestFile, &amp;ustrSrcFile))

18

19 {

20

21 KdPrint(("[Test] CopyFile Success!"));

22

23 }

24

25  else

26

27 {

28

29 KdPrint(("[Test] CopyFile Error!"));

30

31 }

32

33 return status;

34

35 }

36

37 自定义函数:

38

39 /************************************************************************

40

41 * 函数名称:MyCopyFile

42

43 * 功能描述:复制文件

44

45 * 参数列表:

46

47 ustrDestFile:目的文件

48

49 ustrSrcFile:源文件

50

51 * 返回 值:返回状态

52

53 *************************************************************************/

54

55 BOOLEAN

56

57 MyCopyFile( IN PUNICODE_STRING ustrDestFile,

58

59 IN PUNICODE_STRING ustrSrcFile)

60

61 {

62

63 HANDLE hSrcFile, hDestFile;

64

65 PVOID buffer = NULL;

66

67 ULONG length = 0;

68

69 LARGE_INTEGER offset = {0};

70

71 IO_STATUS_BLOCK Io_Status_Block = {0};

72

73 OBJECT_ATTRIBUTES obj_attrib;

74

75 NTSTATUS status;

76

77 BOOLEAN bRet = FALSE;

78

79 do

80

81 { // 打开源文件

82

83 InitializeObjectAttributes(&amp;obj_attrib,

84

85 ustrSrcFile,

86

87 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

88

89 NULL,

90

91 NULL);

92

93 status = ZwCreateFile(&amp;hSrcFile,

94

95 GENERIC_READ,

96

97 &amp;obj_attrib,

98

99 &amp;Io_Status_Block,

100

101 NULL,

102

103 FILE_ATTRIBUTE_NORMAL,

104

105 FILE_SHARE_READ,

106

107 FILE_OPEN,

108

109 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,

110

111 NULL, 0);

112

113 if (!NT_SUCCESS(status))

114

115 {

116

117 bRet = FALSE;

118

119 goto END;

120

121 }

122

123 // 打开目标文件

124

125 InitializeObjectAttributes(&amp;obj_attrib,

126

127 ustrDestFile,

128

129 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

130

131 NULL,

132

133 NULL);

134

135 status = ZwCreateFile(&amp;hDestFile,

136

137 GENERIC_WRITE,

138

139 &amp;obj_attrib,

140

141 &amp;Io_Status_Block,

142

143 NULL,

144

145 FILE_ATTRIBUTE_NORMAL,

146

147 FILE_SHARE_READ,

148

149 FILE_OPEN_IF,

150

151 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,

152

153 NULL, 0);

154

155 if (!NT_SUCCESS(status))

156

157 {

158

159 bRet = FALSE;

160

161 goto END;

162

163 }

164

165 // 为buffer 分配4KB 空间

166

167 buffer = ExAllocatePool(NonPagedPool, 1024 * 4);

168

169 if (buffer == NULL)

170

171 {

172

173 bRet = FALSE;

174

175 goto END;

176

177 }

178

179 // 复制文件

180

181 while (1)

182

183 {

184

185 length = 4 * 1024;

186

187 // 读取源文件

188

189 status = ZwReadFile(hSrcFile,

190

191 NULL,

192

193 NULL,

194

195 NULL,

196

197 &amp;Io_Status_Block,

198

199 buffer,

200

201 length,

202

203 &amp;offset,

204

205 NULL);

206

207 if (!NT_SUCCESS(status))

208

209 {

210

211 // 如果状态为STATUS_END_OF_FILE,说明文件已经读取到末尾

212

213 if (status == STATUS_END_OF_FILE)

214

215 {

216

217 bRet = TRUE;

218

219 goto END;

220

221 }

222

223 }

224

225 // 获得实际读取的长度

226

227 length = Io_Status_Block.Information;

228

229 // 写入到目标文件

230

231 status = ZwWriteFile(hDestFile,

232

233 NULL,

234

235 NULL,

236

237 NULL,

238

239 &amp;Io_Status_Block,

240

241 buffer,

242

243 length,

244

245 &amp;offset,

246

247 NULL);

248

249 if (!NT_SUCCESS(status))

250

251 {

252

253 bRet = FALSE;

254

255 goto END;

256

257 }

258

259 // 移动文件指针

260

261 offset.QuadPart += length;

262

263 }

264

265 } while (0);

266

267 END:

268

269 if (hSrcFile)

270

271 {

272

273 ZwClose(hSrcFile);

274

275 }

276

277 if (hDestFile)

278

279 {

280

281 ZwClose(hDestFile);

282

283 }

284

285 if (buffer = NULL)

286

287 {

288

289 ExFreePool(buffer);

290

291 }

292

293 return bRet;

294

295 }

6)创建文件

NTSTATUS ZwCreateFile(

OUT PHANDLE FileHandle,

IN ACCESS_MASK DesiredAccess,

IN POBJECT_ATTRIBUTES ObjectAttribute,

IN PLARGE_INTEGER AllocationSize OPTIONAL,

IN ULONG FileAttributes,

IN ULONG ShareAccess,

IN ULONG CreateDisposition,

IN ULONG createOptions,

IN PVOID EaBuffer OPTIONAL,

IN ULONG EaLength);

FileHandle:是一个句柄的指针。如果这个函数调用返回成成功(STATUS_SUCCESS),那就么打开的文件句柄就返回在这个地址内。

DesiredAccess:申请的权限。打开写文件用FILE_WRITE_DATA,读文件内容用FILE_READ_DATA,删除文件或者把文件改名用DELETE,想设置文件属性,请使用FILE_WRITE_ATTRIBUTES,读文件属性则使用 FILE_READ_ATTRIBUTES。这些条件可以用|(位或)来组合。有两个宏分别组合了常用的读权限和常用的写权限,为GENERIC_READ和GENERIC_WRITE,宏 GENERIC_ALL代表全部权限。如果想同步的打开文件,加上SYNCHRONIZE。

union {

PVOID Pointer;

};

ULONG_PTR Information;

} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

实际编程中很少用到Pointer。返回的结果在Status中。成功则为STATUS_SUCCESS。否则则是一个错误码。进一步的信息在 Information中。不同的情况下返回的Information的信息意义不同。针对ZwCreateFile调用的情况,Information 的返回值有以下几种可能:

·FILE_CREATED:文件被成功的新建了。

·FILE_OPENED: 文件被打开了。

·FILE_OVERWRITTEN:文件被覆盖了。

·FILE_SUPERSEDED: 文件被替代了。

·FILE_EXISTS:文件已存在。(因而打开失败了)。

·FILE_DOES_NOT_EXIST:文件不存在。(因而打开失败了)。

这些返回值和打开文件的意图有关(有时希望打开已存在的文件,有时则希望建立新的文件等。

AllocationSize参数很少使用,请设置为NULL。

FileAttributes控制新建立的文件的属性,设为FILE_ATTRIBUTE_NORMAL。

ShareAccess,实际上,这是在本代码打开这个文件的时候,允许别的代码同时打开这个文件所持有的权限。所以称为共享访问。一共有三种共享标记可以设置:FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_DELETE。这三个标记可以用|(位或)来组合。举例如下:如果本次打开只使用了 FILE_SHARE_READ,那么这个文件在本次打开之后,关闭之前,别次打开试图以读权限打开,则被允许,可以成功打开。如果别次打开试图以写权限打开,则一定失败。返回共享冲突。

CreateDisposition参数说明了这次打开的意图。可能的选择如下(这些选择不能组合):

·FILE_CREATE:新建文件。如果文件已经存在,则这个请求失败。

·FILE_OPEN:打开文件。如果文件不存在,则请求失败。

·FILE_OPEN_IF:打开或新建。如果文件存在,则打开。如果不存在,则失败。

·FILE_OVERWRITE:覆盖。如果文件存在,则打开并覆盖其内容。如果文件不存在,这个请求返回失败。

·FILE_OVERWRITE_IF:新建或覆盖。如果要打开的文件已存在,则打开它,并覆盖其内存。如果不存在,则简单的新建新文件。

·FILE_SUPERSEDE:新建或取代。如果要打开的文件已存在。则生成一个新文件替代之。如果不存在,则简单的生成新文件。

CreateOption, 笔者使用FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT,此时文件被同步的打开。而且打开的是文件(而不是目录。创建目录请用FILE_ DIRECTORY_FILE)。同步的打开的意义在于,以后每次操作文件的时候,比如写入文件,调用ZwWriteFile,在ZwWriteFile 返回时,文件写操作已经得到了完成。而不会有返回STATUS_PENDING(未决)的情况。在非同步文件的情况下,返回未决是常见的。此时文件请求没有完成,使用者需要等待事件来等待请求的完成。当然,好处是使用者可以先去做别的事情。

要同步打开,前面的DesiredAccess必须含有SYNCHRONIZE。

此外还有一些其他的情况。比如不通过缓冲操作文件,希望每次读写文件都是直接往磁盘上操作的,此时CreateOptions中应该带标记 FILE_NO_INTERMEDIATE_BUFFERING。带了这个标记后,请注意操作文件每次读写都必须以磁盘扇区大小(最常见的是512字节)对齐,否则会返回错误。

继续阅读