快速将二进制文件中的短字符串以十六进制形式复制到文本文件中,循环+部分MD5计算



我正在尝试创建一个PowerShell脚本,该脚本将为文件夹中的每个二进制文件:1)读取靠近开头的固定偏移量的短字符串(例如偏移量1024处的16字节);2) 将该字符串作为十六进制值复制到文本文件中(最好采用"\xFC\x70\x28\x4C\x00"的形式,或者至少采用可以轻松编辑的形式),并使用换行符。目标是为WinHex创建一个搜索词列表,从R-Studio进行的数据恢复中查找两组文件之间的匹配项。

我在SuperUser做了同样的请求(添加了一些上下文),但无法得到我需要的东西。根据某人的建议,我尝试了这个:

foreach ($file in gci *.mts, *.vob, *.mpg) {
$16Bytes = [System.Text.Encoding]::Default.GetString([System.IO.File]::ReadAllBytes("$file"), 1024, 16)
Add-Content -path "G:search terms.txt" -value $16Bytes
}

它是有效的,但那是在我意识到ASCII字符的搜索项列表不可靠之前(空字节问题),所以我需要将输出转换为十六进制值。有人链接了这篇文章,但我在尝试将[System.BitConverter]::ToString命令添加到上面的脚本时出错,无法进一步了解。在这一页上,我找到了一种将字节数组转换为十六进制字符串的方法,格式如我所需:[bitconverter]::ToString($16Bytes).Replace("-", "x");——但我再次未能将其与其他命令结合起来。

另一个问题是,上面的脚本似乎在提取请求的字符串之前读取了每个文件的全部内容,这使得它的效率非常低,因为大约有1TB的文件要处理。我需要一种方法来只解析每个文件的请求部分,这应该非常快速。基于这个线程,Get-Content也会有同样的缺点,但没有提供更好的方法,所以我再次陷入困境。在这里我找到了更具体的信息,但它太模糊了,没有任何实际用途。

不过,对于超大的文件,在处理整个文件时,将其加载到内存中的速度可能慢得令人无法接受。如果您开始违反此限制运行,解决方案是使用.NET Framework中的文件管理类。这些类包括BinaryReader、StreamReader和其他类。

我目前对PowerShell的了解非常有限,我对每个命令的每一部分的理解都非常模糊,这使得以一种有意义的方式在这里和那里组装这些代码片段变得非常困难,而且大多数时候,当我试图运行命令时,我会收到大量红线、错误警告,由于它们是法语的,我甚至不能用它们作为搜索词来寻找可能的解决方案,因为大多数关于PowerShell的资源都是英语的。这一切都非常令人沮丧,尤其是知道这让我离我想完成的任务越来越远,而这可能根本不值得付出这样的努力。。。

然后,如果我成功地完成了第一部分,并且使用WinHex的搜索按预期工作,那么额外的一步就是执行自动校验和比较。WinHex可以在整个卷内进行"逻辑搜索",这意味着对于每个搜索命中,它可以报告绝对偏移量(相对于分区的起点)和文件偏移量(在通过分区的文件系统识别的文件中找到搜索字符串,即使该文件是碎片或NTFS压缩的)。因此,一旦我有了搜索命中的列表,以及文件的路径/名称和找到字符串的偏移量,我想做的是:1)计算文件"A"(搜索词复制的文件)的MD5校验和;2) 计算文件"B"中一个块的MD5校验和(找到搜索项的块),该块据称与文件"a"一致;3) 将结果打印到报告文件中,并指示两个值是否匹配。如果MD5校验和匹配,则意味着文件"A"完全准确地包含在文件"B"中,因此可以删除;如果不是,要么是误报(搜索词不够具体,要么原始文件是碎片,因此恢复的文件可能包含外来数据),在这种情况下,必须手动检查。[EDIT]要做到这一点,我必须在循环中为每对文件定义文件B中的一个块,从{offset where the hit was found in file B} - {offset where the search string was copied from file A}开始,到{starting offset} + {size of file A} - 1结束。然后计算文件B中该块的MD5校验和,整个文件A的MD5,并报告两个值是否匹配。例如:如果WinHex在偏移量1049600的VTS_01_1.VOB中找到了从文件12345.mpg在偏移量1024处获得的搜索项,并且12345.mpg的大小为20971520,那么我需要计算VTS_01_1中从1049600 - 1024 = 1048576开始到1048576 + 20971520 - 1 = 22020095结束的块的MD5;并将结果与整个12345.mpg文件的MD5进行比较。[/EDIT]我已经知道一个名为dsfo的命令行工具,它可以计算文件中某个块的MD5校验和,所以我可以使用它作为一种变通方法,但在PowerShell中完成这一切会更精简。

