MSEndpointMgr
Your Apps Are Running on Borrowed Time: The Hidden Risk of Out-of-Support Runtime Dependencies

Your Apps Are Running on Borrowed Time: The Hidden Risk of Out-of-Support Runtime Dependencies

Introduction: A Security Blind Spot Hiding in Plain Sight

Enterprise IT teams diligently track application versions and apply security patches to operating systems and applications. Yet one critical blind spot remains: the underlying runtime libraries many applications silently depend on. These can be old, end-of-life software components no longer receiving security fixes, turning seemingly up-to-date apps into hidden vulnerabilities. In other words, your apps might be running on borrowed time if they rely on unsupported runtime dependencies.

What do we mean by runtime dependencies? These are shared libraries or frameworks that applications use at runtime, like Microsoft’s Visual C++ runtime libraries (the “Visual C++ Redistributables”) or .NET runtime versions. When such a runtime reaches end-of-life (EoL) and is no longer supported by the vendor (typically Microsoft), every application compiled against that runtime effectively becomes an unpatched attack surface, even if the application itself is current. No more vendor support means no more security patches, any new vulnerabilities discovered remain unaddressed. This is how obsolete code can persist within otherwise modern systems, quietly undermining your security posture.

As a side note: If you have applications from a vendor, relying on an unsupported framework, but the vendor claim that they support their software, you should probably have a sit-down with the vendor to discuss their support strategy and/or risk exposure.

A Real-World Surprise: Patched Apps, Unpatched Foundations

To illustrate, consider a common scenario in many enterprises today:

  • Application Inventory (what IT sees): “Java Runtime Environment 8 Update 381, installed, version is current.” Also, “Company Backup Utility, installed, latest version.”
  • Hidden Reality (what’s not seen): The Java Runtime’s executables (java.exe, keytool.exe, etc.) are actually native C/C++ applications compiled using Visual C++ 2010 runtime (msvcr100.dll). Visual C++ 2010’s extended support ended in July 2020, meaning no security patches since then. Likewise, that backup utility’s binaries might have been compiled with the Visual C++ 2005 runtime (msvcr80.dll), support for which ended in 2016. These runtime DLLs, now a decade or more out of support, are loaded into every process that uses them. Any vulnerability present in those DLLs is fully exploitable in the context of the application, no matter how “up-to-date” the app itself appears.

In short, an application can receive frequent updates while its underlying native runtime layer remains frozen in time, riddled with known CVEs that will never be patched. This surprising reality means enterprises likely have hundreds of apps running on runtime code that hasn’t been updated in years, and typical software inventory tools simply do not flag this risk.

The Invisible Dependency Chain

Why is this problem so widespread and yet so often overlooked? It comes down to invisible dependency layers. Many enterprise applications have multiple layers:

  • Managed code or high-level logic, which gets updated regularly (for example, Java classes in a JAR, .NET assemblies, or the application’s own code).
  • Native runtime components, which are compiled libraries or executables that provide the foundation for that higher-level code to run.

Often, those native components do not change as frequently as the app logic. For instance, the core of the Java Virtual Machine (JVM) and its launcher is C++ code built with a fixed version of Visual C++. The vendor might release periodic Java updates (patching Java bytecode libraries), but the underlying C++ runtime dependency for that major version of Java remains constant throughout its life. In the case of Java 8, early releases used Visual C++ 2010 (EOL 2020), while later Java 8 updates moved to Visual C++ 2013 (EoL 2024). The application vendor may patch their code, but they often don’t recompile to a newer runtime. This leaves a frozen-in-time foundation under your apps.

