最近有个朋友想读写U盘扇区的内容,让我帮忙,本来以为挺简单的一件事情,用SetFilePointer API 函数来定位,在用ReadFile WriteFile 直接进行操作就可以了,在网上google了一下代码一大堆,拿过来直接用,的确好用,但是是在XP 环境下,到VISTA,windows 7环境下,即使用管理员权限来进行操作也不好用了,经过查资料才知道,自从VISTA以后对硬盘的读写都进行了保护,在也不能像XP 下那样直接读写了,被逼无奈,自己做了个小小的驱动,经过多次蓝屏,最后算是调稳定了,共享出来,如果能帮的到大家,也算没有白辛苦,同时写出来,也算对自己的一个总结。
本程序的基本思路是:
1.通过磁盘名字获得磁盘驱动的指针。
2.根据磁盘驱动的指针枚举此驱动的所有设备指针。
3.根据枚举出来的各个参数来进行比较是否是自己所要找的设备。
4.找到自己进行操作的设备,保存设备指针,以便后面进行操作。
5.因为此处操作的是U盘,U盘的ID 在进行拔插的时候ID 会变化。还有一个系统中存在多个U盘的可能性,所以在此处用了通过盘符获得U盘的硬盘的ID 逻辑号,把这个号传道驱动中进行区分是否是我们所要进行操作的设备。
要是高手的话,有了基本思路,难度就不大了,可以直接飘过
下面简单的写下过程:
1.找个简单的NT 式驱动来改,先设置驱动的派遣函数,创建驱动设备。(这里我是根据张帆的书上的helloddk来改的 驱动的基本知识)
2.通过内核函数 ObReferenceObjectByName 根据驱动的名字获得设备的对象指针,然后根据设备指针来得到来进行遍历来找到我们需要操作的设备,这里我们操作的是U盘 应从设备 的 DeviceType== FILE_DEVICE_DISK && (Flags &DO_DEVICE_HAS_NAME)&&(Characteristics&FILE_REMOVABLE_MEDIA 几方面来进行考虑 一是 磁盘设备类型 标志位是有名字的 是可移除的设备类型,找到我们想要进行操作的设备。代码如下:
#pragma PAGEDCODE
NTSTATUS GetUdiskDeviceObj( IN DWORD DiskID)
{
NTSTATUS status = STATUS_SUCCESS; //返回状态
UNICODE_STRING DestinationString; //硬盘驱动名称
PDRIVER_OBJECT pDiskObject; //驱动对象(这里是找到设备对象的接口)
PDEVICE_OBJECT pDeviceObjectTemp; //磁盘设备的临时对象
// DWORD dwDeviceNumber; //磁盘下挂载设备的数目
PDISK_GEOMETRY pDiskGeometry; //用于装载磁盘信息的数据结构的一个对象(用来获得磁盘的扇区字节数)
// PSTORAGE_DEVICE_DESCRIPTOR pUdiskDescriptor; //磁盘设备的描述符根据这些来得到PID VID 来判断是不是我们要操作的设备
DWORD dwRetLength; //设备名的长度
POBJECT_NAME_INFORMATION pNameBuffer; //存放设备名字的缓冲区
WCHAR *pNameTemp; //设备名单个字符的指针
BOOLEAN bIsFound = FALSE; //能否找到磁盘信息数据结构的标示
// DWORD SectorSize; //每个扇区有多少个字节
// DWORD i;
/* All Disk Objects are created by disk.sys driver*/
RtlInitUnicodeString(&DestinationString, L"[URL="file://driver//Disk"]\\Driver\\Disk[/URL]"); //字符串初始化
DbgPrint("Enter GetUdiskDeviceObj\n");
// Not a documented function in DDK, see import definition in Driver.h
if (ObReferenceObjectByName(
&DestinationString,
OBJ_CASE_INSENSITIVE,//64,
0,
0,
*IoDriverObjectType,
KernelMode,
0,
(PVOID*)&pDiskObject) >= 0)
{
DbgPrint("Find drver object\n");
pDeviceObjectTemp = pDiskObject->DeviceObject;
//dwDeviceNumber = 0;
if (pDeviceObjectTemp) //如果不为空 则证明找到设备
{
//申请一块内存用来存放找到的磁盘信息
pDiskGeometry =(PDISK_GEOMETRY)ExAllocatePool(NonPagedPool, sizeof(DISK_GEOMETRY));
DbgPrint("Find pdisk\n");
if (!pDiskGeometry)
{
return STATUS_INSUFFICIENT_RESOURCES; //如果不能申请成功 则系统内存资源不足,返回状态标志
}
do
{
memset(pDiskGeometry, 0x00, sizeof(DISK_GEOMETRY)); //格式化申请的内存资源
// memset(pUdiskDescriptor, 0x00, sizeof(STORAGE_DEVICE_DESCRIPTOR)); //格式化申请的内存资源
//开始查找我们需要的设备
//我们查找的设备为U盘 也就是 硬盘设备类型 设备的FLAG 为有名字 设备的属性支持可以移除的
if (pDeviceObjectTemp->DeviceType== FILE_DEVICE_DISK && (pDeviceObjectTemp->Flags &DO_DEVICE_HAS_NAME)&&(pDeviceObjectTemp->Characteristics&FILE_REMOVABLE_MEDIA))
{
DbgPrint("Find my pdisk\n");
//假如在win7系统下我们已经找到了我们想要的设备了,VISTA下未知没做过测试 但是XP下 分为
//\\Device\\Harddisk0\\DP(0)和[URL="file://device//Harddisk0//DR0"]\\Device\\Harddisk0\\DR0[/URL] 我们需要找的是后者
//DO_LOW_PRIORITY_FILESYSTEM 置 1 为[URL="file://device//Harddisk0//DP(0"]\\Device\\Harddisk0\\DP(0[/URL])*** 置0 为 [URL="file://device//Harddisk0//DR0"]\\Device\\Harddisk0\\DR0[/URL]
//此处寻找的思路是通过 找到的设备对象转化成名字来判断是不是我们所要找的设备
//先得到设备名的长度
ObQueryNameString(
pDeviceObjectTemp,
NULL,
0,
&dwRetLength);
//申请一块内存用来存放设备名
pNameBuffer = (POBJECT_NAME_INFORMATION)ExAllocatePoolWithTag(PagedPool, dwRetLength, ' sFI');
if (!pNameBuffer)
{
ExFreePool(pDiskGeometry);
return STATUS_INSUFFICIENT_RESOURCES;
}
//这次真正得到设备名了
if (ObQueryNameString(
pDeviceObjectTemp,
pNameBuffer,
dwRetLength,
&dwRetLength) == STATUS_SUCCESS && pNameBuffer->Name.Buffer)
{ //把设备名打印出来 看找到的到底是什么设备
DbgPrint("pNameBuffer->Name.Buffer:%S\n",pNameBuffer->Name.Buffer);
//这里寻找的设备名有点技巧
for (pNameTemp = pNameBuffer->Name.Buffer + wcslen(pNameBuffer->Name.Buffer); pNameTemp > pNameBuffer->Name.Buffer; pNameTemp--)
{
//字符串格式化
int const arraysize = 30;
WCHAR pszDest[arraysize];
size_t cbDest = arraysize * sizeof(WCHAR);
LPCWSTR pszFormat = L"%s%d%s";
WCHAR* pszTxt = L"[URL="file://harddisk/"]\\Harddisk[/URL]";
status = RtlStringCbPrintfW(pszDest, cbDest, pszFormat, pszTxt,DiskID,L"[URL="file://dr/"]\\DR[/URL]");
//DbgPrint("pNameTemp:%S pszDest:%S \n",pNameTemp,pszDest);
//if (!_wcsnicmp(pNameTemp, L"[URL="file://harddisk1//DR"]\\Harddisk1\\DR[/URL]", 13))
if (!_wcsnicmp(pNameTemp, pszDest, 13))
{
//如果找到我们需要的的设备名字,做个标记
bIsFound = TRUE;
DbgPrint("Enter here \n");
break;
}
}
if(bIsFound == TRUE)
{
status = GetGeometry(pDeviceObjectTemp, pDiskGeometry);
if (!NT_SUCCESS(status))
{
//pDisk->bGeometryFound = FALSE;
DbgPrint("GetGeometry Error\n");
}
//这里得到每个扇区有多少个字节,大部分是512 但是有个别的也说不定
//这里我们打印出来看下是多少
DbgPrint("SectorSize:%X\n",pDiskGeometry->BytesPerSector);
G_pDevice=pDeviceObjectTemp;
DbgPrint("pDeviceObjectTemp:%lX\n",G_pDevice);
}
}
ExFreePoolWithTag(pNameBuffer, 0);
}
//DDK 3790.1830 版本有问题,NTDDK.H里面竟然没有ExFreePoolWithTag这个函数,只能在.h文件里面声明个了
pDeviceObjectTemp=pDeviceObjectTemp->NextDevice;
}while(pDeviceObjectTemp);
ExFreePool(pDiskGeometry); //释放申请的内存资源
//ExFreePool(pUdiskDescriptor); //释放申请的内存资源
}
}
return status;
}
在这里找到我们进行操作的设备指针之后,还要得到磁盘的一些信息,比如一个扇区多少个字节,现在大多数是512的,不过我看定义里面有不同的,不知道用在什么地方。
所以我这里得到磁盘的扇区数,函数如下:
#pragma PAGEDCODE
NTSTATUS GetGeometry(PDEVICE_OBJECT pDiskDevObj, PDISK_GEOMETRY pDiskGeo)
/*++
Routine Description:
Returns the Geometry of Disk
Arguments:
Target Device Object representing the disk and Pointer to geometry structure
Return Value:
STATUS
--*/
{
IO_STATUS_BLOCK IoStatusBlock;
KEVENT Event;
PIRP pIrp;
NTSTATUS status;
KeInitializeEvent(&Event, NotificationEvent, FALSE);
pIrp = IoBuildDeviceIoControlRequest(
IOCTL_DISK_GET_DRIVE_GEOMETRY,
pDiskDevObj,
NULL,
0,
pDiskGeo,
sizeof(DISK_GEOMETRY),
FALSE,
&Event,
&IoStatusBlock);
if (!pIrp)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(pDiskDevObj, pIrp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
status = IoStatusBlock.Status;
}
return status;
}
这样U盘的一些信息得到了,下面就是创建读写U盘的IRP,然后发送给磁盘的驱动,就可以进行磁盘的读写了
#pragma PAGEDCODE
NTSTATUS ReadAndWriteSectors(PDEVICE_OBJECT pDevObj,LARGE_INTEGER lDiskOffset,PVOID pbuf,BOOLEAN iSreadorWrite)
{
NTSTATUS status=STATUS_SUCCESS;
// CHAR *sBuf;
KEVENT Event;
SIZE_T size = 512; //每次读写的大小
PIRP pIrp = NULL; //创建读写的IRP
IO_STATUS_BLOCK ioStatus;
// DWORD i;
KeInitializeEvent(&Event, NotificationEvent, FALSE); //创建一个通知事件来进行U盘扇区读写完成
// DbgPrint("ReadAndWriteSectors pDevObj :%lX\n",pDevObj);
// DbgPrint("ReadAndWriteSectors QuaPart :%lX \n",lDiskOffset->QuadPart);
DbgPrint("ReadAndWriteSectors QuaPart:%X\n",lDiskOffset.QuadPart);
lDiskOffset.QuadPart=lDiskOffset.QuadPart*512;
// sBuf =(CHAR *) ExAllocatePool(NonPagedPool, size);
// memset(sBuf, '0', size);
if(iSreadorWrite==TRUE)
{
pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, pDevObj, pbuf, size, &lDiskOffset, &Event, &ioStatus);
DbgPrint("TRUE");
}
else
{
pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE, pDevObj, pbuf, size, &lDiskOffset, &Event, &ioStatus);
DbgPrint("FALSE");
}
if (!pIrp)
{
//ExFreePool(sBuf);
KdPrint(("Creat IRP faile\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(pDevObj, pIrp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
/*
for (i=0;i<size;i++)
{
KdPrint(("%X \n",pbuf[i]&0XFF));
if ((i % 16) == 15)
{
KdPrint(("\n"));
}
}
*/
// ExFreePool(sBuf);
return status;
}
这样基本功能就实现了,一个简单的驱动就做出来了,然后把参数什么的都填好,把IOCTRL 和应用程序的通信假设好,一个完整的工程就OK了,在写上驱动安装,卸载函数,做成DLL 也可以供其他语言的程序来使用了,这些我都完善好了。但是总是感觉这不是最好的办法。如果大家有别的好的办法,请告知,谢谢,本人QQ:253229030 欢迎一起交流!
[课程]Android-CTF解题方法汇总!