WebLink and the Case for Browser-Space HTTP Offload
Contents
Series: Minimizing EDR Attention
This post is part of the Minimizing EDR Attention series on adapting red team implant tradecraft to the current detection landscape shaped by modern EDRs. The broader series is about narrowing the gap between how implants are often built in labs and how they are increasingly exposed in real environments. Each post takes one concrete problem seen in implants in the wild and looks at a focused design response.
Series Objectives
The overarching goal of the series is minimizing EDR attention.
That does not mean pretending EDR can be “bypassed” once and for all. It means treating EDR for what it often is in practice: a scoring and weighting machine, increasingly influenced by cross-signal correlation and ML-trained expectations about what implant behavior looks like. If detection products are trained to recognize linear implant execution patterns, then a useful design goal is to consistently reduce the linearity, concentration, and obviousness of those artifacts.
The broad strategy is:
- move implant behavior away from single-process concentration
- break responsibilities across cooperating components where that lowers attribution confidence
- engage existing system facilities rather than forcing all logic through one implant-owned code path
- reduce the number of places where host, memory, lineage, and network signals collapse into one obvious object
Put differently, the series is about sub-threshold operation across as many sensory surfaces as possible. WebLink is one concrete example of that philosophy.
If a shorthand is useful, the direction is closer to chromatophore than cloak: not invisibility, but closer behavioral mimicry. The aim is to keep pushing the detection stack to reason across more context, more components, and more ambiguity instead of handing it one clean linear story.
The Problem WebLink Addresses
This post focuses on WebLink and specifically on the HTTP offload use case.
Many implants still concentrate too much into one executable identity. The same process owns lineage, memory artifacts, tasking logic, and outbound HTTP/S traffic. That makes host-side and network-side signal collapse into one object, which is exactly the kind of linear story modern EDR products are good at scoring.
WebLink addresses that by moving part of implant operation into browser space. In this post, the concrete implementation is HTTP offload: the HTTP call site becomes the embedded browser runtime rather than a custom implant-owned HTTP client. The broader architectural value is larger than that one use case, but HTTP offload is the easiest place to show why the split matters.
This specific TTP is Windows-only. The implementation described here depends on the Windows WebView2 runtime, COM-based control of the embedded browser, and Windows process creation behavior used for relocation and parent shaping.
That architectural change comes first, and use cases flow from it. Later posts can focus on other implementations built on the same base, especially the browser-as-co-processor idea: key generation, encryption, and payload staging remaining in browser space until native code explicitly needs them.
It is also important to be explicit about what this repo is not doing. WebLink is not trying to minimize beacon-detection analytics in its current form, and the question / answer flow in this project is a deliberately contrived example of a beaconing implant modeled as a predefined task catalog. It is a non-operationalized demonstration of the concept, intended to show the browser/native split clearly rather than present a finished implant design.
Three Design Components
For this post, WebLink should be understood as three cooperating design components:
WebLinkthe native implant or client processWebLinkLibthe library that drives the WebView2 bridge and message exchange- an ephemeral remote server the server-side controller for the JavaScript running on the browser side
In the current repo, the browser-side server is represented by WebLinkSrv, which serves the page, the JavaScript bridge code, and the local question/answer flow. In the broader design, that server-side component is the control plane for the JavaScript payload living inside browser space.
The key point is that WebLinkLib is the enabling layer consumed by the implant. HTTP offload is one use case built on top of it. Other use cases are extensions of the capabilities exposed by WebLinkLib and consumed by the native implant.
Why This Matters Architecturally
WebLink is not just a trick to change process-to-network attribution. That is one useful effect, but the deeper value is that browser memory becomes part of implant operations.
Instead of treating the browser as a rendering shell or a convenient HTTP wrapper, WebLink makes the embedded browser a usable operational component that native code can drive over the COM interface.
Why that is interesting:
- browser memory is a distinct execution space adjacent to native code
- that space is shaped by JavaScript runtimes, JIT behavior, browser-managed object lifetimes, and browser-specific process and memory mechanics
- native code and browser-side code can remain separated while still cooperating through WebView2 COM messaging
- operators can choose when material crosses from browser space into native space
- the WebView2 runtime itself (
msedgewebview2.exe) is a Microsoft-signed component, which may carry higher baseline trust expectations around the browser-side execution surface
For this post, that model is expressed as HTTP offload:
- the HTTP call site becomes the embedded browser runtime
- the JavaScript payload performs web requests from a browser-backed surface
- native and JavaScript communicate without introducing an extra localhost network bridge
Other implementations can follow from the same architecture:
- key generation for payload handling in browser memory
- encryption and decryption operations in browser memory
- browser-side preparation or hosting of payload material
- controlled timing and exposure of what crosses from JavaScript into native memory
The important point is to understand WebLink first as an architectural change. HTTP offload is the use case this post showcases. Other posts can expand the capability surface of WebLinkLib from there.
WebView2 Control Model
At a high level, the control model looks like this:
This is the core design rule:
- native code owns lifecycle and scheduling
- browser-side JavaScript owns browser-originated HTTP
- native and browser cooperate over WebView2 COM messaging rather than an extra local network bridge
How WebLink Fits
The HTTP offload use case in this project looks like this:
The implant is still native and the bridge is still native, but browser-side JavaScript becomes the place where HTTP originates. That changes attribution without introducing an additional localhost transport layer.
Concrete Bridge Example
The Mermaid diagram is easier to read alongside the actual bridge code.
On the native side, WebLink configures the poll script in ClientConfig.cpp:
|
|
That string is passed into WebLinkLib, and WebViewBridgeHost::TriggerPollFromNative() executes it through ICoreWebView2::ExecuteScript(...). In other words, native code does not call the Bun server directly. Native code asks the page to run its own JavaScript entrypoint inside the embedded browser.
On the page side, WebLinkSrv/bridge-page.js defines the function native is invoking:
|
|
That is the native-to-JavaScript half of the bridge:
- native fires
ExecuteScript(...) - the page runs
window.hostBridge.pollOnce() - the page performs browser-originated HTTP
- if work exists, the page posts a typed message back into native
The page_ready path is the inverse example. When the page finishes loading its bridge, it sends:
|
|
createPageReadyMessage() is just:
|
|
WebLinkLib catches that in WebViewBridgeHost::SetupWebViewHandlers() through add_WebMessageReceived(...). The raw JSON is then handed to DispatchHostInboundMessageJson(...), which recognizes PageReadyMessage, sets isPageReady, stops the reconnect timer, and starts the steady-state native poll timer.
The question path uses the same message channel. When browser-side JavaScript posts a question message, DispatchHostInboundMessageJson(...) parses the payload and resolves the question id through the native callback registry. The default registry is built in AppQuestionCallbacks.cpp, where the implant can register callback handlers for question ids and return native answers back to JavaScript. That is the extension point where an implant adds its own responses to JS-to-native bridge messages.
Why Embedded WebView2 Instead Of A Normal Browser Tab
An embedded WebView2 gives the operator a useful middle ground:
- it preserves a real browser execution model, including normal browser-origin semantics
- browser-side code can still use browser features such as origin-bound storage, including OPFS
- the native host owns process lifetime, scheduling, and when browser work is triggered
- the operation does not depend on a user keeping a normal browser tab open
That does not mean WebView2 is immune to background throttling or power-management effects. A hidden WebView can still be backgrounded and subject to Chromium-style timer policies. It is still a better operational surface than a normal browser tab when the goal is to preserve browser behavior while keeping lifecycle control in the native host.
The practical tradeoff is:
- browser tab: stronger browser identity, weaker lifecycle control
- embedded WebView2: browser identity plus native lifecycle control, but still with browser-engine background constraints
For WebLink, that tradeoff is exactly the point. It keeps browser-shaped origins and browser storage semantics while letting the native side drive when work happens.
Why This Matters For Implant Design
Many classic implants concentrate too much responsibility into a single process. The same executable has to hold staging material, own the beacon loop, make outbound HTTP/S requests, maintain keys, deserialize tasking, and perform post-exploitation actions. That is true of a large share of beacon-style tradecraft: one process becomes both the host-side artifact and the network-side artifact.
That concentration is expensive from a detection perspective. EDR does not have to correlate across cooperating components when one executable is doing everything itself. The same process owns suspicious parentage, suspicious memory shape, suspicious API usage, and suspicious network activity. In practical scoring terms, that can become a high-confidence object because both host evasion and network evasion have to succeed inside one executable identity. If that process looks wrong on either side, process lineage or outbound traffic, the whole design becomes easier to score and triage.
WebLink changes that architecture. It does not remove detection pressure, but it gives the implant room to breathe by splitting operational roles across cooperating components. The native implant can remain the controller. WebLinkLib can own the WebView2 bridge. Browser-side JavaScript can own browser-originated HTTP. That forces the detection problem into multi-process and cross-boundary attribution rather than a single executable that encapsulates every suspicious behavior at once.
That matters because EDR may now have to reason about a native process, an embedded browser runtime, and browser-side activity together. The operation is still detectable, but the product and the analyst may need to correlate process lineage, WebView2 runtime behavior, page-originated network activity, and native/browser message passing instead of simply seeing one process that both looks wrong and talks wrong.
Where WebLink Plays
Legend: 🟢 high fit, 🟡 adjacent, 🔴 low / not a focus
| Quadrant / Detection Surface | Fit | Why |
|---|---|---|
| Parent-child process anomalies | 🟢 | WebLink can run under a chosen parent and keep browser-backed activity attached to that process lineage. |
| Unexpected processes making outbound HTTP/S connections | 🟢 | This post’s primary use case is that task traffic can originate from the embedded WebView2/browser surface instead of a custom implant-owned HTTP client. |
| Browser-origin legitimacy / browser-shaped HTTP call site | 🟢 | WebLink can make web activity originate from an embedded browser widget driven over COM. |
| Browser memory space as an operational surface | 🟢 | This is the deeper architectural value: browser/JavaScript memory becomes part of implant operations rather than just presentation or transport. |
| Local network bridge minimization | 🟢 | Native and JavaScript exchange messages through ICoreWebView2 messaging instead of an extra localhost TCP bridge. |
| C2 over legitimate services | 🟡 | WebLink can support browser-backed web traffic, but it does not itself provide a legitimate-service C2 channel. |
| Jitter-pattern heartbeat evasion | 🟡 | Browser-side logic can randomize upstream timing and traffic shape more flexibly, although WebLink does not by itself erase beacon-pattern analytics. |
| EDR behavioral signature evasion | 🟡 | WebView2 offload can reduce the amount of suspicious network and tasking behavior concentrated in the implant process, though it does not remove native-side behavioral visibility. |
| Memory forensics resistance | 🟡 | Browser-resident staging, payload material, or key handling can reduce how much sensitive material must exist in native implant memory, but this is not full memory stealth. |
| Process injection stealth | 🔴 | WebLink does not inherently remove injection artifacts or syscall-trace visibility. |
Single Process vs Split Model
| Single-process beacon design | WebLink-style split design |
|---|---|
| one executable owns host artifact and network artifact | native control and browser-origin HTTP are separated |
| one process must solve both host and network evasion simultaneously | detection has to correlate across native process, WebView2 runtime, and page activity |
| easier for EDR to assign high confidence to one bad object | forces more attribution work across multiple cooperating components |
| memory, API, lineage, and network all collapse into one executable identity | browser-backed HTTP and native control logic no longer live in the same place |
Process Tree Example
One practical advantage of this model is that parentage can be shaped to mimic a process that already has a normal relationship with WebView2. In the example below, WebLink.exe is relocated under M365Copilot.exe, a legitimate parent that already owns an msedgewebview2.exe subtree.
For quick inspection, WMI is useful because it gives a clean view of ProcessId, ParentProcessId, image name, and command line without needing a GUI tool. The snapshots below were collected with Get-CimInstance Win32_Process.
Before WebLink.exe was launched:
|
|
|
|
WebLink.exe was then launched with process relocation:
|
|
The important detail is that -p triggers the relocation step. The relocated child then continues as WebLink.exe -r -p M365Copilot.exe.
After relocation:
|
|
|
|
This is the point for red team use: the parent-child story can be made to look like an extension of a process that already works with WebView2. The browser-backed subtree is not appearing under a random custom beacon with no legitimate WebView2 relationship. It is being placed under a parent that already has an established msedgewebview2.exe execution pattern.
What This Helps Emulate
For red team purposes, this HTTP offload implementation helps emulate an adversary that wants browser-backed network origin without relying on a custom local web broker.
What it models well:
- a native controller that does not directly issue the task HTTP requests
- a JavaScript payload loaded into an embedded browser widget
- native-to-browser control through
ICoreWebView2::ExecuteScript(...) - browser-to-native callbacks through
add_WebMessageReceived(...) - native-to-browser responses through
PostWebMessageAsJson(...) - separation of browser-resident working material from native-side execution logic
- browser-runtime process trees where one embedded WebView fans out into several
msedgewebview2.exeprocesses
What that gives a red team:
- a way to legitimize the network origin of a call site by using browser-backed HTTP
- a cleaner separation between native control logic and the JavaScript payload
- removal of an extra localhost network bridge as an artifact
- a foundation for later designs where payload staging and key material remain browser-resident until explicitly handed to native code
Defensive Artifact Value
This is useful for defensive artifact generation because it creates a realistic split between:
- the native controlling process
- the embedded browser runtime
- the JavaScript payload making HTTP requests
- any material that remains browser-resident before crossing into native code
That lets defenders study:
- whether process-network attribution correctly follows browser-originated traffic back to the controlling native process
- whether embedded browser components are being abused in ways that still stand out
- whether detections depend on obviously wrong outbound HTTP clients versus browser-backed call sites
- how WebView2 runtime artifacts, native process artifacts, and page-side artifacts line up during an operation
One useful detail in that analysis is that msedgewebview2.exe is a Microsoft-signed runtime component. That may create higher baseline trust in some environments, which is part of why browser-backed activity can be a meaningful architectural shift. It should not be confused with guaranteed safety, but it can affect how the execution surface is perceived and scored.
MITRE ATT&CK Mapping
This post is about one concrete implementation, not a full adversary emulation chain. The ATT&CK mappings below are the most relevant techniques touched by the design described here.
| ATT&CK | Relevance to this post |
|---|---|
| T1134.004 - Access Token Manipulation: Parent PID Spoofing | Relevant to the -p relocation example, where WebLink.exe is created under a selected parent such as M365Copilot.exe to shape process lineage. |
| T1071.001 - Application Layer Protocol: Web Protocols | The central use case in this post is browser-originated HTTP/S communication, where tasking and results move over normal web protocols from the embedded browser surface. |
| T1059.007 - Command and Scripting Interpreter: JavaScript | WebLink uses browser-side JavaScript as part of the operational split, with page logic handling polling, HTTP requests, and message exchange with the native side. |
| T1106 - Native API | The native half of the design relies on Windows APIs and COM interfaces to create processes, control WebView2, and pass messages between native and browser space. |
These mappings should be read narrowly. The post is not claiming that WebLink fully implements every aspect of those techniques. The point is that the design intersects those ATT&CK areas in ways that are useful for red team emulation and useful for defenders to reason about.
Red Team Takeaway
WebLink should be presented as a focused tradecraft component, not a universal evasion framework.
In this post, the showcased use case is HTTP offload. The architectural move underneath it is larger:
- bring browser memory space into implant operations as a usable execution and staging surface
- let native code drive that browser widget over the COM interface
- use browser-originated HTTP where that changes attribution value
- avoid introducing an additional localhost network bridge
- preserve a clear split between native control and JavaScript-side web activity
HTTP offload is one implementation of that model. Future posts can build on the same WebLinkLib foundation for key handling, staging, and other browser-assisted implant capabilities.
Code
The source is available on GitHub: dsnezhkov/weblink