The same pattern appears in many environments:

  • Backup and Utility Tools: Many backup solutions or disk utilities are C++ programs compiled long ago. In our investigation, we found a popular backup tool (Backupper) shipping executables still compiled with Visual C++ 2005 runtime (msvcr80.dll), which lost support on April 12, 2016.
  • Database and DevOps Tools: A surprising number of enterprise tools (even installers and updaters) use native C++ executables for certain tasks. We’ve observed some Python distribution installers bundling a Visual C++ runtime for their install wizard, again sometimes an older version than you’d expect.
  • Managed Platforms like Java and .NET: As noted, even “managed” platforms that we think of as high-level (Java, .NET) have native boots. .NET applications may rely on a .NET runtime version that itself can go out of support. For example, .NET 6 (which many apps adopted in recent years) reached its end-of-life on November 12, 2024, after which environment if an app still runs on .NET 6 runtime, no further security fixes will be provided for that runtime.

Scope of the Problem: Legacy Runtimes Lurking Everywhere

Let’s quantify how large this hidden risk can be. On a typical Windows endpoint in our enterprise sample, we discovered multiple generations of Visual C++ runtime libraries present, and often the older ones are still actually in use by some application. Here are the major legacy Visual C++ runtime versions and their support status:

Visual C++ Runtime VersionAssociated Visual StudioEnd of Support (Extended)Last Patch Released
8.0 (MSVC 2005)Visual Studio 2005April 12, 2016Final patch in 2016 (unsupported for ~10 years)
9.0 (MSVC 2008)Visual Studio 2008April 10, 2018Final patch in 2018 (unsupported ~8 years)
10.0 (MSVC 2010)Visual Studio 2010July 14, 2020Unsupported ~6 years
11.0 (MSVC 2012)Visual Studio 2012Jan 10, 2023Unsupported ~3 years
12.0 (MSVC 2013)Visual Studio 2013April 9, 2024Unsupported ~2 years
14.x (MSVC 2015-2026)VS 2015, 2017, 2019, 2022, 2026Still supported (binary compatible)Many installations outdated (patchable)

As shown above, five generations of Visual C++ (2005 through 2013) are completely unsupported today. Many enterprises still have one or more of these on devices, very often deployed during provisioning “just in case”. If an app on your system depends on any of those, it means it’s executing code that hasn’t seen a security update in years, often a decade or more.

Don’t overlook Visual C++ 14.x either. This unified runtime, used by Visual Studio 2015 through 2026, is still supported by Microsoft, but the catch is that many machines have outdated installations of it. The v14 runtime is binary-compatible across versions, meaning any app using vcruntime140.dll will work fine if you update the system’s VC++ 14 package to the latest. However, older 14.x packages ship with known security vulnerabilities, if you deployed Visual C++ 2015-2019 redistributables a few years ago and haven’t updated them since, those runtime DLLs likely have known CVEs. This is a “low-hanging fruit” remediation we’ll revisit: updating VC++ 14.x packages to the latest version is straightforward and immediately removes a swath of vulnerabilities.

The .NET 6 Runtime Timebomb

Parallel to Visual C++, Microsoft .NET 6 is a case study in how quickly a runtime can become a liability if left untracked. .NET 6 was a Long-Term Support (LTS) release (widely adopted since late 2021) with support through November 12, 2024. That date has come and gone, leaving countless applications still targeting .NET 6. Because .NET 6 was often packaged self-contained with applications (the runtime “hidden” inside the app directory), many businesses are unaware those apps even have an external dependency. An app packaged as a single executable or self-contained directory doesn’t require a separate .NET runtime install, so it won’t appear in system inventory as .NET at all. This makes .NET 6 particularly insidious, it can lurk inside modern-looking apps, quietly out-of-support.

Why traditional inventory tools miss these dependencies

