SECCON 2017 CTF - Powerful_Shell

This challenge consisted of an obfuscated powershell script (powerful_shell.ps1)

Analysis

I noticed two anti-debugger checks while scrolling through the script:

1
2
3
4
# Line 15113:
If(Test-Path variable:global:psISE){"D`eb`u`g`g`in`g is `pr`o`h`ib`it`e`d";Exit}
# Line 15559:
If($host."na`m`e" -ne "C`on`s`o`l`e`H`o`st"){"D`eb`u`g`g`in`g is `pr`o`h`ib`it`e`d";Exit}

After commenting-out those two checks, I modified the last line of the script to extract the embedded code instead of executing it:

1
2
3
Write-Progress -Completed -Activity "Extracting Script";
# .([ScriptBlock]::Create($ECCON))
$ECCON | Set-Content 'powerful_shell_extracted.ps1'

Running the modified version of the script gave me yet another script (powerful_shell_extracted.ps1)

The extracted script is displaying some ASCII art, and then counting the number of security events on the computer which executes it (lines 5-16):

powerful_shell_extracted.ps1
1
2
3
4
5
6
7
8
9
10
11
12
<# Host Check #>
Write-Host -b 00 -f 15 Checking Host... Please wait... -n
Try{
If ((Get-EventLog -LogName Security | Where EventID -Eq 4624).Length -Lt 1000) {
Write-Host "This host is too fresh!"
Exit
}
}Catch{
Write-Host "Failed: No admin rights!"
Exit
}
Write-Host "Check passed"

The snippet above made sure the system executing the script had more than 1000 login events, otherwise it exitted
with the message "This host is too fresh!"
Since my VM had less than 1000 login events, I commented out this section of the script, and executed it:

This next section of the script was expecting the user to play the correct keys before continuing:

powerful_shell_extracted.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
# give a 'keytone' to each valid key the user can enter
$keytone=@{'a'=261.63}
$keytone_inv=@{261, 'a'}
$pk='a'
ForEach($k in ('w','s','e','d','f','t','g','y','h','u','j','k')){
$val = $keytone[$pk]*[math]::pow(2,1/12)
$keytone+=@{$k=$val};
$keytone_inv += @{$val=$k}
Write-Host "v: $val - k: $k"
$pk=$k
}

Write-Host -b 00 -f 15 "Play the secret melody."

# ..
# Removed this code, since its just drawing the piano keys ..
# ..

# capture user input
$stage1=@();$f="";
While($stage1.length -lt 14){
$key=(Get-Host).ui.RawUI.ReadKey("NoEcho,IncludeKeyDown")
$k=[String]$key.Character
$f+=$k;
If($keytone.Contains($k)){
$stage1+=[math]::floor($keytone[$k])
[console]::beep($keytone[$k],500)
}
}

# check user input against $secret var
$secret=@(440,440,493,440,440,493,440,493,523,493,440,493,440,349)
If($secret.length -eq $stage1.length){
For ($i=1; $i -le $secret.length; $i++) {
If($secret[$i] -ne $stage1[$i]){
Exit
}
}
x "Correct. Move to the next stage."
}

The keys entered must match those in the $secret array.
I wrote a quick script to get the corresponding key letter for each value in $secret:

solve_stage2.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$keytone=@{'a'=261.63}
$keytone_inv=@{261='a'}
$pk='a'
ForEach($k in ('w','s','e','d','f','t','g','y','h','u','j','k')){
$val = $keytone[$pk]*[math]::pow(2,1/12)
$keytone+=@{$k=$val};
$val = [int][math]::floor($val)
$keytone_inv+=@{$val=$k}
$pk=$k
}

$secret=@(440,440,493,440,440,493,440,493,523,493,440,493,440,349)
Write-Host -NoNewLine "The secret keys are: "
For ($i=0; $i -lt $secret.length; $i++) {
$skey = $keytone_inv[$secret[$i]]
Write-Host -NoNewLine "$skey "
}
Write-Host ''

1
The secret keys are: h h j h h j h j k j h j h f

My script works by creating the $keytone_inv hashtable which basically flips the keys <-> values of the $keytone hashtable.
It then loops through the $secret array, using each value to index the $keytone_inv hashtable, giving me the correct key letter.

This allowed me to reach the final stage:

Next, is the code that prompts for a password:

powerful_shell_extracted.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$text=@"
Removed this big Base64 string...
"@

