Breaking IPC – Hacky XPC Fuzzing – Mac

Got a chance to play with antivirus on an engagement. I have tested them multiple times on Windows, but not on Mac until now. One of the key attack surfaces was XPC – a IPC mechanism used to exchange messages between 2 processes. IPCs are generally facilitated by the OS. If the communication is between 2 processes of same privileges, then it is usually not of much interest. Whenever we are dealing with IPC, 3 key areas of concern comes into play.

Authentication:

How does the peer that exposes the IPC interface, authenticate the peer that it communicates with?

The IPC mechanism usually provides a way to verify the caller. This can be based on process id, code signature etc.

Message Parsing:

How are the messages parsed into fields, values, objects?

There are 2 layers of parsing here –
    1. OS parsing IPC messages
      • Most IPC mechanisms are only involved in the setting up in the connection and then just forward the messages Eg Named pipes, domain sockets etc. But there are some IPC mechanisms such as RPC, XPC where OS does some of the parsing before forwarding it to the application. For a targeted application testing, we are typically not interested in the OS parsing of these IPC messages.
    2. Application parsing IPC messages
      • IPC Messages may have a custom structure or OS provided structure. It might be parsing these values into fields, objects etc. So all parsing vulnerabilities including serialization/deserialization vulnerabilities plays a role here.
Function Abuse:

If any of the function exposed through IPC can be abused. Eg Path traversal, Arbitrary file write, command injection etc

It should be clear that this is not much different from testing a network service or a web service.

XPC

From a testing perspective, typically the tools for performing recon and attacking IPC are not readily available. However there are 2 excellent tools for displaying XPC messages – XPocE & xpcspy

Authentication:

To test authentication, you may have to resort to reversing. Look for SecCodeCopyGuestWithAttributes function in the binary and then kSecGuestAttributePid or kSecGuestAttributeAudit. If Pid is used to authenticate, then it is vulnerable to spoofing. This has been well documented in here

Message Parsing:

To test message parsing, fuzzing is the quickest way. As of this writing, I didn’t see a XPC fuzzer out there. If you do find one, feel free to point out that my googling skill is subpar.

The  XPC fuzzer below essentially exploits XPC’s messages dictionary like structure and pyjsonfuzz’s ability to generate fuzzed messages from dictionaries. This works for most messages but not all. XPC’s messages have a rich format that sometimes cannot be correctly converted. So it is limited. But if you are in a hurry, this might be a good starting point. The code has been commented, give it a read, it should be straight forward.

 

# xpcconnection: OS X XPC Bindings for Python
#
# Copyright (c) 2015 Matthew Else
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from argparse import Namespace
from pyjfuzz.lib import *
from xpcconnection import XpcConnection
from threading import Event
import json
import sys

import time
import PyJFuzz
from uuid import UUID

class Connection(XpcConnection):
    def __init__(self, target, event):
        super(Connection, self).__init__(target)
        self.event = event

    def onEvent(self, data):
        print("onEvent - Data:"+str(data))
        
    def onError(self, data):
        print("Error:"+data)
        

    def handler(self, event):
        e_type, data = event

        if e_type == 'event':
            self.onEvent(data)
        elif e_type == 'error':
            self.onError(data)
        else:
            # que?
            print("Unknown event - "+ e_type)
            print("Data"+str(data))
            pass

        self.event.set()

e = Event()

############################
#Capture from XPoCe/xpcspy
############################
# { name = com.packageid.aaaaaaaaa, listener = false, pid = 0, euid = 4294967295, egid = 4294967295, asid = 4294967295 }>
# <OS_xpc_dictionary: <dictionary: 0x6000035fd3e0> { count = 1, transaction: 0, voucher = 0x0, contents =
# 	"x" => <dictionary: 0x60000350b0c0> { count = 4, transaction: 0, voucher = 0x0, contents =
# 		"y" => <string: 0x600001e332a0> { length = 19, contents = "ssssss-valueClient" }
# 		"z" => <string: 0x600001e310e0> { length = 13, contents = "sssssss" }
# 		"w" => <int64: 0x47023ca6d860368f>: 1
# 		"m" => <int64: 0x47023ca6d860268f>: 0
# 	}
# }>