If unsupported runtimes are so widespread, why don’t our normal tools catch them? Simply put, most IT inventory and vulnerability scanners focus on top-level applications and OS components, not the underlying binaries loaded at runtime. A few reasons:

  • Software Inventory Limits: Tools like Microsoft Intune or SCCM list installed applications, including  installed Visual C++ redistributables, but not their usage. Intune might show that “Microsoft Visual C++ 2010 Redistributable” is installed on a device, but it won’t reveal which applications need it. If you were to simply uninstall that package without analysis, any app relying on it would break. Conversely, if an app included its own copy of a runtime DLL, there may be no installer record at all.
  • Self-Contained & App-Local Deployments: Many vendors deploy runtime dependencies privately. .NET self-contained apps bundle their own runtime in the app folder. App-local Visual C++ means the DLLs are copied into the application directory rather than using a system-wide redistributable. Neither scenario registers in Add/Remove Programs. These components exist purely as files on disk, invisible to traditional software inventory.
  • No Central Manifest of Native Dependencies: Unlike, say, a Python requirements.txt or .NET project file, native binary dependencies (like a DLL name inside an executable’s import table) are not listed in any easily scraped manifest. The operating system handles loading them at runtime, so the presence of a dependency is only discoverable via binary analysis of the executable file itself.
  • Registry Doesn’t Tell the Whole Story: Some older runtimes (VC++ 2005/2008) used Windows Side-by-Side (WinSxS) manifests. But again, typical inventory tools don’t parse application manifest files scattered on disk for such references. And no standard inventory tool is enumerating registry uninstall keys and cross-matching to file system content to tell you if a runtime is truly in use.

In summary, to find these unsupported runtime dependencies, you have to go beyond normal inventory, you must examine the file system and binaries themselves. This is nondisruptive but requires a targeted approach.

How to tetect unsupported runtime dependencies at scale

Confronted with this challenge in my environment, I developed a multi-method detection approach using Intune Remediation scripts to scan each managed Windows endpoint for signs of legacy runtime usage. These detection scripts run on the endpoints themselves, enabling a deep inspection that central tools cannot easily do remotely.

Combining multiple detection methods

For each category (.NET runtime and VC++ runtime), I implemented five complementary detection techniques to maximize coverage:

  • Visual C++ Runtime Detection (for VC++ 2005–2013 and outdated 14.x):
    • Registry Scan for Installed Legacy Packages: Scans Windows uninstall registry keys for entries indicating an old VC++ Redistributable package (2005–2013).
    • PE Import Table Analysis: Reads the import tables of executables (in Program Files directories) to detect any that explicitly import legacy runtime DLLs (e.g. msvcr80.dll, msvcr90.dll, … msvcr120.dll). This finds apps compiled against those runtimes.
    • App-Local DLL Search: Searches Program Files for any copies of the legacy runtime DLLs sitting in application folders (bypassing the system install).
    • Side-by-Side Manifest Check: Looks through application .manifest files in Program Files for references to VC++ 2005/2008 Side-by-Side assemblies (e.g. microsoft.vc80.crt).
    • Outdated VC++ 14.x Version Check: Queries registry for the installed Visual C++ 2015-2026 (v14) version and flags it if below the latest security-patched version.
  • .NET 6 Runtime Detection:
    • runtimeconfig.json Scan: Recursively scans for any .runtimeconfig.json files indicating apps targeting .NET 6.x (e.g. JSON entries with framework.version: 6.x).
    • deps.json Scan: Looks into any .deps.json files for references to .NETCoreApp,Version=v6, a sign of .NET 6 framework usage. (This catches cases where an app’s config might not explicitly list the version but its dependency file does.)
    • Self-contained Binary Scan: Searches for the presence of coreclr.dll in app directories and checks its file version. If it starts with 6.x, that directory contains a self-contained .NET 6 runtime.
    • Single-File Bundle Scan: Scans for large .exe files and checks for a .NET single-file bundle signature plus embedded 6.x framework string, catching .NET 6 apps packaged as one file.
    • Registry + InstallLocation: For installed apps listed in registry, combine their InstallLocation with a quick scan for any .runtimeconfig.json in that folder referencing .NET 6. This can find registered apps that include .NET 6 subcomponents.

Each method catches a different subset of cases. Used together, they provide a comprehensive net to identify virtually any application that relies on .NET 6 or legacy VC++ runtimes on the system. The detection scripts are carefully optimized (e.g. file size and depth limits, timeouts) to run within Intune’s execution time limit (~3 minutes per device) and to avoid overwhelming endpoints. They output a compact summary of findings per device.

Intune Remediations: Pros & Cons

I chose Microsoft Intune’s Remediation feature to deploy these detection scripts broadly.

