14 minute read

In the last post I gave quite some theory and an exercise: did you do it? Do you have your threat model? If not, go to the previous post and prepare your profile ASAP.

We’re about to get to the nitty-gritty details, here.

Previous posts on this series

From here onwards, I will assume you are working on a fresh machine. To emulate this set of circumstances, I created a fresh VM. In general, I use either VirtualBuddy or UTM for virtualisation. I am going to use the former. I have installed the latest macOS version, which at the time of this writing is Tahoe 26.3.

We are going to install software and change settings. For this reason, I want to create a baseline, to observe what changes as we go.

Note that most of what we are going to do can also be done on an existing machine. I will often write “do something on your new machine” just because I am working on a fresh VM - but chances are that the concepts hold on an existing machine, mutatis mutandis.

Persistence

We start with the obsession of every hacker: Persistence. In this context, persistent software is a software that runs constantly on your machine, the typical example being startup items or processes. Then, let’s enumerate what we have on a clean machine:

desdinova@desdinovas-Virtual-Machine ~ % ls ~/Library/LaunchAgents
ls: /Users/desdinova/Library/LaunchAgents: No such file or directory
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchAgents

desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchDaemons

desdinova@desdinovas-Virtual-Machine ~ % launchctl list | head -20

PID	Status	Label
1478	0	com.apple.trustd.agent
-	0	com.apple.MailServiceAgent
-	0	com.apple.mdworker.mail
-	0	com.apple.appkit.xpc.ColorSampler
818	0	com.apple.cfprefsd.xpc.agent
-	0	com.apple.coreimportd
-	0	com.apple.CoreDevice.remotepairingd
-	0	com.apple.TrustedPeersHelper
-	0	com.apple.cvmsCompAgent3600_arm64_1
-	0	com.apple.SafariHistoryServiceAgent
-	0	com.apple.progressd
-	0	com.apple.enhancedloggingd
1285	0	com.apple.cloudphotod
1001	0	com.apple.MENotificationService
1233	0	com.apple.Finder
941	0	com.apple.homed
969	0	com.apple.dataaccess.dataaccessd
-	0	com.apple.quicklook
-	0	com.apple.parentalcontrols.check

The directories we checked (~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons) are where macOS looks for software that should start automatically. The launchctl list command shows what’s actually running right now.

On a clean machine, these directories are empty or don’t exist, and launchctl list shows only com.apple.* processes — all Apple’s own. Anything else is something you added, or something that was added for you.

KnockKnock

KnockKnock is a program developed by Patrick Wardle that checks for persistent software. The description from the website is:

Malware often installs itself persistently, to ensure it is automatically (re)executed each time a computer is restarted. KnockKnock uncovers persistently installed software in order to generically reveal such malware.

The question that arises here is: why do you trust this software? It is a totally fair question, because as a matter of fact we are giving this software the role of controller.

As Juvenal teaches us: Quis custodiet ipsos custodes? - who watches the watchmen? This could become an infinite regress, so here are some principles:

  • if it’s developed by you and you’re not at war with yourself, you can trust the software;
  • if you can audit it, and it doesn’t look suspicious (and once again, you’re not a Jekyll-Hyde sysadmin), trust the software;
  • if it’s a well-known component, adopted by people in the community, chances are that you can trust the software;
  • if your only reason to trust software is a gut feeling, don’t. You’re not lucky — nobody is. When it comes to security, a healthy dose of paranoia is warranted.

Installation

Installing KnockKnock is extremely straightforward:

  1. Point your browser to https://objective-see.org/products/knockknock.html
  2. Click on Download
  3. Open a finder window on the Download folder, and drag KnockKnock to /Applications.

The configuration is simple:

  • you want the software to start at login - this will show you what runs at startup
  • grant KnockKnock full disk access (see below)
  • I suggest enabling VirusTotal integration.
    • In order to do so, you need to have a VirusTotal API key, which is free
    • Point your browser to https://docs.virustotal.com/docs/please-give-me-an-api-key and follow the instructions

Application: KnockKnock

Settings: KnockKnock Settings

The setup documentation in KnockKnock download page is exhaustive - for the sake of brevity, I steer you to it for the initial setup.

Run a scan. The first thing you install immediately shows up. KnockKnock reports itself as a login item (because we configured it that way) and as a background task. No surprises — this is exactly what we want to see.

Below, KnockKnock detecting itself

KnockKnock

KnockKnock

But I am more of a terminal guy, so I launch the commands I executed before:

desdinova@desdinovas-Virtual-Machine ~ % ls ~/Library/LaunchAgents
ls: /Users/desdinova/Library/LaunchAgents: No such file or directory
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchDaemons

desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchAgents 

desdinova@desdinovas-Virtual-Machine ~ % launchctl list | head -20