谢谢。


编辑:

当我尝试添加ToString命令时:

foreach ($file in gci *.wmv) {
$16Bytes = [System.Text.Encoding]::Default.GetString([System.IO.File]::ReadAllBytes("$file"), 1024, 16)
$var = [bitconverter]::ToString($16Bytes).Replace("-", "x"); Add-Content -path "G:search terms.txt" -value $16var
}

我得到每个文件的这个错误:

Impossible de convertir l'argument « 0 » (valeur «                  ») de « ToString » en type « System.Byte[] » : « Impossible de convertir la valeur «                  » en type « System.Byte[] ». Erreur : « Impossible de convertir la valeur «                  » en type « System.Byte ». Erreur :
« Le format de la chaîne d'entrée est incorrect. » » »
Au niveau de ligne : 3 Caractère : 32
+ $var = [bitconverter]::ToString <<<< ($16Bytes).Replace("-", "x"); Add-Content -path "G:search terms.txt" -value $16var
+ CategoryInfo          : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

更新20200628#1:

根据Theo的建议,在安装PowerShell 5.1后,我在一个包含8个MKV视频文件的文件夹上测试了这个脚本:

foreach ($file in gci *.mkv) {
$buffer = [Byte[]]::new(16)
$stream = [System.IO.FileStream]::new($file.FullName, 'Open', 'Read')
$stream.Position = 65536
$readSize = $stream.Read($buffer, 0, 16)
$stream.Dispose()
if ($readSize) {
# create a hex string and write to file
$hex = for ($i = 0; $i -lt $readSize; $i++) { 'x{0:X2}' -f $buffer[$i] } -join ''
Add-Content -Path "G:search terms.txt" -Value $hex
}
}
$buffer = $null

它几乎按预期工作,值是正确的(用WinHex验证),而且非常快(用一瞬间处理了总共2.64GB的所有8个文件),但在生成的文本文件中,每行只有一个字节,所以它有128行,而不是8行。它看起来像这样:

xE9
xF6
x4B
x72
x6C
x6B
x47
xBC
x48
xD5
...

它应该是这样的:

xE9xF6x4Bx72x6Cx6Bx47xBCx48xD5x4Ax2Bx6Ex12x8FxDB
x3Cx20xD7xFDxB4x63x55x57xB3x0Dx95x99x24xF5xCBx85
x72x88x2BxBFx0BxC6xE2x5Fx1FxB2x72x3CxD3xBBx21xA3
xA4x6Fx78x6FxA1x2Cx1CxD6x17x84x3DxDCxB8xBBx20x54
...

那么,我如何调整这个脚本来获得这个输出呢?

此外,如果能对每个命令,特别是$hex = ...行,做一个简短的解释也很好。


更新20200628#2:

我试过这个:

$hex1 = [System.BitConverter]::ToString($buffer).Replace("-", "")
$hex2 = for ($i = 0; $i -lt $readSize; $i++) { 'x{0:X2}' -f $hex1[$i] } -join ''
Add-Content -Path "G:search terms.txt" -Value $hex2

但是现在输出是每行半个字节,并且每行在16字节字符串的一半处停止:

xE => beginning of string 1
x9
xF
x6
x4
xB
x7
x2
x6
xC
x6
xB
x4
x7
xB
xC
x3
xC => stop halfway through string 1
x2 => beginning of string 2
x0
...

如果我这样做:

$hex = [System.BitConverter]::ToString($buffer).Replace("-", "x")
Add-Content -Path "G:search terms.txt" -Value $hex

