Researching VPN applications - part 3 testing macOS applications

Introduction

In our previous blogs we explained how VPN applications work internally and what implementation mistakes can be made that could result in elevation of privileges. We've also presented some simple steps one could use to find vulnerabilities in Windows VPN applications.

In this blog we will describe our approach to finding these issues in macOS applications. The VPN applications discussed here are used to illustrate the investigation methodology.

Step 0 - pre install actions and observations

On macOS, before even installing and running our target VPN client for analysis, we can extract useful information from the installation file itself. VPN client installers, as with most other applications on macOS, generally come in two formats. This can be either a PKG file or a DMG file. Both are some form of a compressed archive format and can be easily unpacked.

For now the DMG file will not be that interesting for us since it will usually show a .app directory that can be copied as is to the /Applications directory. This form of installer will perform no additional actions when copying the file (note that the DMG file might contain one or more PKG files). The PKG installer on the other hand can provide us with awesome information. The first step will be to unpack this file. Running a quick file command on the PKG file will show us that it is an XAR archive file. Run the following commands to unpack this file.

# To prevent naming errors the packages should be renamed. E.g. `mv Perfect_Privacy_VPN.pkg foo.pkg`
$ xar -xf foo.pkg

This will result in the PKG file being unpacked into the following structure.

$ ls -l
-rw-r--r--  1 user   staff   1.0K Aug 1  2018 Distribution
drwxr-xr-x  6 user   staff   192B Aug 1  2018 Perfect_Privacy_VPN.pkg/
drwxr-xr-x  3 user   staff    96B Aug 1  2018 Resources/

For us the most interesting files reside in the Perfect_Privacy_VPN/ folder. If we run a quick file command on each file we will see that there are more archives inside.

$  for i in `ls`; do file $i; done;
Bom: Mac OS X bill of materials (BOM) file
PackageInfo: ASCII text
Payload: gzip compressed data, from Unix, original size 19144520
Scripts: gzip compressed data, from Unix, original size 1628

If we unpack the Scripts archive we can see which actions this installer will run before and after installation.

$ tar -xvf Scripts
x ./postinstall
x ./preinstall

The preinstall step will show some cleaning up steps before the actual files will be installed. The postinstall script will show us an instruction for launchd to load a deamon as configured in the plist file. At this point we already know which service we might need to target in our journey to gain elevated privileges as this deamon will be run as the root user.

$ cat postinstall
#!/usr/bin/env bash

OPENVPN_CONFIG_DIR="/Library/Application Support/com.perfect-privacy.perfect-privacy-vpn/config/"
OPENVPN_CONFIG_DIR_UPDATE="/Library/Application Support/com.perfect-privacy.perfect-privacy-vpn/config_update/"
OPENVPN_CONFIG_DIR_BUILTIN="/Library/Application Support/com.perfect-privacy.perfect-privacy-vpn/config_builtin/"

rm -rf "${OPENVPN_CONFIG_DIR_UPDATE}"
cp -r "${OPENVPN_CONFIG_DIR_BUILTIN}" "${OPENVPN_CONFIG_DIR_UPDATE}"

launchctl load /Library/LaunchDaemons/com.perfect-privacy.perfect-privacy-vpnd.plist # tell launchd to start daemon

exit 0

The Payload file will show us all the applications and helpers that will be installed as is the structure of installation. Please note that in contrast to the DMG installer, the PKG installer may install files anywhere on the system within its permission boundaries.

Inspecting the PKG installer file may reveal interesting information even before installing the actual VPN client.

Step 1 - Install and run it

At this point you may want to install the target VPN Client and run it to gain a general understanding of its behavior and internal working. We know that clients that use OpenVPN to setup a VPN connection need to perform privileged actions at some point during this process. This normally happens when setting up or tearing down the connections with OpenVPN. In order to perform these privileged actions several approaches can be taken.

  • The client can ask the user for the password every time a privileged action needs to be performed. This is highly inconvenient from a user experience perspective. But also from an attacker perspective, we are less able to exploit issues here. Therefore this flow was also out-scoped from our initial research.

  • The client will ask the user for a password a single time during installation. Usually in this step a service daemon will be installed that can perform privileged tasks on behalf of the client. This is interesting from an attacker's perspective. This is usually the case when the PKG installer is used.

  • The client will not ask for a password during installation. However, the first time a privileged actions needs to be performed, it will ask a single time for the user's password. At this point the service daemon will be installed to perform the privileged actions on behalf of the client. This flow is also very interesting from an attacker's perspective. Please note that this flow usually happens if the DMG installer is used. Since there is no real installation process they have to delay the password prompt to a moment that the user actually uses the app.

