- Name - zsud.exe
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
- M:\\whiskey_tango_flareon.dll
- flareon.dll
- !This program cannot be run in DOS mode.
- .text
- .rsrc
- .reloc
using System; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; using System.Security.Cryptography; using System.Text; namespace flareon { public class four { private static string Decrypt2(byte[] cipherText, string key) { byte[] bytes = Encoding.UTF8.GetBytes(key); byte[] array = new byte[16]; byte[] iV = array; string result = null; using (RijndaelManaged rijndaelManaged = new RijndaelManaged()) { rijndaelManaged.Key = bytes; rijndaelManaged.IV = iV; ICryptoTransform transform = rijndaelManaged.CreateDecryptor(rijndaelManaged.Key, rijndaelManaged.IV); using (MemoryStream memoryStream = new MemoryStream(cipherText)) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader(cryptoStream)) { result = streamReader.ReadToEnd(); } } } } return result; } public static int Smth(string arg) { using (PowerShell powerShell = PowerShell.Create()) { try { byte[] cipherText = Convert.FromBase64String(arg); string script = four.Decrypt2(cipherText, "soooooo_sorry_zis_is_not_ze_flag"); powerShell.AddScript(script); CollectionThe DLL accepts a Base64 string, decodes it, AES decrypts it using the key 'soooooo_sorry_zis_is_not_ze_flag' and runs the resultant PowerShell script. Let's try and extract the PS1 script from the binary. At the beginning of function sub_12523D0(), a region in memory is Base64'd and then passed to flareon.dll after this has been loaded into memory. The following image shows the resultant base64 string pointed at by EAX right after it has been encoded: Extract the base64 string and create a C# program, based on the .NET flareon.dll, to recover the PS1 script:collection = powerShell.Invoke(); foreach (PSObject current in collection) { Console.WriteLine(current); } } catch (Exception var_5_70) { Console.WriteLine("Exception received"); } } return 0; } } }
[ ... snip ... ] public static int Smth(string arg) { byte[] cipherText = Convert.FromBase64String(arg); string script = Program.Decrypt2(cipherText, "soooooo_sorry_zis_is_not_ze_flag"); System.IO.StreamWriter file = new System.IO.StreamWriter("zsud.ps1"); file.WriteLine(script); file.Close(); return 0; } public static void Main() { string base64encoded = "Sbogppc38m/yviiq2 … [snip] … AyyAEQ2IwZPNoPEzE="; Smth(base64encoded); Console.ReadKey(); } [ ... snip ... ]The decrypted script can be viewed/downloaded here. If we run the powershell script on its own, we get the same UI depicted in the first image above. First thing to notice in the script is the string 'http://127.0.0.1:9999/some/thing.asp' on line 814 which hints that the script communicates with a local web server embedded in the binary. This is confirmed by the following netstat result when zsud.exe is open:
Command Prompt
C:\>netstat -antp tcp | findstr 9999
TCP 127.0.0.1:9999 0.0.0.0:0 LISTENING InHost
C:\>
Let's go back to the PS1 script and try to uncover what it's doing. The following is an excerpt of the important sections:
[ ... snip ... ] $key = New-Thing "a key" "You BANKbEPxukZfP2EikF8jN04 ... [snip] ... PtEVBhQ==" @("key") [ ... snip ... ] function Invoke-XformKey([String]$keytext, [String]$desc) { $newdesc = $desc Try { $split = $desc.Split() $text = $split[0..($split.Length-2)] $encoded = $split[-1] $encoded_urlsafe = $encoded.Replace('+', '-').Replace('/', '_').Replace('=', '%3D') $uri = "${script:baseurl}?k=${keytext}&e=${encoded_urlsafe}" $r = Invoke-WebRequest -UseBasicParsing "$uri" $decoded = $r.Content if ($decoded.ToLower() -NotContains "whale") { $newdesc = "$text $decoded" } } Catch { Add-ConsoleText "..." } return $newdesc } function Invoke-MoveDirection($char, $room, $direction, $trailing) { $nextroom = $null $movetext = "You can't go $direction." $statechange_tristate = $null $nextroom = Get-RoomAdjoining $room $direction if ($nextroom -ne $null) { $key = Get-ThingByKeyword $char 'key' if (($key -ne $null) -and ($script:okaystopnow -eq $false)) { $dir_short = ([String]$direction[0]).ToLower() ${N} = ${sC`Ri`Pt:MS`VcRt}::("{1}{0}" -f'nd','ra').Invoke() % 6 if ($directions_enum[$dir_short] -eq ($n)) { $script:key_directions += $dir_short $newdesc = Invoke-XformKey $script:key_directions $key.Desc $key.Desc = $newdesc if ($newdesc.Contains("@")) { $nextroom = $script:map.StartingRoom $script:okaystopnow = $true } $statechange_tristate = $true } else { $statechange_tristate = $false } } $script:room = $nextroom $movetext = "You go $($directions_short[$direction.ToLower()])" if ($statechange_tristate -eq $true) { $movetext += "`nThe key emanates some warmth..." } elseif ($statechange_tristate -eq $false) { $movetext += "`nHmm..." } if ($script:autolook -eq $true) { $movetext += "`n$(Get-LookText $char $script:room $trailing)" } } else { $movetext = "You can't go that way." } return "$movetext" } [ ... snip ... ] $baseurl = 'http://127.0.0.1:9999/some/thing.asp' $directions_enum = @{'n' = 0; 's' = 1; 'e' = 2; 'w' = 3; 'u' = 4; 'd' = 5} [ ... snip ... ]The script does the following for each movement we perform in the game:
- Calls Invoke-MoveDirection
- Generates a random number between 0 and 5 (line 39)
- Translates our move to a number (lines 27 & 77)
- If these 2 numbers coincide, call Invoke-XformKey (line 43)
- Sends the list of directions (k param) and url-encoded key description (e param) to local web server (lines 15 & 16)
- If response does not contain 'whale', set it as the new key description (lines 18 & 19)
- If response contains '@', warp to starting room and stop game (lines 45 - 47)
iex ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("U0VULWl0RW [ ... snip ... ] "Xl9LCAke2ZpRWxkVmFgTGB1YGVzfSAgKTsgIHJldHVybiAke2FgVFRyfTsgICB9"))) .("{0}{1}{2}"-f 'SEt-IT','E','M') VariaBLE:q21s [ ... snip ...] ${T`ype`BU`IldEr}.("{2}{0}{1}"-f 'teTy','pe','Crea').Invoke(); } [STring]::joIN( '', ('35h88w112_119}81r74r77h100J94<5 [ ... snip ...] |%{[CHAR]($_ -BXoR 0x03 ) } ) )|.( $ShelliD[1]+$SheLLID[13]+'X') $key = New-Thing "a key" "You BANKbEPxukZfP2EikF8jN04 ... [snip] ... PtEVBhQ==" @("key") $baseurl = 'http://127.0.0.1:9999/some/thing.asp' function Invoke-XformKey([String]$keytext, [String]$desc) { $newdesc = $desc Try { $split = $desc.Split() $text = $split[0..($split.Length-2)] $encoded = $split[-1] $encoded_urlsafe = $encoded.Replace('+', '-').Replace('/', '_').Replace('=', '%3D') $uri = "${script:baseurl}?k=${keytext}&e=${encoded_urlsafe}" $r = Invoke-WebRequest -UseBasicParsing "$uri" $decoded = $r.Content # If response is different than previous one, set as new description if ($decoded.ToLower() -ne $encoded.ToLower()) { $newdesc = "$text $decoded" return $newdesc } } Catch {} } $directions_enum = @{0 = "n"; 1 = "s"; 2 = "e"; 3 = "w"; 4="u"; 5 = "d"} $cumulativekey = "" for ($i=0; $i -lt 25; $i++){ for ($j=0; $j -lt 6; $j++){ $testingchar = $directions_enum[$j] if ($newkey = Invoke-XformKey $cumulativekey$testingchar $key){ $key = $newkey $cumulativekey += $testingchar break } } } echo "Directions: $cumulativekey" echo "Key: $key"Running the script:
Command Prompt
C:\>powershell -file bruteforcer.ps1
Directions: wnneesssnewne
Key: You can start to make out some words but you need to follow the ZipRg2+UxcDPJ8T
iemKk7Z9bUOfPf7VOOalFAepISztHQNEpU4kza+IMPAh84PlNxwYEQ1IODlkrwNXbGXcx/Q==
C:\>
We notice 3 important things when we run the script:
- The key is incompletely decoded
- Only the first 13 direction attempts out of the 25 were executed
- Multiple runs produce the same output even though the expected input is decided by rand()
if (($thing.Keywords -Contains "key") -and ($container_new -eq $script:char)){ ${Msv`c`RT}::("{1}{0}"-f 'rand','s').Invoke(42) }When we pick up the 'key' in the game, srand(42) is executed, which explains why the rand() function always produces the same result and hence the server always expects the same directions. If we factor in the srand(42) though we notice that the output of 'rand() % 6' still does not match 'wnneesssnewne', which has been proven to be right else we would've never decoded parts of the key. During the challenge it took me ages to realise the trick: both the rand() and srand() function are hooked in the binary and replaced with a custom implementation! This happens in function sub_1336530(): In the image we can see that the pointer to the real rand() is being hooked by new_rand() whilst srand() is being hooked by new_srand(). Let's take a look at both of these new functions. The following is the new_srand(): Recall that srand() is called from the PS1 when we collect the key. When this happens, it sets the global variable dword_138BD28 to 1, as displayed in the image above. Let us take a look at the new implementation of rand(), i.e. new_rand(): If the global variable dword_138BD28 has been set to 1, i.e. new_srand() has been called, then it will end up in the green basic block which takes the expected direction from the array dword_1389CB8. This is NOT random at all! Let's create a python script to extract all the expected directions:
import sys directions_enum = {0:"n", 1:"s", 2 : "e", 3 : "w", 4:"u", 5 : "d"} dword_1389CB8 = [0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00] dword_138BD24 = 0 for x in range(200): result = dword_1389CB8[dword_138BD24 * 4] dword_138BD24 = (dword_138BD24 + 1) % 0x35 sys.stdout.write(directions_enum[result])Running the script:
Command Prompt
C:\>python direction_extractor.py
wnneesssnewneewwwdundundunsuneunsewdunsewsewsewsewdunwnneesssnewneewwwdundundun
suneunsewdunsewsewsewsewdunwnneesssnewneewwwdundundunsuneunsewdunsewsewsewsewdu
nwnneesssnewneewwwdundundunsuneunsewdunsew
C:\>
Sw33t! Let's modify the PS1 script now to specifically send these rather than trying to bruteforce our way:
[ ... snip ... ] $solutions = "wnneesssnewneewwwdundundunsuneunsewdunsewsewsewsewdunwnneesssnewneewwwdundundunsuneunsewdunsewsewse" for ($j=1; $j -lt 54; $j++){ $tempSolution = $solutions.substring(0,$j) if ($newkey = Invoke-XformKey $tempSolution $key){ $key = $newkey } } echo "Directions: $cumulativekey" echo "Key: $key"And this time:
Command Prompt
C:\>powershell -file send_right_directions.ps1
Directions: wnneesssnewneewwwdundundunsuneunsewdunsewsewsewsewdun
Key: You can start to make out some words but you need to follow the
RIGHT_PATH!@66696e646b6576696e6d616e6469610d0a
C:\>
Not exactly what we want by close enough. The long hex string at the end translated to 'findkevinmandia'. So let's go and find Kev ... nahh, let's extract the logic directly from the script.
The decrypter to this output can be found in the original PS1 file on lines 463 - 477 which are invoked when we talk to Kevin Mandia whilst wearing a helmet and the key is dropped in the room. Extracting the logic from the script and putting our response from the server we end up with the following script:
$key = "You can start to make out some words but you need to follow the RIGHT_PATH!@66696e646b6576696e6d616e6469610d0a" $md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider $utf8 = New-Object System.Text.UTF8Encoding $hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($key))) $Data = [System.Convert]::FromBase64String("EQ/Mv3f/1XzW4FO8N55+DIOkeWuM70Bzln7Knumospan") $Key = [System.Text.Encoding]::ASCII.GetBytes($hash) # Adapated from the gist by harmj0y et al $R={$D,$K=$Args;$H=$I=$J=0;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Length])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}} $x = (& $r $data $key | ForEach-Object { "{0:X2}" -f $_ }) -join ' ' $resp = "`nKevin says, with a nod and a wink: '$x'." $resp += "`n`nBet you didn't know he could speak hexadecimal! :-)" echo $respThe output:
Command Prompt
C:\>powershell -file kevin_mandia.ps1
Kevin says, with a nod and a wink: '6D 75 64 64 31 6E 67 5F 62 79 5F
79 30 75 72 35 33 6C 70 68 40 66 6C 61 72 65 2D 6F 6E 2E 63 6F 6D'.
Bet you didn't know he could speak hexadecimal! :-)
C:\>
Converting the hex to ascii: mudd1ng_by_y0ur53lph@flare-on.com