基于元神操作系统实现NTFS文件操作(八)

  1. 背景

本文继续介绍当前磁盘分区下的文件遍历操作,详细介绍$Root元文件的A0H属性的Data Runs,并解析每个Data Run中的所有索引项,同时提供了基于元神操作系统的实现代码。

  1. 方法

(1)Data Runs

A0H属性是非常驻属性,其属性体部分为Data Runs,即Data Run列表。其中的每个Data Run标识了一组存放数据的簇,第一个字节的高4位表示记录起始簇号的字节数,第一个字节的低4位表示记录簇数的字节数,第二个字节开始存放簇数,之后存放起始簇号,结合前文读取的$Root元文件数据,其中A0H属性的第一个Data Run数据为"31 02 53 03 2F",根据第一个字节0x31可知,起始簇号占3个字节,簇数占1个字节,所以接下来的1个字节存放簇数,再之后的3个字节存放起始簇号,即簇53 03 2F(0x2F0353)开始的02个簇用于存放数据。解析完第一个Data Run之后,向后偏移3+1+1=5个字节,定位到下一个Data Run并重新解析,直到Data Run的第一个字节为00,表示Data Runs结束。

Data Runs所标识的每个簇都由一个标准索引头和若干个索引项构成。索引项的结构可以参考前文中对90H属性的介绍,标准索引头的结构为:偏移0x00-0x03总是"INDX"的ASCII码;偏移0x04-0x05表示更新序列号的偏移;偏移0x06-0x07表示更新序列号与更新数组的大小;偏移0x08-0x0F表示日志文件序列号;偏移0x10-0x17表示本索引缓存在索引分配中的VCN;偏移0x18-0x1B表示索引项的偏移;偏移0x1C-0x1F表示索引项的大小;偏移0x20-0x23表示索引项分配大小;偏移0x24处为子节点标志,值为1时表示还有子节点(即不是叶节点);偏移0x28开始为更新序列。

(2)定位A0H属性中的Data Runs

根据前文的描述,在$Root元文件的文件头的偏移0x14-0x15处获取第一个属性的偏移,并据此偏移到第一个属性位置;然后判断该属性是否是A0H属性,如果不是,则根据该属性的偏移0x04-0x07处的属性长度来定位和偏移到下一个属性位置,并重新判断;直至找到A0H属性,然后根据非常驻属性头的偏移0x20-0x21处的值来定位到Data Runs。代码如下所示:

	movzx edx, word [fs:esi+0x14]
	add esi, edx
 .search_index_allocation:
	cmp dword [fs:esi+0x00], 0xA0
	je .index_allocation
	add esi, dword [fs:esi+0x04]
	jmp .search_index_allocation
 .index_allocation:
	movzx edx, word [fs:esi+0x20]
	add esi, edx

(3)解析Data Run

根据上面的介绍,每个Data Run标识了一组簇,包括起始簇号和簇数,按照上面的介绍对其进行解析,代码如下所示:

	movzx ecx, byte [fs:esi+0x00]
	and ecx, 0x0F
	add esi, ecx
	xor eax, eax
 .next_cluster_count:
	shl eax, 8
	mov al, byte [fs:esi]
	dec esi
	loop .next_cluster_count
	
	movzx ecx, byte [fs:esi+0x00]
	mov edx, ecx
	shr ecx, 4
	and edx, 0x0F
	add esi, edx
	add esi, ecx
	xor edx, edx
 .next_cluster_no:
	shl edx, 8
	mov dl, byte [fs:esi]
	dec esi
	loop .next_cluster_no

从上面的代码可见,该Data Run标识的的起始簇号存放在EDX寄存器中,而簇数存放在EAX寄存器中。此处需要注意的是数据的存放顺序,例如,起始簇号53 03 2F,其中的2F在高字节,53在低字节,最终解析出的应该是0x2F0353,而非0x53032F。

(4)读取和解析Data Run标识的一个簇

读取簇的方法延用前面所介绍的API_READ_DISK_SECTOR调用,只是需要重复调用若干次,每次读取一个扇区。读取完一个簇后,根据标准索引头的偏移0x18-0x1B处的值偏移到第一个索引项处,然后根据索引项的结构解析并保存文件名称,代码如下所示:

;input:
;	edx: cluster no.
;	edi: address of buffer for storing result data
;output:
;	edi: updated address of buffer
parse_a0_cluster:
	pusha
	mov esi, sector_dbr
	movzx ecx, byte [ds:esi+0x0D]
	imul edx, ecx
	add edx, [ds:partition_base]
	mov ebx, [ds:mem_addr]
	add ebx, 2048
	
	mov edi, API_PARAM
	mov dword [fs:edi], API_READ_DISK_SECTOR
	mov dword [fs:edi+4], 2
 .read:
	mov dword [fs:edi+8], edx
	mov dword [fs:edi+12], ebx
	call pword [fs:OS_API]
	
