System and Network Extensions are fairly easy programmatically. However, there is some nuance around building them. Much of this is in getting the correct entitlements – but also a little in troubleshooting.
To see (or set) those entitlements, look at the .entitlements file located in the root of an Xcode Project. That will be a plist with a few entries. In this one, we’ll see com.apple.developer.networking.networkextension so we’re working on a network extension.
com.apple.security.app-sandbox
com.apple.security.application-groups
$(TeamIdentifierPrefix)com.krypted.firewall
com.apple.developer.networking.networkextension
content-filter-provider
To add one, go to the General screen for the project, and locate the section for Frameworks, Libraries, and Embedded Content.
Then use the plus sign to add and provide the name of the framework to add.
That allows us to use network extensions (so we’ll add or see import NetworkExtension
in the beginning of a swift file that uses it, as well as an import SystemExtension
when using those. That import will allow us to use classes Apple provides for tasks that expose functionality such as NEFilterManager (https://developer.apple.com/documentation/networkextension/nefiltermanager). I won’t get into everything one might do with these, but once we get to doing the things, we invariably need to troubleshoot. And given that they have specific needs, the things I’m about to cover shouldn’t be done in production and are really just there for troubleshooting new extensions.
First, we can’t do much with SIP on so this is one of the very few cases where we’re going to disable it on a development machine. To do that, we’ll boot into Recovery Mode (using Command-R to boot) and run the following command.
csrutil disable
Now, let’s boot the machine and set
systemextensionsctl developer on
If it’s an arm and it’s a driver extension, we might then need to run the following:
sudo nvram boot-args=-arm64e_preview_abi
Now that we’re booted and able to interface with system extensions directly, we can use lldb. To enter the lldb interactive mode, simply run lldb
:
sudo lldb
Now from the interactive mode, we can attach to a process to see what’s happening when it’s run:
process attach --pid 8766
In the above we’re attaching to a specific pid and could have used --waitfor
following it if we wanted to attach to the next instance. But if we wanted to attach to an app we’d just be running lldb followed by the .app bundle URI. For example, if we’re putting test apps into /Compiled and want to attach to an app called myextension.app:
lldb /Compiled/myextension.app
We can then see any breakpoints as well (from that lldb interactive mode):
breakpoint list
So let’s say we invoke an extension in line 87 of file blocker.c we could create a breakpoint there:
breakpoint set --file blocker.c --line 87
This sets the breakpoint for each methods that implement that line, or its class, as well. Once the breakpoint is hit, use the thread
verb followed by continue
to proceed:
thread continue
This brings up threads. From within lldb use the thread verb followed by list to see the state the process is in:
thread list
Or use thread with backtrace to see those:
thread backtrace
Also checkout waypoint
and frame
in lldb.