- 背景
本文继续介绍当前磁盘分区下的文件遍历操作,详细介绍$Root元文件的A0H属性的Data Runs,并解析每个Data Run中的所有索引项,同时提供了基于元神操作系统的实现代码。
- 方法
(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命名空间下就只能截断,为了不截断,就需要使用另一种命名空间,即长文件名。
以上虽然解析出了所有的文件,但在实际应用中还需要做一些过滤或进一步处理,比如,隐藏掉$开头的元文件,在长文件名和短文件名同时存在时过滤掉短文件名,过滤掉当前文件(即文件名为点"."的文件)等。另外,在打印时还可以采用分栏、分行等方式,并提供更多的文件信息,譬如,文件类型、文件大小等。
- 总结
本文介绍了文件遍历操作中对根目录元文件的A0H属性索引项的解析,下文开始将介绍对子目录的解析。
安装元神操作系统的工具"元神操作系统安装器"可去网站www.gnxxkj.com进行下载。安装账号可去网址http://www.gnxxkj.com/app/wuziqi/register.php 进行注册。