After installation we need to identify the actual service that is installed and is used by the client to delegate its privileged tasks to. In order to do so we can use already installed macOS tools. In the following section will illustrate some of the tools and command that can be used identify the service and verify its usage by the client.

  • In case we already inspected a PKG file we might already know which service is used and where it is installed.

  • Because service are usually managed on macOS by launchd its very likely that we can view the default launchd folders to see if anything is installed here. The following locations should at least be checked.

~/Library/LaunchAgents/
/Library/LaunchAgents/
/Library/LaunchDaemons/
/Library/PrivilegedHelperTools/
/System/Library/LaunchAgents/
/System/Library/LaunchDaemons/

For example in the case of SaferVPN we will see a plist file in /Library/LaunchDaemons/ with the following contents.

<?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>Label</key>
	<string>com.safervpn.HelperTool</string>
	<key>MachServices</key>
	<dict>
		<key>com.safervpn.HelperTool</key>
		<true/>
	</dict>
	<key>Program</key>
	<string>/Library/PrivilegedHelperTools/com.safervpn.HelperTool</string>
	<key>ProgramArguments</key>
	<array>
		<string>/Library/PrivilegedHelperTools/com.safervpn.HelperTool</string>
	</array>
</dict>
</plist>

This file shows us the deamon settings and path of the actual binary that will be started as a deamon. In the case of SaferVPN this will be /Library/PrivilegedHelperTools/com.safervpn.HelperTool. launchd is usually fully in control of the daemons life-cycle and management.

Therefore we can ask launchd information about running services on the system. The following command can be used to see if a service is currently known to launchd. In this example we can see that a deamon for Perfect Privacy is known.

$ launchctl list
...
-	0	com.apple.systemprofiler
264	0	com.apple.cloudd
348	0	com.apple.noticeboard.agent
-	0	com.apple.UserNotificationCenterAgent
-	0	com.apple.cmfsyncagent
-	0	com.apple.dt.CommandLineTools.installondemand
-	0	com.apple.ATS.FontValidator
349	0	com.apple.diagnostics_agent
13925	0	com.perfect-privacy.perfect-privacy-vpn.3624
-	0	com.apple.appleseed.seedusaged
350	0	com.vmware.launchd.vmware-tools-userd
-	0	com.apple.LocalAuthentication.UIAgent
-	-9	com.apple.ap.adprivacyd
-	0	com.apple.PhotoLibraryMigrationUtility.XPC
...

We can also identify deamons from XPC services by using the following command:

$ launchctl dumpjpcategory
...
com.apple.screencaptureui.agent : app
com.apple.appstoreagent : daemon
com.apple.AssistiveControl : daemon
com.apple.soagent : daemon
com.apple.icloud.fmfd : daemon
com.apple.CommCenter : daemon
com.apple.mdmclient.agent : daemon
com.apple.foundation.UserScriptService : xpcservice
com.apple.audio.DriverHelper : xpcservice
...

In case none of the above seem to work we can also use good old ps (or something similar like Activity Monitor, top, etc). Running this will give us information like shown below.

$ ps aux | grep openvpn
root              1040   3.1  0.1  4285172   2288   ??  S     3:37AM   0:00.04 /Library/Application Support/com.safervpn.HelperTool/openvpn --config /Users/user/Library/Application Support/com.safervpn.SaferVPN/config_current.ovpn --management 127.0.0.1 9544 --management-query-passwords --script-security 2

The example above shows us that for example scripting security is lowered to 2 when OpenVPN is started and that a config file is loaded from a possible user controlled location. A quick ls on the folder will show us the following permissions.