$plain=@()
$byteString = [System.Convert]::FromBase64String($text)
$xordData = $(for ($i = 0; $i -lt $byteString.length; ) {
for ($j = 0; $j -lt $f.length; $j++) {
$plain+=$byteString[$i] -bxor $f[$j]
$i++
if ($i -ge $byteString.Length) {
$j = $f.length
}
}
})
iex([System.Text.Encoding]::ASCII.GetString($plain))

A powershell expression is encoded in the variable $text which is then XORed with the previous stage’s input, stored in $plain, and executed.
Examining the contents of $plain, after providing a valid input, resulted in an obfuscated powershell expression:

obfuscated.ps1
1
2
3
4
5
${;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;};
${]}=++${;};${(}=++${;};${)}=++${;};${&}=++${;};${|}=++${;};
${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]";
${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]);
${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"];"${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${(}${+}+${"}${&}${@}+${"}${+}${=}${+}+${"}${|}${)}+${"}${+}${=}${=}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${[}${]}+${"}${&}${=}+${"}${+}${+}${[}+${"}${+}${+}${+}+${"}${+}${=}${|}+${"}${+}${+}${@}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${(}${|}+${"}${+}${+}${=}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${+}${[}+${"}${.}${@}+${"}${+}${+}${(}+${"}${+}${=}${[}+${"}${+}${=}${+}+${"}${.}${@}+${"}${+}${+}${@}+${"}${|}${)}+${"}${+}${+}${]}+${"}${+}${+}${]}+${"}${+}${+}${|}+${"}${+}${+}${+}+${"}${+}${+}${[}+${"}${+}${=}${=}+${"}${.}${|}+${"}${+}${.}+${"}${+}${=}+${"}${)}${.}+${"}${+}${=}${@}+${"}${[}${=}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${.}${@}+${"}${[}${]}+${"}${+}${=}${+}+${"}${+}${+}${.}+${"}${.}${@}+${"}${.}${|}+${"}${&}${=}+${"}${[}${&}+${"}${+}${+}${|}+${"}${(}${|}+${"}${+}${+}${[}+${"}${.}${(}+${"}${)}${@}+${"}${]}${+}+${"}${[}${|}+${"}${[}${|}+${"}${.}${|}+${"}${[}${+}+${"}${+}${@}${.}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${)}${+}+${"}${+}${+}${+}+${"}${+}${+}${+}+${"}${+}${=}${=}+${"}${.}${@}+${"}${)}${[}+${"}${+}${+}${+}+${"}${|}${&}+${"}${.}${.}+${"}${.}${|}+${"}${]}${|}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${[}+${"}${&}${.}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${.}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${]}+${"}${.}${[}+${"}${+}${.}+${"}${+}${=}+${"}${+}${@}${]}|${;}"|&${;}

I ran the obfuscated expression in the powershell terminal, and it resulted in the following:

1
[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]61+[CHar]82+[CHar]101+[CHar]97+[CHar]100+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]45+[CHar]80+[CHar]114+[CHar]111+[CHar]109+[CHar]112+[CHar]116+[CHar]32+[CHar]39+[CHar]69+[CHar]110+[CHar]116+[CHar]101+[CHar]114+[CHar]32+[CHar]116+[CHar]104+[CHar]101+[CHar]32+[CHar]112+[CHar]97+[CHar]115+[CHar]115+[CHar]119+[CHar]111+[CHar]114+[CHar]100+[CHar]39+[CHar]13+[CHar]10+[CHar]73+[CHar]102+[CHar]40+[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]32+[CHar]45+[CHar]101+[CHar]113+[CHar]32+[CHar]39+[CHar]80+[CHar]48+[CHar]119+[CHar]69+[CHar]114+[CHar]36+[CHar]72+[CHar]51+[CHar]49+[CHar]49+[CHar]39+[CHar]41+[CHar]123+[CHar]13+[CHar]10+[CHar]9+[CHar]87+[CHar]114+[CHar]105+[CHar]116+[CHar]101+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]39+[CHar]71+[CHar]111+[CHar]111+[CHar]100+[CHar]32+[CHar]74+[CHar]111+[CHar]98+[CHar]33+[CHar]39+[CHar]59+[CHar]13+[CHar]10+[CHar]9+[CHar]87+[CHar]114+[CHar]105+[CHar]116+[CHar]101+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]34+[CHar]83+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]123+[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]125+[CHar]34+[CHar]13+[CHar]10+[CHar]125|iex

Next, I entered the expression above in the terminal (without the pipe to iex):

1
2
3
4
5
$ECCON=Read-Host -Prompt 'Enter the password'
If($ECCON -eq 'P0wEr$H311'){
Write-Host 'Good Job!';
Write-Host "SECCON{$ECCON}"
}

FLAG: SECCON{P0wEr$H311}