比较不同偏移量的二进制文件,报告第一个差异的偏移量

比较不同偏移量的二进制文件,报告第一个差异的偏移量

[大约一个月前,我问了一个类似的问题,要求一个特定的 CLI 软件工具来执行所需的任务;它很快就被屏蔽了,理由是这里禁止索要特定的软件工具,理由是“它们很快就会过时,而且会吸引基于意见的答案”——坦率地说,这是荒谬的,因为一切最终会过时,一切可以吸引基于意见的答案,而且有时基于意见的答案是受欢迎的。但有人有时间发布回复,建议这很可能使用 PowerShell 完成。所以现在,大约一个月后,我问如何使用 PowerShell 来执行当时停滞不前的任务。


我正在寻找一种 PowerShell 方法来比较两个文件的二进制内容,从不同的偏移量开始,在第一个识别的差异处停止并报告相应的偏移量。例如,文件 A 的大小为 500MB,文件 B 的大小为 100MB,可以确定文件 B 的开头在偏移量 104857600 处与文件 A 匹配,但是(基于文件 B 与从 104857600 开始的 100MB 文件 A 块之间的校验和比较)文件 B 并不完全包含在文件 A 中。所以现在我需要编写一个脚本,对从偏移量 104857600 开始的文件 A 和从偏移量 0 开始的文件 B 进行逐字节比较,然后报告第一个不匹配字节的偏移量值。

Windows 原生 CLI 工具comp不允许fc设置比较的起始偏移量(comp甚至不允许比较大小不同的文件)。基于此主题我测试了diffutils,它似乎也不符合这些要求。可以使用十六进制编辑器(如 WinHex)或专用的比较/合并 GUI 实用程序(如 WinMerge)来完成,但这里需要命令行实用程序才能使用脚本一次处理数百个文件。该cmp工具描述在这里似乎完全符合我的需要,但它似乎不能作为独立的 Windows 可执行文件使用。

我对 PowerShell 的经验很少,所以我不知道从哪里开始。


目标:我从 4TB 硬盘驱动器中完全恢复了数据,包括文件系统分析模式和所谓的“原始文件雕刻”模式(通过文件签名搜索,使用 Photorec 和 R-Studio);通过第二种方法恢复的大多数文件实际上是可以通过第一种方法完全恢复的文件的重复项或片段。完全重复项很容易识别,有许多专用工具可用于此目的。我很难识别完全包含在另一个文件中的文件片段;我设法使用 1) PowerShell 脚本从每个未识别的文件中将短字符串提取到纯文本列表中,2) WinHex 根据字符串列表运行“同时搜索”,以及 3) PowerShell 脚本根据 WinHex 的搜索结果计算校验和。现在我留下了可以找到匹配项的文件,但这些文件并不是完全匹配的,这意味着它们包含不同原始文件的部分(很可能是因为源驱动器有碎片)。最后应该只剩下在主恢复目录中没有任何对应部分的文件碎片。


[编辑 20210401]

MD5 比较:如上所述,我对 PowerShell 的使用经验很少,因此,为了进行 MD5 校验和比较,我依赖于第三方 CLI 工具dsfo来自 dsfok),效果很好,可能使脚本不那么复杂(但我有兴趣知道如何仅使用 PowerShell 完成相同的操作,以及它是否更高效或更可靠)。此工具主要用于提取数据,但可用于计算 MD5,语法为:(dsfo [source path] [offset] [size] $“空大小被解释为最大可能的输出”)。我还使用了dsfi同一个工具包,它用于用另一个文件的数据覆盖文件的一部分,语法为:(dsfi [destination path] [offset] [size] [source path]这里我用它来擦除除第一个簇和最后一个簇 + 4KB 之外的完全识别文件的内容;我找不到直接写入空字节的方法,因此我创建了一个空的 2GB“虚拟”文件并将fsutil file createnew其用作源;同样,我很想知道如何仅使用 PowerShell 实现相同的功能)。

例如,我从 WinHex 的“同时搜索”中得到了这个结果:

861627456       *¢M$4M9ˆ  ÿ_ Ì{)    R:\HGST 4To\Root\...\...\...\...\...\...\...\VIDEO_TS\VTS_01_3.VOB  1073739776  1       22  22
40000       *¢M$4M9ˆ  ÿ_ Ì{)    R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg 36079616    1       1   1

提取的字符串(使用另一个 PowerShell 脚本) 在文件 VTS_01_3.VOB (大小 1073739776) 的偏移量 861627456 处被发现,偏移量来自文件 f6821785796.mpg (大小 36079616) 的偏移量 40000。 (右侧的其他值无关紧要;它们表示“链接数”、“命中数”、“术语数”。) 然后,我用TED 记事本创建以下脚本:

$offset = 861627456 - 40000
echo "dsfo R:\HGST 4To\Root\...\...\...\...\...\...\...\VIDEO_TS\VTS_01_3.VOB $offset 36079616 $" >> "G:\HGST 4To MPG.txt"
$dsfoA = dsfo "R:\HGST 4To\Root\...\...\...\...\...\...\...\VIDEO_TS\VTS_01_3.VOB" $offset 36079616 $
$MD5A = $dsfoA[-1] -split "= " | select -last 1
echo $MD5A >> "G:\HGST 4To MPG.txt"
echo "dsfo R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg 0 0 $" >> "G:\HGST 4To MPG.txt"
$dsfoB = dsfo "R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg" 0 0 $
$MD5B = $dsfoB[-1] -split "= " | select -last 1
echo $MD5B >> "G:\HGST 4To MPG.txt"
$lengthdsfi = 36079616 - (36079616 % 4096) - 8192
If (($MD5A -eq $MD5B) -and ($lengthdsfi -gt 16384)) {
    echo "dsfi R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg 4096 $lengthdsfi G:\Dummy" >> "G:\HGST 4To MPG.txt"
    dsfi "R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg" 4096 $lengthdsfi G:\Dummy >> "G:\HGST 4To MPG.txt"
    compact /C "R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg" >> "G:\HGST 4To MPG.txt"
} ElseIf (($MD5A -eq $MD5B) -and ($lengthdsfi -lt 16384)) {
    echo "length < 16384" >> "G:\HGST 4To MPG.txt"
} ElseIf ($MD5A -ne $MD5B) {
    echo "MD5 #" >> "G:\HGST 4To MPG.txt"
}
echo ------ >> "G:\HGST 4To MPG.txt"

