-
Notifications
You must be signed in to change notification settings - Fork 0
/
ManipulateBinary.ps1
581 lines (450 loc) · 21.1 KB
/
ManipulateBinary.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
##############################################################################
#.DESCRIPTION
# A variety of functions for manipulating hex and binary files.
#.NOTES
# Date: 29.Nov.2012
# Version: 3.0
# Author: Jason Fossen, Enclave Consulting LLC (http://www.sans.org/sec505)
# Legal: 0BSD
##############################################################################
# Convert the following base data types into an array of
# bytes: Boolean, Char, Double, Int*, UInt*, and Single.
# Char is the Unicode code point number of a glyph (two bytes).
[Byte[]] $bytes = [System.BitConverter]::GetBytes( [Int64] 99999999999999999 )
[Byte[]] $bytes = [System.BitConverter]::GetBytes( [Char] 65 )
# You can read a binary (or text) file into an array of bytes
# using the -encoding parameter of get-content:
[System.Byte[]] $filebytes = get-content HelloWorld.ps1 -encoding byte
# You can of course construct the array and then write it to a file:
$filebytes = @(13,10,34,72,101,108,108,111,32,87,111,114,108,100,33,34,13,10,13,10)
# To create a single Byte object or an array of Bytes:
[Byte] $x = 0x4D
[Byte[]] $y = 0x4D,0x5A,0x90,0x00,0x03
# Once you have an array of bytes, you can write them back to a file:
$filebytes | set-content HelloWorld2.ps1 -encoding byte
# To read the bytes of a file into an array (SLOW with large files):
[byte[]] $x = get-content -encoding byte -path .\file.exe
# To overwrite or create a file with raw bytes, avoiding any hidden string conversion, where $x is a Byte[] array:
set-content -value $x -encoding byte -path .\outfile.exe
# To append to or create a file with raw bytes, avoiding any hidden string conversion, where $x is a Byte[] array:
add-content -value $x -encoding byte -path .\outfile.exe
################################################################
#.Synopsis
# Returns an array of System.Byte[] of the file contents.
#
#.Parameter Path
# Path to the file as a string or as System.IO.FileInfo object.
# FileInfo object can be piped into the function. Path as a
# string can be relative or absolute, but cannot be piped.
################################################################
function Read-FileByte
{
[CmdletBinding()] Param (
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
[Alias("FullName","FilePath")]
$Path )
[System.IO.File]::ReadAllBytes( $(resolve-path $Path) )
}
################################################################
#.Synopsis
# Overwrites or creates a file with an array of raw bytes.
#
#.Parameter ByteArray
# System.Byte[] array of bytes to put into the file. If you
# pipe this array in, you must pipe the [Ref] to the array.
#
#.Parameter Path
# Path to the file as a string or as System.IO.FileInfo object.
# Path as a string can be relative, absolute, or a simple file
# name if the file is in the present working directory.
#
#.Example
# write-filebyte -bytearray $bytes -path outfile.bin
#
#.Example
# [Ref] $bytes | write-filebyte -path c:\temp\outfile.bin
################################################################
function Write-FileByte
{
[CmdletBinding()] Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,
[Parameter(Mandatory = $True)] $Path )
if ($Path -is [System.IO.FileInfo])
{ $Path = $Path.FullName }
elseif ($Path -notlike "*\*") #Simple file name.
{ $Path = "$pwd" + "\" + "$Path" }
elseif ($Path -like ".\*") #pwd of script
{ $Path = $Path -replace "^\.",$pwd.Path }
elseif ($Path -like "..\*") #parent directory of pwd of script
{ $Path = $Path -replace "^\.\.",$(get-item $pwd).Parent.FullName }
else
{ throw "Cannot resolve path!" }
[System.IO.File]::WriteAllBytes($Path, $ByteArray)
}
################################################################
#.Synopsis
# Converts a byte array to a string
#
#.Description
# Returns the string representation of a System.Byte[] array.
# ASCII string is the default, but Unicode, UTF7, UTF8 and
# UTF32 are available too.
#
#.Parameter ByteArray
# System.Byte[] array of bytes to put into the file. If you
# pipe this array in, you must pipe the [Ref] to the array.
# Also accepts a single Byte object instead of Byte[].
#
#.Parameter Encoding
# Encoding of the string: ASCII, Unicode, UTF7, UTF8 or UTF32.
# ASCII is the default. "Unicode" is actually UTF16-LE.
################################################################
function Convert-ByteArrayToString
{
[CmdletBinding()] Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,
[Parameter()] [String] $Encoding = "ASCII" )
switch ( $Encoding.ToUpper() )
{
"ASCII" { $EncodingType = "System.Text.ASCIIEncoding" }
"UNICODE" { $EncodingType = "System.Text.UnicodeEncoding" }
"UTF7" { $EncodingType = "System.Text.UTF7Encoding" }
"UTF8" { $EncodingType = "System.Text.UTF8Encoding" }
"UTF32" { $EncodingType = "System.Text.UTF32Encoding" }
Default { $EncodingType = "System.Text.ASCIIEncoding" }
}
$Encode = new-object $EncodingType
$Encode.GetString($ByteArray)
}
################################################################
#.Synopsis
# Convert a hex string to a byte array.
#
#.Description
# Convert a string of hex data into a System.Byte[] array. An
# array is always returned, even if it contains only one byte.
#
#.Parameter String
# A string containing hex data in any of a variety of formats,
# including strings like the following, with or without extra
# tabs, spaces, quotes or other non-hex characters:
# 0x41,0x42,0x43,0x44
# \x41\x42\x43\x44
# 41-42-43-44
# 41424344
# The string can be piped into the function too.
################################################################
function Convert-HexStringToByteArray
{
[CmdletBinding()]
Param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [String] $String )
#Clean out whitespaces and any other non-hex crud.
$String = $String.ToLower() -replace '[^a-f0-9\\\,x\-\:]',''
#Try to put into canonical colon-delimited format.
$String = $String -replace '0x|\\x|\-|,',':'
#Remove beginning and ending colons, and other detritus.
$String = $String -replace '^:+|:+$|x|\\',''
#Maybe there's nothing left over to convert...
if ($String.Length -eq 0) { ,@() ; return }
#Split string with or without colon delimiters.
if ($String.Length -eq 1)
{ ,@([System.Convert]::ToByte($String,16)) }
elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1))
{ ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) }
elseif ($String.IndexOf(":") -ne -1)
{ ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) }
else
{ ,@() }
#The strange ",@(...)" syntax is needed to force the output into an
#array even if there is only one element in the output (or none).
}
################################################################
#.Synopsis
# Converts a byte array to a hex string.
#
#.Description
# Returns a hex representation of a System.Byte[] array as
# one or more strings. Hex format can be changed.
#
#.Parameter ByteArray
# System.Byte[] array of bytes to put into the file. If you
# pipe this array in, you must pipe the [Ref] to the array.
# Also accepts a single Byte object instead of Byte[].
#
#.Parameter Width
# Number of hex characters per line of output.
#
#.Parameter Delimiter
# How each pair of hex characters (each byte of input) will be
# delimited from the next pair in the output. The default
# looks like "0x41,0xFF,0xB9" but you could specify "\x" if
# you want the output like "\x41\xFF\xB9" instead. You do
# not have to worry about an extra comma, semicolon, colon
# or tab appearing before each line of output. The default
# value is ",0x".
#
#.Parameter Prepend
# An optional string you can prepend to each line of hex
# output, perhaps like '$x += ' to paste into another
# script, hence the single quotes.
#
#.Parameter AppendComma
# Appends a comma to each line of output, except the last.
#
#.Parameter AddQuotes
# A switch which will enclose each line in double-quotes.
#
#.Example
# [Byte[]] $x = 0x41,0x42,0x43,0x44
# Convert-ByteArrayToHexString $x
#
# 0x41,0x42,0x43,0x44
#
#.Example
# [Byte[]] $x = 0x41,0x42,0x43,0x44
# Convert-ByteArrayToHexString $x -width 2 -delimiter "\x" -addquotes
#
# "\x41\x42"
# "\x43\x44"
################################################################
function Convert-ByteArrayToHexString
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,
[Parameter()] [Int] $Width = 10,
[Parameter()] [String] $Delimiter = ",0x",
[Parameter()] [String] $Prepend = "",
[Parameter()] [Switch] $AddQuotes,
[Parameter()] [Switch] $AppendComma
)
if ($Width -lt 1) { $Width = 1 }
if ($ByteArray.Length -eq 0) { Return }
$FirstDelimiter = $Delimiter -Replace "^[\,\\:\t]",""
$From = 0
$To = $Width - 1
Do
{
$String = [System.BitConverter]::ToString($ByteArray[$From..$To])
$String = $FirstDelimiter + ($String -replace "\-",$Delimiter)
$From += $Width
$To += $Width
if ($AppendComma -and $From -lt $ByteArray.Length) { $String = $String + ',' }
if ($AddQuotes) { $String = '"' + $String + '"' }
if ($Prepend -ne "") { $String = $Prepend + $String }
$String
} While ($From -lt $ByteArray.Length)
}
################################################################
#.Synopsis
# Switch the bytes in an array between little/big-endian.
#
.Description
# Swaps the ordering of bytes in an array where each swappable
# unit can be one or more bytes, and, if more than one, the
# ordering of the bytes within that unit is NOT swapped. Can
# be used to toggle between little- and big-endian formats.
# Cannot be used to swap nibbles or bits within a single byte.
#
#.Parameter ByteArray
# System.Byte[] array of bytes to be rearranged. If you
# pipe this array in, you must pipe the [Ref] to the array, but
# a new array will be returned (originally array untouched).
#
#.Parameter SubWidthInBytes
# Defaults to 1 byte. Defines the number of bytes in each unit
# (or atomic element) which is swapped, but no swapping occurs
# within that unit. The number of bytes in the ByteArray must
# be evenly divisible by SubWidthInBytes.
#
#.Example
# $bytearray = toggle-endian $bytearray
#
#.Example
# [Ref] $bytearray | toggle-endian -SubWidthInBytes 2
################################################################
function Toggle-Endian
{
[CmdletBinding()] Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,
[Parameter()] [Int] $SubWidthInBytes = 1 )
if ($ByteArray.count -eq 1 -or $ByteArray.count -eq 0) { $ByteArray ; return }
if ($SubWidthInBytes -eq 1) { [System.Array]::Reverse($ByteArray); $ByteArray ; return }
if ($ByteArray.count % $SubWidthInBytes -ne 0)
{ throw "ByteArray size must be an even multiple of SubWidthInBytes!" ; return }
$newarray = new-object System.Byte[] $ByteArray.count
# $i tracks ByteArray from head, $j tracks NewArray from end.
for ($($i = 0; $j = $newarray.count - 1) ;
$i -lt $ByteArray.count ;
$($i += $SubWidthInBytes; $j -= $SubWidthInBytes))
{
for ($k = 0 ; $k -lt $SubWidthInBytes ; $k++)
{ $newarray[$j - ($SubWidthInBytes - 1) + $k] = $ByteArray[$i + $k] }
}
$newarray
}
##########################################################################
#.Synopsis
# Save array of doubles to a file.
#
#.Description
# Save a large arrays of doubles to binary files quickly. Use the
# System.Runtime.Serialization.Formatters.Binary.BinaryFormatter class,
# which means any other .NET application can easily read it too, but
# the file contents are not text, i.e., not good for non-.NET apps.
#
#.Parameter FilePath
# File to which to save the numerical array.
#
#.Parameter Array
# Array of doubles.
#
#.Example
# [Double[]] $m1 = 1..1000000
# Save-NumericalArrayToFile -FilePath 'f:\temp\serial.bin' -Array $m1
##########################################################################
function Save-NumericalArrayToFile ([String] $FilePath, $Array)
{
if (($FilePath.IndexOf(':') -eq -1) -and (-not $FilePath.StartsWith('\\')))
{ throw 'FilePath must be a full explicit path!' ; return }
Try
{
$FileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @( $FilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) -ErrorAction Stop
$BinFormatter = New-Object -TypeName 'System.Runtime.Serialization.Formatters.Binary.BinaryFormatter' -ErrorAction Stop
$BinFormatter.Serialize( $FileStream, $Array )
}
Catch { return $_ }
Finally { if ($FileStream){ $FileStream.Close() } }
}
##########################################################################
#.Synopsis
# Read an array of doubles from a file.
#
#.Description
# Read a large arrays of doubles from a binary file quickly. Use the
# System.Runtime.Serialization.Formatters.Binary.BinaryFormatter class.
#
#.Parameter FilePath
# File to which to save the numerical array.
#
#.Example
# $data = Read-NumericalArrayFromFile -FilePath "f:\temp\serial.bin"
##########################################################################
function Read-NumericalArrayFromFile ([String] $FilePath)
{
Try { $FilePath = (dir $FilePath -ErrorAction Stop | Resolve-Path -ErrorAction Stop).ProviderPath } Catch { return $_ }
Try
{
$FileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @( $FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) -ErrorAction Stop
$BinFormatter = New-Object -TypeName 'System.Runtime.Serialization.Formatters.Binary.BinaryFormatter' -ErrorAction Stop
,($BinFormatter.Deserialize( $FileStream )) #Don't delete the comma; returns the entire filled array.
}
Catch { return $_ }
Finally { if ($FileStream){ $FileStream.Close() } }
}
##############################################################################
#
# Demo various ways of manipulating binary bits inside a byte.
#
##############################################################################
# Convert a string representation of binary bits to a decimal integer:
function Get-IntFromBits ([String] $Bits) { [System.Convert]::ToUInt32($Bits,2) }
Get-IntFromBits -Bits "11111111"
Get-IntFromBits -Bits "10101010"
Get-IntFromBits -Bits "00000001"
# Convert a decimal integer to an binary string representation:
function Get-BitsFromInt ([UInt32] $Integer, [Switch] $NoLeadingZeros)
{
if ($NoLeadingZeros) { [System.Convert]::ToString($Integer,2) }
else { ([System.Convert]::ToString($Integer,2)).PadLeft(8,"0") }
}
Get-BitsFromInt -Integer 255
Get-BitsFromInt -Integer 19
Get-BitsFromInt -Integer 1
# Careful when converting bytes to a 16/32/64-bit number! x86/x64 machines are
# little-endian, which means the byte array might need to be reversed first.
# See http://blogs.msdn.com/b/bclteam/archive/2008/04/09/working-with-signed-non-decimal-and-bitwise-values-ron-petrusha.aspx
[Byte[]] $In = @(0,0,0,1)
if ([System.BitConverter]::IsLittleEndian) { [System.Array]::Reverse($In) }
[System.BitConverter]::ToUInt32($In,0) #Returns 16777216 without the reversal.
# Show bit-shifting (requires PoSh 3.0)
0..7 | foreach { "+$_ : " + ([System.Convert]::ToString( (1 -shl $_),2)).PadLeft(8,"0") } # -SHL = bit-shift left
0..9 | foreach { "-$_ : " + ([System.Convert]::ToString( (128 -shr $_),2)).PadLeft(8,"0") } # -SHR = bit-shift right
# Do binary XOR, OR, AND, NOT.
# For more information about bitwise operators: get-help about_Comparison_Operators
"101 = " + (Get-BitsFromInt -Integer 101)
"228 = " + (Get-BitsFromInt -Integer 228)
"`nbxor"
101 -bxor 228
Get-BitsFromInt -Integer (101 -bxor 228)
"`nbor"
101 -bor 228
Get-BitsFromInt -Integer (101 -bor 228)
"`nband"
101 -band 228
Get-BitsFromInt -Integer (101 -band 228)
"`nbnot"
-bnot 228 #Unary operator.
Get-BitsFromInt -Integer (-bnot 2)
<#
Handling byte arrays can be a challenge for many reasons:
Bytes can be arranged in big-endian or little-endian format, and the endianness may need to be switched by one's code on the fly, e.g., Intel x86 processors use little-endian format internally, but TCP/IP is big-endian.
A raw byte can be represented in one's code as a .NET object of type System.Byte, as a hexadecimal string, or in some other format, and this format may need to be changed as the bytes are saved to a file, passed in as an argument to a function, or sent to a TCP port over the network.
Hex strings can be delimited in different ways in text files ("0xA5,0xB6" vs. "\xA5\xB6" vs. "A5-B6") or not delimited at all ("A5B6").
Some cmdlets inject unwanted newlines into byte streams when piping.
The redirection operators (> and >>) mangle byte streams as they attempt on-the-fly Unicode conversion.
Bytes which represent text strings can encode those strings using ASCII, Unicode, UTF, UCS, etc.
Newline delimiters in text files can be one or more different bytes depending on the application and operating system which created the file.
Some .NET classes have unexpected working directories when their methods are invoked, so paths must be resolved explicitly first.
StdIn and StdOut in PowerShell on Windows are not the same as in other languages on other platforms, which can lead to undesired surprises.
Manipulating very large arrays can lead to performance problems if the arrays are mishandled, e.g., not using a [Ref] where appropriate, constantly recopying to new arrays under the hood, recasting to different types unnecessarily, using the wrong .NET class or cmdlet, etc.
Notes:
The "0xFF,0xFE" bytes at the beginning of a Unicode text file are byte order marks to indicate the use of little-endian UTF-16.
"0x0D" and "0x0A" are the ASCII carriage-return and linefeed ASCII bytes, respectively, which together represent a Windows-style newline delimiter. This Windows-style newline delimiter in Unicode is "0x00,0x0D,0x00,0x0A". But in Unix-like systems, the ASCII newline is just "0x0A", and older Macs use "0x0D", so you will see these formats too; but be aware that many cmdlets will do on-the-fly conversion to Windows-style newlines (and possibly Unicode conversion too) when saving back to disk. When hashing text files, be aware of how the newlines and encoding (ASCII vs. Unicode) may have changed, since "the same" text will hash to different thumbprints if the newlines or encoding have unexpectedly changed.
#>
##########################################################################
##
## Need to flesh these functions out...
## Save/read large arrays of doubles to raw binary files with more
## PowerShell control over the process (much slower than binary serialization).
##
##########################################################################
[Double[]] $m1 = 1..1000
function Save-DoublesToBinaryFile ( [String] $FilePath, [Double[]] $Array)
{
if ($FilePath.IndexOf(':') -eq -1) { throw 'Need the full path for now...' ; return $false }
Try
{
$FileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @( $FilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) -ErrorAction Stop
$BinaryWriter = New-Object -TypeName System.IO.BinaryWriter -ArgumentList $FileStream
ForEach ($number in $Array){ $BinaryWriter.Write( $number ) }
$BinaryWriter.Flush()
}
Catch { $error[0] } #Not doing any error handling or path checking yet...
Finally
{
if ($BinaryWriter){ $BinaryWriter.Close() }
if ($FileStream ){ $FileStream.Close() }
}
}
Save-DoublesToBinaryFile -FilePath 'f:\temp\delme.bin' -Array $m1
function Read-DoublesFromBinaryFile ( [String] $FilePath )
{
Try
{
$FilePath = (dir $FilePath | Resolve-Path -ErrorAction Stop).ProviderPath
$FileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @( $FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) -ErrorAction Stop
$BinaryReader = New-Object -TypeName System.IO.BinaryReader -ArgumentList $FileStream
$FileSize = $FileStream.Length
While ($FileStream.Position -lt $FileSize){ $BinaryReader.ReadDouble() }
}
Catch { $error[0] } #Not doing any error handling or path checking yet...
Finally
{
if ($BinaryReader){ $BinaryReader.Close() }
if ($FileStream ){ $FileStream.Close() }
}
}
$m2 = Read-DoublesFromBinaryFile -FilePath "f:\temp\delme.bin"
$m1 -eq $m2