通过Powershell进行十六进制编辑二进制文件的方法

12
我正在尝试使用PowerShell从命令行执行二进制十六进制编辑。使用这段代码,我在进行十六进制替换时取得了部分成功。但是当123456出现多次时,我的问题就出现了,因为替换只应该发生在特定位置。

注意:这段代码需要使用这里显示的Convert-ByteArrayToHexStringConvert-HexStringToByteArray函数。

$readin = [System.IO.File]::ReadAllBytes("C:\OldFile.exe");
$hx = Convert-ByteArrayToHexString $readin -Width 40 -Delimiter "";
$hx = $hx -replace "123456","FFFFFF";
$hx = "0x" + $hx;
$writeout = Convert-HexStringToByteArray $hx;
Set-Content -Value $writeout -Encoding byte -Path "C:\NewFile.exe";

如何在PowerShell中指定偏移位置,而不使用这个不可靠的-replace命令?

1
这里有很多好的答案,但很少有人能够到达目标。如果有一个函数可以接受以下参数作为输入:(1) 文件名,(2) 要搜索的十六进制字符串,或者 (3) 偏移量,(4) 要替换的十六进制字符串,那就太好了。我猜我们只能等待... - not2qubit
4个回答

19
你已经有一个字节数组,因此你可以简单地修改任何给定偏移量处的字节。
$bytes  = [System.IO.File]::ReadAllBytes("C:\OldFile.exe")
$offset = 23

$bytes[$offset]   = 0xFF
$bytes[$offset+1] = 0xFF
$bytes[$offset+2] = 0xFF

[System.IO.File]::WriteAllBytes("C:\NewFile.exe", $bytes)

3

如何在PowerShell中指定偏移位置以替换这个不可靠的-replace命令。

Ansgar Wiechers的有用答案解决了偏移量的问题,brianary的有用答案展示了更符合PowerShell习惯的变体。

话虽如此,如果您有一个只替换第一个搜索字符串出现的解决方案,您原来的解决方案可能会起作用。


仅首次出现替换字符串:

不幸的是,PowerShell 的 -replace 运算符和 .NET 的 String.Replace() 方法都没有提供将替换限制为 一次(或固定数量)的选项。

然而,这里有一个解决方法

$hx = $hx -replace '(?s)123456(.*)', 'FFFFFF$1'
  • (?s)是一个内联正则选项,使正则元字符.匹配换行符

  • (.*)捕获捕获组1中的所有剩余字符,并在替换字符串中引用$1,这有效地仅删除了第一个出现的字符。(有关-replace和替换操作数语法的更多信息,请参见this answer。)

  • 常规注意事项:

    • 如果您的搜索字符串恰好包含您想要字面上使用的正则表达式元字符,请单独使用\进行转义,或者更普遍地将整个搜索术语传递给[regex]::Escape()

    • 如果您的替换字符串恰好包含您希望被字面接受的$字符,请对其进行$转义,或者更普遍地,应用-replace'\$','$$$$' (sic)。

然而,正如iRon指出的那样,虽然上述方法通常解决了单次替换问题,但它并不是完全健壮的解决方案,因为不能保证搜索字符串在字节边界上匹配; 例如,单字节搜索字符串12将匹配0123中间的12,即使输入字符串中没有字节12,由字节0123组成。
为了解决这种歧义,必须以不同的方式构建输入的“字节字符串”和搜索字符串:只需用空格分隔构成每个字节的数字,如下所示。

使用搜索而不是固定偏移量替换字节序列:

这里是一个全 PowerShell 解决方案(PSv4+),不需要第三方功能:

注意:

  • 就像你的尝试一样,整个文件内容一次性读取,并执行了字符串转换;PSv4+语法

  • 为了使用与构建输入字节串相同的方法构造“搜索和替换字符串”作为从字节数组输入创建的以空格分隔的十六进制表示的“字节串”,例如:

    • (0x12, 0x34, 0x56, 0x1).ForEach('ToString', 'X') -join ' ' -> '12 34 56 1'
      • .ForEach('ToString', 'X') 相当于在每个数组元素上调用 .ToString('X') 并收集结果。
    • 如果希望每个字节都以两个十六进制数字的形式一致表示,即使对于小于0x10的值也是如此(例如,01而不是1),请使用'X2',这会增加内存消耗。
      此外,您还需要在搜索字符串中添加前导零来表示单个数字字节,例如:
      '12 34 56 01'
# Read the entire file content as a [byte[]] array.
# Note: Use PowerShell *Core* syntax. 
# In *Windows PowerShell*, replace `-AsByteStream` with `-Encoding Byte`
# `-Raw` ensures that the file is efficiently read as [byte[]] array at once.
$byteArray = Get-Content C:\OldFile.exe -Raw -AsByteStream

