写了两天的程序,总算把RING3下的搞定了。这个太强悍了,可以穿透所有的主动防御和还原软件和影子系统~~危害太大,代码我就不发了~~关于RING0驱动过还原的可以看机器狗的代码~
随着机器狗病毒和MJ的Tophet.a的放出,无论是RING0还是RING3下穿透还原软件的技术突然浮现在了大众的眼前。让你的毒毒和小马能穿透还原软件/卡,的确很吸引人~~
最近学习SCSI总线命令,结合以前的知识做了一下穿透还原软件,学习是个曲线前进过程,有可能文中有些观点不对,主要来自《SCSI程序员指南》和《scsi总线及IDE接口(协议、应用和编程)》~~
IRP是从文件系统->卷驱动 -> 磁盘驱动-> 类驱动-> 端口驱动-> 微端口驱动
ntfs/fat32.sys->partmgr.sys->ftdisk.sys->disk.sys->classpnp.sys->acpi.sys->atapi.sys
其中不少是value-added和filter驱动,主要的是文件系统驱动ntfs/fat32.sys,storage驱动disk.sys和总线驱动atapi.sys,我们知道,文件系统驱动做的工作就是把下层读扇区得到的RAW数据转换成文件,向上提供,磁盘驱动这层就是把上层请求的读扇区的IRP换成带SCSI命令的IRP发给下层的miniport ATAPI.sys,ATAPI.sys调用hal中的write_port_char最后来直接端口I/O了。还原程序是在哪一层呢?是在disk.sys之上插了一个filter,如果直接绕过上层的驱动直接发带SRB的IRP给Disk.sys或是ATAPI.sys,那么就实现了穿透。另外,当然还有别的方法,譬如端口I/O,但是通用性不好,与硬盘具体的接口有关(IDE,SCSI等)
kd>bu atapi!IdePortStartIo
上图可看到,在disk这层的irp处理交给了classpnp了,在disk这层之上有个DeepFrz的驱动,这是我测试用的冰点还原的驱动,可以的确冰点还原的驱动是位于disk之上的,从理论上说我们发SRB给disk是可以绕过还原的。到了atapi这层就是走atapi!IdePortStartIo->nt!IoStartPacket->IdePortStartIo,之后就分有无DMA能力区别对待了。
首先SCSI总线上设备数据传输的过程:
申请—仲裁(建立连接)—消息—命令—数据(如果命令产生数据传输)—消息(通知结束,发送状态码)
SCSI有以下几个阶段:
1总线空闲阶段
2总裁阶段
3选择阶段
4重新选择阶段
5消息输出阶段
6命令阶段(接受CDB)
CDB分为0,1,2,5号组命令,命令组号告诉目标器在CDB中有多少字节,然后会产生以下三种阶段情况(本段参考《SCSI程序员指南》,比较老
了,根据SCSI SPC-3上有16字节的等)
0号组CDB是6字节长
1,2号组CDB是10字节长
5号组CDB是12字节长
CDB的第一个字节是描述该命令的操作码,高三位表示命令所属的命令组0-7,低五位表示命令码
CDB的第二个字节高三位表示一个LUN(逻辑单元号)
其中LUN是什么呢?LUN的全称是Logical Unit Number,也就是逻辑单元号。我们知道SCSI总线上可挂接的设备数量是有限的,一般为6个或者15个,我们可以用Target ID(也有称为SCSI ID的)来描述这些设备,而实际上我们需要用来描述的对象,是远远超过该数字的,于是我们引进了LUN的概念,也就是说LUN ID的作用就是扩充了Target ID。每个Target下都可以有多个LUN Device,我们通常简称LUN Device为LUN,这样就可以说每个设备的描述就有原来的Target x变成Target x LUN y了,那么显而易见的,我们描述设备的能力增强了。
从第三字节开始是命令参数字段,对直接存取设备来说是逻辑块地址,对传输数据的命令是传输长度
最后一个字段是Control字段,如Link标志指出该CDB是否是一系列连接命令的一部分,Flag标志决定一条连接的命令成功执行后目标器返回的状态码(6-7厂商自定,2-5保留,1标志位,0连接位)
强制命令(适用于所有设备的命令,采用大端点数)
00H Test unitReady
简单的报告设备是否已经为执行命令做好准备,6字节组(高三位为0)
03H Request Sense(读取)
12H Inquiry
查询设备的制造商,模型信息等
1DH Send Diagnostic
设备类型特定命令
mode sense(获取目标机操作参数信息)和mode select(改变参数)有6字节和10字节
Read和Write命令有6字节和10字节版本,分属与0号组和2号组
数据阶段
状态阶段
消息输入阶段
SCSI miniport驱动器实现了对SCSI接口适配器的直接控制。miniport驱动器对SCSI适配器进行初始化,并向硬件传输I/O请求,处理中断的产生,执行适配器水平的错误修复和记录。miniport驱动器是一种小型的仿制的SCSI I/O模型,它隐藏了SCSI适配器硬件水平上的细节内容。它提供了带有连续,低层次的借口的高水平的SCSI模型,而根本不用考虑实际的硬件接口。只要实现了规定的SCSI miniport 接口,SCSI miniport驱动器就不必对传统的SCSI适配器进行控制,这就允许外设制造商在他们的硬件上使用不同的总线接口。ATAPI设备拥有一套几乎与SCSI完全一致的命令,但是它们进行的数据传输却是基于IDE总线的。windows中的ATAPI的miniport驱动接受了低层次的SCSI命令,并把它们通过IDE总线发送出去
SCSIPORT驱动器对于系统中的所有SCSI请求提供了唯一的入口点,对系统中各种各样的miniport驱动器进行初始化,把系统特有的SCSI I/O请求转化成标准的SRB,并把这些请求发送给适当的miniport驱动器。由于硬件细节被隐藏在了miniport驱动器中,所以高层次的驱动器可以调用SCSIPORT驱动器来执行所有的SCSI I/O操作,而不必在乎所使用的硬件接口,在windows NT中,有很多种标准的SCSI类驱动器,包括用来处理磁盘驱动器(disk.sys)和CD-ROM驱动器的类驱动器。文件系统的驱动器可以调用磁盘类驱动器来执行高层次,面向块的I/O请求。磁盘类驱动会把文件系统的请求转化成一系列的SCSI I/O请求,然后它们会被传送到SCSIPORT驱动。
sense data(检测数据) :当一个SCSI设备(通常是一个LUN)发现它自己处于异常状态时,它就拒绝执行下面的命令,并返回一个check condition状态。产生至少18个字节长的数据,包括经过编码的关于错误的信息,称为检测数据。
IOCTL_SCSI_PASS_THROUGH
IOCTL_SCSI_PASS_THROUGH_DIRECT
NTFS使用逻辑簇号(Logical Cluster Number,LCN)和虚拟簇号(Virtual Cluster Number,VCN)来对簇进行定位。LCN是对整个卷中所有的簇从头到尾所进行的简单编号。用卷因子乘以LCN,NTFS就能够得到卷上的物理字节偏移量,从而得到物理磁盘地址。VCN则是对属于特定文件的簇从头到尾进行编号,以便于引用文件中的数据。VCN可以映射成LCN,而不必要求在物理上连续。
总之,不用担心下层的具体硬件接口是什么,是IDE还是SCSI,都可以用SCSI命令发给ATAPI,实现文件的读写。其实正常的文件读写也是这样的流程,只不过我们自己绕过上层,自己构造SRB而已~
首先看驱动发SRB
参见http://www.osronline.com/DDKx/storage/k306_0hte.htm
typedef struct _SCSI_REQUEST_BLOCK {
USHORT Length; // 该SRB的长度
UCHAR Function; // 功能码,如果想发SCSI命令,设置为SRB_FUNCTION_EXECUTE_SCSI
UCHAR SrbStatus; // SRB发送后返回的状态,一般SRB发送后不会立即执行,会放入一个队列中,所以通常返回
SRB_STATUS_PENDING
UCHAR ScsiStatus; // 是否成功发送,与上面一样均为返回值
UCHAR PathId; // 指明是总线ID,(PathId,TargetId,Lun)来确定一个设备
UCHAR TargetId; //
UCHAR Lun; //
UCHAR QueueTag; // 队列TAG
UCHAR QueueAction; // 与上述结构有关
UCHAR CdbLength; // SCSI命令块长度
UCHAR SenseInfoBufferLength; // 存储返回经过编码的关于错误的信息的缓冲区的长度
ULONG SrbFlags; // Srb的相关参数,SRB_FLAGS_DATA_IN为读出,SRB_FLAGS_DATA_OUT为写入设备
ULONG DataTransferLength; // 指出传输数据的大小
ULONG TimeOutValue; // 设定超时
PVOID DataBuffer; // 数据缓冲区
PVOID SenseInfoBuffer; // 检测缓冲区
struct _SCSI_REQUEST_BLOCK *NextSrb; // 下一个SRB,一般的命令只要一个SRB
PVOID OriginalRequest; // 指向原始IRP
PVOID SrbExtension; //
union {
ULONG InternalStatus; //
ULONG QueueSortKey; //
};
UCHAR Cdb[16]; // SCSI总线命令了,这里不一定是16字节,正如上面所说,具体几个字节,得看第一个字节。
} SCSI_REQUEST_BLOCK, *PSCSI_REQUEST_BLOCK;
SRB是与操作系统相关的,即是操作系统定义的结构,不在SCSI规范里的,相当于对CDB的一层包裹。而真正执行命令的是CDB
取自SCSI Primary Commands - 3
因为一般硬盘不会太大,10字节的DiskPos是32位的,可以操作4g*512字节的空间,一般已经足够了,如果要大点的话,可以用12字节,16字节的CDB(可去参考SCSI Primary Commands - 3)具体结合机器狗的AtapiReadWriteDisk函数进行学习~
ULONG AtapiReadWriteDisk(PDEVICE_OBJECT dev_object,ULONG MajorFunction, PVOID buffer,ULONG DiskPos, int BlockCount)
{
//dev_object为通过ObReferenceObjectByName得到的disk.sys设备对象,另外说句题外话,根
//据MJ的说法,disk.sys对于上层的SCSI_PASS_THROUGH是直接转发给总线设备,那么构造
//相应的IRP直接发给总线设备也可以~这样貌似更底层
//MajorFunction为自定义的一个参数,根据传输进来IRP功能IRP_MJ_READ和IRP_MJ_WRITE
//来转换。可以查SCSI Primary Commands - 3的B章的operation codes知道10字节的写为2Ah,
//读为28h,机器狗代码中是将2*((UCHAR)MajorFunction+ 17)付给了srb->Cdb[0],我们知道ntd
//dk.h中定义IRP_MJ_READ为0x3,IRP_MJ_WRITE为0x4,其实这只是个简单的换算而已
//buffer为输入输出的缓冲区
//DiskPos为32位指定的磁盘起始逻辑扇区号
//BlockCount为要读(写)的扇区数
......
srb->Length=sizeof(SCSI_REQUEST_BLOCK);
srb->Function=0; //0即是SRB_FUNCTION_EXECUTE_SCSI
srb->DataBuffer=buffer;
srb->DataTransferLength=BlockCount<<9;//因为每扇区是512字节
srb->QueueAction=SRB_FLAGS_DISABLE_AUTOSENSE;
srb->SrbStatus=0;
srb->ScsiStatus=0;//这两个都是输出,随便填
srb->NextSrb=0;
srb->SenseInfoBuffer=sense;
srb->SenseInfoBufferLength=sizeof(SENSE_DATA);
if(MajorFunction==IRP_MJ_READ)
srb->SrbFlags=SRB_FLAGS_DATA_IN;
else
srb->SrbFlags=SRB_FLAGS_DATA_OUT;
if(MajorFunction==IRP_MJ_READ)
srb->SrbFlags|=SRB_FLAGS_ADAPTER_CACHE_ENABLE;
srb->SrbFlags|=SRB_FLAGS_DISABLE_AUTOSENSE;
srb->TimeOutValue=(srb->DataTransferLength>>10)+1;
srb->QueueSortKey=DiskPos;//指定从目标设备的开始位置
srb->CdbLength=10;
srb->Cdb[0]=2*((UCHAR)MajorFunction+ 17);//参见上图10字节CDB的格式
srb->Cdb[1]=srb->Cdb[1] & 0x1F | 0x80;//高三位是保留位,与0x1f清0高三位,高位置1
srb->Cdb[2]=(unsigned char)(DiskPos>>0x18)&0xFF; //大端点数,右移24位,取高8位放入Cdb
srb->Cdb[3]=(unsigned char)(DiskPos>>0x10)&0xFF; //
srb->Cdb[4]=(unsigned char)(DiskPos>>0x08)&0xFF; //
srb->Cdb[5]=(UCHAR)DiskPos; //填写sector位置
srb->Cdb[7]=(UCHAR)BlockCount>>0x08;
srb->Cdb[8]=(UCHAR)BlockCount;
......
}
当然SRB也是属于IRP中一部分,自己发IRP给总线设备,当然还得自己填充IRP包,这个就不具体讨论了
总的思路是:先发IoControlCode=FSCTL_GET_RETRIEVAL_POINTERS的IRP给文件系统驱动,得到某个文件的起始逻辑扇区号和大小,然后得到disk.sys的dev,再发构造好的SRB的IRP给disk.sys就ok了,这样就绕过了还原软件
RING3发SCSI_PASS_THROUGH:
遍历符号链接找到总线设备对应的符号链接,发送SCSI_PASS_THROUGH_DIRECT给总线设备实现文件读写,从而绕过主动防御,其中思路MJ0011说了,不过他给的代码基本没用,好几个暗桩~