;	pusha
;	mov esi, [esp+16]
;	mov eax, 512
;	movzx ebx, word [fs:cursor_y]
;	movzx ecx, word [fs:cursor_x]
;	call print_bytes_hex	
;	add word [fs:cursor_y], 20
;	popa

	inc edx
	add ebx, 512
	loop .read
	
	mov esi, [ds:mem_addr]
	add esi, 2048
	mov edi, [esp+0]
	
	add esi, dword [fs:esi+0x18]
	add esi, 0x18
 .check_filename:
	cmp word [fs:esi+0x0C], 0x02
	je .end
	
	call copy_file_name_unicode
	movzx edx, byte [fs:esi+0x50]
	add edi, edx
	inc edi
	
	movzx edx, word [fs:esi+0x08]
	add esi, edx
	jmp .check_filename
	
 .end:
	mov [esp+0], edi
	popa
	ret

由上述代码可见,对于所申请的内存,开头的1024字节用于存放$Root元文件;之后的1024字节用于存放解析的结果,即文件名称列表;再之后的部分用于存放Data Run标识的一个簇。中间的1024字节如果不够存放文件列表,可以重新调整其大小。

打印出读取到的簇的第一个扇区数据,结果如下图所示:

结合上图和前文的结构介绍,标准索引头的偏移0x00-0x03处的值为"INDX"的ASCII码;偏移0x04-0x05处的值为28 00(即0x0028),表示更新序列号的偏移为0x28;偏移0x06-0x07处的值为09 00(即0x0009),表示更新序列号的数量;偏移0x18-0x1B处的值为40 00 00 00(即0x00000040),表示索引项的偏移为0x40;偏移0x1C-0x1F处的值为E0 07 00 00(即0x000007E0),表示索引项的大小为0x7E0字节,即2016字节;偏移0x20-0x23处的值为E8 0F 00 00(即0x00000FE8),表示索引项分配的大小为0xFE8字节,即4072字节;偏移0x24处的值为00,表示当前为叶节点,即没有子节点;偏移0x28-0x29处的值为8A 05(即0x058A),表示更新序列号为0x058A。

根据标准索引头的偏移0x18-0x1B处的值定位到第一个索引项,结合上图,索引项的偏移0x00-0x07处的值为04 00 00 00 00 00 04 00,表示的是文件的MFT参考号,用于定位文件存储位置;偏移0x08-0x09处的值为68 00(即0x0068),表示本索引项的大小为0x68字节,即104字节,用于偏移到下一个索引项;偏移0x0A-0x0B处的值为52 00(即0x0052),表示文件名的偏移为0x52,用于定位和读取文件名称;偏移0x0C-0x0D处的索引标志值为00 00,表示常规文件;偏移0x40-0x47处的值为A0 8C 00 00 00 00 00 00(即0x0000000000008CA0),表示文件的实际大小为0x8CA0字节,即36000字节;偏移0x50处的值为08,表示文件名的长度为8个字符;偏移0x51处的值为03,表示Win32 & DOS命名空间;偏移0x52开始的值为24 00 41 00 74 00 74 00 72 00 44 00 65 00 66 00 00 00 00 00 00 00,将前8个UNICODE字符的第一个字节的ASCII码转换为字符得出文件名为"$AttrDef",即4号元文件。以上这些索引项的字段是本博文中较多使用的,索引项的其它字段的意义可以参考90H属性中的说明。

解析完第一个索引项之后,根据该索引项的偏移0x08-0x09处的值定位到下一个索引项,然后再次进行解析。依此类推,直到某个索引项的偏移0x0C-0x0D处的值为0x02,表示索引项结束。

(5)完整的代码实现及运行结果

将上述各部分内容整合到一起,代码如下所示:

use32

include 'api_def.inc'

OS_API equ 0x00030C16
API_PARAM equ 0x03000000
SEG_BASE equ 0x00040000
cursor_x equ 0x02004B10
cursor_y equ 0x02004B12

START:
	pusha
	
	call read_disk_mbr
	
	mov eax, 0
	call read_disk_dbr	;read dbr in partition eax
	
	call mem_alloc
	call read_disk_root
	
;	call parse_90h
	call parse_a0h
	
	movzx ebx, word [fs:cursor_y]
	movzx ecx, word [fs:cursor_x]
	call print_strings
	
	add word [fs:cursor_y], 10
	
	popa
	iret