# Convert the byte array to a single-line "byte string", 
# where the whitespace-separated tokens are the hex. encoding of a single byte.
# If you want to guaranteed that even byte values < 0x10 are represented as
# *pairs* of hex digits, use 'X2' instead.
$byteString = $byteArray.ForEach('ToString', 'X') -join ' '

# Perform the replacement.
# Note that since the string is guaranteed to be single-line, 
# inline option `(?s)` isn't needed.
# Also note how the hex-digit sequences representing bytes are also separated
# by spaces in the search and replacement strings.
$byteString = $byteString -replace '\b12 34 56\b(.*)', 'FF FF FF$1'

# Convert the byte string back to a [byte[]] array, and save it to the
# target file.
# Note how the array is passed as an *argument*, via parameter -Value, 
# rather than via the pipeline, because that is much faster.
# Again, in *Windows PowerShell* use `-Encoding Byte` instead of `-AsByteStream`.
[byte[]] $newByteArray = -split $byteString -replace '^', '0x'
Set-Content "C:\NewFile.exe" -AsByteStream -Value $newByteArray

这里发布的方法因为重复被关闭,但它更快且使用的内存更少。https://stackoverflow.com/questions/57336893/use-powershell-to-find-and-replace-hex-values-in-binary-files - js2010
@js2010:我猜你是在提到你自己的答案:(a) 这个问题仍然是一个重复的问题,(b) 你的答案展示了如何替换文件中出现多次的单个字节值(而原始问题是完全开放式的)。我建议你重新创建你的答案,并修改它以满足这个问题的特定要求,指出处理十进制值可以提供更短、更高效的解决方案。如果你还在 Get-Content 调用中添加 -Raw 并修复笨拙的 -as 'byte[]',你将得到我的赞同。 - mklement0
不是我的答案,而是第一个提出问题的人所给出的答案。 - js2010
@js2010:将一个“答案”编辑到“问题”中是不恰当的。如果您觉得有什么值得注意的地方(对我来说并不明显,因为其中有非常具体基于CSV的代码,并且整个文件仍然被读入内存,加上低效的管道代码和一个关于内存使用的明确警告),请鼓励作者发布一个“答案”,最好是在这里发布。 - mklement0

3
据我所知,如果要进行替换操作,并不需要对字节流进行任何十六进制转换。您可以在一个由空格(单词结束)限定的十进制值列表(默认字符串转换)上进行替换操作,例如:
(关于文件输入/输出已经在@mklement0的答案中解释过了,我这里就不再赘述。)
$bInput = [Byte[]](0x69, 0x52, 0x6f, 0x6e, 0x57, 0x61, 0x73, 0x48, 0x65, 0x72, 0x65)
$bOriginal = [Byte[]](0x57, 0x61, 0x73, 0x48)
$bSubstitute = [Byte[]](0x20, 0x77, 0x61, 0x73, 0x20, 0x68)
$bOutput = [Byte[]]("$bInput" -Replace "\b$bOriginal\b", "$bSubstitute" -Split '\s+')

如果你想使用十六进制字符串(例如用于替换参数),可以按照以下方式将十六进制字符串转换为字节数组:[Byte[]]('123456' -Split '(..)' | ? { $_ } | % {[Convert]::toint16($_, 16)})

请注意,此解决方案支持不同的$bOriginal$bSubstitute长度。在这种情况下,如果你想从特定偏移量开始替换,可以使用Select-Object cmdlet:

$Offset = 3
$bArray = $bInput | Select -Skip $Offset
$bArray = [Byte[]]("$bArray" -Replace "\b$bOriginal\b", "$bSubstitute" -Split '\s+')
$bOutput = ($bInput | Select -First $Offset) + $bArray

1
+1 是因为巧妙的隐式(十进制)字符串化;这会导致更大的字符串,但这可能并不重要。请注意,使用“-replace”仍然存在意外替换多个出现的风险 - 这是OP最初的问题之一(尽管由于您指出的字节边界问题而不是唯一的问题)。此外,我会使用数组切片而不是“Select-Object”,因为这将在性能上产生明显的差异。 - mklement0

2

在PowerShell中,最常用的方式可能是:

$offset = 0x3C
[byte[]]$bytes = Get-Content C:\OldFile.exe -Encoding Byte -Raw

$bytes[$offset++] = 0xFF
$bytes[$offset++] = 0xFF
$bytes[$offset] = 0xFF

,$bytes |Set-Content C:\NewFile.exe -Encoding Byte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接