ls -l /Users/user/Library/Application\ Support/com.safervpn.SaferVPN/
total 48
drwxr-xr-x  37 user  staff   1184 Apr  6 03:30 CachedImages
drwxr-xr-x   3 user  staff     96 Apr  6 03:30 VPNConfigs
-rw-r--r--   1 user  staff  15493 Apr  6 03:30 app_config.json
drwxr-xr-x@  5 user  staff    160 Apr  6 13:12 com.microsoft.appcenter
-rw-r--r--@  1 user  staff   1966 Apr  6 13:28 config_current.ovpn
-rw-r--r--@  1 user  staff   1989 Apr  6 13:25 payload.ovpn

The permissions show that they are writable as the current user. This might potentially be vulnerable for local privilege escalation attacks as described in the previous blog. For now we will not focus on this possible issue and continue our investigation.

$ ps aux | grep -i safer
root               632   0.0  0.9  4324420  18004   ??  Ss    3:31AM   0:04.33 /Library/PrivilegedHelperTools/com.safervpn.HelperTool
user               592   0.0  4.0  4975488  83776   ??  S     3:28AM   1:23.90 /Applications/SaferVPN.app/Contents/MacOS/SaferVPN -psn_0_229432

At this point is should be clear what the client application is and what the service deamon binary is that starts the openvpn binary. The ps output shows that the SaferVPN daemon is starting OpenVPN with specific parameters. In any case it shows several potential starting points for exploitation.

Step 2 - Figure out IPC channel

So at this point we have the following information after analyzing the behavior of a VPN client like SaferVPN. First of all we have a client that shows the user a UI and allows it to perform tasks like selecting VPN configuration and connecting to a VPN server. Secondly we know there is a service running that when the user connects from the client UI, will invoke the openvpn command with specific parameters and configuration options. What we are missing at this point is the mechanism that is used to communicate between the client and the privileged service.

So generally on macOS this IPC channel is, but not limited to, either some form (UNIX) sockets or Apple's XPC inter process communication mechanism. There is no standard way identifying the correct IPC channel. However, we can get hints that might help us to search in the right direction.

We could run the following command as mentioned before to identify potential XPC services used by the VPN client under investigation:

$ launchctl dumpjpcategory | grep xpcservice

However this might not show all the necessary information we need. Additionally we could also try to dump some symbols from either the client or the deamon to see if it incorporates XPC behavior. To dump some symbols we can used default macOS developer tools.

nm -a <binaryname> | grep -i xpc

                 U __xpc_error_connection_interrupted
                 U __xpc_error_connection_invalid
                 U __xpc_type_dictionary
                 U _xpc_connection_create_from_endpoint
                 U _xpc_connection_create_mach_service
                 U _xpc_connection_resume
                 U _xpc_connection_send_barrier
                 U _xpc_connection_send_message
                 U _xpc_connection_send_message_with_reply_sync

If we can identify that it is indeed XPC that is used to communicate between the client and the deamon we can move on to the next step which usually involves a bit of reverse engineering. We need to identify the type of XPC used, either XPC connection API or the XCP services API. What commands are implemented and what service descriptor is registered with launchd.

In the case of UNIX sockets we could run a command like:

$ sudo lsof -U

...
perfect-p 11074           root    4u  unix 0xfb214fa1d7137c33      0t0      /usr/local/libexec/perfect-privacy-vpnd/uds
perfect-p 11074           root    6u  unix 0xfb214fa1d713784b      0t0      ->0xfb214fa1d7137cfb
perfect-p 11074           root    9u  unix 0xfb214fa1d7137f53      0t0      /usr/local/libexec/perfect-privacy-vpnd/uds
parsecd   11099           root    9u  unix 0xfb214fa1d7137e8b      0t0      ->0xfb214fa1d705ab6b
Perfect   11104           root    8u  unix 0xfb214fa1d71376bb      0t0      ->0xfb214fa1d7137f53
Perfect   11104           root   10u  unix 0xfb214fa1d71376bb      0t0      ->0xfb214fa1d7137f53
...

In the previous example we listed all UNIX sockets and it shows in case of Perfect Privacy that it is indeed using a UNIX socket and it is mapped to /usr/local/libexec/perfect-privacy-vpnd/uds

Usually just as with XPC we might need some reverse engineering to continue our quest. However, we could try to dump the communication between the client and the deamon by sniffing the socket communication. This can for example be done by using socat.

Step 3a - "Reverse engineering" ;)

In most cases a little reverse engineering is required in order to find and exploit possible issues in VPN clients. The previous sections described interesting locations and tools to find information that could be used in order to find and possibly exploit security issues in VPN clients.