PID	Status	Label
1478	0	com.apple.trustd.agent
-	0	com.apple.MailServiceAgent
-	0	com.apple.mdworker.mail
-	0	com.apple.appkit.xpc.ColorSampler
818	0	com.apple.cfprefsd.xpc.agent
-	0	com.apple.coreimportd
-	0	com.apple.CoreDevice.remotepairingd
-	0	com.apple.TrustedPeersHelper
-	0	com.apple.cvmsCompAgent3600_arm64_1
-	0	com.apple.SafariHistoryServiceAgent
-	0	com.apple.progressd
-	0	com.apple.enhancedloggingd
1285	0	com.apple.cloudphotod
1001	0	com.apple.MENotificationService
1233	0	com.apple.Finder
941	0	com.apple.homed
969	0	com.apple.dataaccess.dataaccessd
-	0	com.apple.quicklook
-	0	com.apple.parentalcontrols.check

Interestingly, our manual commands don’t show KnockKnock at all — the directories are still empty, launchctl list shows only Apple processes. Yet KnockKnock found itself.

This is because modern macOS has multiple persistence mechanisms beyond LaunchAgents and LaunchDaemons. Login Items and Background Tasks use different APIs entirely. The manual commands give you a partial view; KnockKnock gives you the full picture.

In macOS Ventura and later versions, the Background Task Manager can be interrogated with:

desdinova@desdinovas-Virtual-Machine ~ % sudo sfltool dumpbtm
Password:
========================
 Records for UID -2 : FFFFEEEE-DDDD-CCCC-BBBB-AAAAFFFFFFFE
========================

 ServiceManagement migrated: true
 LaunchServices registered: false

 Items:

... SNIP ...

========================
 Records for UID 501 : D3853C4B-E40A-4909-B854-6100D33520CE
========================

 ServiceManagement migrated: true
 LaunchServices registered: true

 Items:

 #1:
                 UUID: CD1245CF-C031-4940-B31B-E0C7A007368F
                 Name: KnockKnock
       Developer Name: (null)
      Team Identifier: VBG97UB4TA
                 Type: app (0x2)
                Flags: [  ] (0)
          Disposition: [enabled, allowed, notified] (0xb)
           Identifier: 2.com.objective-see.KnockKnock
                  URL: file:///Applications/KnockKnock.app/
           Generation: 1
    Bundle Identifier: com.objective-see.KnockKnock

There it is: com.objective-see.KnockKnock, disposition enabled, allowed, notified. The system knows about it — we just needed to ask the right way

Package management

Dragging and dropping every app into Applications would make this guide tedious. Enter Homebrew:

desdinova@desdinovas-Virtual-Machine ~ %  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
==> Checking for `sudo` access (which may request your password)...
Password:
==> This script will install:
/opt/homebrew/bin/brew
/opt/homebrew/share/doc/homebrew
/opt/homebrew/share/man/man1/brew.1
/opt/homebrew/share/zsh/site-functions/_brew
/opt/homebrew/etc/bash_completion.d/brew
/opt/homebrew
/etc/paths.d/homebrew
...
SNIP
...
Software Update Tool

Finding available software

Downloading Command Line Tools for Xcode 26.2
Downloaded Command Line Tools for Xcode 26.2
Installing Command Line Tools for Xcode 26.2
Done with Command Line Tools for Xcode 26.2
Done.
...
SNIP
...
==> Next steps:
- Run these commands in your terminal to add Homebrew to your PATH:
    echo >> /Users/desdinova/.zprofile
    echo 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> /Users/desdinova/.zprofile
    eval "$(/opt/homebrew/bin/brew shellenv zsh)"
- Run brew help to get started
- Further documentation:
    https://docs.brew.sh

desdinova@desdinovas-Virtual-Machine ~ % 

Observe that the Command Line Tools for Xcode 26.2 are downloaded and installed. This will come in handy later.

Launch the last three commands specified in the installation script, and you’re ready to go.

After installing Homebrew, a new KnockKnock scan reveals one change: .zprofile. Homebrew adds itself to your shell’s PATH there. No launch agents, no daemons — just a line in a config file that runs every time you open a terminal.

This is another persistence mechanism the manual commands miss entirely. Profile files are a classic and often overlooked way for software (legitimate or not) to ensure it runs.

I encourage you to run the manual commands again, and launch a new KnockKnock scan. You’ll confirm what I just described — and more importantly, you’ll build the habit of checking before and after every installation.

Groundwork done. Time to harden.

Hardening strategy

A note: it’s impossible to focus on a single domain (say, “let’s harden the network”) without touching others. We’ll install software and change configurations at the same time, jumping between domains as needed. This requires some mental organisation from you, reader. But we can do it :)

