Skip to content

Instantly share code, notes, and snippets.

@knightsc
Created February 7, 2019 18:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save knightsc/74a76db70379850d67abead3ddf7deb0 to your computer and use it in GitHub Desktop.
Save knightsc/74a76db70379850d67abead3ddf7deb0 to your computer and use it in GitHub Desktop.
#include <spawn.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mach/mach.h>
#define MACH_ERR(str, err) do { \
if (err != KERN_SUCCESS) { \
mach_error("[-]" str "\n", err); \
exit(EXIT_FAILURE); \
} \
} while(0)
#define FAIL(str) do { \
printf("[-] " str "\n"); \
exit(EXIT_FAILURE); \
} while (0)
#define LOG(str) do { \
printf("[+] " str"\n"); \
} while (0)
/***************
* port dancer *
***************/
// set up a shared mach port pair from a child process back to its parent without using launchd
// based on the idea outlined by Robert Sesek here: https://robert.sesek.com/2014/1/changes_to_xnu_mach_ipc.html
// mach message for sending a port right
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t port;
} port_msg_send_t;
// mach message for receiving a port right
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t port;
mach_msg_trailer_t trailer;
} port_msg_rcv_t;
typedef struct {
mach_msg_header_t header;
} simple_msg_send_t;
typedef struct {
mach_msg_header_t header;
mach_msg_trailer_t trailer;
} simple_msg_rcv_t;
#define STOLEN_SPECIAL_PORT TASK_BOOTSTRAP_PORT
// a copy in the parent of the stolen special port such that it can be restored
mach_port_t saved_special_port = MACH_PORT_NULL;
// the shared port right in the parent
mach_port_t shared_port_parent = MACH_PORT_NULL;
void setup_shared_port() {
kern_return_t err;
// get a send right to the port we're going to overwrite so that we can both
// restore it for ourselves and send it to our child
err = task_get_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, &saved_special_port);
MACH_ERR("saving original special port value", err);
// allocate the shared port we want our child to have a send right to
err = mach_port_allocate(mach_task_self(),
MACH_PORT_RIGHT_RECEIVE,
&shared_port_parent);
MACH_ERR("allocating shared port", err);
// insert the send right
err = mach_port_insert_right(mach_task_self(),
shared_port_parent,
shared_port_parent,
MACH_MSG_TYPE_MAKE_SEND);
MACH_ERR("inserting MAKE_SEND into shared port", err);
// stash the port in the STOLEN_SPECIAL_PORT slot such that the send right survives the fork
err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, shared_port_parent);
MACH_ERR("setting special port", err);
}
mach_port_t recover_shared_port_child() {
kern_return_t err;
// grab the shared port which our parent stashed somewhere in the special ports
mach_port_t shared_port_child = MACH_PORT_NULL;
err = task_get_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, &shared_port_child);
MACH_ERR("child getting stashed port", err);
LOG("child got stashed port");
// say hello to our parent and send a reply port so it can send us back the special port to restore
// allocate a reply port
mach_port_t reply_port;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
MACH_ERR("child allocating reply port", err);
// send the reply port in a hello message
simple_msg_send_t msg = {0};
msg.header.msgh_size = sizeof(msg);
msg.header.msgh_local_port = reply_port;
msg.header.msgh_remote_port = shared_port_child;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
err = mach_msg_send(&msg.header);
MACH_ERR("child sending task port message", err);
LOG("child sent hello message to parent over shared port");
// wait for a message on the reply port containing the stolen port to restore
port_msg_rcv_t stolen_port_msg = {0};
err = mach_msg(&stolen_port_msg.header, MACH_RCV_MSG, 0, sizeof(stolen_port_msg), reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
MACH_ERR("child receiving stolen port\n", err);
// extract the port right from the message
mach_port_t stolen_port_to_restore = stolen_port_msg.port.name;
if (stolen_port_to_restore == MACH_PORT_NULL) {
FAIL("child received invalid stolen port to restore");
}
// restore the special port for the child
err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, stolen_port_to_restore);
MACH_ERR("child restoring special port", err);
//LOG("child restored stolen port");
return shared_port_child;
}
mach_port_t recover_shared_port_parent() {
kern_return_t err;
// restore the special port for ourselves
err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, saved_special_port);
MACH_ERR("parent restoring special port", err);
// wait for a message from the child on the shared port
simple_msg_rcv_t msg = {0};
err = mach_msg(&msg.header,
MACH_RCV_MSG,
0,
sizeof(msg),
shared_port_parent,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
MACH_ERR("parent receiving child hello message", err);
LOG("parent received hello message from child");
// send the special port to our child over the hello message's reply port
port_msg_send_t special_port_msg = {0};
special_port_msg.header.msgh_size = sizeof(special_port_msg);
special_port_msg.header.msgh_local_port = MACH_PORT_NULL;
special_port_msg.header.msgh_remote_port = msg.header.msgh_remote_port;
special_port_msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg.header.msgh_bits), 0) | MACH_MSGH_BITS_COMPLEX;
special_port_msg.body.msgh_descriptor_count = 1;
special_port_msg.port.name = saved_special_port;
special_port_msg.port.disposition = MACH_MSG_TYPE_COPY_SEND;
special_port_msg.port.type = MACH_MSG_PORT_DESCRIPTOR;
err = mach_msg_send(&special_port_msg.header);
MACH_ERR("parent sending special port back to child", err);
return shared_port_parent;
}
void do_child(mach_port_t shared_port, char *argv[], char *envp[]) {
kern_return_t err;
// create a reply port to receive an ack that we should exec the target
mach_port_t reply_port;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
MACH_ERR("child allocating reply port", err);
port_msg_send_t msg = {0};
msg.header.msgh_size = sizeof(msg);
msg.header.msgh_local_port = reply_port;
msg.header.msgh_remote_port = shared_port;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX;
msg.body.msgh_descriptor_count = 1;
msg.port.name = mach_task_self();
msg.port.disposition = MACH_MSG_TYPE_COPY_SEND;
msg.port.type = MACH_MSG_PORT_DESCRIPTOR;
err = mach_msg_send(&msg.header);
MACH_ERR("child sending task port message", err);
// wait for an ack on the reply port:
simple_msg_rcv_t ack_msg = {0};
err = mach_msg(&ack_msg.header,
MACH_RCV_MSG,
0,
sizeof(ack_msg),
reply_port,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
MACH_ERR("parent receiving child hello message", err);
// exec the target
// execve(argv[1], &argv[1], envp);
// spawn suspended
pid_t pid;
posix_spawnattr_t attr;
err = posix_spawnattr_init(&attr);
if (err) {
FAIL("can't init spawnattr");
}
err = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED|POSIX_SPAWN_SETEXEC);
if (err) {
FAIL("can't set flags");
}
err = setsid();
err = posix_spawnp(&pid, argv[1], NULL, &attr, &argv[1], envp);
if (err) {
printf("%s\n", strerror(errno));
FAIL("posix_spawn failed");
}
}
void do_parent(mach_port_t shared_port) {
kern_return_t err;
// wait for our child to send us its task port
port_msg_rcv_t msg = {0};
err = mach_msg(&msg.header,
MACH_RCV_MSG,
0,
sizeof(msg),
shared_port,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
MACH_ERR("parent receiving child task port message", err);
// get the userclient send right from the child:
mach_port_t task = msg.port.name;
if (task == MACH_PORT_NULL) {
FAIL("invalid task send right received from child");
}
LOG("parent received child task port");
thread_act_array_t th_array1;
mach_msg_type_number_t th_count1;
err = task_threads(task, &th_array1, &th_count1);
MACH_ERR("task_threads", err);
printf("[+] %d threads found\n", th_count1);
// ack the child so that it will exec the target
simple_msg_send_t ack_msg = {0};
ack_msg.header.msgh_size = sizeof(ack_msg);
ack_msg.header.msgh_local_port = MACH_PORT_NULL;
ack_msg.header.msgh_remote_port = msg.header.msgh_remote_port;
ack_msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
err = mach_msg_send(&ack_msg.header);
MACH_ERR("parent acking child to exec target", err);
usleep(100000);
// Modify process memory
thread_act_array_t th_array2;
mach_msg_type_number_t th_count2;
err = task_threads(task, &th_array2, &th_count2);
MACH_ERR("task_threads", err);
printf("[+] %d threads found\n", th_count2);
err = task_resume(task);
MACH_ERR("resuming child task", err);
}
int
main(int argc, char *argv[], char *envp[])
{
if (argc < 2) {
printf("usage: %s program [args...]\n", argv[0]);
return 1;
}
setup_shared_port();
pid_t child_pid = fork();
if (child_pid == -1) {
FAIL("forking");
}
if (child_pid == 0) {
mach_port_t shared_port_child = recover_shared_port_child();
do_child(shared_port_child, argv, envp);
} else {
mach_port_t shared_port_parent = recover_shared_port_parent();
do_parent(shared_port_parent);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment