基于元神操作系统实现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 进行注册。

相关推荐
qq_403742551 小时前
Ubuntu 24.04 安装 LaTeX + VSCode 环境指南
vscode·其他
数据安全科普王10 小时前
从 HTTP/1.1 到 HTTP/3:协议演进如何改变 Web 性能?
网络·其他
方安乐1 天前
命理学研究
其他
闪闪发亮的小星星3 天前
欧拉角的定义
其他
探序基因4 天前
R语言读取h5格式的文件
其他
t057774 天前
立足通用航空生态 德意志飞机与COMTRONIC开启D328eco合作新篇章
其他
方见华Richard4 天前
递归对抗引擎RAE V4.0(AGI自主进化版)
经验分享·笔记·其他·交互·学习方法
老陈头聊SEO4 天前
长尾关键词在SEO优化中的应用及其对流量提升的影响分析
其他·搜索引擎·seo优化
晚霞的不甘6 天前
Flutter for OpenHarmony:迈向专业:购物APP的架构演进与未来蓝图
其他·flutter·架构·fiddler·前端框架·harmonyos
t057776 天前
精工美学新标杆!VELO ANGEL RIDE 坐垫尽显骑行美学
其他