Pros: It allows pushing a PowerShell script to every enrolled Windows device and scheduling it (daily or weekly). The script runs locally (with SYSTEM privileges for full access) and can report compliance or findings back to Intune. This approach leverages existing infrastructure, no new agent or tool needed, and the detection runs silently in the background. It’s an effective way to “crowdsource” a scan across thousands of endpoints to gather data on runtime dependencies.

Cons/Tactical Trade-offs: Intune outputs are limited to 2048 characters, so the script output for each device must be summarized and possibly truncated if too many findings. We addressed this by only listing the first 10 detected apps per device in detail, then indicating if more were found. Additionally, thorough scanning of Program Files can be heavy, although we limit depth and skip well-known large directories like Office Click-to-Run to keep runtime under control. In practice, on typical hardware the scanning completes in 5–30 seconds per device for the .NET script (possibly a bit more for the VC++ script due to binary parsing).

Another consideration: The data aggregation. Intune will show you which devices are “non-compliant” (i.e. have findings) and up to the first 2048 characters of their report. To get a holistic picture, we exported the results from Intune (“Device run states” CSV) and then wrote separate post-processing scripts to parse and analyze the data across the fleet. This gave us consolidated lists of detected apps, their prevalence (device counts), and vendor groupings for prioritization. While this adds effort, it’s a one-time development and allowed turning raw data into an actionable inventory of problem areas.

What Did We Find? (Real-World Example)

The findings were eye-opening. Even on a single test device, the detection script reported dozens of issues. For example, on one enterprise Windows 11 laptop (provisioned 2 years agon), our VC++ runtime scan returned 47 distinct findings in one run. This included:

  • 21 installed legacy VC++ packages (various x86/x64 versions from 2005 through 2013). Intune’s software inventory showed these as installed, but not flagged them as a problem, the device looked “compliant” prior to our scan.
  • 23 executables on disk importing out-of-support VC++ DLLs (including multiple Java JRE tools compiled against VC++ 2010, a backup tool compiled against VC++ 2005, and others).
  • 3 application directories containing private copies of legacy VC++ DLLs (so-called “app-local” runtimes that never use the system’s updated libraries).

This was one device (albeit an outlier)! Multiply that by thousands of endpoints (in our case 100k Windows devices) and you start to see why this is a significant blind spot. In our broader scan, we identified hundreds of unique applications across the environment that in some way depended on an out-of-support runtime.

On the .NET side, for any devices using .NET 6 apps, similar counts appeared, though .NET 6 being newer (just “recently” EoL) meant fewer legacy apps yet, but still enough to warrant action. The bottom line is the hidden dependency problem is real and prevalent, and quantifying it via targeted scanning is the first step to mitigation.

Acting on the Findings: Remediation Strategies

Once you’ve uncovered a trove of applications running on unsupported runtimes, the natural question is “What now?” There’s no one-size-fits-all fix, but we can outline a tiered remediation strategy from easiest wins to hardest challenges:

1. Quick Wins (Low Effort, High Benefit): Update the Visual C++ 14.x Runtime on all devices to the latest version. Since VC++ 14 is binary-compatible across 2015–2026 releases, updating the redistributable package won’t break any apps. It will, however, close known vulnerabilities present in older 14.x versions. Deploying the latest “Microsoft Visual C++ Redistributable for Visual Studio 2026” (which covers all 14.x needs) via Intune or SCCM is straightforward. This step immediately secures a huge swath of “outdated but still supported” runtime usage with minimal risk.