parse_a0h:
	pusha
	
	mov esi, [ds:mem_addr]
	mov edi, esi
	add edi, 1024
	mov dword [fs:edi], 0
	
	movzx edx, word [fs:esi+0x14]
	add esi, edx
 .search_index_allocation:
	cmp dword [fs:esi+0x00], 0xA0
	je .index_allocation
	add esi, dword [fs:esi+0x04]
	jmp .search_index_allocation
 .index_allocation:
	movzx edx, word [fs:esi+0x20]
	add esi, edx
	
	mov ebp, 0
	mov ebx, esi
 .check_run_list:
	movzx ecx, byte [fs:esi+0x00]
	cmp ecx, 0x00
	je .end

	and ecx, 0x0F
	add esi, ecx
	xor eax, eax
 .next_cluster_count:
	shl eax, 8
	mov al, byte [fs:esi]
	dec esi
	loop .next_cluster_count
	
	movzx ecx, byte [fs:esi+0x00]
	mov edx, ecx
	shr ecx, 4
	and edx, 0x0F
	add esi, edx
	add esi, ecx
	xor edx, edx
 .next_cluster_no:
	shl edx, 8
	mov dl, byte [fs:esi]
	dec esi
	loop .next_cluster_no
	
	add edx, ebp
	mov ebp, edx
	
 .check_cluster:	
	call parse_a0_cluster
	cmp eax, 1
	je .end
	dec eax
	inc edx
	jmp .check_cluster

	mov esi, ebx
	movzx ecx, byte [fs:esi+0x00]
	mov edx, ecx
	and ecx, 0x0F
	shr edx, 4
	add esi, ecx
	add esi, edx
	add esi, 1
	mov ebx, esi
	jmp .check_run_list

 .end:
	mov byte [fs:edi], 0
	popa
	ret

;input:
;	edx: cluster no.
;	edi: address of buffer for storing result data
;output:
;	edi: updated address of buffer
parse_a0_cluster:
	pusha
	mov esi, sector_dbr
	movzx ecx, byte [ds:esi+0x0D]
	imul edx, ecx
	add edx, [ds:partition_base]
	mov ebx, [ds:mem_addr]
	add ebx, 2048
	
	mov edi, API_PARAM
	mov dword [fs:edi], API_READ_DISK_SECTOR
	mov dword [fs:edi+4], 2
 .read:
	mov dword [fs:edi+8], edx
	mov dword [fs:edi+12], ebx
	call pword [fs:OS_API]
	
;	pusha
;	mov esi, [esp+16]
;	mov eax, 512
;	movzx ebx, word [fs:cursor_y]
;	movzx ecx, word [fs:cursor_x]
;	call print_bytes_hex	
;	add word [fs:cursor_y], 20
;	popa

	inc edx
	add ebx, 512
	loop .read
	
	mov esi, [ds:mem_addr]
	add esi, 2048
	mov edi, [esp+0]
	
	add esi, dword [fs:esi+0x18]
	add esi, 0x18
 .check_filename:
	cmp word [fs:esi+0x0C], 0x02
	je .end
	
	call copy_file_name_unicode
	movzx edx, byte [fs:esi+0x50]
	add edi, edx
	inc edi
	
	movzx edx, word [fs:esi+0x08]
	add esi, edx
	jmp .check_filename
	
 .end:
	mov [esp+0], edi
	popa
	ret

上述代码中,先注释掉对90H属性的解析,然后单独调用对A0H属性的解析,执行结果如下图所示:

由上图可见,当前磁盘分区的根目录下解析出了40个文件,其中前11个为元文件,以开头;第12个文件名为点".",表示当前文件,即Root元文件;接下来的文件便是我们通常见到的文件和目录,但是,其中有重复的部分,例如,Program Files和PROGRA~1是同一个目录,System Volume Information和SYSTEM~1是同一个目录,这主要是长文件名和短文件名的差异,即同时支持长文件名和短文件名。所谓短文件名,指的就是DOS命名空间下的8.3格式的文件名,当文件名的长度超过8之后,在DOS命名空间下就只能截断,为了不截断,就需要使用另一种命名空间,即长文件名。

以上虽然解析出了所有的文件,但在实际应用中还需要做一些过滤或进一步处理,比如,隐藏掉$开头的元文件,在长文件名和短文件名同时存在时过滤掉短文件名,过滤掉当前文件(即文件名为点"."的文件)等。另外,在打印时还可以采用分栏、分行等方式,并提供更多的文件信息,譬如,文件类型、文件大小等。

  1. 总结

本文介绍了文件遍历操作中对根目录元文件的A0H属性索引项的解析,下文开始将介绍对子目录的解析。

安装元神操作系统的工具"元神操作系统安装器"可去网站www.gnxxkj.com进行下载。安装账号可去网址http://www.gnxxkj.com/app/wuziqi/register.php 进行注册。

相关推荐
中壹联实验室1 天前
实验室装修:一系列设计方案需考虑哪些因素?
其他
哲伦贼稳妥3 天前
技术人生-电脑突然卡顿怎么办
运维·经验分享·其他·电脑
这是我583 天前
C++版iwanna1
c++·其他·游戏·ic·visual studio·iwanna·坑爹
NQKSF4 天前
安装油封在轴上时应采取的措施
其他·汽车
哲伦贼稳妥5 天前
一天认识一个硬件之机房地板
运维·网络·经验分享·其他
gavin_gxh5 天前
项目管理-信息系统管理
经验分享·其他
哲伦贼稳妥6 天前
一天认识一个硬件之电源
运维·其他·电脑·硬件工程
De-Chang Wang6 天前
基于元神操作系统实现NTFS文件操作(四)
其他
Lijunyan12987 天前
Python、C++、java阶乘算法
python·其他·音视频·twitter·segmentfault