|
#pragma GCC diagnostic warning "-Wunused-function" |
|
|
|
#define _GNU_SOURCE |
|
#include <sys/types.h> |
|
#include <sys/prctl.h> |
|
#include <fcntl.h> |
|
#include <limits.h> |
|
#include <signal.h> |
|
#include <sys/wait.h> |
|
#include <stddef.h> |
|
#include <stdbool.h> |
|
#include <linux/audit.h> |
|
#include <sys/syscall.h> |
|
#include <sys/stat.h> |
|
#include <linux/filter.h> |
|
#include <linux/seccomp.h> |
|
#include <sys/ioctl.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
#include <errno.h> |
|
|
|
#include <string.h> |
|
#include <pthread.h> |
|
|
|
#include <gum/guminterceptor.h> |
|
|
|
extern int notifyFd; |
|
extern void onMessage(char*); |
|
|
|
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ |
|
} while (0) |
|
|
|
#define X32_SYSCALL_BIT 0x40000000 |
|
|
|
#define X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR \ |
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \ |
|
(offsetof(struct seccomp_data, arch))), \ |
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 2), \ |
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \ |
|
(offsetof(struct seccomp_data, nr))), \ |
|
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, X32_SYSCALL_BIT, 0, 1), \ |
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS) |
|
|
|
static int |
|
seccomp(unsigned int operation, unsigned int flags, void *args) |
|
{ |
|
return syscall(__NR_seccomp, operation, flags, args); |
|
} |
|
|
|
int |
|
installNotifyFilter(void) |
|
{ |
|
struct sock_filter filter[] = { |
|
X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR, |
|
|
|
/* mkdir() triggers notification to user-space tracer */ |
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mkdir, 0, 1), |
|
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF), |
|
|
|
/* Every other system call is allowed */ |
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), |
|
}; |
|
|
|
struct sock_fprog prog = { |
|
.len = (unsigned short) (sizeof(filter) / sizeof(filter[0])), |
|
.filter = filter, |
|
}; |
|
|
|
int notifyFd = seccomp(SECCOMP_SET_MODE_FILTER, |
|
SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); |
|
if (notifyFd == -1) |
|
errExit("seccomp-install-notify-filter"); |
|
|
|
return notifyFd; |
|
} |
|
|
|
static void |
|
checkNotificationIdIsValid(int notifyFd, __u64 id, char *tag) |
|
{ |
|
if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_ID_VALID, &id) == -1) { |
|
fprintf(stderr, "Tracer: notification ID check (%s): " |
|
"target has died!!!!!!!!!!!\n", tag); |
|
} |
|
} |
|
|
|
void * |
|
// watchForNotifications(int notifyFd, struct cmdLineOpts *opts) |
|
watchForNotifications() |
|
{ |
|
struct seccomp_notif *req; |
|
struct seccomp_notif_resp *resp; |
|
struct seccomp_notif_sizes sizes; |
|
char path[PATH_MAX]; |
|
int procMem; /* FD for /proc/PID/mem of target process */ |
|
|
|
/* Discover the sizes of the structures that are used to receive |
|
notifications and send notification responses, and allocate |
|
buffers of those sizes. */ |
|
|
|
if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) == -1) |
|
errExit("Tracer: seccomp-SECCOMP_GET_NOTIF_SIZES"); |
|
|
|
req = malloc(sizes.seccomp_notif); |
|
if (req == NULL) |
|
errExit("Tracer: malloc"); |
|
|
|
resp = malloc(sizes.seccomp_notif_resp); |
|
if (resp == NULL) |
|
errExit("Tracer: malloc"); |
|
|
|
/* Loop handling notifications */ |
|
|
|
for (;;) { |
|
|
|
/* Wait for next notification, returning info in '*req' */ |
|
|
|
if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req) == -1) |
|
errExit("Tracer: ioctlSECCOMP_IOCTL_NOTIF_RECV"); |
|
|
|
printf("Tracer: got notification for PID %d; ID is %llx\n", |
|
req->pid, req->id); |
|
|
|
/* If a delay interval was specified on the command line, then delay |
|
for the specified number of seconds. This can be used to demonstrate |
|
the following: |
|
|
|
(1) The target process is blocked until the tracer sends a response. |
|
(2) If the blocked system call is interrupted by a signal handler, |
|
then the SECCOMP_IOCTL_NOTIF_SEND operation fails with the error |
|
ENOENT. |
|
(3) If the target process terminates, then we can discover this |
|
using the SECCOMP_IOCTL_NOTIF_ID_VALID operation (which is |
|
employed by checkNotificationIdIsValid()). */ |
|
|
|
// if (opts->delaySecs > 0) { |
|
// printf("Tracer: delaying for %d seconds:", opts->delaySecs); |
|
// checkNotificationIdIsValid(notifyFd, req->id, "pre-delay"); |
|
|
|
// for (int d = opts->delaySecs; d > 0; d--) { |
|
// printf(" %d", d); |
|
// sleep(1); |
|
// } |
|
// printf("\n"); |
|
|
|
// checkNotificationIdIsValid(notifyFd, req->id, "post-delay"); |
|
// } |
|
|
|
/* Access the memory of the target process in order to discover |
|
the pathname that was given to mkdir() */ |
|
|
|
snprintf(path, sizeof(path), "/proc/%d/mem", req->pid); |
|
|
|
procMem = open(path, O_RDONLY); |
|
if (procMem == -1) |
|
errExit("Tracer: open"); |
|
|
|
/* Check that the process whose info we are accessing is still alive */ |
|
|
|
checkNotificationIdIsValid(notifyFd, req->id, "post-open"); |
|
|
|
/* Since, the SECCOMP_IOCTL_NOTIF_ID_VALID operation (performed in |
|
checkNotificationIdIsValid()) succeeded, we know that the |
|
/proc/PID/mem file descriptor that we opened corresponded to the |
|
process for which we received a notification. If that process |
|
subsequently terminates, then read() on that file descriptor will |
|
return 0 (EOF). This can be tested by (1) uncommenting the sleep() |
|
call below (and rebuilding the program); (2) running the program |
|
with flags to ensure that the tracer is not killed if the target |
|
dies; and (3) killing the target process during the sleep(). */ |
|
|
|
// printf("About to sleep in target\n"); |
|
// sleep(15); |
|
|
|
/* Seek to the location containing the pathname argument (i.e., the |
|
first argument) of the mkdir(2) call and read that pathname */ |
|
|
|
if (lseek(procMem, req->data.args[0], SEEK_SET) == -1) |
|
errExit("Tracer: lseek"); |
|
|
|
ssize_t s = read(procMem, path, sizeof(path)); |
|
if (s == -1) |
|
errExit("read"); |
|
else if (s == 0) { |
|
fprintf(stderr, "Tracer: read returned EOF\n"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
printf("Tracer: mkdir(\"%s\", %llo)\n", path, req->data.args[1]); |
|
|
|
// Callback to Frida |
|
onMessage(path); |
|
|
|
if (close(procMem) == -1) |
|
errExit("close-/proc/PID/mem"); |
|
|
|
/* The response to the notification includes the notification ID */ |
|
|
|
resp->id = req->id; |
|
// resp->flags = 0; /* Must be zero as at Linux 5.0 */ |
|
|
|
// Allow the syscall to continue |
|
resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; |
|
|
|
/* Success return value is the length of the pathname given to |
|
mkdir() */ |
|
|
|
// resp->val = strlen(path); |
|
|
|
/* If the directory is in /tmp, then create it on behalf of the tracer; |
|
give an error for a directory pathname in any other location. */ |
|
|
|
// if (strncmp(path, "/tmp/", strlen("/tmp/")) == 0) { |
|
// mkdir(path, req->data.args[1]); |
|
// resp->error = 0; |
|
// } else { |
|
// resp->error = -EPERM; |
|
// } |
|
|
|
/* Provide a response to the target process */ |
|
|
|
|
|
if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp) == -1) { |
|
if (errno == ENOENT) |
|
printf("Tracer: response failed with ENOENT; perhaps target " |
|
"process's syscall was interrupted by signal?\n"); |
|
else |
|
perror("ioctl-SECCOMP_IOCTL_NOTIF_SEND"); |
|
} |
|
|
|
/* If the pathname is just "/bye", then the tracer terminates. This |
|
allows us to see what happens if the target process makes further |
|
calls to mkdir(2). */ |
|
|
|
if (strcmp(path, "/bye") == 0) { |
|
printf("Tracer: terminating <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); |
|
exit(EXIT_FAILURE); |
|
} |
|
} |
|
} |
|
|
|
int |
|
installFilter() |
|
{ |
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) |
|
errExit("prctl"); |
|
|
|
notifyFd = installNotifyFilter(); |
|
|
|
pthread_t threadId; |
|
// TODO catch err |
|
pthread_create(&threadId, NULL, &watchForNotifications, NULL); |
|
|
|
return notifyFd; |
|
} |