2. Remediate Outdated Installations (Medium Effort): Address devices that have legacy VC++ runtime packages (2005-2013) installed. This is tricky, you cannot just remove those packages without potentially breaking applications. Instead:

  • Inventory first: Use your detection results to map which apps require each legacy runtime. Focus on those with the widest use or highest risk (we prioritized anything on >500 devices as “high impact”).
  • If a runtime is installed but no apps actively use it (possible in some cases), you can safely uninstall it from those machines, eliminating the risk entirely. Often, though, presence indicates at least one dependent app exists unless you’ve had a habit of always deploying the runtimes during device provisioning.
  • For each app that needs a legacy runtime, see if a newer version of that app is available that uses a modern runtime. For example, if an app was compiled in 2010, perhaps the vendor has since updated it in 2018 to use a newer VC++, upgrading the app might eliminate the old dependency.
  • If no update is available, this becomes a “hard problem” case (see #3 below).

3. Hard Problems (High Effort or Vendor Dependency): This covers the toughest category:

  • Applications that embed an end-of-life runtime (discovered via PE import, app-local DLLs, or SxS manifest findings). For these, usually the only true fix is to get an updated version from the vendor or to plan for retiring/replacing the application. If the vendor still exists and supports the product, engage them, share your findings, ask if a recompiled version using a supported runtime is available or planned. If the vendor is defunct or unresponsive, you may need to accelerate a transition to an alternative solution that isn’t built on a 10-20 year old codebase.
  • .NET 6 Applications: If you find applications (especially internal/custom ones) that target .NET 6, plan to upgrade them to a supported .NET version (like .NET 10 LTS). For internally developed apps, moving from .NET 6 to .NET 10 can be relatively straightforward, often just a project setting change and recompiling, plus testing. For vendor software, request their roadmap for .NET 10 or later support, as running on an unsupported .NET runtime is not tenable long-term. If they cannot commit, treat it like any other end-of-life application scenario.

Throughout remediation, it’s crucial to prioritize based on impact and exploitability. Not all findings are equally urgent. If one legacy runtime is only used by an obscure app on 10 machines, whereas another is present on 5,000 machines, focus your immediate efforts on the latter.

Conclusion: Shine a light on your Runtime Dependencies (Call to Action)

Unsupported runtime components represent a serious enterprise security risk precisely because they remain unseen by standard tools. It’s a blind spot we can no longer afford. The examples above underscore how even well-managed environments harbor hidden pockets of decades-old code, code that adversaries know they can exploit if left in place.

The good news: We now have ways to find and address these issues. By using techniques like the Intune Remediation scripts described (which I have made available via an open-source toolset, see the accompanying GitHub repository for details), you can quickly map out all the outdated runtime dependencies in your Windows fleet. Once you have this data, you can take immediate steps: push out updated runtime packages, schedule application upgrades or replacements, and eliminate those silent vulnerabilities bit by bit.

It’s time to end the era of security by accident when it comes to runtime libraries. Don’t assume an app is safe just because its own version is current, verify the foundation it’s running on. With some proactive scanning and remediation, you can ensure your enterprise’s software isn’t quietly running on borrowed time.

Next Steps, Your Move: Start by deploying a runtime dependency detection script to audit a pilot set of devices. Gather the data, identify the biggest pain points, and make a plan. We invite you to check out the detection scripts and guides in our GitHub repository, and join the conversation on LinkedIn or in the community about strategies for tackling legacy runtime risks. Shining light on this hidden threat is the first step to defeating it, it’s time to take action.

Anders Ahl

Anders has been wrangling IT systems since the days when “cloud” just meant bad weather and deployments came on floppy disks (yes, the actual floppy kind, that look like the Save-icon). With over 30 years in the industry, he’s seen it all - from Enterprise management and Security to Windows device deployments that didn’t involve USB sticks or Wi-Fi.
He spent seven years architecting solutions at IBM and then clocked nearly two decades at Microsoft, where he wore many hats (none of them floppy): Consultant, Architect, and most recently, a Principal Product Manager on the Intune team. If it involves managing devices, securing endpoints, or navigating the maze of modern IT, Anders has probably done it, automated it, and is proudly wearing the t-shirt.
He’s also a big fan of Zero Trust (even though he can absolutely be trusted!). Whether he’s talking policy, posture, or patching, Anders brings deep technical insight with just the right amount of dry humor and real-world wisdom.

Add comment

Sponsors

Categories

MSEndpointMgr.com use cookies to ensure that we give you the best experience on our website.