To draft a good strategy, we need to remember the threats we identified in the Threat Modelling phase. For convenience, let’s rewrite the list here:

  • Software
    • information loss due to technical issues
    • information loss due to myself
    • information theft
    • malware
  • Public image
    • customer’s data exfiltration
    • my websites being hacked
  • Laptop
    • Accidents
    • Laptop Loss
    • Laptop Stolen
  • My personal data
    • My preferences being gathered without my consent
    • Tracking

We can prune the Laptop entry from the list — the best mitigation there is insurance on the hardware. Unfortunately, I haven’t been able to find an insurance company that covers the contents of a laptop. I doubt I ever will.

To configure our machine we will use several websites, so the first “enemies” we will encounter are… cookies, trackers, and all those indecent phukeries.

This, incidentally, means that the first component that we need to configure is the network. My strategy is:

  • Armour the DNS
  • Install an Application Firewall
  • Block cookies

Armouring the DNS: DNS is the very first thing queried when a browser or any other application makes a request. Before HTTP, there’s DNS. If we block trackers, adware, and malware at this level, we cripple harmful components before they start. Benefits:

  • Preventive block, before traffic is initiated
  • Works for all apps
  • No software to install
  • Query logging

Application firewall: what if a component queries an IP address directly, rather than a FQDN? What if it connects to a domain that’s not blacklisted? An application firewall shows all outbound connection attempts, asking for permission to establish that connection. Benefits:

  • Visibility on who connects where
  • Granular, per-app control
  • Spots “phone home” and telemetry
  • Helps write network firewall rules
  • If DNS blocks known harmful domains, an application firewall blocks unknown connections

HTTP filtering: in line with our risk profile, we also need to work at the application level — inspecting HTTP requests themselves:

  • Header spoofing (User-Agent, Referer)
  • Blocking tracking pixels and inline scripts

This is layered defence in action: all three must fail for the system to fail.

A cautionary note

In this series I am teaching you not only to harden your machine, but also how to protect your identity.

We will harden systems and services, within the (strict) scope of what is under your control. Shortly we’ll discuss how to harden the access to your email; obviously we cannot harden the mail server if it’s under someone else’s control.

This leads to a delicate topic: federated logins. I know many people who claim federated login has advantages, and many of my customers push their customers to use them. Understandable. But from our perspective, this practice is at best doubtful. For instance, our next step will be hardening DNS queries using an external service (NextDNS). Suppose you opt for Google login. If you go this route, you’ve just linked:

  • the service that manages all your DNS queries
  • your Google identity

which contradicts our hardening principles.

In short: “Sign in with Google/Facebook/Apple/GitHub”: are convenient, but they link your identity across services. If you’re hardening for privacy, consider using a dedicated email and password instead. Apple’s “Hide My Email” is a reasonable middle ground.

Already signed up with Google or Facebook? The link is made — that data point exists. Switching to a dedicated email now limits future tracking, but doesn’t erase the past. If the service allows changing your login method, do it. If not, consider whether it’s worth creating a fresh account. For new services, start clean.

DNS Hardening

NextDNS

NextDNS is a DNS resolver that filters malicious domains, trackers, and ads before they even reach your machine. Think of it as a bouncer at the door — if a domain is on the blacklist, the connection never happens.

Why NextDNS:

  • Blocks at DNS level — works for all apps, not just browsers
  • No software required on the machine (though an app exists)
  • Encrypted queries (DNS-over-HTTPS) — your ISP can’t see what you’re resolving
  • Logging with Swiss storage — useful for auditing, privacy-respecting jurisdiction

Setup:

  1. Create an account at nextdns.io — use a dedicated email, not your main one
  2. Create a new Configuration (I called mine “Cyber Implants Profile”)

NextDNS Setup

This is what you would see after creating a fresh account. As you may notice, the service detects the active DNS.

Security tab — enable everything:

  • Threat Intelligence Feeds
  • Google Safe Browsing
  • Cryptojacking Protection
  • DNS Rebinding Protection
  • IDN Homograph Attacks Protection
  • Typosquatting Protection
  • DGA Protection
  • Block Newly Registered Domains
  • Block Dynamic DNS Hostnames
  • Block Parked Domains

NextDNS Security Privacy tab — add blocklists:

  • NextDNS Ads & Trackers Blocklist
  • OISD
  • AdGuard Tracking Protection

Under “Native Tracking Protection”, enable Apple and Windows.

NextDNS Privacy

Settings tab:

  • Enable Logs
  • Storage location: Switzerland
  • Retention: your choice (I use 3 months)

Now copy the account ID, download and install the NextDNS utility from the Apple Store, and write your ID in the field: NextDNS Utility

Refresh the NextDNS Setup page. You should see “All good! This device is using NextDNS with this profile.”

NextDNS Setup Good

Test it:

