Run cel binarny przy użyciu biblioteki napięcia wstępnego, że połowy fork()
.Dopóki wszystkie procesy potomne korzystają również z biblioteki wstępnego ładowania, wszystkie lokalne procesy podrzędne będą widoczne bez względu na sposób ich wykonania.
Oto przykładowa implementacja.
Najpierw plik nagłówkowy forkmonitor.h
. Definiuje komunikaty przekazywane z biblioteki napięcia wstępnego, do procesu monitorowania:
#ifndef FORKMONITOR_H
#define FORKMONITOR_H
#define FORKMONITOR_ENVNAME "FORKMONITOR_SOCKET"
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif
#define TYPE_EXEC 1 /* When a binary is executed */
#define TYPE_DONE 2 /* exit() or return from main() */
#define TYPE_FORK 3
#define TYPE_VFORK 4
#define TYPE_EXIT 5 /* _exit() or _Exit() */
#define TYPE_ABORT 6 /* abort() */
struct message {
pid_t pid; /* Process ID */
pid_t ppid; /* Parent process ID */
pid_t sid; /* Session ID */
pid_t pgid; /* Process group ID */
uid_t uid; /* Real user ID */
gid_t gid; /* Real group ID */
uid_t euid; /* Effective user ID */
gid_t egid; /* Effective group ID */
unsigned short len; /* Length of data[] */
unsigned char type; /* One of the TYPE_ constants */
char data[0]; /* Optional payload, possibly longer */
};
#endif /* FORKMONITOR_H */
Zmienna FORKMONITOR_SOCKET
środowisko (nazwany przez FORKMONITOR_ENVNAME
makro powyżej) określa Unix datagramowy domeny gniazdo addess do procesu monitorowania. Jeśli nie jest zdefiniowany lub pusty, nie są wysyłane żadne wiadomości monitorowania.
Oto sama biblioteka, libforkmonitor.c
. Należy zauważyć, że znacznie uprościłem kod, pomijając inicjalizację wielowątkową (ponieważ biblioteka rzadko wywołuje dowolne przechwycone funkcje, a jeszcze rzadziej robi to z wielu wątków). Lepiej byłoby użyć wbudowanych atomów (__sync_bool_compare_and_swap()), aby zaktualizować wskaźnik funkcji i getter atomowy (__sync_fetch_and_or (, 0)) w celu pobrania wskaźnika funkcji, aby uniknąć problemów z bibliotekami o nietypowych rozmiarach. (To jest zupełnie bezpieczna dla programów wielowątkowych, ponieważ wskaźniki zostaną zmienione tylko przed main()
jest wykonywany.)
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <dlfcn.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"
static pid_t (*actual_fork)(void) = NULL;
static pid_t (*actual_vfork)(void) = NULL;
static void (*actual_abort)(void) = NULL;
static void (*actual__exit)(int) = NULL;
static void (*actual__Exit)(int) = NULL;
static int commfd = -1;
#define MINIMUM_COMMFD 31
static void notify(const int type, struct message *const msg, const size_t extra)
{
const int saved_errno = errno;
msg->pid = getpid();
msg->ppid = getppid();
msg->sid = getsid(0);
msg->pgid = getpgrp();
msg->uid = getuid();
msg->gid = getgid();
msg->euid = geteuid();
msg->egid = getegid();
msg->len = extra;
msg->type = type;
/* Since we don't have any method of dealing with send() errors
* or partial send()s, we just fire one off and hope for the best. */
send(commfd, msg, sizeof (struct message) + extra, MSG_EOR | MSG_NOSIGNAL);
errno = saved_errno;
}
void libforkmonitor_init(void) __attribute__((constructor));
void libforkmonitor_init(void)
{
const int saved_errno = errno;
int result;
/* Save the actual fork() call pointer. */
if (!actual_fork)
*(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");
/* Save the actual vfork() call pointer. */
if (!actual_vfork)
*(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");
/* Save the actual abort() call pointer. */
if (!actual_abort)
*(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");
/* Save the actual _exit() call pointer. */
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");
/* Save the actual abort() call pointer. */
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");
/* Open an Unix domain datagram socket to the observer. */
if (commfd == -1) {
const char *address;
/* Connect to where? */
address = getenv(FORKMONITOR_ENVNAME);
if (address && *address) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address, sizeof addr.sun_path - 1);
/* Create and bind the socket. */
commfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (commfd != -1) {
if (connect(commfd, (const struct sockaddr *)&addr, sizeof (addr)) == -1) {
/* Failed. Close the socket. */
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
commfd = -1;
}
}
/* Move commfd to a high descriptor, to avoid complications. */
if (commfd != -1 && commfd < MINIMUM_COMMFD) {
const int newfd = MINIMUM_COMMFD;
do {
result = dup2(commfd, newfd);
} while (result == -1 && errno == EINTR);
if (!result) {
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
commfd = newfd;
}
}
}
}
/* Send an init message, listing the executable path. */
if (commfd != -1) {
size_t len = 128;
struct message *msg = NULL;
while (1) {
ssize_t n;
free(msg);
msg = malloc(sizeof (struct message) + len);
if (!msg) {
len = 0;
break;
}
n = readlink("/proc/self/exe", msg->data, len);
if (n > (ssize_t)0 && (size_t)n < len) {
msg->data[n] = '\0';
len = n + 1;
break;
}
len = (3 * len)/2;
if (len >= 65536U) {
free(msg);
msg = NULL;
len = 0;
break;
}
}
if (len > 0) {
/* INIT message with executable name */
notify(TYPE_EXEC, msg, len);
free(msg);
} else {
/* INIT message without executable name */
struct message msg2;
notify(TYPE_EXEC, &msg2, sizeof msg2);
}
}
/* Restore errno. */
errno = saved_errno;
}
void libforkmonitor_done(void) __attribute__((destructor));
void libforkmonitor_done(void)
{
const int saved_errno = errno;
int result;
/* Send an exit message, no data. */
if (commfd != -1) {
struct message msg;
notify(TYPE_DONE, &msg, sizeof msg);
}
/* If commfd is open, close it. */
if (commfd != -1) {
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
}
/* Restore errno. */
errno = saved_errno;
}
/*
* Hooked C library functions.
*/
pid_t fork(void)
{
pid_t result;
if (!actual_fork) {
const int saved_errno = errno;
*(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");
if (!actual_fork) {
errno = EAGAIN;
return (pid_t)-1;
}
errno = saved_errno;
}
result = actual_fork();
if (!result && commfd != -1) {
struct message msg;
notify(TYPE_FORK, &msg, sizeof msg);
}
return result;
}
pid_t vfork(void)
{
pid_t result;
if (!actual_vfork) {
const int saved_errno = errno;
*(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");
if (!actual_vfork) {
errno = EAGAIN;
return (pid_t)-1;
}
errno = saved_errno;
}
result = actual_vfork();
if (!result && commfd != -1) {
struct message msg;
notify(TYPE_VFORK, &msg, sizeof msg);
}
return result;
}
void _exit(const int code)
{
if (!actual__exit) {
const int saved_errno = errno;
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");
errno = saved_errno;
}
if (commfd != -1) {
struct {
struct message msg;
int extra;
} data;
memcpy(&data.msg.data[0], &code, sizeof code);
notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
}
if (actual__exit)
actual__exit(code);
exit(code);
}
void _Exit(const int code)
{
if (!actual__Exit) {
const int saved_errno = errno;
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");
errno = saved_errno;
}
if (commfd != -1) {
struct {
struct message msg;
int extra;
} data;
memcpy(&data.msg.data[0], &code, sizeof code);
notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
}
if (actual__Exit)
actual__Exit(code);
exit(code);
}
void abort(void)
{
if (!actual_abort) {
const int saved_errno = errno;
*(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");
errno = saved_errno;
}
if (commfd != -1) {
struct message msg;
notify(TYPE_ABORT, &msg, sizeof msg);
}
actual_abort();
exit(127);
}
Funkcja libforkmonitor_init()
jest wywoływana automatycznie przez łącznik wykonawczego przed procesem main()
nazywa i libforkmonitor_done()
jest wywoływane, gdy proces powraca z main()
lub wywołuje exit()
.
Otwarcie gniazda datagramów domeny uniksowej do procesu monitorowania i przesłanie poświadczeń oraz ścieżki do bieżącego pliku wykonywalnego. Każdy proces potomny (tak długo, jak biblioteka wstępnego ładowania wciąż jest wczytywana), wykonuje to po załadowaniu, więc nie ma potrzeby, aby w ogóle korzystać z funkcji exec*()
lub posix_spawn*()
lub "popen()" itd.
Funkcje biblioteki C fork()
i vfork()
są przechwytywane. Te przechwycenia są potrzebne, aby uchwycić przypadki, w których oryginalny program prosi o tworzenie procesów niewolniczych bez wykonywania jakichkolwiek innych plików binarnych. (Przynajmniej GNU biblioteki C wykorzystuje fork()
wewnątrz tak, aby te złapie popen()
, posix_spawn()
, itp.) Za
Dodatkowo, funkcje biblioteki C _exit()
, _Exit()
i abort()
przechwytywane są za. Dodałem te, ponieważ niektóre pliki binarne, szczególnie Dash, lubią używać _exit()
, i pomyślałem, że byłoby miło złapać wszystkie formy normalnych wyjść. (Śmierć z powodu sygnałów nie jest jednak wykrywana, a jeśli plik binarny wykonuje inny plik binarny, otrzymasz tylko nową wiadomość EXEC.) Zanotuj proces i identyfikator procesu nadrzędnego.)
Oto prosty program monitorujący, forkmonitor.c
:
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"
static volatile sig_atomic_t done = 0;
static void done_handler(const int signum)
{
if (!done)
done = signum;
}
static int catch_done(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = done_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static const char *username(const uid_t uid)
{
static char buffer[128];
struct passwd *pw;
pw = getpwuid(uid);
if (!pw)
return NULL;
strncpy(buffer, pw->pw_name, sizeof buffer - 1);
buffer[sizeof buffer - 1] = '\0';
return (const char *)buffer;
}
static const char *groupname(const gid_t gid)
{
static char buffer[128];
struct group *gr;
gr = getgrgid(gid);
if (!gr)
return NULL;
strncpy(buffer, gr->gr_name, sizeof buffer - 1);
buffer[sizeof buffer - 1] = '\0';
return (const char *)buffer;
}
int main(int argc, char *argv[])
{
const size_t msglen = 65536;
struct message *msg;
int socketfd, result;
const char *user, *group;
if (catch_done(SIGINT) || catch_done(SIGQUIT) || catch_done(SIGHUP) ||
catch_done(SIGTERM) || catch_done(SIGPIPE)) {
fprintf(stderr, "Cannot set signal handlers: %s.\n", strerror(errno));
return 1;
}
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s MONITOR-SOCKET-PATH\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program outputs events reported by libforkmonitor\n");
fprintf(stderr, "to Unix domain datagram sockets at MONITOR-SOCKET-PATH.\n");
fprintf(stderr, "\n");
return 0;
}
msg = malloc(msglen);
if (!msg) {
fprintf(stderr, "Out of memory.\n");
return 1;
}
socketfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (socketfd == -1) {
fprintf(stderr, "Cannot create an Unix domain datagram socket: %s.\n", strerror(errno));
return 1;
}
{
struct sockaddr_un addr;
size_t len;
if (argv[1])
len = strlen(argv[1]);
else
len = 0;
if (len < 1 || len >= UNIX_PATH_MAX) {
fprintf(stderr, "%s: Path is too long (max. %d characters)\n", argv[1], UNIX_PATH_MAX - 1);
return 1;
}
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, argv[1], len + 1); /* Include '\0' at end */
if (bind(socketfd, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
fprintf(stderr, "Cannot bind to %s: %s.\n", argv[1], strerror(errno));
return 1;
}
}
printf("Waiting for connections.\n");
printf("\n");
/* Infinite loop. */
while (!done) {
ssize_t n;
n = recv(socketfd, msg, msglen, 0);
if (n == -1) {
const char *const errmsg = strerror(errno);
fprintf(stderr, "%s.\n", errmsg);
fflush(stderr);
break;
}
if (msglen < sizeof (struct message)) {
fprintf(stderr, "Received a partial message; discarded.\n");
fflush(stderr);
continue;
}
switch (msg->type) {
case TYPE_EXEC:
printf("Received an EXEC message:\n");
break;
case TYPE_DONE:
printf("Received a DONE message:\n");
break;
case TYPE_FORK:
printf("Received a FORK message:\n");
break;
case TYPE_VFORK:
printf("Received a VFORK message:\n");
break;
case TYPE_EXIT:
printf("Received an EXIT message:\n");
break;
case TYPE_ABORT:
printf("Received an ABORT message:\n");
break;
default:
printf("Received an UNKNOWN message:\n");
break;
}
if (msg->type == TYPE_EXEC && (size_t)n > sizeof (struct message)) {
if (*((char *)msg + n - 1) == '\0')
printf("\tExecutable: '%s'\n", (char *)msg + sizeof (struct message));
}
printf("\tProcess ID: %d\n", (int)msg->pid);
printf("\tParent process ID: %d\n", (int)msg->ppid);
printf("\tSession ID: %d\n", (int)msg->sid);
printf("\tProcess group ID: %d\n", (int)msg->pgid);
user = username(msg->uid);
if (user)
printf("\tReal user: '%s' (%d)\n", user, (int)msg->uid);
else
printf("\tReal user: %d\n", (int)msg->uid);
group = groupname(msg->gid);
if (group)
printf("\tReal group: '%s' (%d)\n", group, (int)msg->gid);
else
printf("\tReal group: %d\n", (int)msg->gid);
user = username(msg->euid);
if (user)
printf("\tEffective user: '%s' (%d)\n", user, (int)msg->euid);
else
printf("\tEffective user: %d\n", (int)msg->euid);
group = groupname(msg->egid);
if (group)
printf("\tEffective group: '%s' (%d)\n", group, (int)msg->egid);
else
printf("\tEffective group: %d\n", (int)msg->egid);
printf("\n");
fflush(stdout);
}
do {
result = close(socketfd);
} while (result == -1 && errno == EINTR);
unlink(argv[1]);
return 0;
}
Zajmuje jeden parametr wiersza polecenia, adres gniazda domeny uniksowej. Powinna to być absolutna ścieżka do systemu plików.
można zatrzymać program monitorowania poprzez INT
(Ctrl + C), HUP
, QUIT
i TERM
sygnałów.
Kompilacja biblioteki korzystając
gcc -W -Wall -O3 -fpic -fPIC -c libforkmonitor.c
gcc -shared -Wl,-soname,libforkmonitor.so libforkmonitor.o -ldl -o libforkmonitor.so
oraz program monitora przy użyciu
gcc -W -Wall -O3 forkmonitor.c -o forkmonitor
w jednym oknie terminala uruchomić forkmonitor pierwszy:
./forkmonitor "$PWD/commsocket"
W innym oknie terminala, w w tym samym katalogu, uruchom monitorowane polecenie, automatycznie wstępnie ładując libforkmonitor.so
biblioteka i określając gniazdo do monitora:
env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" command args...
Zauważ, że ten wykorzystuje zmienne LD_PRELOAD
i FORKMONITOR_SOCKET
środowiskowe, procesy potomne są ignorowane, gdy ich rodzic modyfikuje środowisko (usuwanie dwie zmienne środowiskowe), a podczas wykonywania Pliki binarne: setuid
lub setgid
. Ograniczenia tego można uniknąć poprzez wyeliminowanie zmiennych środowiskowych i ich kodowanie.
Bieg czasu łącznik nie będzie wstępnego ładowania bibliotek dla setuid
lub setgid
plików binarnych, chyba że biblioteka jest w jednym ze standardowych katalogów bibliotecznych, a także oznakowane setgid
.
Dodanie nazwy biblioteki do /etc/ld.so.preload
będzie wczytywać bibliotekę dla wszystkich plików binarnych, ale prawdopodobnie należy dodać mechanizm do libforkmonitor_init()
który ogranicza kontrolę do żądanych plików binarnych i/lub określonej prawdziwego użytkownika (jak zmienia skuteczny użytkownik podczas uruchamiania setuid dwójkowy).
Na przykład, gdy biegnę
env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" sh -c 'date ; ls -laF'
wyjście monitoringu (anonimowego):
Received an EXEC message:
Executable: 'bin/dash'
Process ID: 11403
Parent process ID: 9265
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received a FORK message:
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received an EXEC message:
Executable: 'bin/date'
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received a DONE message:
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received a FORK message:
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received an EXEC message:
Executable: 'bin/ls'
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received a DONE message:
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Received an EXIT message:
Process ID: 11403
Parent process ID: 9265
Session ID: 9265
Process group ID: 11403
Real user: 'username' (1000)
Real group: 'username' (1000)
Effective user: 'username' (1000)
Effective group: 'username' (1000)
Jest to bardzo lekkie rozwiązanie do monitorowania drzewo proces. Inne niż uruchamianie, zamykanie i wywoływanie jednej z przechwyconych funkcji (fork()
, vfork()
, , , _Exit()
,) nie wpłynie w żaden sposób na wykonanie programu. Ponieważ biblioteka jest tak lekka, nawet osoby nią dotknięte będą dotknięte tylko bardzo, bardzo małą ilością; prawdopodobnie nie wystarczy, aby zmierzyć w sposób wiarygodny.
Oczywiście można przechwytywać inne funkcje i/lub korzystać z komunikacji dwukierunkowej, "wstrzymując" wykonywanie przechwyconej funkcji, dopóki aplikacja monitorująca nie zareaguje.
Istnieje kilka ogólnych pułapek, szczególnie związanych z procesami setuid/setgid i procesami, które generują nowe środowisko (pomijając zmienne środowiskowe LD_PRELOAD
i FORKMONITOR_SOCKET
), ale można je obejść, jeśli uprawnienia administratora są dostępne.
Mam nadzieję, że znajdziesz to pouczające. Pytania?
Masz na myśli coś takiego jak "strace" (http: //linux.die.net/man/1/strace)? Następnie sprawdź wywołanie systemowe ['ptrace'] (http://linux.die.net/man/2/ptrace). –
@JoachimPileborg Tak, potrzebuję czegoś takiego jak 'strace'. Sprawdzę 'ptrace'. Dzięki. – LuisABOL
@JoachimPileborg 'ptrace' i' waitpid' wydają się działać dobrze. Jednak przypadki 'fork' i' exec' wydają się niemożliwe, ponieważ nie ma żadnych odpowiadających im sygnałów dla tych funkcji, czy istnieje? Czy proces wysyła dowolny sygnał, gdy wywołuje 'fork' lub' exec'? – LuisABOL