Czasami może zaistnieć sytuacja, w której chcielibysmy uruchomić program na jakiejś maszynie, który chodziłby jak najdłużej i nie rzucał się w oczy (backdoor?;)... Zwykły user może pobawić się trochę z adminem - przypominać to będzie trochę walkę Dawida z Goliatem, jednak w każdej chwili Goliat może zadać śmiertelny cios i po zabawie... Można to mu oczywiście nieco utrudnić... Przpyatrzmy się więc następującemu programowi:
---hello.c---
#include <stdio.h>
#include <unistd.h>
int main() {
/* tu się zaczyna nasz program */
printf("hello, world...\n");
if(fork()) exit(0);
while(1);
}
---hello.c---
Program po uruchomieniu wita się, przechodzi w tło i czeka w nieskończonej
pętli... Kompilujemy go gcc hello.c -o hello, następnie uruchamiamy
./hello.
Administrator widzi, że program podejrzanie długo [przypuscmy;] chodzi, wydaje
więc polecenie killall hello, kill [pid] czy jeszcze na inny sposób uścmierca
nasz bezbronny programik... Ale zaraz, domyślnie kill, killall i inne takie
wysyłają sygnał TERM, który możemy przecież przechwycić (możemy przechwycić
wszystkie sygnały oprócz KILL(9) i STOP(19)), tym samym nasz program można
troszkę uodpornić... Pomocne nam będą następujące funkcje:
sigaction - zmienia akcję wykonywaną po otrzymaniu danego sygnału;
sigprocmask - zmienia listę aktualnie blokowanych sygnałów;
sigfillset - zapisuje w podanej zmiennej pełną listę sygnałow;
sigdelset - kasuje dany sygnał z listy;
Możemy więc przystąpić do pisania drugiej wersji programu:
---hello2.c---
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main() {
struct sigaction act;
sigset_t set;
void catchsig(int sig) {
if(fork()) exit(0);
}
sigfillset(&set); /* tworzymy pełną listę sygnałów w zmiennej set */
sigdelset(&set, SIGINT); /* usuwamy z niej te trzy sygnały, */
sigdelset(&set, SIGQUIT); /* gdyż będą nam potrzebne */
sigdelset(&set, SIGTERM); /* później :) */
sigprocmask(SIG_SETMASK, &set, NULL); /* ustawiamy blokadę na sygnały
znajduące się w zmiennej set */
act.sa_handler=catchsig;
sigaction(SIGINT, &act, NULL); /* teraz po otrzymaniu któregoś z tych */
sigaction(SIGQUIT, &act, NULL); /* sygnałow zostanie wykonana funkcja */
sigaction(SIGTERM, &act, NULL); /* catchsig czyli fork i exit*/
/* tu się zaczyna nasz program... */
printf("hello, world...\n");
if(fork()) exit(0);
while(1);
}
---hello2.c---
Skompilujmy program, uruchommy go, napiszmy ps, zabijmy proces, napiszmy ps,
zabijmy proces, napiszmy ps itd... Widzimy, że (o ile nie zostanie wysłany
sygnał KILL) program po każdej próbie zabicia 'odradza sie' na nowo. O to nam
właśnie chodziło... Jednak program cały czas widznieje na liście procesów
pod tą samą nazwą... Trzeba to zmienić - napiszemy więc funkcje, która będzie
zmieniała nazwę procesu (argv[0]) po próbie zabicia go na losowo wybraną
z podanej tablicy. Powstanie tak program hello3.c :)
---hello3.c---
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
char *fakenames[]={
"ps", "ls", "mail", "sh", "bash", "less", "man bash", "NULL"
};
char *givenewname(char **names) {
int i, n, m;
for(i=0;names[i]!=NULL;i++) ;
m=(int)time((time_t)NULL);
srand(m);
n=random()%(i-1);
return(names[n]);
}
int main(int argc, char **argv) {
struct sigaction act;
sigset_t set;
void catchsig(int sig) {
int i;
for(i=0;i<argc;i++)
bzero(argv[i], strlen(argv[i]));
strcpy(argv[0], givenewname(fakenames));
if(fork()) exit(0);
}
sigfillset(&set); /* tworzymy pełną listę sygnałów w zmiennej set */
[...i dalej już tak samo jak w hello2.c...]
---hello3.c---
Git - teraz po każdej próbie zabicia prorgam uruchamia się jako nowy proces
pod inną nazwą... Jednak nie do końca. Gdy administrator wyda polecenie ps auxc
wyświetli się 'true command name' czyli prawdziwa nazwa, również link exe w
/proc/[pid]/ będzie wskazywał na rzeczywisty program. Jedynym ominięciem tego
jest skopiowanie pliku gdzieś, następnie uruchomienie go z nowej lokalizacji,
a potem skasowanie lub przywrócenie poprzedniej wersji. Spójrzmy więc na
hello4.c - ostatni listing:
---hello4.c---
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
char *fakenames[]={
"/tmp/ps", "/tmp/ls", "/tmp/mail", "/tmp/sh", "/tmp/bash", "/tmp/less",
"/tmp/man", "tmp/ps", "NULL"
};
int getrealname(char *name, pid_t pid) {
char exe[512];
sprintf(exe, "/proc/%d/exe", pid);
return (readlink(exe, name, 512));
}
char *givenewname(char **names) {
int i, n, m;
for(i=0;names[i]!=NULL;i++) ;
m=(int)time((time_t)NULL);
srand(m);
n=random()%(i-1);
return (names[n]);
}
int main(int argc, char **argv) {
struct sigaction act;
sigset_t set;
static char realname[512];
int a;
void catchsig(int sig) {
int ok=0;
char *newpatch, *shortname, tmppatch[64];
newpatch=givenewname(fakenames);
shortname=strrchr(newpatch, '/');
shortname++;
sprintf(tmppatch, "%s.orygin", newpatch);
if(!access(newpatch, F_OK) && access(newpatch, W_OK)) ok=-1;
else {
rename(newpatch, tmppatch);
link(realname, newpatch);
}
if(!fork()) {
printf("child starting %s\n", newpatch);
execl((!ok)?newpatch:realname, shortname, realname, NULL);
}
else {
sleep(1);
if(ok) exit(0);
unlink(newpatch);
rename(tmppatch, newpatch);
exit(0);
}
}
chdir("/");
if(argc>1) {
if(strlen(argv[1])>512) {
printf("buffer overflow test?\n");
exit(1);
}
strcpy(realname, argv[1]);
}
else getrealname(realname, getpid());
for(a=1; a<argc; a++)
bzero(argv[a], strlen(argv[a]));
sigfillset(&set); /* tworzymy pełną listę sygnałów w zmiennej set */
sigdelset(&set, SIGINT); /* usuwamy z niej te trzy sygnały, */
sigdelset(&set, SIGQUIT); /* gdyż będą nam potrzebne */
sigdelset(&set, SIGTERM); /* później :) */
sigprocmask(SIG_SETMASK, &set, NULL); /* ustawiamy blokadę na sygnały
znajduące się w zmiennej set */
act.sa_handler=catchsig;
sigaction(SIGINT, &act, NULL); /* teraz po otrzymaniu któregoś z tych */
sigaction(SIGQUIT, &act, NULL); /* sygnałow zostanie wykonana funkcja */
sigaction(SIGTERM, &act, NULL); /* catchsig czyli fork i exit*/
/* tu się zaczyna nasz program... */
printf("hello, world...\n");
if(fork()) exit(0);
while(1);
}
---hello4.c---
Jak to działa? Program po otrzymaniu jakiegoś sygnału wybiera losowa jakąś
nazwę (z fakenames), następnie jeżeli taki plik już istnieje zmienia jego
nazwę, kopiuje się pod nową nazwę, uruchamia się i na końcu zmienia zmienioną
nazwę starego programu spowrotem :)...
Można jeszcze kombinować np. uruchamiając program na nowo co 10 sekund, ale
to już zależy tylko od inwencji twórczej programisty...