http://www.jybase.net/windows/20120106743.html
关于文件的保护的话题,笔记在前几期的黑防中进行了一些个人的分析,根据IRP的下
发流程,从系统的文件驱动ntfs.sys到atapi.sys的dispatch hook和深度的AtapiStartIo
hook,共记三篇。可以说,从 IRP 发送到文件驱动开始,到下发至 atapi 的 dispatch,以
及 atapi 的一些内部处理都大致讲了一遍。
如果读者没有忘记前面几期的内容,那么应该知道 atapi 后是在 HAL.DLL 中进行端口
IO 的处理了,那么本次笔者就带大家来初步的了解 HAL 中所做的一些工作,以及如何自己
实现直接的端口IO,从而绕过 atapi这层驱动。
如果读者还记得第6期《Atapi的深度 HOOK》一文的内容,那么就会知道IdeReadWrite
这个函数起着“承上启下”的作用,这个函数的“下方”就是IO端口的操作了,而“上方”
则是 atapi 层。那么要了解如何实现直接端口 IO,这个函数对我们来说是至关重要的。首
先这个函数的原型是:
NTSTATUS IdeReadWrite(UCHAR devExt_ach, PVOID Srb)
下面给出一些关键的汇编代码:
.text:00011239 push dword ptr [esi+24h] ; 0x1F7,状态
寄存器
.text:0001123C call ds:__imp__READ_PORT_UCHAR@4 ;
.text:00011242 test al, al
.text:00011244 jns short loc_1124D
……
.text:0001124D loc_1124D: ; CODE XREF:
.text:0001124D test al, 40h ; 测试第6位(准备
位)
.text:0001124F jnz short loc_11263
……
.text:00011263
.text:00011263 loc_11263: ; CODE XREF:
IdeReadWrite(x,x)+3Dj
.text:00011263 mov eax, [edi+18h] ; 设备可以接受命令
.text:00011266 mov [esi+84h], eax
.text:0001126C mov eax, [edi+10h]
.text:0001126F mov [esi+88h], eax
.text:00011275 mov byte ptr [esi+0C4h], 1
.text:0001127C mov eax, [edi+10h] ;
Srb.DataTransferLength
.text:0001127F add eax, 1FFh
.text:00011284 shr eax, 9 ; 除以 512,扇区大
小
.text:00011287 push eax ; Value
.text:00011288 push dword ptr [esi+10h] ; Port = 0x1F2
.text:0001128B call ebx ; WRITE_PORT_UCHAR(x,x) ; 写
扇区计数寄存器
……
.text:000112A7 mov dword ptr [ebp+devExt_ach], eax ;
从 CDB指令中算出起始扇区
.text:000112CA push ecx ; Value = 块地址
24~27
.text:000112CB push dword ptr [esi+20h] ; Port = 0x1F6
.text:000112CE call ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112D0 push dword ptr [ebp+devExt_ach] ; Value
= 000de1cf
.text:000112D3 push dword ptr [esi+14h] ; Prot = 0x1F3
.text:000112D3 ; 块地址0~7位
.text:000112D6 call ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112D8 mov eax, dword ptr [ebp+devExt_ach]
.text:000112DB shr eax, 8
.text:000112DE push eax ; Value
.text:000112DF push dword ptr [esi+18h] ; Port = 0x1F4
.text:000112DF ; 块地址8~15位
.text:000112E2 call ebx ; WRITE_PORT_UCHAR(x,x) ;
WRITE_PORT_UCHAR(x,x)
.text:000112E4 mov eax, dword ptr [ebp+devExt_ach]
.text:000112E7 shr eax, 10h
.text:000112EA push eax
.text:000112EB push dword ptr [esi+1Ch] ; Port = 0x1F5
.text:000112EB ; 块地址16~23位
.text:000112EE jmp short loc_1135B
.text:0001135B loc_1135B: ; CODE XREF:
.text:0001135B call ebx ; WRITE_PORT_UCHAR(x,x) ;
上面的汇编代码就算是结合注释想理解也很困难。因为它涉及到了IDE接口的一
些技术。在IDE读写时,先要对一些IDE的寄存器进行设置,最后才是调用HAL 中的
WRITE_PORT_xxxx 或者 READ_PORT_xxxx 系列的函数读写数据。下面给出这些 IDE 寄存器的
相关介绍:
1.Task File Registers命令寄存器组
I/O 地址 读(主机从硬盘读数据) 写(主机数据写入硬盘)
1F0H 数据寄存器 数据寄存器
1F1H 错误寄存器(只读寄存器) 特征寄存器
1F2H 扇区计数寄存器 扇区计数寄存器
1F3H 扇区号寄存器或LBA块地址0~7 扇区号或LBA 块地址0~7
1F4H 磁道数低 8位或 LBA块地址 8~15 磁道数低 8位或 LBA块地址8~15
1F5H 磁道数高 8位或 LBA块地址 16~23 磁道数高8位或LBA块地址 16~23
1F6H 驱动器/磁头或LBA块地址 24~27 驱动器/磁头或LBA块地址24~27
1F7H 状态寄存器 命令寄存器
特殊 Task File Registers的位含义
0x1F6: 7~5位,010,第4位 0 表示主盘,1 表示从盘,3~0位,0
状态寄存器(0x1F7):
第八位:忙。1表示设备正忙
第七位:准备。1 表示设备可以接受命令
第六位:错误。1 表示写错误发生
第五位:寻址。1表示完成寻道操作
第四位:请求。1 表示请求主机数据传输
第三位:校验。1 表示已校正磁盘
第二位:索引。这个不重要
第一位:错误。1 表示前次命令时发生错误
命令寄存器(0x1F7)
0x20为读, 0x30 为写
2.Control/Diagnostic Registers控制/诊断寄存器
I/O 地址 读 写
3F6H 交换状态寄存器(只读寄存器) 设备控制寄存器(复位)
3F7H 驱动器地址寄存器
特殊控制/诊断寄存器的位含义
0x3F6 = 0x80 (0000 1RE0): R=reset, E=0 =enable interrupt
有了这些资料,再加上笔者的注释,不难知道上面的流程:先对设备进行查询是否可以
接受指令,可以则对设备的命令组寄存器进行设置,其中有对SRB 结构中的CDB 结构进行一
些计算,得出LBA 等数据,看过上期文章的读者对这几个结构应该不会陌生。
于是可以仿照这个过程写出我们的初始代码,如下:
WRITE_PORT_UCHAR(0x3F6, 0x0c);
WRITE_PORT_UCHAR(0x3F6, 0x08);
WRITE_PORT_UCHAR(0X1F2, 1);
WRITE_PORT_UCHAR(0x1F3, 0);
WRITE_PORT_UCHAR(0X1F4, 0);
WRITE_PORT_UCHAR(0X1F5, 0);
WRITE_PORT_UCHAR(0x1F6, 0xE0);
关于其中的 1、2 行代码作用分别是重置设备和启用中断。剩下的代码作用是从磁盘的
0 扇区开始读出一个扇区的内容,即MBR。
接下来应该看 IdeReadWrite 剩下的代码了。但是笔者在浏览了 IDA 反汇编出来的代码
后,却在发现 IdeReadWrite 的磁盘读操作分支中只进行一些简单的处理后调用了
WRITER_PORT_UCHAR,之后再调用了 PCIIDEX!BmArm 来启动 DMA 通道,并进行数据的传输,
当然这不是笔者想要的,我们要做的是直接 IO,于是我们只能依靠资料来进行推测并尝试
着去写代码。或许笔者比较幸运,在几次尝试后就成功了。代码如下:
WRITE_PORT_UCHAR(0x1F7, 0x20);
while((READ_PORT_UCHAR((PUCHAR)0x1f7)&0xf) != 0x08)
KeStallExecutionProcessor(0X96);
READ_PORT_BUFFER_USHORT(0x1F0, (PUSHORT)buffer, 512/2);
上面的代码笔者解释下。在向 0x1F7 端口写入 0x20 命令(也就是读命令)后,用
READ_PORT_UCHAR命令从0x1F7端口中读出设备的状态,并且比较第四位,从而判断是否已
经完成数据的传输。这里注意,当0x1F7 用做读时,为状态寄存器,当0x1F7用做写时,当
做命令寄存器,在上面给出的 IDE 介绍中也可以看出这一点。如果数据传输完成将会被读出
到 0x1F0 端口处,用READ_PORT_BUFFER_USHORT读取到自己分配的内存区即可。
其中调用 READ_PORT_BUFFER_USHORT时,第一次笔者用了READ_PORT_UCHAR 读出来的数
据是跳着读的,即读出前一位数据后,跳过中间一位数据,再读后一位。笔者百思不得其解,
希望有人能解答。
最后运行的结果,与WinHex读出的 MBR相比较发现是一致的,如图1。
至此笔者从最初的只是想看看ntfs流程,到 atapi,最后再到端口IO。共计四篇文章,
简单讨论了三个层面对文件读写的响应,不敢说对读者有很大帮助,但只要读者能从这四
篇文章得到一些益处,笔者就满足了。
本文所有代码在XPSP3+WDK中编译通过,并在XPSP3上成功运行。本文只实现了对硬盘
的读操作,并未实现写操作。示例代码也非常的简单,可以说只是提供了思路,并未经过
严格的测试。希望本文起到抛砖引玉的作用,期待更精彩的文章出现。行文仓促,加之硬
件设备复杂,资料少,所以纰漏难免,欢迎批评指正。