I'm a big fan of codes, ciphers and secret messages. I obviously am a big PowerShell fan as well. So why not mash these things together? Today's Friday Fun is a PowerShell module I call PSCode. The module contains a few functions for encoding and decoding text. Now, before you get too excited, these won't stand up to NSA scrutiny or even anyone proficient at code breaking, although I'd like to think I've made it a little challenging. Of course, the whole point of these Friday Fun posts is to learn a little PowerShell as well.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The basic encoding premise I'm using is a relative simple transposition. That is, replace S with E. But how to figure out what character substitute? There needs to be some "key" so that the message can later be decoded. My approach was to turn plaintext into its corresponding series of [CHAR] objects. I could have simply used the ToCharArray() method, but PowerShell is too helpful and I wanted to get the underlying integer value. My approach was to process each character of the word with a For statement.
for ($i=0;$i -lt $word.length;$i++) {
$x=$word[$i]
#get the current numeric value
$x=[char]::ConvertToUtf32("$x",0)
The trick here is to use the ConvertToUTF32() method of the [CHAR] class to get the integer value. From the console this is what it looks like:
PS S:\> [char]::ConvertToUtf32("P",0)
80
My substitution idea is to get another character based on some calculated offset. I could have simply said always get the current value plus 5. But then frequency analysis would break that pretty quickly. So my default algorithm, which can be modified via a parameter, is to calculate an offset based on the length of the current word. I take this value and perform a modulo operation using pi, adding 3 to it and rounding it to an integer. This guarantees an offset.
PS S:\> $word="PowerShell"
PS S:\> $word.length%[math]::pi
0.575222039230621
PS S:\> $word.length%[math]::pi+3
3.57522203923062
PS S:\> [int]$off=$word.length%[math]::pi+3
PS S:\> $off
4
This value is added to the [CHAR] value of the current letter. So in my example, the new value is 84. To get the corresponding character I invoke the ConvertFromUtf32() method and write the result to the pipeline.
PS S:\> [char]::ConvertFromUtf32(84)
T
Because the offset is based on a per word length, it varies throughout the plaintext. Thus each word would require a separate analysis, at least for most unclassified civilians. Anyway, the last piece is to use the -Join operator to assemble each character back into a word and write it to the pipeline. Here's the ConvertTo-PSCode function.
Function ConvertTo-PSCode {
<#
comment based help
#>
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[AllowEmptyString()]
[AllowNull()]
[string[]]$Text,
[ValidateNotNullorEmpty()]
[scriptblock]$Scriptblock={($word.length%[math]::pi)+3}
)
Begin {
#an array to hold encoded output
Write-Verbose "Starting $($myinvocation.mycommand)"
$Out=@()
}
Process {
#split text into words and lines
foreach ($line in $text) {
$newLine=""
Write-Verbose "Processing line: $line"
$NewLine+= foreach ($word in ($line.Split())) {
write-Verbose " Processing word: $word"
$chars = for ($i=0;$i -lt $word.length;$i++) {
$x=$word[$i]
#get the current numeric value
$x=[char]::ConvertToUtf32("$x",0)
#Calculate an offset. The default is the length of the
#word modulo pi rounded to an integer plus 3
[int]$off=&$Scriptblock
Write-Verbose "Using an offset of $off based on length $($word.length)"
[int]$y=$x+$off
#get the new value
[char]::ConvertFromUtf32($y)
} #for
#write the new "word" to the pipeline
$NewWord=$chars -join ""
$NewWord
} #foreach word
$Out+=$NewLine
} #foreach line
} #end process
End {
Write $Out
}
} #function
The function takes plaintext as a parameter or you can pipe to it. The offset algorithm is passed as a scriptblock. You can pass your own which I'll show you in a moment.
PS S:\> convertto-pscode "I am the walrus"
M fr znk }grx{y
PS S:\> "I am the walrus" | convertto-pscode
M fr znk }grx{y
To decode the text, I can use the same technique. Except instead of adding the offset, I subtract it.
#rounded to an integer
[int]$off=&$Scriptblock
Write-Verbose "Using an offset of $off based on length $($word.length)"
[int]$y=$x-$off
#get the new value
[char]::ConvertFromUtf32($y)
The ConvertFrom-PSCode function also takes pipelined input which makes it very easy to test.
PS S:\> "I am the walrus" | convertto-pscode | convertfrom-pscode
I am the walrus
If I decide to use a different algorithm, I need to specify it for both functions. Depending on your formula you might end up with non-alpha characters.
As you can see in the figure the offset is the length of the word plus 5 which makes for some interesting code. Now for the really cool part. Because the convert functions write to the pipeline you can direct them to Out-File to save your results. Let's say I have a simple script.
#requires -version 2.0
[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$name="m*"
)
write-host "$(Get-Date) Starting a sample script" -ForegroundColor Green
Write-verbose "getting services where name = $name"
Get-service -name $name | where {$_.status -eq "running"}
write-host "$(Get-Date) Ending a sample script" -ForegroundColor Green
I'll encode the script using the defaults.
PS S:\> cat c:\work\abc.txt | convertto-pscode | out-file c:\work\secretscript.txt
PS S:\> cat C:\work\secretscript.txt
)xkw{oxky 2{jwxnts 846
_gqhpixfmrhmrk,-a
Vgxgs.
_Teveqixiv,TswmxmsrA4-a
_ZepmhexiRsxRyppsvIqtx},-a
`xywnslb)sfrjB'r/'
-
{vmxi1lswx (*.Mkz3Jgzk/ Xyfwynsl e ygsvrk wgvmtx& 0IruhjurxqgFroru Lwjjs
Zulwh0yhuervh 'ljyynsl xjw{nhjx |mjwj reqi A *tgsk(
Ljy2xjw{nhj 2sfrj )sfrj |mjwj (c2wxexyw 3kw &vyrrmrk&
{vmxi1lswx (*.Mkz3Jgzk/ Ktjotm e ygsvrk wgvmtx& 0IruhjurxqgFroru Lwjjs
What I thought would be extra fun would be a way to run this encoded script. So I wrote Invoke-PSCode.
Function Invoke-PSCode {
<# comment based help #>
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[AllowEmptyString()]
[AllowNull()]
[string[]]$Text,
[ValidateNotNullorEmpty()]
[scriptblock]$Scriptblock={($word.length%[math]::pi)+3},
[hashtable]$Arguments
)
Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"
#define an empty string to hold the contents of the decoded command
$a=""
}
Process {
foreach ($item in $text) {
#add each decoded line to the variable plus a line return
Write-Verbose "Decoding $item"
$a+="{0} `n" -f (ConvertFrom-PSCode -Text $item -Scriptblock $Scriptblock)
}
} #process
End {
Write-Verbose "Running command"
#create a scriptblock
$myCommand=$ExecutionContext.InvokeCommand.NewScriptBlock($a)
#splat the arguments to the script block
&$myCommand @Arguments
Write-Verbose "Ending $($myinvocation.mycommand)"
}
} #end function
The function takes each line of code and decodes it. Each decoded line is saved as part of a long string which is eventually turned into a scriptblock and executed. To pass parameters to the secret command, which you have to know in advance, Invoke-PSCode takes a hashtable of parameters and splats them to the scriptblock.
I haven't tested this against all possible types of scripts and code samples but I thought it was fun.
The last little function in the module is something to quickly take a string and write its reverse to the pipeline. The business part is basically a single line that gets each character from a string in reverse order and writes it to the pipeline. But using the -join operator puts it all back together.
PS S:\> $item="PowerShell is fun!"
PS S:\> -join $(for ($i=$item.length;$i -ge 0;$i--) {$item[$i]})
!nuf si llehSrewoP
The For loop starts at the length and counts down to zero. The rest of the function is merely a wrapper to this core command.
PS S:\> "I am the walrus" | ConvertTo-ReverseString
surlaw eht ma I
PS S:\> "I am the walrus" | ConvertTo-ReverseString | convertto-Reversestring
I am the walrus
I suppose if I wanted I could even combine encoding and reversal.
PS S:\> $x = "PowerShell is fun for walruses." | ConvertTo-ReverseString | ConvertTo-PSCode
PS S:\> $x
4yky{xrg} xul t{l xn ppilWvi{sT
PS S:\> $x | ConvertFrom-PSCode | ConvertTo-ReverseString
PowerShell is fun for walruses.
Although I'd better not forget the order!
So have some fun with this and maybe learn something new about PowerShell. Download PSCode.psm1. This is a module so you'll need to put it in a folder called PSCode in your modules directory or be sure to specify the full path when you import it.
Ymtxj }nu luxmkz yt yixovz gxk juuskj yt xkvkgz ymjnw {svo