nslookup ads.google.com

If NextDNS is working, you’ll see blockpage.nextdns.io instead of Google’s IP.

Application Firewalls

NextDNS blocks known bad domains. But what about:

  • Apps connecting to IP addresses directly?
  • Domains not on any blocklist?
  • Legitimate domains used for telemetry?

An application firewall intercepts every outbound connection and asks: should this app be allowed to connect here?

LuLu vs Little Snitch

I use Little Snitch on my daily driver — it’s polished, feature-rich, and worth the ~€69. But for this guide, we’ll use LuLu:

  • Free and open source
  • Same author as KnockKnock (Patrick Wardle, Objective-See)
  • Does the core job well

If you want more features later, Little Snitch is the upgrade path.

Installation

Installing LuLu is straightforward, and well described on the Objective-See page. After you enable the System Extension, the installation is complete.

On first launch, LuLu will ask about many Apple processes. This is normal — your system is chatty. Allow the legitimate ones, and the noise will settle.

The default settings and modes cover the basic needs of most users:

LuLu Settings

LuLu Rules

LuLu Mode

Testing

To test it, install wget:

brew install wget

Brew wget

If LuLu is configured properly, you’ll see an alert:

LuLu Alert

In this case, curl (used by Homebrew) needs to reach GitHub. Allow it — this is legitimate. If you see something unexpected trying to connect, that’s when LuLu earns its keep. LuLu Rule Edit You can also write your own rules manually. LuLu Rule Edit

My philosophy: block everything unless explicitly needed. When in doubt, block. If something breaks, you’ll know immediately — and you’ll learn what your system actually needs to function. This is more valuable than any predefined ruleset I could give you.

HTTP Filtering

DNS blocks domains. The application firewall blocks connections. But what about the content of HTTP requests?

Privoxy is a filtering proxy that sits between your browser and the internet. It can:

  • Spoof headers (User-Agent, Referer) to reduce fingerprinting
  • Block tracking pixels and analytics scripts
  • Filter ad-related content that slipped through DNS

Installation

brew install privoxy

Start the service:

brew services start privoxy

Privoxy runs on 127.0.0.1:8118 by default.

System Configuration

To route your traffic through Privoxy:

  1. Open System Settings → Network → Wi-Fi → Details → Proxies
  2. Enable Web Proxy (HTTP) and Secure Web Proxy (HTTPS)
  3. Set both to 127.0.0.1 port 8118

Now all HTTP/HTTPS traffic passes through Privoxy.

The Problem: Toggle

Sometimes Privoxy breaks things. A site won’t load, a login fails, something times out. You need to quickly disable it, fix your problem, and re-enable it. And it’s not that easy anyway. For instance, the web interface of my MikroTik cannot coexist with Privoxy. Thanks god for the Terminal interface.

Going through System Settings every time is tedious. Enter SwiftBar.

SwiftBar

SwiftBar lets you run scripts from the menu bar. We’ll use it to toggle Privoxy on and off with one click.

brew install swiftbar

Create a file called privoxy.1m.sh in your SwiftBar plugins folder:

#!/bin/bash
# Privoxy
# true

if pgrep -x privoxy > /dev/null; then
    echo "🛡️"
    echo "---"
    echo "Privoxy running | color=green"
    echo "Stop | shell=/opt/homebrew/bin/brew param1=services param2=stop param3=privoxy terminal=false refresh=true"
else
    echo "🛡️"
    echo "---"
    echo "Privoxy stopped | color=red"
    echo "Start | shell=/opt/homebrew/bin/brew param1=services param2=start param3=privoxy terminal=false refresh=true"
fi

Make it executable:

chmod +x privoxy.1m.sh

Now you have a shield icon in your menu bar. Green means running, red means stopped. One click to toggle.

Testing

With Privoxy enabled, visit a site that shows your headers (e.g., httpbin.org/headers). You should see Privoxy’s modifications.

With Privoxy disabled, the same site shows your real headers.

Conclusion

The structure we are creating is consistent with the principles that we gave in the previous post. We need to build layers of defence.

In the previous post, we assessed some threats and declared the objectives we want to tackle.

Here we started talking about:

  • package management
  • persistence management
  • network defence.

In detail, we have built three layers of network defence:

  1. DNS (NextDNS) — blocks known bad domains before connections start
  2. Application Firewall (LuLu) — catches what DNS misses, gives per-app control
  3. HTTP Filtering (Privoxy) — inspects and modifies request content

All three must fail for tracking or malware to succeed. That’s layered defence.

This is just the network layer. In the next post, we’ll tackle browser compartmentalisation — because even with all this, your browser is still the biggest attack surface.

Stay paranoid, but have fun!

References

Wanna interact?

Then come visit me on mastodon: https://infosec.exchange/@gbiondo