Beyond the good ol' LaunchAgents - 23 - emond, The Event Monitor Daemon
This is part 23 in the series of “Beyond the good ol’ LaunchAgents”, where I try to collect various persistence techniques for macOS. For more background check the introduction.
This post will be about emond
, Apple’s Event Monitor daemon.
I think almost everything has been already told about this method and emond
in general by James Reynolds here and xorrior here so really not much left for me. There is no point for me replicating their awesome posts, so please just read them. That’s it! Thank you for reading! ¯\(ツ)/¯
.
.
.
.
.
.
.
.
.
OK, well… I will still give a super-brief summary of how to persist with emond, and then, I decided to add my contribution to the emond
documentation, so stay tuned.
The TL;DR Link to heading
The emond
service is defined in /System/Library/LaunchDaemons/com.apple.emond.plist
.
<?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>Disabled</key>
<false/>
<key>Label</key>
<string>com.apple.emond</string>
<key>MachServices</key>
<dict>
<key>com.apple.emond.evtq</key>
<dict>
<key>HideUntilCheckIn</key>
<true/>
</dict>
</dict>
<key>Program</key>
<string>/sbin/emond</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/emond</string>
</array>
<key>QueueDirectories</key>
<array>
<string>/private/var/db/emondClients</string>
</array>
<key>DrainMessagesOnFailedInit</key>
<true/>
<key>EnablePressuredExit</key>
<true/>
<key>EnableTransactions</key>
<false/>
</dict>
</plist>
It will start, whenever there is a file in the directory /private/var/db/emondClients/
, which is specified by QueueDirectories
in the PLIST file. We can create even an empty file, the content is not so important.
sudo touch /private/var/db/emondClients/some
Then emond
will started by launchd
and it will process the rules found inside /etc/emond.d/rules/
. These rules define what action to perform when a specific event occurs. For a detailed explanation of these events and actions please refer to the blog posts I linked at the very beginning. For the persistence example I will show how we can run a simple command when emond
is starting up. First, let’s make a copy of the default sample rule.
sudo cp /etc/emond.d/rules/SampleRules.plist /etc/emond.d/rules/startup.plist
and edit it so it looks like this.
<?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">
<array>
<dict>
<key>name</key>
<string>create file</string>
<key>enabled</key>
<true/>
<key>eventTypes</key>
<array>
<string>startup</string>
</array>
<key>actions</key>
<array>
<dict>
<key>command</key>
<string>/bin/bash</string>
<key>user</key>
<string>root</string>
<key>arguments</key>
<array>
<string>-c</string>
<string>touch /Library/emondstarted.txt</string>
</array>
<key>type</key>
<string>RunCommand</string>
</dict>
</array>
</dict>
</array>
</plist>
Here we define a command that will be executed when emond
starts (eventTypes
:startup
). Note that emond
will startup during boot time, when the user is not yet logged in. We can control the command with the command
, arguments
and user
keys. The above will execute /bin/bash -c touch /Library/emondstarted.txt
as root. Note that the enabled
key is set to true
.
Now if we restart emond
by killing it or rebooting our machine the above command will be executed and the file /Library/emondstarted.txt
will be created.
The /etc/emond.d/emond.plist
file contains configuration options, like other rule directories (additionalRulesPaths
) and also the following filter:
<key>filterByUID</key>
<string>0</string>
<key>filterByGID</key>
<string></string>
This filter controls who can call emond’s XPC service, what we will discuss next.
com.apple.emond.evtq Link to heading
I decided to dig into a bit into the XPC internals of emond
, which I think was not documented before.
As defined in the launchd file, the XPC service name is com.apple.emond.evtq
. If we open emond
in a disassembler, we can find that it’s created here:
/* @class Monitor */
-(int)SetXPCListenerFor:(void *)arg2 {
r15 = arg2;
r14 = self;
rax = xpc_connection_create_listener("com.apple.emond.evtq", arg2, arg2);
if (rax != 0x0) {
xpc_connection_set_legacy(rax);
var_48 = *__NSConcreteStackBlock;
*(&var_48 + 0x8) = 0xffffffffc2000000;
*(&var_48 + 0x10) = sub_10000e621;
*(&var_48 + 0x18) = 0x10002c420;
*(&var_48 + 0x20) = r14;
*(&var_48 + 0x28) = r15;
xpc_connection_set_event_handler(rax, &var_48);
*(r14 + 0x18) = rax;
xpc_retain(rax);
xpc_connection_resume(rax);
rax = 0x0;
}
else {
rax = 0xffffffffffffffff;
}
return rax;
}
Inside the connection handler we find a call to filterBySourceUID:GID:
.
int sub_10000e621(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
...
r15 = xpc_connection_get_euid(r13);
rax = xpc_connection_get_egid(r13);
r12 = rax;
sub_10000bbc5(0x3, "XPC Connection Request recieved from %d:%d (uid:gid)", r15, rax, r8, r9, var_60);
if ([*(r14 + 0x20) filterBySourceUID:r15 GID:r12] != 0x0) {
...
The actual UID/GID filter is populated at the EntryPoint
.
*qword_100038cb8 = [[Monitor alloc] initWithRulebase:*qword_100038ca8 andVariables:r13];
[*qword_100038cb8 setEventFilterUID:sub_10000c060([*qword_100038ca0 objectForKey:@"filterByUID", r13]) FilterGID:sub_10000c060([*qword_100038ca0 objectForKey:@"filterByGID", r13])];
Without going into further details, if we follow these function, we will find that it parses the config file, and reads in the values defined in filterByUID
and filterByGID
. This means that by default only root
level processes can communicate with emond
, but we can also modify this setting if needed, allowing other processes.
So we know that only root can connect. But how normally messages are sent? The /System/Library/LaunchDaemons/com.apple.emlog.plist
defines a perl script at /usr/libexec/emlog.pl
. This script uses the /usr/libexec/xssendevent
binary to send messages to emond
.
For example:
echo "{ eventType = auth.failure; eventSource = emlog.pl; eventDetails = {clientIP = \"1.1.1.1\"; hostPort = 21; protocolName = \"ftp\";};}" | /usr/libexec/xssendevent
Since only root can send messages, we need to execute this command as root.
I edited my /private/etc/emond.d/rules/SampleRules.plist
to log a message for the auth.failure
event type.
<?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">
<array>
<dict>
<key>name</key>
<string>sample rule</string>
<key>enabled</key>
<true/>
<key>eventTypes</key>
<array>
<string>auth.failure</string>
</array>
<key>actions</key>
<array>
<dict>
<key>message</key>
<string>Event Monitor started at ${builtin:now}</string>
<key>type</key>
<string>Log</string>
<key>logLevel</key>
<string>Notice</string>
<key>logType</key>
<string>syslog</string>
</dict>
</array>
</dict>
</array>
</plist>
Now if we execute the command above, we will see a log message in the log stream.
sh-3.2# echo "{ eventType = auth.failure; eventSource = emlog.pl; eventDetails = {clientIP = \"1.1.1.1\"; hostPort = 21; protocolName = \"ftp\";};}" | /usr/libexec/xssendevent
-----------
csaby@mac ~ % log stream | grep "Event Monitor"
2021-11-27 14:47:02.926999+0100 0x66a7f3 Default 0x0 18157 0 emond: Event Monitor started at 659713622.926977
We can save the event in a json plist file, and specify it as the input for xssendevent
. The json formatted event PLIST file:
{ eventType = auth.failure; eventSource = emlog.pl; eventDetails = {clientIP = "1.1.1.1"; hostPort = 21; protocolName = "ftp";};}
Then using the -e
option, we can specify the previously saved file.
/usr/libexec/xssendevent -e event.plist
But how can we do this directly with xpc? xssendevent
uses the /usr/lib/libXSEvent.dylib
library to send a message.
sh-3.2# otool -L /usr/libexec/xssendevent
/usr/libexec/xssendevent:
/usr/lib/libXSEvent.dylib (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60157.40.30)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1855.103.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1855.103.0)
Instead of reversing everything, we can use Jonathan Levin’s XPoCe
utility to sniff the XPC communication, and then recreate the code ourselves. This is much easier as we will see the messages directly without doing lot’s of manual reversing.
To do this we need a machine with SIP disabled, as XPoCe
injects code into the target process. Then we can send our event again.
sh-3.2# echo "{ eventType = auth.failure; eventSource = emlog.pl; eventDetails = {clientIP = \"1.1.1.1\"; hostPort = 21; protocolName = \"ftp\";};}" | ./XPoCe /usr/libexec/xssendevent
xpc_connection_create ( "com.apple.emond.evtq",0x7f9c3640a0f0) = 0x7f9c36504080 ()
xpc_connection_send_message ( connection@0x7f9c36504080,dictionary@0x7f9c3640b070)
= "<connection: 0x7f9c36504080> { name = com.apple.emond.evtq, listener = false, pid = 0, euid = 4294967295, egid = 4294967295, asid = 4294967295 }"
= "<dictionary: 0x7f9c3640b070> { count = 3, transaction: 0, voucher = 0x0, contents =
"eventDetails" => <dictionary: 0x7f9c3640b0d0> { count = 4, transaction: 0, voucher = 0x0, contents =
"clientIP" => <string: 0x7f9c3640b1f0> { length = 7, contents = "1.1.1.1" }
"protocolName" => <string: 0x7f9c3640b290> { length = 3, contents = "ftp" }
"hostPort" => <string: 0x7f9c3640b260> { length = 2, contents = "21" }
"eventSource" => <string: 0x7f9c3640b370> { length = 8, contents = "emlog.pl" }
}
"eventType" => <string: 0x7f9c3640b340> { length = 12, contents = "auth.failure" }
"eventTimestamp" => <double: 0x7f9c3640b130>: 659713961.621497
}"
We can easily translate the above output to the following C code suing the standard XPC libraries.
#include <stdio.h>
#include <stdlib.h>
#include <xpc/xpc.h>
int main(int argc, const char **argv) {
xpc_connection_t service;
xpc_object_t msg, event_details;
msg = xpc_dictionary_create(NULL, NULL, 0);
event_details = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(event_details, "clientIP", "1.1.1.1");
xpc_dictionary_set_string(event_details, "protocolName", "ftp");
xpc_dictionary_set_string(event_details, "hostPort", "21");
xpc_dictionary_set_string(event_details, "eventSource", "myxpc");
xpc_dictionary_set_string(msg, "eventType", "auth.failure");
xpc_dictionary_set_value(msg, "eventDetails", event_details);
xpc_dictionary_set_double(msg, "eventTimestamp", 659713961.621497);
service = xpc_connection_create_mach_service("com.apple.emond.evtq", NULL, 0);
if (service == NULL) {
perror("xpc_connection_create_mach_service");
}
xpc_connection_set_event_handler(service, ^(xpc_object_t event) {
xpc_type_t type = xpc_get_type(event);
if (type == XPC_TYPE_ERROR)
{
printf("emond, client, xpc error: %s", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
}
else
{
char* description = xpc_copy_description(event);
printf("emond, client, xpc unexpected type: %s", description);
}
});
xpc_connection_resume(service);
xpc_connection_send_message(service, msg);
}
Then, once we compile it and execute, we can verify that our event message was properly logged by emond
, by our rule.
csaby@mac emond % sudo ./emondxpc
------------------------------
csaby@mac ~ % log stream | grep "Event Monitor"
2021-11-27 15:09:50.539508+0100 0x66de62 Default 0x0 18157 0 emond: Event Monitor started at 659714990.539463
If we set the “eventType” to “startup” we can get our file created.
What it is good for? I don’t know :))) I don’t think it’s good for any privilege escalation as we can’t communicate it with emond
as a regular user and emond
doesn’t have any cool entitlements. The effectiveness of our event message is also tied to the rules configured on the machine, which is nothing by default. However we might find an environment where emontd
is configured differently, and maybe there is some interesting rule that we can trigger. Other than that I just entertained myself with some reverse engineering and get to know emond
a bit better.