zoukankan      html  css  js  c++  java
  • C#解析ntfs下的$usnjrnl($J)文件

    园子里面已经有文章介绍如何在windows下如何借助windows提供的原生API读取USN日志,本随笔介绍的是解析现有的$usnjrnl文件,得到其中的内容。

    经过分析msdn对usn记录的描述(传送门https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-usn_journal_data_v2);

    typedef struct USN_JOURNAL_DATA_V2 {
      DWORDLONG UsnJournalID;
      USN       FirstUsn;
      USN       NextUsn;
      USN       LowestValidUsn;
      USN       MaxUsn;
      DWORDLONG MaximumSize;
      DWORDLONG AllocationDelta;
      WORD      MinSupportedMajorVersion;
      WORD      MaxSupportedMajorVersion;
      DWORD     Flags;
      DWORDLONG RangeTrackChunkSize;
      LONGLONG  RangeTrackFileSizeThreshold;
    }  *PUSN_JOURNAL_DATA_V2;

    结合分析usn文件本身的内容,本人认为该文件所存储的内容即是如上结构体数据的线性排列,结构比较简单,便只贴代码了。

     /// <summary>
        /// Contains the USN Record Length(32bits), USN(64bits), File Reference Number(64bits), 
        /// Parent File Reference Number(64bits), Reason Code(32bits), File Attributes(32bits),
        /// File Name Length(32bits), the File Name Offset(32bits) and the File Name.
        /// </summary>
        public class UsnRecordV2 {
            private const int FR_OFFSET = 8;
            private const int PFR_OFFSET = 16;
            private const int USN_OFFSET = 24;
            private const int DateTime_OFFSET = 32;
            private const int REASON_OFFSET = 40;
            private const int FA_OFFSET = 52;
            private const int FNL_OFFSET = 56;
            private const int FN_OFFSET = 58;
            private const int StructWithoutFileName_Size = 60;
    
            /// <summary>
            /// 记录在流中的位置,非原结构体中成员;
            /// </summary>
            public long RecordPosition { get; private set; }
    
            public UInt32 RecordLength { get; private set; }
            public UInt64 FileReferenceNumber { get; private set; }
            public UInt64 ParentFileReferenceNumber { get; private set; }
            public Int64 Usn { get; private set; }
            public DateTime? DateTime { get;private set; }
            public UInt32 Reason { get; private set; }
            public UInt32 FileAttributes { get; private set; }
            public Int32 FileNameLength { get; private set; }
            public ushort FileNameOffset { get; private set; }
            public string FileName { get; private set; }
    
           private UsnRecordV2() {
    
            }
    
    #if DEBUG
            ~UsnRecordV2() {
    
            }
    #endif
            
            //记录所在位置必为8的倍数;
            const int UnitLength = 8;
            const int ClusterSize = 4096;
            /// <summary>
            /// 从流的当前位置读取一个Usn记录;
            /// </summary>
            /// <param name="stream"></param>
            /// <returns></returns>
            public static UsnRecordV2 ReadFromStream(Stream stream) {
                if (stream == null) {
                    throw new ArgumentNullException(nameof(stream));
                }
    
                if (stream.Position >= stream.Length) {
                    return null;
                }
    
                var buffer = new byte[ClusterSize];
    
                stream.Position = stream.Position / UnitLength * UnitLength;
    
                var readLength = 0;
                var nonZeroIndex = -1;
                while ((readLength = stream.Read(buffer, 0, ClusterSize)) != 0) {
                    for (int i = 0; i < readLength; i++) {
                        if (buffer[i] != 0) {
                            nonZeroIndex = i;
                            break;
                        }
                    }
                    if (nonZeroIndex != -1) {
                        break;
                    }
                }
    
                if (nonZeroIndex == -1) {
                    return null;
                }
    
                stream.Position -= (readLength - nonZeroIndex);
                while (stream.ReadByte() == 0) ;
                stream.Position--;
    
                var startPosition = stream.Position;
    
                var leftCount = stream.Length - stream.Position;
                if (leftCount < StructWithoutFileName_Size) {
                    return null;
                }
    
                var usnRecord = new UsnRecordV2();
                var bts = stream.ReadExact(StructWithoutFileName_Size);
                usnRecord.RecordLength = bts.ToUInt32LittleEndian(0);
                usnRecord.FileReferenceNumber = bts.ToUInt64LittleEndian(FR_OFFSET);
                usnRecord.ParentFileReferenceNumber = bts.ToUInt64LittleEndian(PFR_OFFSET);
                usnRecord.Usn = (long)bts.ToUInt64LittleEndian(USN_OFFSET);
                usnRecord.DateTime = System.DateTime.FromFileTime(bts.ToInt64LittleEndian(DateTime_OFFSET));
                usnRecord.Reason = bts.ToUInt32LittleEndian(REASON_OFFSET);
                usnRecord.FileAttributes = bts.ToUInt32LittleEndian(FA_OFFSET);
                usnRecord.FileNameLength = bts.ToUInt16LittleEndian(FNL_OFFSET);
                usnRecord.FileNameOffset = bts.ToUInt16LittleEndian(FN_OFFSET);
                usnRecord.RecordPosition = startPosition;
    
                leftCount = stream.Length - stream.Position;
                if (leftCount < usnRecord.FileNameLength) {
                    return usnRecord;
                }
                var nameBts = stream.ReadExact(usnRecord.FileNameLength);
                usnRecord.FileName = Encoding.Unicode.GetString(nameBts);
    
                stream.Position = startPosition + usnRecord.RecordLength;
                return usnRecord;
            }
    
            
    
            //轮询缓冲区长度;
            const int TraverseBufferLength = ClusterSize * 1024;
            /// <summary>
            /// 从流中读取所有可能的Usn序列,注意,此方法延迟返回;
            /// </summary>
            /// <param name="stream"></param>
            /// <returns></returns>
            public static IEnumerable<UsnRecordV2> ReadRecordsFromStream(Stream stream) {
                if (stream == null) {
                    throw new ArgumentNullException(nameof(stream));
                }
                
                stream.Position = 0;
    
                //To-Do,根据观察,usn存储在文件中的位置以簇大小(大多为4096,由于尚未遇见非4096的情况,所以未做其它考虑)取整
                //即在上一个usn记录存储完后,所在簇的剩余空间
                //不足以存入下下一个记录时,那么,下一条记录将以下一个簇的起始位置为起始被存储,本簇的余下部分将被零填充;
                //利用如上特性,将缓冲区的大小设置为簇大小的倍数,可以将流(文件)切分为多个块进行内存内解析,以提高处理速度;
                UsnRecordV2 record = null;
                var traverseBuffer = new byte[TraverseBufferLength];
                byte[] buffer = null;
                while(stream.Position < stream.Length) {
                    var bufferPosition = stream.Position;
                    var startIndex = 0;
    
                    if(stream.Length - stream.Position > TraverseBufferLength) {
                        stream.Read(traverseBuffer, 0, TraverseBufferLength);
                        buffer = traverseBuffer;
                    }
                    else {
                        buffer = new byte[stream.Length - stream.Position];
                        stream.Read(buffer, 0, buffer.Length);
                    }
                    
                    while((record = ReadFromBuffer(buffer,ref startIndex)) != null) {
                        record.RecordPosition = bufferPosition + startIndex;
                        yield return record;
                    }
                }
            }
    
            /// <summary>
            /// 从缓冲区中某个起始位置读取一个记录;
            /// </summary>
            /// <param name="buffer"></param>
            /// <param name="startIndex">可能的起始位置</param>
            /// <returns></returns>
            private static UsnRecordV2 ReadFromBuffer(byte[] buffer,ref int startIndex) {
                if(buffer == null) {
                    throw new ArgumentNullException(nameof(buffer));
                }
    
                while(startIndex < buffer.Length && buffer[startIndex] == 0) {
                    startIndex++;
                }
    
                //记录余下的长度;
                var leftLength = buffer.Length - startIndex;
                if (leftLength < StructWithoutFileName_Size) {
                    return null;
                }
    
                var usnRecord = new UsnRecordV2();
                usnRecord.RecordLength = buffer.ToUInt32LittleEndian(startIndex);
                usnRecord.FileReferenceNumber = buffer.ToUInt64LittleEndian(FR_OFFSET + startIndex);
                usnRecord.ParentFileReferenceNumber = buffer.ToUInt64LittleEndian(PFR_OFFSET + startIndex);
                usnRecord.Usn = (long)buffer.ToUInt64LittleEndian(USN_OFFSET);
                usnRecord.DateTime = System.DateTime.FromFileTime(buffer.ToInt64LittleEndian(DateTime_OFFSET + startIndex));
                usnRecord.Reason = buffer.ToUInt32LittleEndian(REASON_OFFSET + startIndex);
                usnRecord.FileAttributes = buffer.ToUInt32LittleEndian(FA_OFFSET + startIndex);
                usnRecord.FileNameLength = buffer.ToUInt16LittleEndian(FNL_OFFSET + startIndex);
                usnRecord.FileNameOffset = buffer.ToUInt16LittleEndian(FN_OFFSET + startIndex);
                
                leftLength = buffer.Length - startIndex - StructWithoutFileName_Size;
    
                if(leftLength >= usnRecord.FileNameLength) {
                    //读取名称;
                    try {
                        usnRecord.FileName = Encoding.Unicode.GetString(buffer, startIndex + StructWithoutFileName_Size, usnRecord.FileNameLength);
                    }
                    catch(Exception ex) {
                        LoggerService.WriteException(ex);
                    }
                }
                
    
                startIndex += (int)usnRecord.RecordLength;
                return usnRecord;
            }
            
        }

     在下一篇随笔中,将会介绍如何解析$logfile,由于该文件结构相对usn文件比较复杂,所以需要花费一些时间。

  • 相关阅读:
    二进制文件分析工具-hexdump使用指南
    麦克风阵列技术-beaforming开源算法源码分析
    CMake的作用和价值--概念简介
    matlab load函数用法 实例
    SparkSQL(3.1.1)源码分析
    Spark SQL: Relational Data Processing in Spark (SIGMOD’15)
    论文解析 -- QuickSel: Quick Selectivity Learning with Mixture Models (SIGMOD’20)
    Is Query Optimization a “Solved” Problem? (2014, Guy Lohman)
    Adaptive Statistics in Oracle 12c(PVLDB 2017)
    论文解析 -- A Survey on Advancing the DBMS Query Optimizer: Cardinality Estimation, Cost Model, and Plan Enumeration (Data Science and Engineering 2021)
  • 原文地址:https://www.cnblogs.com/ponus/p/9408429.html
Copyright © 2011-2022 走看看