tl;dr

while analysing a macos persistence demonstration, we followed an execution path that started with the command we use to read documentation.

man pages explain how commands work and which options they accept. on macos, the utility that opens them can use a system configuration file to choose the program that displays the text. that was where it got interesting: the configuration could remain on the device until a user opened a manual page and triggered the program.

the user would be looking up a command. the same interaction would also start the executable selected by that configuration.


following the manual reader

to understand the example, we first separated the two programs involved. man finds and formats a command's manual page. a pager handles displaying that text in the terminal, including scrolling through pages that do not fit on the screen. less is one such pager.

we used grep to inspect the pager configuration and found a commented entry for MANPAGER:

grep -nE '^[[:space:]]*#?[[:space:]]*MANPAGER[[:space:]]' /private/etc/man.conf

the relevant entry in the example is:

# MANPAGER        /usr/bin/less -s

MANPAGER names the setting, /usr/bin/less is the display program, and -s tells less to collapse consecutive blank lines. the leading # matters: this line is a comment, not an active override. it shows the format of a setting that selects a program, rather than just changing how the text looks.

that distinction explains the persistence mechanism. the manual reader delegates the display job to an executable. when that choice is redirected, a routine request for documentation can start a different program with the user's permissions. the configuration remains on disk between invocations; execution depends on someone opening a manual page through the configured pager.

check the current shell for pager overrides as well:

printenv MANPAGER PAGER

these environment variables can affect pager selection, so the configuration file alone does not describe the full execution context.

we next looked at how the example recorded execution. its small c program writes a timestamp, process identifiers and operating-system information to a local file. the fields that matter here are:

pid: 1483, uid: 501, ppid: 1480
system: Darwin 24.4.0 x86_64

this excerpt comes from the execution marker shown below. pid identifies the running test program, uid records its user identity, and ppid identifies its parent process. the parent id alone does not tell us the parent's executable name; that would require the corresponding process information.

execution marker showing uid 501 and darwin 24.4.0

the example shows the marker being recreated after the manual reader is invoked. uid 501 is also the important distinction between setup and execution: modifying the global configuration required root, but the resulting program ran as a regular user. reading the manual did not elevate its privileges.

the output reports Darwin 24.4.0, while the accompanying vm description calls the system sonoma. we kept the version visible in the output rather than treating that label as a verified environment.

finally, we followed the cleanup shown in the screenshots. deleting the marker only removes the output of the test program; it leaves the setting that selected the program untouched. the sequence therefore restores the original configuration before removing the test files.

cleanup restores man.conf before removing the test files


what to take away from this

this example works because the manual reader delegates part of its job to another program. a configuration file decides which program that is, and a routine request for documentation provides the trigger.

an investigation that only checks what starts at login can miss settings used by programs later in a session. here, the useful question is which executable the manual reader has been told to trust.