In this section we will dig a little deeper in a VPN client that utilizes a XPC service to communicate between the client (UI) and the privileged helper.

Let say we identified the usage of XPC within a certain VPN client using previous mentioned techniques. Than we need to establish the following information.

  • Which of the two XPC API's is used?
  • What bundle identifier is used to identify our privileged helper.
  • What services are exposed via XPC and is one or more of them vulnerable.

Apple ships two forms of XPC services, namely the XPC services API which is the lower C level API and the XPC connection API which resides on the Objective-C/Swift layer. Because the usage of XPC is well defined by Apple and uses default API calls to do stuff we can search for these default functions to gather information. Obviously the XPC services API uses different calls than the XPC connection API. In many cases we can use the nm command to check if and which type might be used. Please note that in this stage it also does not really matter if we research the client or the privileged helper as they both need to implement the same protocol and messages.

This first examples shows the usage of the XPC services API.

$ nm -a com.safervpn.HelperTool | grep xpc

                 U __xpc_error_connection_interrupted
                 U __xpc_error_connection_invalid
                 U __xpc_type_dictionary
                 U _xpc_connection_create_from_endpoint
                 U _xpc_connection_create_mach_service
                 U _xpc_connection_resume
                 U _xpc_connection_send_barrier
                 U _xpc_connection_send_message
                 U _xpc_connection_send_message_with_reply_sync
                 U _xpc_connection_set_context
                 U _xpc_connection_set_event_handler
                 U _xpc_connection_set_finalizer_f
                 U _xpc_connection_suspend
                 U _xpc_dictionary_create
                 U _xpc_dictionary_create_reply
                 U _xpc_dictionary_get_int64
                 U _xpc_dictionary_get_remote_connection
                 U _xpc_dictionary_get_string
                 U _xpc_dictionary_get_value
                 U _xpc_dictionary_set_bool
                 U _xpc_dictionary_set_string
                 U _xpc_get_type
                 U _xpc_release
                 U _xpc_retain

The following example will show the usage of the XPC connection API due to the protocol references in the dump.

$ nm -a com.sparklabs.ViscosityHelper | grep -i xpc

                 U _OBJC_CLASS_$_NSXPCConnection
                 U _OBJC_CLASS_$_NSXPCInterface
                 U _OBJC_CLASS_$_NSXPCListener
0000000000000000 - 00 0000  GSYM __OBJC_LABEL_PROTOCOL_$_NSXPCListenerDelegate
000000010007a5f8 s __OBJC_LABEL_PROTOCOL_$_NSXPCListenerDelegate
0000000000000000 - 00 0000  GSYM __OBJC_LABEL_PROTOCOL_$_SLViscosityIPCXPCProtocol
000000010007a608 s __OBJC_LABEL_PROTOCOL_$_SLViscosityIPCXPCProtocol
0000000000000000 - 00 0000  GSYM __OBJC_PROTOCOL_$_NSXPCListenerDelegate
00000001000877b8 d __OBJC_PROTOCOL_$_NSXPCListenerDelegate
0000000000000000 - 00 0000  GSYM __OBJC_PROTOCOL_$_SLViscosityIPCXPCProtocol
0000000100087878 d __OBJC_PROTOCOL_$_SLViscosityIPCXPCProtocol

If nm does not show the desired result we can use a disassembler like IDA or Hopper (or whatever you prefer) to dig deeper in the binary. The next step is to identify the bundle identifier. Let's assume the VPN client uses the lower level XPC services API as it is used by the SaferVPN client. Than we need to search for the function xpc_connection_create_mach_service() as described by Apple to create a Mach service that will be used by clients to communicate with the service.

The following example will show the bundle identifier in the Hopper disassembler output. The example shows the bundle identifier being assigned to rax and then passed to the xpc_connection_create_mach_service() function.

    ...
    rdi = *(*_charon + 0x58);
    (*rdi)(rdi, r14);
    rax = [@"com.safervpn.HelperTool" UTF8String];
    rax = xpc_connection_create_mach_service(rax, *(rbx + 0x20), 0x1);
    *(rbx + 0x10) = rax;
    ...