它适用于PowerShell 5.1,输出几乎是预期的,开头只缺少一个"\x",我可以使用它,并使用文本编辑器将缺少的字符添加到每一行。但最好马上得到预期的产出,在这一点上应该不难。

E9xF6x4Bx72x6Cx6Bx47xBCx48xD5x4Ax2Bx6Ex12x8FxDB
3Cx20xD7xFDxB4x63x55x57xB3x0Dx95x99x24xF5xCBx85
72x88x2BxBFx0BxC6xE2x5Fx1FxB2x72x3CxD3xBBx21xA3
A4x6Fx78x6FxA1x2Cx1CxD6x17x84x3DxDCxB8xBBx20x54
E4x76x1Ax79x7Cx77xA0x83x80xCDx46x80x78x25x64x07
35xD3xBDxDDxD9xA2x5DxF2x70x1Ax07x72xEExB4xE0x24
5Ax25xA5x48x6Ax61x48xF5xA3xFDx14xEDx09x84xF9xB4
47x02xF5x81x81x6Bx54xB0xB1x6FxCDx90x11xABx09xA2

TL;DR

您可以使用[System.IO.FileStream]对象将字节读取到字节缓冲区中,并为同一循环中的这些字节计算MD5。

类似这样的东西:

$offset = 1024
$length = 16
$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('MD5')
$result = Get-ChildItem -Filter '*.mts', '*.vob', '*.mpg' -File | ForEach-Object {
# Old PowerShell versions need this:
# $buffer = New-Object Byte[] $length
$buffer = [Byte[]]::new($length)
# Old PowerShell versions need this:
# $stream = New-Object System.IO.FileStream -ArgumentList $_.FullName, 'Open', 'Read'
$stream = [System.IO.FileStream]::new($_.FullName, 'Open', 'Read')
$stream.Position = $offset
$readSize = $stream.Read($buffer, 0, $length)
$stream.Dispose()
if ($readSize) {
# create a hex string and write to file
$hex = for ($i = 0; $i -lt $readSize; $i++) { 'x{0:X2}' -f $buffer[$i] }
$hex = $hex -join ''
Add-Content -Path "G:search terms.txt" -Value $hex
# calculate the MD5 for this block of bytes
$md5 = $hasher.ComputeHash($buffer, 0, $readSize)
# output an object with more useful stuff to export as CSV later
[PsCustomObject]@{
FileName   = $_.FullName
SearchTerm = $hex
SearchMD5 = [System.BitConverter]::ToString($md5) -replace '-'
# or if you rather have the hash in Base64 format:
# SearchMD5  = [Convert]::ToBase64String($md5)
# you can also add the hash of the file itself here:
# FileMD5 = (Get-FileHash -Path $_.FullName -Algorithm MD5).Hash
}
}
$buffer = $null
}
$hasher.Dispose()
# output on screen
$result | Format-List
# output to CSV file
$result | Export-Csv -Path "G:search terms.csv" -NoTypeInformation

一些解释

该代码使用$readSize = $stream.Read($buffer, 0, $length)读取文件的一部分。

CCD_ 11然后包含实际读取到CCD_ 12阵列中的字节数
可能与预期大小不同。例如:这里的缓冲区是16字节长。从$offset开始,文件中剩下的字节可能没有这么多,所以$readSize可能会更小。

接下来,我们需要特定格式(xNN)的字节,这样就可以将其存储在文本文件中,并在正则表达式搜索表达式中使用。这是用完成的

$hex = for ($i = 0; $i -lt $readSize; $i++) { 'x{0:X2}' -f $buffer[$i] }

这将原始字节转换为其十六进制表示,前面是x$hex现在是一个字符串数组,需要连接在一起才能形成一个字符串,这通过很容易

$hex = $hex -join ''

然后对于输出的格式:
由于哈希值是很长的字符串,在控制台中显示为表(带format Table)很可能不适合控制台的宽度,所以我选择了Format-List

这样,我们创建的PsCustomObject的所有属性都写在单独的一行上
这取决于您希望如何显示。如果您更喜欢Format-Table -AutoSize,请尝试它,但请记住,屏幕上的行会被截断。

我的代码还使用Export-Csv创建了一个没有截断的表样式文件,您只需在Excel中打开即可。

最新更新