# <OS_xpc_connection: <connection: 0x7fd23dcb99c0> { name = com.packageid.aaaaaaaaa.peer.0x7fd23dcb99c0, listener = false, pid = 60, euid = 0, egid = 0, asid = 100000 }>
# <OS_xpc_dictionary: <dictionary: 0x7fd23dcb9d00> { count = 1, transaction: 1, voucher = 0x7fd23dcb98a0, contents =
# 	"x" => <dictionary: 0x7fd23dcb9930> { count = 4, transaction: 0, voucher = 0x0, contents =
# 		"y" => <string: 0x7fd23dcb9990> { length = 16, contents = "value-valueClient" }
# 		"m" => <int64: 0xb5f711287750c183>: 0
# 		"w" => <int64: 0xb5f711287750d183>: 1
# 		"z" => <string: 0x7fd23dcb9040> { length = 6, contents = "value" }
# 	}
# }>


############################
#Establish connection to the pid
############################

conn = Connection('com.packageid.aaaaaaaaa', e)
print("Connection"+str(conn))



############################
#List of message you would like to fuzz derived based on XPoCe/xpcspy
############################

#
msg = []
msg.append({
        'x' : {
            'y':'value-valueClient',
            'z' :'value',
            'w':1,
            'm':0
        }
    })
msg.append({ 
	"object" : "xxxx",
	"userinfo" : "tests",
	"name" :  "com.packageid.xxxx.settings", 
	"token" :  25769803781,
	"method" : "methodname",
	"version" : 1
})
msg.append( { 
	"x" : { 
		"aaaaaaaaTransaction" : { 
			"aaaaaaaaaGenerationCount" : 523,
			"NewValue" : [
				{ 
					"Change" : 1,
					"Path" :"/ssssss/ddddd-dddd-ddd-dddd-ddddddddddd", 
					"NewValue": { 
						"xxxx" :"ssss ddddd vvvv", 
						"yyyy" : 300,
						"zzzz" : 1200,
						"ddddd": "ddddd-dddd-ddd-dddd-ddddddddddd",
						"ssss" : True,
						"aaaaa" : 632959821,
						"ccccc" : 0,
						"ddddddddddd" : 1.000000,
						"zzzzzzz" : 2,
						"vvvvvvvvvvv" : 1.000000,
						"sssssssss" : "ddddd-dddd-ddd-dddd-ddddddddddd",
						"bbbbbbbb" : 632961021,
						"qqqqqqqqq" : 1
					}
				}
            ],
			"z" :"value" 
		},
		"y" :"value-valueClient", 
		"m" : 1,
		"w" : 1,
		"z" :"value" 
	}
})

############################
#JSON Fuzzer configuration
###########################
config=[]
fuzzer=[]

l = len(msg)
for i in range(0,l):
        
    config.append(PJFConfiguration(Namespace(json=msg[i], nologo=True, level=6, techniques="P")))
    # once a config object is defined you can access to config.techniques to view the selected techniques for your group
    print("Techniques IDs: {0}".format(str(config[i].techniques)))
    # you can eventually modify them!
    config[i].techniques = [2]
    # This way only attack number 2 (LFI Attack) will be performed!
    fuzzer.append(PJFFactory(config[i]))


######################################
#Send the fuzzed values to the server
######################################
    
i=0
while True:
    try:
        
        payload= fuzzer[i].fuzzed
        print(payload)
        payload_dict=json.loads(payload)
        conn.sendMessage(payload_dict)
    except:
        print("Failed:Unexpected error:", sys.exc_info()[0])
    i=(i+1)%l



#init()
Function abuse

If you find a interesting function that you want to test abuse case, just use xpcconnection library shown above to invoke the function with your test case.