We successfully identified the bundle identifier of the service we want to communicate with. Now it is time to find the possible messages that can be send to this service. In contrast to the XPC connection API where the whole communication is described in a protocol class, the developer has some more freedom when using the XPC connection API. This makes it potentially a bit harder to identify API calls to the privileged helper. We will continue with the XPC services API example. A lot of information can be found about the XPC connection API on the internet.

Using the XPC services API usually the following happens after the machservice is created.

Messages between the client and the XPC service are exchanged using XPC dictionary objects. They are created with the method xpc_dictionary_create().

Values can be added to this dictionary using the methods starting with xpc_dictionary_set_*.

The client can send these XPC dictionary objects to the XPC service using methods like xpc_connection_send_*.

On the XPC service end, a handler must be registered that can handle the XPC dictionary objects that are received from the client. This can be done using the method xpc_connection_set_event_handler().

To read the values from the received XPC dictionary object method like xpc_dictionary_get_*.

The XPC connection API incorporates a lot more methods than described above. However, these methods can be used as a starting point in the reverse engineering process.

At this point we know that the helper needs to setup an event handler to process incoming XPC dictionary objects from the client. Usually this handler is setup right after the service is created as also can be seen in the SaferVPN helper.

int sub_10020d409() {
    ...
    rax = [@"com.safervpn.HelperTool" UTF8String];
    rax = xpc_connection_create_mach_service(rax, *(rbx + 0x20), 0x1);
    *(rbx + 0x10) = rax;
    if (rax != 0x0) {
            xpc_retain(rax);
            rdi = *(rbx + 0x10);
            var_40 = *__NSConcreteStackBlock;
            *(&var_40 + 0x8) = 0xffffffffc0000000;
            *(&var_40 + 0x10) = sub_10020d970;
            *(&var_40 + 0x18) = 0x1002a8810;
            *(&var_40 + 0x20) = rbx;
            xpc_connection_set_event_handler(rdi, &var_40);
            xpc_connection_resume(*(rbx + 0x10));
    }
    ...
}

