usnjournal监控
概述三、图解1:判断元数据$usnjournal是否存在2:保存所有需要的信息3:定位到监控的位置
四、代码实现
首页
概述
1:百度百科 2:USN 日志( 更新序列号日志)或更改日志[ 1]是 Windows NT 文件系统 ( NTFS )的一项功能,它维护对卷所做更改的记录。不要将它与用于NTFS 文件系统日志的日志相混淆。
当Windows 2000发布时,Microsoft创建了NTFS版本 3.0,其中包括几个新功能和对旧版本文件系统的改进。其中之一是对某些类型的应用程序非常有用的新系统管理功能。在 Windows 2000 下,可以设置 NTFS 3.0分区来跟踪卷上文件和目录的更改,提供对各种对象执行的时间和操作的记录。启用后,系统会在 USN 日志中记录对卷所做的所有更改,该名称也用于描述功能本身。
为每个 NTFS 卷维护一个日志并存储在名为 $Extend$UsnJrnl的NTFS 元文件中。它以一个空文件开始。每当对卷进行更改时,都会将一条记录添加到文件中。每条记录由64 位更新序列号或 USN 标识(因此更改日志有时称为 USN 日志)。Change Journal 中的每条记录都包含 USN、文件的名称以及有关更改内容的信息。
更改日志使用位标志(例如 USN_REASON_DATA_OVERWRITE [2])描述发生的更改,因此它不包括与更改相关的所有数据或详细信息。因此,更改日志不能用于撤消对 NTFS 内文件的操作。维基百科 3:usnjournal结构 4:属性
三、图解
1:判断元数据$usnjournal是否存在
如果$ usnjournal不存在(一般刚刚初始化或者新建盘符),这个时候创建$usnjournal,否则就没办法监控usnjournal。
//%1 : 盘符
bool isOk = QProcess::startDetached("cmd", QStringList() << "/c" << QString("fsutil usn createjournal m=33554432 a=8388608 %1:").arg(m_partitionName.at(0)));
fsutil usn使用方法
2:保存所有需要的信息
后面还有两个$J,不在一一列出。
3:定位到监控的位置
四、代码实现
//获取usnJournal基本信息
bool CNtfsInfo::_getUsnJournalBasicInfo(S_MFT_USN_JOURNAL& usnJournalBasicInfo,QByteArray byteData)
{
S_FILE_RECORD fileRecordHead;//文件头
bool isOk = NTFS_MFTFileHeader(fileRecordHead,byteData);
if(!isOk)
return false;
quint32 allLength = fileRecordHead.BytesInUse-4;//所有属性的大小
quint32 byteOffset = fileRecordHead.AttributeOffset;
int whileCount = allLength ;
while(byteOffset if((--whileCount) == 0){ //死循环异常 NTFS_setErrorType(CNTFS::WhileError); return false; } if(!(byteData.mid(byteOffset,sizeof (quint32)).toHex()).compare("FFFFFFFF",Qt::CaseInsensitive)) return true; S_USN_JOURNAL_ATTRIBUTE_HEADER attH;//属性头 qint8 NonResidentFlag = NTFS_MFTUsnAtt(attH,byteData.mid(byteOffset,sizeof(S_USN_JOURNAL_ATTRIBUTE_HEADER)));//获取属性 返回0常驻,1非常驻,-1:失败 if(NonResidentFlag == -1) return false; switch(attH.AttributeType) { //0x20 case AttributeAttributeList: { byteOffset += attH.NonResidentFlag==0 ? attH.CCommon.CResident.StreamOffset : attH.CCommon.CNonResident.RunListOffset; if(attH.NonResidentFlag==0 && (attH.AttributeLength - attH.CCommon.CResident.StreamOffset)>0) { QList for(int i=0;i QList //只获取不同的mft 属性x80 if((x20L.at(i).AttributeType == AttributeData ) && x20L.at(i).Fdir.FileReferenceNumber != fileRecordHead.MFTRecordNumber){ if(!_temp.contains(x20L.at(i).Fdir.FileReferenceNumber)) { //以防多次执行,不同的mft只执行一次 _temp.append(x20L.at(i).Fdir.FileReferenceNumber); quint64 offset = getMTFOffset(x20L.at(i).Fdir.FileReferenceNumber); QByteArray data = getRawData(m_NTFSOffset + offset,BYTES_PER_MFT_FILE); if(data.isNull()) return false; bool isOk = _getUsnJournalBasicInfo( usnJournalBasicInfo,data); if(!isOk) return false; } } } byteOffset += (attH.AttributeLength-attH.CCommon.CResident.StreamOffset); } else if(attH.NonResidentFlag==1 && (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset)>0) { //预防 QList DRunList = NTFS_DatasRun(byteData.mid(byteOffset,attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset)); for(int i=0;i QByteArray data = getRawData(m_NTFSOffset + DRunList[i].dataStartCluster * BYTES_PER_CLUSTER,attH.CCommon.CNonResident.StreamRealSize); if(!data.isNull()) { QList for(int i=0;i QList //只获取不同的mft 属性x10和x30和x80 if((x20L.at(i).AttributeType == AttributeData ) && x20L.at(i).Fdir.FileReferenceNumber != fileRecordHead.MFTRecordNumber) { if(!_temp.contains(x20L.at(i).Fdir.FileReferenceNumber)){//以防多次执行,不同的mft只执行一次 _temp.append(x20L.at(i).Fdir.FileReferenceNumber); quint64 offset = getMTFOffset(x20L.at(i).Fdir.FileReferenceNumber); QByteArray data = getRawData(m_NTFSOffset + offset,BYTES_PER_MFT_FILE); if(data.isNull()) return false; bool isOk = _getUsnJournalBasicInfo( usnJournalBasicInfo,data); if(!isOk) return false; } } } } } byteOffset += (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset); } } break; //x30 case AttributeFileName: { byteOffset += attH.NonResidentFlag==0 ? attH.CCommon.CResident.StreamOffset : attH.CCommon.CNonResident.RunListOffset; if(attH.NonResidentFlag==0 && (attH.AttributeLength - attH.CCommon.CResident.StreamOffset)>0) { S_ATTRIBUTE_0X30 x30; QByteArray dataName = NTFS_0X30AttName(x30,byteData.mid(byteOffset,attH.AttributeLength - attH.CCommon.CResident.StreamOffset)); if(!dataName.isNull()) { if(dataName.compare("$UsnJrnl",Qt::CaseInsensitive)==0) usnJournalBasicInfo.ISOK = true; } byteOffset += (attH.AttributeLength-attH.CCommon.CResident.StreamOffset); } else if(attH.NonResidentFlag==1 && (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset)>0){//预防 byteOffset += (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset); } } break; //0x80 case AttributeData: { byteOffset += attH.NonResidentFlag==0 ? attH.CCommon.CResident.StreamOffset : attH.CCommon.CNonResident.RunListOffset; if(attH.NonResidentFlag==0 && (attH.AttributeLength - attH.CCommon.CResident.StreamOffset)>0){ QString name = QString::fromWCharArray(attH.CCommon.CResident.CAttName,attH.AttributeNameLength);//QString ret2 = QString((QChar*)x30.Name, wcslen(x30.Name)); if(name=="$Max") { errno_t err = memcpy_s(&usnJournalBasicInfo.MaxAtt,16,byteData.mid(byteOffset,16).data(),16);//返回值为0,拷贝成功 if(err) { qDebug()< return false; } } byteOffset += (attH.AttributeLength-attH.CCommon.CResident.StreamOffset); } else if(attH.NonResidentFlag==1 && (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset)>0) { /* * 获取属性名,可能存在偏移位置不同 */ char dataTemp[100]; memset(dataTemp,0,sizeof(dataTemp)); errno_t err = memcpy_s(dataTemp,sizeof(dataTemp), &attH,sizeof (S_USN_JOURNAL_ATTRIBUTE_HEADER));//返回值为0,拷贝成功 if(err) { qDebug()< return false; } QString name = QString::fromWCharArray((wchar_t *)&dataTemp[attH.ContentOffset],attH.AttributeNameLength); if(name=="$J"){ usnJournalBasicInfo.JAtt.append(attH); QByteArray temp = byteData.mid(byteOffset,attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset); QList DRunList = NTFS_DatasRun(byteData.mid(byteOffset,attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset)); if(!DRunList.isEmpty()) usnJournalBasicInfo.lDRun.append(DRunList); byteOffset += (attH.AttributeLength - attH.CCommon.CNonResident.RunListOffset); } } } break; case 0xFFFFFFFF: return true; default: byteOffset += (attH.AttributeLength); break; } } return false; } //获取usnJournal MFT信息 S_MFT_USN_JOURNAL CNtfsInfo::getUsnJournalBasicInfo() { S_MFT_USN_JOURNAL usnJournalBasicInfo; usnJournalBasicInfo.ISOK = false; quint64 mftOffset = getMTFOffset($Extend$UsnJrnl); QByteArray mftByte_1 = getRawData(m_NTFSOffset + mftOffset,BYTES_PER_MFT_FILE); if(mftByte_1.isNull()) return usnJournalBasicInfo; S_MFT_INFO mftInfo_1; bool mftInfo_1IsOk= NTFS_MFTFileInfo(mftInfo_1,mftByte_1); if(!mftInfo_1IsOk) return usnJournalBasicInfo; if(mftInfo_1._x90.isEmpty()) return usnJournalBasicInfo; if(mftInfo_1._x90.at(0).first.NonResidentFlag != 0) return usnJournalBasicInfo; QList if (varData.isEmpty()) return usnJournalBasicInfo; for(int i=0;i if(!varData.at(i).second.compare(QString("$UsnJrnl"),Qt::CaseInsensitive)) { quint64 mftOffset = getMTFOffset(varData.at(i).first.dir.MFTDirectoryFile); QByteArray mftByte_1 = getRawData(m_NTFSOffset + mftOffset,BYTES_PER_MFT_FILE); if(mftByte_1.isNull()) return usnJournalBasicInfo; bool isOk = _getUsnJournalBasicInfo(usnJournalBasicInfo,mftByte_1); if(isOk) usnJournalBasicInfo.ISOK = true; else{ qDebug()< usnJournalBasicInfo.ISOK = false; return usnJournalBasicInfo; } for(int i=0;i if(QString::compare(QString::fromWCharArray(usnJournalBasicInfo.JAtt.at(i).CCommon.CNonResident.CNonAttName,usnJournalBasicInfo.JAtt.at(i).AttributeNameLength),"$J")){ usnJournalBasicInfo.JAtt.removeAt(i); }else{ i++; } } if(usnJournalBasicInfo.JAtt.isEmpty()) return usnJournalBasicInfo; } } return usnJournalBasicInfo; } //更新数据库 void CNtfsInfo::updataDataBaseV2(PUSN_RECORD_UNION usnRecordUnion,quint64 usnRecordOffset /*=0*/) { //qDebug()< QString fileName = QString::fromWCharArray(usnRecordUnion->V2.FileName,usnRecordUnion->V2.FileNameLength/2); QScopedPointer if(!dataBody) return; dataBody.data()->MFTNumber = usnRecordUnion->V2.FileReferenceNumber; dataBody.data()->PMFTNumber = usnRecordUnion->V2.ParentFileReferenceNumber; S_DATA_SUBSET aD; aD.guid = m_initDatasInfo.info.uuid; aD.reason = usnRecordUnion->V2.Reason; aD.usnOffset = usnRecordOffset; aD.mft = usnRecordUnion->V2.FileReferenceNumber; /*//0x00000100:捕捉创建文件或文件夹;0x80000200:捕捉删除文件;0x8000200:捕捉重命名文件 或者本盘剪切 * * * 0x01 -> 0x80000001 文件中覆盖数据 (文件字节大小不变) * 0x02 -> 0x80000002 文件中增加数据(从无到有) * 0x03 -> 0x80000003 文件中增加数据 (在原来的存在数据中增加数据) * 0x05 -> 0x80000005 文件中删除部分数据 * 0x04 -> 0x80000004 文件中删除全部数据 * * 0x04 -> 0x8004 -> 0x80008004 粘贴一个空文件替换掉原来的文件 * 0x04 -> 0x6 -> 0x7 -> 0x8007 -> 0x80008007 粘贴一个非空文件替换掉原来的文件 */ if(!(LOG_PATH_PMft == dataBody.data()->PMFTNumber || USN_RECORD_PATH_PMft == dataBody.data()->PMFTNumber || DATA_UPDATE_PATH_PMft == dataBody.data()->PMFTNumber || RUN_LOG_PATH_PMft == dataBody.data()->PMFTNumber)){//log文件夹下的所有文件发生改变不采集 S_USN_LOG array; array.dataTime = DATETIMS; array.partition = m_partitionName.toUtf8(); array.usnOffset = QByteArray::number(usnRecordOffset, 10); array.mft = QByteArray::number((MFTNUM) usnRecordUnion->V2.FileReferenceNumber, 10); array.pMft = QByteArray::number((MFTNUM) usnRecordUnion->V2.ParentFileReferenceNumber, 10); array.usn = QByteArray::number(usnRecordUnion->V2.Usn, 10); array.reasonDataTime = GlobalFunction::getInstance()->FILETIMEToDateTime(usnRecordUnion->V2.TimeStamp.QuadPart); array.reason = QByteArray::number((quint32)usnRecordUnion->V2.Reason,10); array.attributes = QByteArray::number((quint32)usnRecordUnion->V2.FileAttributes,10); array.fileName = fileName.toUtf8(); emit SignalUsnRecordLog(UsnRecordLog,QVariant::fromValue(array));//记录usn日志 } switch (usnRecordUnion->V2.Reason) { case 0x00000100://捕捉创建 复制 case 0x80002000://重命名文件 本盘剪切也是0x00002000 case 0x80000200://捕捉删除文件 case 0x80000001://文件中覆盖数据 (文件字节大小不变) case 0x80000002://文件中增加数据(从无到有) // break; case 0x80000003://文件中增加数据 (在原来的存在数据中增加数据) // break; case 0x80000004://文件中删除全部数据 // break; case 0x80000005://文件中删除部分数据 // break; case 0x80008004://粘贴一个空文件替换掉原来的文件 // break; case 0x80008007://粘贴一个非空文件替换掉原来的文件 { MFT_BASIC_INFO basicInfo; quint64 mftOffset = getMTFOffset(dataBody.data()->MFTNumber); QByteArray byteData = getRawData(m_NTFSOffset + mftOffset,BYTES_PER_MFT_FILE); if(byteData.isNull()) return ; if(!getMFTBasicInfo(basicInfo,byteData)) return; for(int i=0;i basicInfo.fileL.at(i)->bodyAtt = basicInfo.attBody; aD.dataBodyL = basicInfo.fileL; emit SignalDataBase(aD);//更新数据 } return; default: break; } } void CNtfsInfo::updataDataBaseV3(PUSN_RECORD_UNION usnRecordUnion) { qDebug()< } void CNtfsInfo::updataDataBaseV4(PUSN_RECORD_UNION usnRecordUnion) { qDebug()< } //监控usn记录 void CNtfsInfo::usnRecordMonitoring() { qDebug()< while (!QThread::currentThread()->isInterruptionRequested()) { if(m_mftUsnJournal.ISOK) usnStartRun(m_mftUsnJournal); while (!QThread::currentThread()->isInterruptionRequested() && MemoryPool::getInstance()->getSettingArgs().usnRecordNoMonitoring) QThread::sleep(1); QThread::sleep(1); CNtfsInfo::Error type = anewInitDrive(); switch (type){ case NoError: { qDebug()< while (!QThread::currentThread()->isInterruptionRequested()){ QThread::sleep(1); m_mftUsnJournal = getUsnJournalBasicInfo(); if(!m_mftUsnJournal.ISOK) break; //判断后来的StreamAiiocSize和DataSize和类中的比较是否相等,相等表示,数据块未发生变化,继续采集 //不相等表示,数据块发生变化,覆盖类中的usnjournal MFT信息,重新采集usnjournal记录 if(m_mftUsnJournal.JAtt.isEmpty()) continue; //必须大于lastUSNNumber 说明才有数据,不然一直循环获取usn,直到有数据。 等于说明,最后(0x228最大usnjournal日志大小)存在数据,需要重新获取最后 if(m_mftUsnJournal.JAtt[0].CCommon.CNonResident.StreamRealSize >= m_lastUsnNumber) { m_firstAnew = false; break; } } } break; case InitError: { qDebug()< return; } break; case AnewInit: { qDebug()< initData(); } break; } } } //开启usn采集 void CNtfsInfo::usnStartRun(S_MFT_USN_JOURNAL usnJour) { qDebug()< quint32 count_i = 0; quint64 USNoffset=0;//实际偏移地址lcn quint64 dataRunSize=0;//当前使用的数据块大小 quint64 dataRunOffset=0;//当前使用的数据块的偏移地址vcn (不能大于dataRunSize) quint64 tempSize = 0;//记录数据块总共大小 //firstAnew为TRUE是第开启usn if(m_firstAnew) m_lastUsnNumber = usnJour.JAtt[0].CCommon.CNonResident.StreamRealSize; //获取要读取usn记录的位置 for(int i=0;i //累加数据块大小 tempSize += usnJour.lDRun[i].datalengthCluster * BYTES_PER_CLUSTER; //数据块大小和最后一个usn相等,从下一个块读取 if(tempSize == m_lastUsnNumber){ //当相加的数据块大小等于usn号时,此时usn号时下一个块的开头,所以要验证下一个数据块是否存在 if((++i)>=usnJour.lDRun.count()) return ; //要读取的数据块偏移vcn dataRunOffset = 0; //要读取的数据块大小vcn dataRunSize = usnJour.lDRun[i].datalengthCluster * BYTES_PER_CLUSTER; //要读取的物理偏移地址lcn USNoffset = m_NTFSOffset + usnJour.lDRun[i].dataStartCluster * BYTES_PER_CLUSTER; break; } else if(tempSize > m_lastUsnNumber){ //数据块大小大于最后一个usn,从当前数据块读取 //要读取的数据块偏移vcn dataRunOffset = usnJour.lDRun[i].datalengthCluster * BYTES_PER_CLUSTER + m_lastUsnNumber - tempSize; //要读取的数据块大小vcn dataRunSize = usnJour.lDRun[i].datalengthCluster * BYTES_PER_CLUSTER; //要读取的物理偏移地址lcn 取扇区首地址才能正常读取数据 USNoffset = m_NTFSOffset + usnJour.lDRun[i].dataStartCluster * BYTES_PER_CLUSTER + dataRunOffset - (dataRunOffset % PAGE_USN_RECORD_SIZE); break; } } qDebug()< while (dataRunOffset //不监控usn if(MemoryPool::getInstance()->getSettingArgs().usnRecordNoMonitoring) return; if(!m_fileHandle->seek(USNoffset)) return ; QByteArray rawdata = m_fileHandle->read(PAGE_USN_RECORD_SIZE+1);//读取 if(rawdata.size()!=PAGE_USN_RECORD_SIZE+1) return ; quint32 readSize=0; if((PAGE_USN_RECORD_SIZE - dataRunOffset % PAGE_USN_RECORD_SIZE) >= MAX_USN_REOCRD_SIZE)//判断要copy的字节大小 readSize = MAX_USN_REOCRD_SIZE; else readSize = (PAGE_USN_RECORD_SIZE - dataRunOffset % PAGE_USN_RECORD_SIZE); //memcpy(m_usnRecordUnion,rawdata.mid(dataRunOffset % PAGE_USN_RECORD_SIZE,readSize).data(),readSize); errno_t err = memcpy_s(m_usnRecordUnion,MAX_USN_REOCRD_SIZE,rawdata.mid(dataRunOffset % PAGE_USN_RECORD_SIZE,readSize).data(),readSize);//返回值为0,拷贝成功 if(err){ qDebug()< return ; } if(m_usnRecordUnion->V2.Usn && !m_firstAnew && (quint64)m_usnRecordUnion->V2.Usn != m_lastUsnNumber) return ; if(!m_usnRecordUnion->Header.RecordLength){//当记录都为0的时候,表示没有数据, count_i++; if(count_i%50==0){ //连续n次数据为空给cpu腾出时间,进入休眠,给cpu腾出时间片,缓解cpu压力 //qDebug()< CNtfsInfo::Error error = anewInitDrive(); if(error != NoError) return ; QThread::sleep(1); count_i =0; } //QThread::msleep(500);//源码调用的也是Windows下Sleep(); //QThread::msleep(0);//给cpu腾出时间片, } /*//0x00000100:捕捉创建文件或文件夹;0x80000200:捕捉删除文件;0x8000200:捕捉重命名文件 或者本盘剪切 * * * 0x01 -> 0x80000001 文件中覆盖数据 (文件字节大小不变) * 0x02 -> 0x80000002 文件中增加数据(从无到有) * 0x03 -> 0x80000003 文件中增加数据 (在原来的存在数据中增加数据) * 0x05 -> 0x80000005 文件中删除部分数据 * 0x04 -> 0x80000004 文件中删除全部数据 * * 0x04 -> 0x8004 -> 0x80008004 粘贴一个空文件替换掉原来的文件 * 0x04 -> 0x6 -> 0x7 -> 0x8007 -> 0x80008007 粘贴一个非空文件替换掉原来的文件 */ switch (m_usnRecordUnion->Header.MajorVersion) { case 0x2: { if(m_usnRecordUnion->V2.Reason == 0)//等于0读取的数据不是完整的,重新读取 continue; updataDataBaseV2(m_usnRecordUnion,USNoffset + dataRunOffset % PAGE_USN_RECORD_SIZE - m_NTFSOffset); m_lastUsnNumber = m_usnRecordUnion->V2.Usn + m_usnRecordUnion->Header.RecordLength;//记录最后一个usn号 } break; case 0x3: { updataDataBaseV3(m_usnRecordUnion); } break; case 0x4: { updataDataBaseV4(m_usnRecordUnion); } break; default: { //continue; } break; } dataRunOffset += m_usnRecordUnion->Header.RecordLength; quint32 temp = PAGE_USN_RECORD_SIZE - (dataRunOffset % PAGE_USN_RECORD_SIZE); //剩余的数据小于0x40,丢弃//0x40 == sizeof(USN_RECORD_V2) + (8 - sizeof(USN_RECORD_V2)%8) //8字节是一个硬盘一块数据的最小占用 if(temp < 0x40) { USNoffset += PAGE_USN_RECORD_SIZE; dataRunOffset += temp; m_lastUsnNumber += temp;//记录最后一个usn号 if(dataRunOffset == dataRunSize)//数据读到最后了,存在丢弃最后的小于0x228个字节 break; else if(dataRunOffset > dataRunSize)//大于就抛出异常 return ; }else if(!m_usnRecordUnion->Header.RecordLength && temp if(dataRunOffset + temp == dataRunSize){ //数据读到最后了,存在丢弃最后的小于0x228个字节 S_MFT_USN_JOURNAL mftUsnJournal = getUsnJournalBasicInfo();//先获取usn记录管理 1 if(!mftUsnJournal.ISOK){ //usnjournal 错误: 卷更改日志处于非活动状态。 m_lastUsnNumber += temp;//记录最后一个usn号 break; } if(mftUsnJournal.JAtt[0].CCommon.CNonResident.StreamRealSize >= m_lastUsnNumber ){ m_firstAnew = false; m_lastUsnNumber += temp;//记录最后一个usn号 break; } } else if(dataRunOffset + temp > dataRunSize){ //大于就抛出异常 return ; }else if((quint8)rawdata.back()>0){ m_lastUsnNumber += temp;//记录最后一个usn号 dataRunOffset += temp; USNoffset += PAGE_USN_RECORD_SIZE; } }else if(temp == PAGE_USN_RECORD_SIZE && m_usnRecordUnion->Header.RecordLength != 0){ USNoffset += PAGE_USN_RECORD_SIZE; } } return ; } 上面代码用到的结构体,可在这里查找