对于 1431 次比较,MD5 匹配,并且可以擦除/压缩/丢弃该片段,但是对于其他 1706 次比较,例如上面的例子,我得到了以下结果:

dsfo R:\HGST 4To\Root\...\...\...\...\...\...\...\VIDEO_TS\VTS_01_3.VOB 861587456 36079616 $
e365b41f311ff2d111d2324510668331
dsfo R:\TestDisk 7.1 WIP\MPG\f6821785796.mpg 0 0 $
ebfa8a48ea3670b3618089699d66a986
MD5 #

因此,现在的想法是,对所有导致 MD5 不匹配的比较进行逐字节比较,以获得文件不再匹配的偏移量。实际上,最好获取相应簇的开头的偏移量。如果我在 WinHex 中比较上述两个文件,我可以看到它们匹配到偏移量 897581061 / 35993605(因此片段 f6821785796.mpg 的 99.7% 包含在较大的文件 VTS_01_3.VOB 中),但我还可以看到偏移量 897581056 / 35993600(对应于 4096 的精确倍数)对应于新 MPG 段的开头(典型的 00 00 01 BA 签名),因此这应该是比较的结果。(否则可以使用 . 计算897581061 - (897581061 % 4096)


另一种可能的方法:查找文件片段和较大文件之间的全部或部分匹配的另一种方法是 1)获取组 B 中每个文件片段的第一个簇的 MD5 校验和,2)然后计算组 A 中每个文件的每个簇的 MD5 校验和,直到找到匹配的 MD5,3)然后从组 B 中的整个文件片段获取 MD5,并从组 A 中的文件获取部分 MD5,从已知相同簇的开头开始,长度等于组 B 中文件的大小 => 如果 MD5 校验和匹配,则文件 B 完全包含在文件 A 中,4)如果不匹配,则执行逐字节比较,或从每个后续簇计算校验和,直到发现不匹配,并报告匹配段的偏移量。
这将规避前一种方法的一个主要缺陷,即从固定偏移量的二进制文件中提取的短字符串数据不一定足够具体(它可能是一个充满“00”或“FF”字节的段,或者是一个更复杂但非常冗余的字符串,将产生数百个不相关的命中),需要从不同的偏移值重新开始,直到所有文件都可以处理。WinHex
本身有一个“逐块散列和匹配”功能,我试过了,但它依赖于一个扇区级散列数据库,必须先构建该数据库,因此非常麻烦(每个 512 字节扇区需要 32 字节的存储空间来存储相应的 MD5 散列,我不得不停止,因为它占用了太多空间),然后结果几乎没有用(它报告数据库中的散列与物理偏移量之间的匹配,而不是相对于特定文件开头的逻辑偏移量,就像可以在“逻辑”模式下执行的“同时搜索”功能一样)。

答案1

这是一个不错的起点这个很好的答案类似的问题。此函数将确定 fileA 是否包含在 fileB 中,并返回 fileA 在 fileB 中的偏移量。

我添加了返回停止匹配时的偏移量的功能。MinimumMatch如果文件的开头非常相似,您可能需要尝试以下设置:

Function Find-BytesUntilMismatch([byte[]]$Bytes, [byte[]]$Search, [int]$Start, [Switch]$All, [int]$MinimumMatch=200) {

    # Starting from offset $start, iterate through each byte
    For ($Index = $Start; $Index -le $Bytes.Length; $Index++) {

        # Check if byte matches, iterate through each following byte until 
        # bytes don't match or all bytes of $search are found:
        For ($i = 0; $i -lt $Search.Length -and $Bytes[$Index + $i] -eq $Search[$i]; $i++) {}

        # Search has exited, so check for complete file or return offset
        If ($i -lt $Search.Length -and $i -gt $MinimumMatch) { 
            Write-Output "file stopped matching at offset:$($index + $i); Total bytes matched:$($i)" 
            break 
        }
        If ($i -ge $Search.Length) { 
            Write-Output "full match completed at offset: $($Index + $i); Total bytes matched:$($i)" 

            # Check for additional matches if $All is set
            If (!$All) { Return } 
        } 
    }
    Write-Output "Search Complete"
}

如果您知道的开头FileA包含在FileB偏移量内10000,则运行以下命令:

# Import byte strings from files:
$FileA = [System.IO.File]::ReadAllBytes("C:\path\to\FileA.dat")
$FileB = [System.IO.File]::ReadAllBytes("C:\path\to\FileB.bin")

# Run function:
Find-BytesUntilMismatch -Search $FileA -Bytes $FileB -Start 91855 -MinimumMatch 100

输出:

file stopped matching at offset:1629233; Total bytes matched: 1537377
Search Complete

笔记:

  • 偏移Start量从 0 开始,因此您可能需要向此命令提供偏移字节 -1。
  • 数组是 32 位的,因此可以处理的最大文件大小可能是 2 GB

相关内容