We can see that right after the machservice is created a handler is defined with sub_10020d970` as the handler that will be called. Digging deeper into this function we eventually will hit the actual implementation of the handler.

The following code example will indeed show logic that will process incoming XPC dictionary objects.

void sub_1002065e9(int arg0, int arg1) {
    r13 = arg1;
    rbx = arg0;
    if (xpc_get_type(r13) == *__xpc_type_dictionary) goto loc_100206620;

.l1:
    return;

loc_100206620:
    r14 = *(rbx + 0x20);
    rbx = xpc_dictionary_get_string(r13, "type");
    if (rbx == 0x0) goto loc_10020675a;

loc_10020663f:
    if ((rbx == "rpc") || (strcmp(rbx, "rpc") == 0x0)) goto loc_1002066be;

loc_10020665e:
    if ((rbx == "helper_tool_rpc") || (strcmp(rbx, "helper_tool_rpc") == 0x0)) goto loc_100206790;

loc_100206685:
    rdi = *(*_charon + 0x58);
    r9 = *(rdi + 0x30);
    (r9)(rdi, 0x5, 0x1, "received unknown XPC message type: %s", rbx, r9);
    return;

loc_100206790:
    rbx = xpc_dictionary_create_reply(r13);
    r12 = xpc_dictionary_get_string(r13, "rpc");
    var_30 = rbx;
    if ((rbx == 0x0) || (r12 == 0x0)) goto loc_1002067e7;

loc_1002067bb:
    rbx = 0x1002a97c8;
    r15 = 0x0;
    goto loc_1002067c5;

loc_1002067c5:
    rdi = *(rbx - 0x8);
    if ((rdi == r12) || (strcmp(rdi, r12) == 0x0)) goto loc_10020684

The previous example is just a small snippet of what is actually going on. Therefore, as stated before it is a cumbersome task to identify all possible messages that can be send to the XPC service. This process can be speed up by leveraging dynamic instrumentation if the client or the helper allows it (at this point we don't consider any limitation regarding SIP and Apple's runtime protections). In the next section we will show some simple techniques to dump XPC dictionary and such to speed up the process.

The next step is to find possible XPC calls in the helper that might be vulnerable and exploit them. These usually are user supplied values used in a openvpn command or files that are read or written by the helper which cannot be read or written as a regular user.

Step 3b - Dynamic instrumentation sort of

The previous section described how we can investigate the client or the helper by using reverse engineering techniques in order to find possible vulnerable XPC calls and necessary pieces of information to build an exploit. However in the case of the XPC services API as stated before it can be a cumbersome task to identify all XPC dictionaries that are send back and forth between the client and the helper.

To speed up this process we can utilize a dynamic instrumentation framework and try to dump the XPC dictionaries while the program is running. While there are a wide variety of frameworks to choose from, all with their own pros, cons, special features and support, we will be using Frida. As it is easy to install and easy to use. Please note that for the sake of simplicity we will ignore the existence of SIP and Apple's runtime protection for the time being ;).

There are a ton of Frida tutorials on how to install and use it out there. I will be using the following basic setup. However, any other setup will probably suffice for the upcoming examples.

$ python3 -m venv env
$ . ./env/bin/activate
$ pip install frida
$ pip install frida-tools

In the first example we will try to retrieve the XPC bundle identifier while running the VPN application. For this task we can run the following frida-trace command.

$ frida-trace -i "xpc_connection_create_mach_service" /Application/SaferVPN.app/Contents/MacOS/SaferVPN

Because XPC services are widely used on macOS, the trace command will show a lot of XPC bundle identifiers. Therefore we need to tweak the hook for xpc_connection_create_mach_service() a little bit so it will not show any Apple related XPC service. The following example could be used.

var _x = Module.findExportByName(null,"xpc_connection_create_mach_service");
Interceptor.attach(_x, {
    onEnter: function(args){
        var m = args[0].readUtf8String();
        if (! m.includes("apple")) {
            console.log("# XPC Service identifer ==> " + m);
        }
}});

The following log will be produced when we run previous script on the SaferVPN client.

$ frida /Application/SaferVPN/Contents/MacOS/SaferVPN -l file_containing_the_script.js --no-pause
...
AuthService -- login process started..
Log Out Before Login skipped -- Started Normal Log In
PARSE call -- authenticateUser started..
# XPC Service identifer ==> com.safervpn.HelperTool
...

According to the Apple documentation we can use the xpc_copy_description() method to dump any XPC object internals. This is convenient since this can help us to determine the internal XPC dictionary structure which in turn are needed to talk to the XPC helper in order to exploit any possible vulnerable XPC call.

First we need to pinpoint a location that will handle XPC dictionaries. For this example we will use the xpc_connection_send_message() method, which will hold the XPC dictionary that will be send to the helper as its second argument. After hooking this method we will run the xpc_copy_description() method on the second parameter to dump the XPC dictionary structure. This process can be seen in the next Frida example.

var _y = Module.findExportByName(null,"xpc_connection_send_message"); /* find method pointer */
var _z = Module.findExportByName(null,"xpc_copy_description"); /* find method pointer */
var xcd = new NativeFunction(_z, "pointer", ["pointer"]); /* create native function for xpc_copy_description so we can call it later */

/* create a hook for xpc_connection_send_message */
Interceptor.attach(_y, {
    onEnter: function(args){
        console.log("# XPC_CONNECTION_SEND_MESSAGE ==> ");
        console.log(xcd(args[1]).readUtf8String()); /* run xpc_copy_description on second param and read as cstring */
        console.log("---\n");
    }
});

Running this script on the SaferVPN client will produce a log similar as the one below.

frida /Application/SaferVPN/Contents/MacOS/SaferVPN -l file_containing_the_script.js --no-pause
...
<dictionary: 0x7f846396c570> { count = 5, transaction: 0, voucher = 0x0, contents =
	"center_type" => <int64: 0x7f846396b700>: 0
	"system_originated" => <bool: 0x7fffaffe7bd8>: false
	"message_type" => <uint64: 0x7692e8cd6ab5bf8b>: 1
	"app_originated" => <bool: 0x7fffaffe7bb8>: true
	"bundle_identifier" => <string: 0x7f8461dc5ba0> { length = 21, contents = "com.safervpn.SaferVPN" }
}
---

# XPC_CONNECTION_SEND_MESSAGE ==>
<dictionary: 0x7f84638885a0> { count = 7, transaction: 0, voucher = 0x0, contents =
	"options" => <uint64: 0x7692e8cd68b5ef8b>: 8196
	"object" => <string: 0x7f84638cb220> { length = 24, contents = "kCFNotificationAnyObject" }
	"token" => <uint64: 0x7692e8cd9e84df8b>: 1000215
	"name" => <string: 0x7f84638c20b0> { length = 31, contents = "CGFontOverrideSettingsDidChange" }
	"pn" => <string: 0x7f84638d5c90> { length = 50, contents = "/Applications/SaferVPN.app/Contents/MacOS/SaferVPN" }
	"method" => <string: 0x7f84638d1350> { length = 8, contents = "register" }
	"version" => <uint64: 0x7692e8cd6ab5bf8b>: 1
}
---

# XPC_CONNECTION_SEND_MESSAGE ==>
<dictionary: 0x7f846398ee90> { count = 7, transaction: 0, voucher = 0x0, contents =
	"options" => <uint64: 0x7692e8cd68f5bf8b>: 9217
	"object" => <string: 0x7f84639c0c00> { length = 24, contents = "kCFNotificationAnyObject" }
	"token" => <uint64: 0x7692e8cd9e844f8b>: 1000222
	"name" => <string: 0x7f84639c08a0> { length = 33, contents = "AppleShowScrollBarsSettingChanged" }
	"pn" => <string: 0x7f84639c1bd0> { length = 50, contents = "/Applications/SaferVPN.app/Contents/MacOS/SaferVPN" }
	"method" => <string: 0x7f84639bf8d0> { length = 8, contents = "register" }
	"version" => <uint64: 0x7692e8cd6ab5bf8b>: 1
}
---

This information can be used to recontruct the xpc objects that we can possibly send to a vulnerable XPC service.

The previous examples are limited and we can construct far more complex scripts that will give us more accurate and fine grained information. Especially when we combine dynamic instrumentation techniques with reverse engineering.

Step 4 - Creating a client

Assuming we successfully identified XPC usage by the VPN software we can create a client which will need the following components.

  • The first step is to create a so called mach service to announce which service will used for communication.
  • The next step will be to re-create the XPC dictionary that will be send to the privileged XPC helper.
  • Actually send the XPC dictionary to the privileged XPC helper.

The following example will show a basic compileable XPC client skeleton that can be used as a template for any XPC services based helper.

/*
# example.c
# usage:
#   clang example.c -o example
#   ./example
*/

#include <stdio.h>
#include <xpc/xpc.h>

int main(int argc, const char * argv[]) {
    xpc_connection_t  conn;
    xpc_object_t msg;
    xpc_object_t params;
    char cmd[512];

    printf(" -> creating connection\n");
    conn = xpc_connection_create_mach_service("nl.sfy.serviceidentifier", 0x0, 0x2);

    printf(" -> setting connection event handler\n"); /* this will handle responses */
    xpc_connection_set_event_handler(conn, ^(xpc_object_t e){
        printf(" -> connection event handler called\n");
        printf(" -> %s\n", xpc_copy_description(e)); /* dump incomming msg */
    });

    printf(" -> resuming connection\n");
    xpc_connection_resume(conn);

    printf(" -> creating example payload\n");

    msg = xpc_dictionary_create(0x0, 0x0, 0x0); /* create a xpc dictionary */
   
    /* set some values */
    xpc_dictionary_set_string(msg, "type", "some_type");
    xpc_dictionary_set_string(msg, "rpc", "some_call");

    params = xpc_dictionary_create(0x0, 0x0, 0x0); /* second dictionary */

    xpc_dictionary_set_string(params, "openvpncmd", "'openvpn' --plugin '/tmp/exploit.dylib'");
 
    xpc_dictionary_set_value(msg, "parameters", params); /* set the second dictionary as parameter for the initial dicitonary */
    xpc_dictionary_set_connection(msg, "channel", conn);

    printf(" -> sending payload to the helper\n");
    xpc_connection_send_message_with_reply(conn, msg, NULL, ^(xpc_object_t event){
        printf(" -> send msg event handler called\n");
        printf(" -> %s\n", xpc_copy_description(event));
    });
}

Step 5: Finding & exploiting vulnerabilities

With the information gained from the previous steps and your custom client, it is now possible to find and exploit vulnerabilities in privileged helpers. We'll leave this as a reader's exercise 😉. See our VPN internals blog for some inspiration for the type of issues you can look for.

Vragen of feedback?