How EDR Detects Dynamic API Resolution at Runtime

Endpoint Detection and Response (EDR) solutions closely monitor how processes interact with the Windows loader. One common behavioral signal used by EDRs is dynamic API resolution, which is frequently abused by malware to evade static analysis and signature-based detection.

This section explains, from a custom EDR (NORM) perspective, how dynamic API resolution can be detected at runtime.

What Is Dynamic API Resolution at Runtime?

Dynamic API resolution at runtime means a program loads DLLs and resolves Windows API functions while it is already running, instead of listing them in the PE import table at compile time. This is commonly done using LoadLibrary and GetProcAddress.

Because these APIs are not present in the static import table, static analysis tools cannot see them in advance. However, when the program executes, the Windows loader still has to map the requested DLLs into memory. EDR solutions monitor this loader activity and can detect when a process loads DLLs that were not originally declared.


Static vs Dynamic DLL Loading

On Windows, DLLs can be loaded in two fundamentally different ways:

1. Static DLL Imports

Static imports are defined at compile time.
When a binary is compiled, the linker records all required DLLs and imported functions inside the Import Directory of the PE file.

At runtime:

  • The Windows loader automatically loads these DLLs.
  • The list of statically imported DLLs is available in the IMAGE_IMPORT_DESCRIPTOR table.
  • These DLLs are expected and predictable.

From an EDR standpoint, this forms a baseline of what the process claims it needs in order to run.


2. Dynamic DLL Imports

Dynamic imports occur at runtime, typically using APIs such as:

  • LoadLibraryA / LoadLibraryW
  • GetProcAddress

In this case:

  • The DLL is not listed in the Import Directory.
  • The loader maps the DLL only when the code explicitly requests it.
  • Malware often uses this technique to hide sensitive API usage (e.g., networking, injection, encryption).

Dynamic API resolution is not inherently malicious, but it is a strong behavioral signal when combined with other suspicious activity.


EDR Detection Strategy (High-Level)

OUR custom EDR, NORM, detects dynamic DLL loading by correlating two runtime data sources:

Step 1: Extract Static Import Baseline

When an executable image is loaded:

  • NORM parses the PE headers directly from the memory-mapped image.
  • The IMAGE_IMPORT_DESCRIPTOR table is read.
  • All statically imported DLL names are stored in an internal table for that process.

This table represents:

“Which DLLs does this binary claim it needs if loaded normally?”


Step 2: Monitor Runtime Image Loads

NORM registers a kernel callback using:

PsSetLoadImageNotifyRoutine

This callback is invoked whenever:

  • A new executable image is mapped
  • A DLL is loaded into a process address space

For every DLL load, NORM receives:

  • Process ID
  • Image base
  • Image size
  • Full image path

Step 3: Static vs Dynamic Correlation

For each DLL load event:

  1. Identify the owning process.
  2. Compare the loaded DLL name against the static import table collected earlier.
  3. If the DLL does not exist in the static import list:
    • It is classified as a dynamically loaded DLL.
    • A detection signal is generated.

This logic allows NORM to distinguish:

  • Expected loader behavior
  • Runtime API resolution performed by the application itself

Test Case: Dynamic DLL Loading Example

To validate this detection logic, the following test program is used:

#include <windows.h>

#include <stdio.h>

int main()

{

    printf("Process started\n");

    Sleep(2000);  // Allow the EDR driver to observe process creation

    HMODULE h = LoadLibraryA("wininet.dll"); // Not statically imported

    if (h)

    {

        printf("wininet.dll loaded dynamically\n");

        FreeLibrary(h);

    }

    else

    {

        printf("Failed to load DLL\n");

    }

    Sleep(5000);

    return 0;

}

Expected Behavior

  • wininet.dll is not present in the Import Directory.
  • When LoadLibraryA is called, the Windows loader maps:
    • wininet.dll
    • Its dependent DLLs

Detection Result in NORM

While NORM is running:

  • Only the statically imported DLLs are observed initially.
  • When wininet.dll is loaded at runtime:
    • The image load callback is triggered.
    • The DLL is missing from the static import baseline.
    • NORM flags the event as dynamic API resolution.

This is exactly how many commercial EDRs detect:

  • Malware loaders
  • Downloaders
  • Post-exploitation frameworks
  • Living-off-the-land binaries (when abused)

Figure 1: Starting the EDR NORM

Figure 2: Executing the POC.exe

Figure 3: Callback when POC.exe created

Figure 4: Getting alert when static and dynamic DLL not match


Important Note: Signal, Not a Verdict

Dynamic API resolution alone is not malicious.

Legitimate software may:

  • Load optional components
  • Delay-load networking libraries
  • Use plugins or modular architectures

Therefore, production-grade EDRs treat this as a behavioral signal, not a standalone detection. It becomes meaningful when correlated with:

  • Suspicious parent process
  • Memory injection
  • Unusual network activity
  • Execution from non-standard paths

Conclusion

By correlating:

  • Static import analysis
  • Runtime image load monitoring

NORM demonstrates how EDRs can reliably identify dynamic API resolution without relying on user-mode hooks or API interception.

This approach is:

  • Kernel-driven
  • Loader-aware
  • Resistant to basic user-mode evasion

Leave a Comment