DeepFactor Whitepaper Observing Application Behavior Using API Interception
DeepFactor Whitepaper Observing Application Behavior Using API Interception
Observing
Application Behavior
via API Interception
// Contents
Challenges to Understanding Application Behavior 2
Inline Hooking 6
Callbacks 7
Method Rewriting 10
300 %
environments remains low. This is one of many examples to highlight how
developers have struggled to thoroughly understand and assess the expected
behavior of constituent modules being imported.
> Static Application Security > Application Performance > Cloud Management platforms
Testing (SAST) and Software Monitoring is the practice enable organizations to
Composition Analysis (SCA) are of tracking key software manage and optimize cloud
focused on statically scanning application execution metrics (i.e. on-prem and/or private)
the application’s makeup (also to ensure system availability, services and resources, such
known as “Bill of Materials”) optimize service performance, as provisioning and managing
to identify vulnerable and improve response times. the lifecycle of the cloud
components. infrastructure being used by
the application.
artner, “A CTO’s Guide to Top Practices for Open-Source Software”, 21 March 2021, Arun Chandrasekaran, Mark Driver. GARTNER is a registered trademark and
1 G
service mark of Gartner, Inc. and/or its affiliates in the U.S. and internationally and are used herein with permission
2 VentureBeat, “Report: Software supply chain attacks increased 300% in 2021”, 27 January 2022, VB Staff.
API Interception is the practice of “hooking” and redirecting calls to an application programming interface (API) into
custom code. Though API interception can be used to alter application functionality, developers can also use the
technique to observe application behavior in near real-time. By observing which APIs are called, what parameters are
passed to those APIs, what return values are handed back to the application, how long the APIs took to execute, and
so forth, API interception can help the developer understand end-to-end application behavior.
There are many ways to perform API interception, and the objective of this whitepaper is to outline the most common
techniques, and detail strengths and weaknesses of each approach. In addition, the last section of this whitepaper
reviews how Deepfactor, a developer security platform, has used API interception for the purposes of providing
engineering teams with contextual application security insights.
System Calls Programmatic way in which applications request a service from the kernel of the
operating system it is executed on. Typically, an operating system will have a few
hundred system calls spanning memory management, file operations, network
connections, and so on.
Base Library Calls Simple runtime library (e.g. libc) for modern programming languages found on
Linux and UNIX operating systems. Provides additional parameters —such as
checking and/or error handling—for system calls, and functions such as random
number generators, string manipulation, etc.
Higher Level Library Calls Delegates to lower base libraries. There are thousands of libraries in this class,
including glib, openssl, protobuf, and so on.
Web APIs Framework that helps create and develop RESTFUL services; transactions occur
over the web using the HTTP protocol. Examples include payment processing APIs,
CSP (cloud service provider) APIs, or any number of REST APIs exposed by network-
facing services.
Obviously, this presents a challenge—how do developers even begin to answer the question of which APIs to
intercept? In many cases, this decision can be driven by experience and personal bias.
For example, web developers who work exclusively with RESTful applications, may conclude that web APIs are the
only APIs worth intercepting to fully understand application behavior, security, and performance. However, assuming
that a developer exclusively used a tool or platform for intercepting and analyzing said web APIs, they might miss
identifying vulnerable libraries, such as OpenSSL, being used by the web framework. Regardless of the security of the
web APIs, the underlying operating system and application would remain exploitable.
The same could be said for exclusively intercepting low-level APIs. Developers or operation teams who chose to
narrowly observe the stream of system calls being invoked by the application could fail to notice contextual security
clues only evident in higher level library calls. As a result, developers would be unaware of a SQL injection vulnerability
or that the encryption library is using deterministic random numbers.
So, assuming there’s a chance of omitting valuable information through the interception of a single layer of APIs,
engineering teams should then ensure complete coverage by intercepting all APIs, right? While technically true, this
is obviously an impossible task! This especially true when you consider many developers are unable to produce an
exhaustive list of APIs used in a given application.
Rather, in order to answer the question, ”What APIs should we observe (i.e. intercept) to understand our application’s
behavior?” engineering teams should strive to understand the answers to the following questions:
By thoroughly answering these questions, engineering teams can begin to create well-informed strategies around
the appropriate API intercept technique. Ultimately, the goal is to obtain a behavioral fingerprint of the application by
using API interception to create a list of used—and not used—APIs. This fingerprint can be used by developers as a
baseline for later comparison, such as performing drift analysis between releases, and prioritization and remediation
of open vulnerabilities.
There are several common technological requirements to consider when evaluating the various options available for
API interception. In addition to identifying when and where a desired API is called, the API interception technique
must never:
However, regardless of the technique used to observe and log application behavior, control must be diverted to the
interception tool at the exact moment the API is called to conclusively identify usage and purpose. Considering the
requirements outlined above, this can be achieved several different ways:
Inline Hooking Rewrites the first part of a monitored function to divert control flow to the API
interception tool which then logs any desired information about the API being called.
Callbacks Requests that the operating system perform the API monitoring and callback to
the API interception tool (or run some code provided by the tool) when the selected
APIs are called.
Import Table Rewriting Requests the operating system call the interception tool’s own library functions
in preference to those of existing libraries. The API interception tool receives the
invocations first, then logs any desired information about the API being called.
Method Rewriting Appends callback code at the start of each function/method being loaded by
the application to log desired information about the API being called.
In the case of using inline hooking for API interception, control flow is diverted to the API interception tool, where the
program must identify which function was called—this is because more than one function might have been hooked.
This is accomplished by a) diverting control flow to a unique place in the API interception tool for each hooked
function, or by b) diverting control flow to a common location and walking the stack to determine which function was
previously called.
The following diagram, Figure 1, compares the process of normal execution and the control flow changes after inline
hooking has occured:
Source Target
Function Function
// Figure 1:
Application execution with and without inline hooking.
Once the API interception tool logs the necessary information about the invocation, control flow must return to the
original function just after the initial hook occurred. However, before this can happen, the instructions destroyed by
the original hook need to be replayed.
Depending on the operating system and API, overwritten instruction(s) may take many forms, necessitating the
implementation of a full or nearly-full instruction emulator inside the inline hooking module. This complicates the
implementation of the solution and can lead to fragility of the module when it’s not kept up to date. This includes
maintaining support with new CPU instruction sets and ensuring the overwritten instructions remain supported/
recognized by the hooking module.
> The overhead to detour the invocation to the API interception module is minimal, with just a single instruction
(plus the decoding required to emulate the overwritten instructions) required.
> In addition, inline hooking can be used in scenarios where the application is statically linked, which is not
necessarily true of other interception techniques, such as import table rewriting.
> While stacking/chaining is supported, there are limitations when multiple inline hooking systems are used
in conjunction. Notably, extra care must be taken to ensure each hooking module is capable of replaying the
overwritten instructions from the preceding module in the chain.
> Hooks can be extremely difficult to [safely] remove from running applications. Since most hooks are placed before
the application starts, they are not subject to partially patched conditions in the application. However, the same is
not true when reversing the process —only one hook can be removed at a time, resulting in a situation where the
application is always partially patched. This restriction also applies to stacking hooks, as described previously.
> Processor architectures without coherent instruction/data caches make hooking after the application
challenging. This is because placing a new instruction into the instruction stream (via data write), where a
different CPU is executing instructions fetched from the same instruction cache line, might end up executing
the previous, unpatched instructions. Inline hooking modules must be carefully written to avoid cases like these.
Callbacks
Whereas inline hooking relies on the CPU to communicate with the API interception tool, some operating systems
provide a callback interface to register “interest” in a particular set of application APIs. Callbacks enable the operating
system to execute a code snippet—generally provided by the API interception tool—at the moment any API of
interest is called.
The most popular type of callback is Extended Berkeley Packet Filter (eBPF). With eBPF, the API interception tool
can attach small code fragments, also known as programs or snippets, to desired system call(s). eBPF programs are
written in the BPF language, and are event-driven, which means they run whenever the kernel or application passes a
certain hook point. This can include system calls, function entry/exit, kernel tracepoints, network events, etc.
There are various ways to install and execute eBPF programs/code snippets:
1. Sidecar: Container that runs alongside the application container for the purpose of externally monitoring
application behavior.
2. Software Agent: Daemon program that is installed on the host/node running the application which needs to
be monitored.
3. prctl System Call: Monitoring program that bundles eBPF code to be dynamically loaded directly into the
kernel at startup.
Syscall
Linux eBBP
Kernel
Scheduler
// Figure 2:
Simplified overview of eBPF architecture and process.
There are several benefits to using callbacks, such as eBPF. Most notably, eBPF
offers a robust, feature-rich environment for programmability (although, this is
not without its drawbacks—more on that below), making its unified framework
convenient to operationalize. In addition, because eBPF doesn’t modify
application code, there is zero-to-minimal impact to application behavior.
> eBPF is limited to intercepting system calls, which means higher-level APIs are not observable. This limits
eBPF to streaming low-level system calls back to the developer. That said, recent versions of eBPF do provide
uprobes, which can be used to observe certain higher-level APIs. However, the lack of uniform support limits
the usefulness of this feature.
> eBPF requires specific permissions to be granted, such as allowing the application to run code inside the host
kernel. While this may be acceptable for development environments, this is not always possible, either due to
company policy or technical limitations such as services running in the public cloud.
Whereas inline hooking requires modification to application code bytes in memory, import table rewriting simply
modifies the dynamic loader to intercept and wire up (any) library calls to a different location. This means import
table rewriting can be used to capture and observe both base and higher level library calls. Though this technique
does not allow for the direct interception of system calls (similar to eBPF), intercepting the base library call wrappers
for system calls is typically sufficient.
Import table rewriting is usually implemented in Linux/UNIX systems using the LD_PRELOAD environment variable.
Setting LD_PRELOAD instructs the operating system to preload the libraries/functions listed in the variable, thus
prepending them into the runtime dynamic linker’s (loader) resolution cache. Subsequently, when the application
is loaded and has an import dependency on a specific symbol—assuming the symbol exists in the LD_PRELOAD
libraries—the loader will perform a redirect to the new location.
// Figure 3:
Example of import table rewriting using malloc and LD_PRELOAD.
*
> In most circumstances, because it doesn’t require instruction emulation (i.e.
inline hooking) or use an interpreter (i.e. callbacks), important table rewriting
offers the best overall performance*.
There is a common
> Import table rewriting can intercept APIs not available to eBPF. For example,
misconception that
LD_PRELOAD can be used to intercept the rand API (which is a deterministic
having the same
random number generator), or the notably unsafe functions operating on
unchecked length strings (e.g. strcpy). These APIs never result in system calls, library loaded into
thus they are unable to be observed by callback techniques. each process requires
more memory than
The one notable drawback to using LD_PRELOAD for import table rewriting is the other interception
absence of support for statically linked applications, which do not use the dynamic
approaches. However,
linker (loader). In this scenario, either inline hooks and/or eBPF must be used.
this is generally not
true as the operating
Method Rewriting
system will share
Method rewriting is an API interception technology that works with bytecode-
code and read-only
interpreted languages, such as Java and .NET framework applications. This
data pages from the
approach allows API interception tools to prepend custom code to methods being
used by the application. LD_PRELOAD library
across processes.
Similar to inline hooking, the interpreter (e.g. Java Virtual Machine) will execute the
additional code first, followed by the original code. Generally speaking, the code
added to each method is short, usually focused on logging which method was
called, including parameters and timestamp.
When considering how to intercept a Web API (e.g. REST APIs), there are numerous places one can monitor. A key
concept in all approaches is first understanding which APIs need to be monitored; these APIs are usually modeled as
HTTP/HTTPS GET/POST requests with varying URI patterns.
When monitoring which APIs are called from a client, one might approach the interception task depending on the
information required:
// Figure 4:
Overview of the various methods for intercepting web APIs
> Which technique > Which technique > Which technique > Which technique is
is easiest to deploy has the best is the least likely to compatible with the
and maintain? performance? affect application most host systems?
behavior/stability?
These additional evaluation criteria are especially important when considering the intended use case and target
persona. Using API interception to capture application security insights for engineering teams is going to require
different characteristics than a tool built to measure application responsiveness. The same could be said of
interception tools being purpose-built for engineering teams versus operations—each group wants to intercept
different sets of application APIs.
For example:
> Operations teams—who generally want to measure application behavior in live, customer-facing production
environments—are going to prefer interception techniques that are stable and performant. However, given the
focus on observing application responsiveness on workloads with lengthy uptime, finding a solution with broad
API coverage and maintainability can be deprioritized.
> Conversely, during the early stages of the development lifecycle, such as testing in pre-production environments
(i.e. staging), extended API coverage and seamless deployment may be worth performance overhead to create a
seamless and encompassing experience for developers.
Performance
Stability
Compatibility
API Breadth
When reviewing the above table, it’s readily apparent that no single intercept technique can be ranked “Best”. This is
because the right solution should be found in a combination of the various options. For example, although method
rewriting (generally) suffers from sub-par performance, it’s the only technology that offers enough code coverage
for the creation of a usage-based Software Bill of Materials (SBOM). However, as discussed, using method rewriting
exclusively would result in missing APIs of interest. Depending on the use case, this is why method rewriting should
be paired with import table rewriting or callbacks for maximum coverage.
Given these overwhelming challenges, and the industry’s drive to adopt DevSecOps, Deepfactor identified an opportunity to
develop a next-generation developer security platform. The goal was to intercept and analyze application APIs to provide
engineering teams with the information needed to identify, prioritize, and remediate application risks. And with the focus
on developers, the experience needed to be seamlessly integrated into the CI/CD pipeline.
In order to accomplish these objectives, Deepfactor employs a number of interception techniques to deliver
application-aware security insights with detailed information about application behavior, system calls, and stack
traces that help pinpoint vulnerable code. Deepfactor currently uses import table rewriting to intercept APIs in most
compiled, dynamically-linked applications. This enables Deepfactor to capture and observe both base and higher
level library calls—without significant performance overhead—to create detailed security alerts.
In order to make this easier for developers to implement, Deepfactor instrumentation (i.e. setting the interception
library for LD_PRELOAD) can be configured in the following ways:
> Kubernetes Mutating Webhook which can automatically load the Deepfactor interception library into
Kubernetes Pods upon deployment.
> “Docker Run” configuration that automatically launches a containerized application with the Deepfactor
interception library preloaded.
> Standalone CLI tool wherein a developer can manually run any process with the Deepfactor interception library.
In addition, Deepfactor automatically detects and loads programming language-specific plugins for Java, Python,
and nodeJS. These language-specific plugins use method rewriting to provide additional contextual information
around API usage to the developer. For example, the Java plugin provides Java stack traces for each monitored API,
as well as providing a list of all used and unused methods in the application.
Looking to the future, Deepfactor continues to evaluate additional interception techniques to extend
platform capabilities, such as improving security insights, identifying risky runtime behaviors,
and supporting additional languages and deployment models. If you would like to see the Deepfactor
developer security platform in action, you can request a demo here.
Deepfactor is a developer security platform that enables engineering teams to quickly discover and resolve security
vulnerabilities, supply chain risks, and compliance violations early in development and testing. For more information,
follow Deepfactor on Twitter or LinkedIn or contact us.
©2022 Deepfactor, Inc. Deepfactor is a trademark of Deepfactor, Inc. All other brands and products are the marks
of their respective holders.
deepfactor.io W P.0322V2