RareGaZz nº19:(phrack58-0x03.txt):27/03/2002 << Back To RareGaZz nº 19
==Phrack Inc.== Volumen 0x0b, Numero 0x3a, Archivo #0x03 de 0x0e |=----------------------=[ S I G N A L N O I S E ]=----------------------=| |=-----------------------------------------------------------------------=| |=---------------------------=[ phrackstaff ]=---------------------------=| _ _ / "crrr...Everything that does not fit somewhere else...crr" \ |-+ - - - "can be found here. Corrections and additions" - - - +-| |\_ "to previous articles, to short articles or articles that" _/| | "just dont make it....everything...crr..<NO CARRIER>" | _=====_ _=====_ 0x00: SIGOOPS 0x01: No mas SIGSEGV 0x02: cubriendo IPC via TCP sobre signal() 0x03: SIGnalINTelligence warrant of apprehension on gobbles |=[ 0x01 ]=--------------------------------------------------------------=| Asunto: Deshaciendose de SIGSEV - para diversion, no para beneficio. Las se~ales de UNIX proveen un mecanismo para notificar procesos de los eventos del sistema, comunicacion [mira abajo :P] y sincronizacion entre procesos y excepcion de manejo. Muchos lectores estan familiarizados con el termino 'el software genera se~ales' (generadas por el kernel o la aplicacion userland) y 'cpu exceptions'. La mas famosa y por lejos la se~al mas odiada bajo UNIX es SIGSEV. La se~al es usualmente generada por el kernel cuando 'algo realmente malo ha pasado' o algo con lo que 'tu hardware no esta realmente divertidiendose'. A tu hardware 'no le hacen gracia' las referencias ilegales a memoria y notifica al kernel (cpu exception) que notifica al proceso ofendido con una se~al. La accion por defecto es terminar el proceso que esta corriendo y hacer dump core. Que puede pasar si el proceso se puede recuperar de esa SIGSEV y continuar su ejecucion? Despues de una SIGSEV el proceso esta en un estado indefinido y basicamente cualquier cosa puede pasar. Podemos experimentar graficos erroneos en netscape, sin imagen de fondo en Eterm o frames erroneos en una pelicula .avi. Un programa puede usar signal(SIGSEGV, SIG_IGN); para ignorar una SIGSEV enviada por otro proceso. Una cpu exception generada por el hardware seguira causando la terminacion del proceso (accion por defecto). Un proceso puede elegir sobreescribir la accion por defecto y especificar un handler de se~al - una accion user-defined que es invocada cuando una SIGSEV es entregada al proceso. Nos concentraremos solamente en la SIGSEV generada por una cpu exception - recuperarse de los otros casos es trivial. Primero echemos un vistazo al kernel y sigamos el path de SIGSEV antes de que sea entregada a la aplicacion. Despues de nuestra peque~a excursion les mostrare un poco de codigo que, compilado como un shared object, puede ser precargado (LD_PRELOAD) a cualquier programa. El .so precargado se recuperara (en el mejor de los casos) de una SIGSEV y continuara su ejecucion. Cuando el sistema bootea, la funcion arch/i386/kernel/traps.c:trap_init() es llamada y settea la Interrupt Descriptor Table (IDT) para que el vector 0x14 (de tipo 15, dpl0) apunte a la direccion de la entrada page_fault desde arch/i386/kernel/entry.S. la entrada invocada hace do_page_fault() en arch/i386/mm/fault.c siempre que la excepcion especifica ocurra. Esta funcion maneja todo tipo de llamadas 'force_sig_info()' y page_faults si la excepcion fue causada por acceso user mode a memoria invalida. Esta funcion fuerza la entrega de la se~al a la aplicacion userland bloqueando la se~al y setteando SIG_IN a SIG_DFL (si no ha sido asignado ningun handler). Para acortar una larga historia corta el kernel vuelca en send_sig_info() que llama a deliver_signal() que llama a send_signal() que llama a sigaddset() que finalmente settea (pone) el bit en el proceso signalbitmask. Es importante notar que cualquier accion, incluyendo la terminacion del proceso, puede solamente ser tomada por el proceso recibiendo en si mismo. Esto requiere, como minimo, que el proceso sea scheduleado para ejecutarse. Entre la generacion de la se~al y la entrega de la se~al, se dice que la se~al esta pendiente al proceso. Cuando un proceso es scheduleado para ejecutarse, el kernel chequea por se~ales pendientes en los siguientes momentos: - Inmediatamente despues de restablecerse de un evento interruptible. - Antes de volver a user mode desde una llamada a sistema o interrupcion. - Antes de bloquear en un evento interruptible. El kernel llama a arch/i386/kernel/signal.c:do_signal() y busca la primer se~al pendiente desde la cola (kernel/signal.c:dequeue_signal()). No pasa nada espectacular y el kernel procesa con la siguiente se~al pendiente desde la cola si la accion es setteada a SIG_DFL o SIG_IN. El kernel llama a handle_signal() si una accion user-defined ha sido asignada al handler de la se~al (ka->sa.sa_handler). Si el evento de la se~al ocurrio durante una llamada al sistema con capacidad de restauracion, el eip del proceso es restado por el valor de 2 para automaticamente reinvocar la llamada al sistema antes de que el handler de la se~al vuelva. El kernel llama a setup_frame() para guardar el actual set de registros y otros valores (lee 'struct sigframe' in arch/i386/kernel/signal.c) en la pila del proceso. La misma funcion tambien settea un 'stub' que es ejecutado despues de que el handler de la se~al volvio de restaurar el 'sigframe' guardado previamente. struct sigframe { char *pretcode; /* 4 bytes */ int sig; /* 4 bytes */ struct sigcontext sc; /* 88 bytes, lee sigcontext.h */ struct _fpstate fpstate; /* 624 bytes, regs punto flotante */ unsigned long extramask[1]; /* 4 bytes */ char retcode[8]; /* 8 bytes */ }; struct sigcontext expands to: struct sigcontext { ... /* ...56 bytes */ unsigned long eip; /* Aha! */ ... /* ...88 bytes */ }; El antiguo eip es guardado 64 bytes antes que el principio de la estructura sigframe, seguido de la direccion de retorno del handler de la se~al y el puntero del frame guardado. La direccion de retorno apuntara al 'stub' que pasara el control de nuevo al kernel para restaurar los registros una vez que el handler de la se~al vuelva. 0xbfffffff | ... | +------------------------+ | sigframe, antiguo eip | | es guardado 56 bytes | <---+ | desde detras de retaddr| | +------------------------+ distancia de 68 bytes del | retaddr de stub | eip guardado desde ebp. +------------------------+ | ebp-> | punt del frame guardado| <---+ +------------------------+ | variables locales de | | la rutina handler de la| | se~al | +------------------------+ El camino mas facil de recuperarse de un SIGSEV por consiguiente es asignar nuestro propio handler de se~al, recorrer la pila hasta que encontremos el eip guardado, settear el eip a la instruccion siguiendo la instruccion que causo el segfault y volver desde nuestro handler. La libreria tambien ignora SIGILL solo por ese caso en que el proceso empieza a run amok y el IP alcanza el espacio donde ningun IP ha ido antes. /* * someone@segfault.net * * Este es un codigo fuente publicado no-propietario de alguien sin * nombre...alguien que no necesita ser mencionado.... * * No querras usar esto en sistemas de productividad - realmente no. * * Esta preload-library se recupera de un SIGSEGV - solamente para diversion! * * $ gcc -Wall -O2 -fPIC -DDEBUG -c assfault.c * $ ld -Bshareable -o assfault.so assfault.o -ldl # $ LD_PRELOAD=./assfault.so netscape & */ #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <dlfcn.h> #define REPLACE(a, x, y) if ( !(o_##x = dlsym(##a , ##y)) )\ { fprintf(stderr, ##y"() not found in libc!\n");\ exit(-1); } #ifdef DEBUG # define DEBUGF(a...) do{fprintf(stderr, "%s[%d]", __FILE__, __LINE__); \ fprintf(stderr, ##a);}while(0) #else # define DEBUGF(a...) #endif #define err_exit(str) do{fprintf(stderr, "ERROR:%s\n", str);exit(-1);}while(0); static void *(*o_signal)(int, void(*)(int)); static void *libc_handle = NULL; static int sigcount; void assfault_handler(int sig) { DEBUGF("SIG%s occured (%d)\n" , (sig==SIGSEGV)?"SEGV":(sig==SIGILL)?"ILL":"BUS", ++sigcount); asm volatile("incl 0x44(%ebp)"); } void (*signal(int sn, void (*sighandler)(int)))() { if ((sn == SIGSEGV) || (sn == SIGILL) || (sn == SIGBUS)) { DEBUGF("signal(SIG%s, ...) intercepted [%d]\n" , (sn==SIGSEGV)?"SEGV":(sn==SIGILL)?"ILL":"BUS", getpid()); return assfault_handler; } /* En todos los otros casos llama a la funcion original libc signal() */ return o_signal(sn, sighandler); } static void assfault_init(void) { if ( (libc_handle = dlopen("libc.so", RTLD_NOW)) == NULL) if ( (libc_handle = dlopen("libc.so.6", RTLD_NOW)) == NULL) err_exit("error loading libc!"); /* consigue la direccion de la funcion original signal() en libc */ REPLACE(libc_handle, signal, "signal"); /* redireccionar accion para estas se~ales a nuestras funciones */ o_signal(SIGSEGV, assfault_handler); o_signal(SIGILL, assfault_handler); o_signal(SIGBUS, assfault_handler); dlclose(libc_handle); } /* * llamado por el dynamic loader (cargador dinamico). */ void _init(void) { if (libc_handle != NULL) return; /* nunca deberia pasar */ assfault_init(); DEBUGF("assfault.so activated.\n"); } /*** EOF assfault.c ***/ /* * programa de ejemplo que hace segfault a un lote. * $ gcc -Wall -o segfault segfault.c * $ LD_PRELOAD=./assfault.so ./segfault */ #include <stdio.h> int main() { char *ptr=NULL; fprintf(stderr, "|0| everything looks fine. lets produce a SIGSEGV\n"); *ptr=1; fprintf(stderr, "|1| after first provocated SIGSEGV\n"); *ptr=1; fprintf(stderr, "|2| after second provocated SIGSEGV\n"); fprintf(stderr, "|X| We survived - enough played today.\n"); return 0; } /*** EOF segfault.c ***/ |=[ 0x02 ]=--------------------------------------------------------------=| Asunto: TCP sobre signal() Asuntos aburridos hacen cosas malas, entonces por que no transferir datos con se~ales. Con se~ales, no a traves de. El Bueno y viejo morsing nos golpea de nuevo. Hablando en teoria es un canal de conversion. Un metodo para transferir datos que no es reconocido como transfiere al mundo exterior. Las cosas son simples, si el enviador ve que el bit es 1 envia 'HIGH' y 'LOW' si encuentra al bit en 0. Te dejo que te figures como funciona este programa :-) <recv.c> #include <stdio.h> #include <sys/types.h> #include <signal.h> #define L SIGHUP #define H SIGUSR1 #define RESET SIGUSR2 int bit; unsigned char c; void recv_high_low(int x) { if (bit == 8) { bit = 0; putchar(c); fflush(stdout); c = 0; } if (x == H) c = ((c<<1)|1); else c <<= 1; ++bit; } void recv_reset(int x) { bit = 0; c = 0; } int main() { bit = 0; c = 0; signal(L, recv_high_low); signal(H, recv_high_low); signal(RESET, recv_reset); for (;;); return 0; } </recv.c> <send.c> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/types.h> #include <stdlib.h> #define L SIGHUP #define H SIGUSR1 #define RESET SIGUSR2 void die(char *s) { perror(s); exit(errno); } int main(int argc, char **argv) { int pid, fd, j; char *file, c; if (argc < 3) { fprintf(stderr, "Usage: %s <pid> <file>\n", argv[0]); exit(1); } pid = atoi(argv[1]); file = argv[2]; if ((fd = open(file, O_RDONLY)) < 0) die("open"); kill(pid, RESET); sleep(1); while (read(fd, &c, sizeof(c)) > 0) { /* and for every bit of this byte do */ for (j = 7; j >= 0; --j) { if ((1<<j) & c) { printf("1");fflush(stdout); if (kill(pid, H) < 0) die("kill"); /* send HIGH (1) */ } else { printf("0");fflush(stdout); if (kill(pid, L) < 0) /* send LOW (0) */ die("kill"); } usleep(200); } } close(fd); return 0; } </send.c> N. del T.: 0x00 y 0x03 no fueron traducidos debido a que no tenian que ver directamente con el tema tratado. Traducido por Active Matrix - ActiveMatrix@technologist.com Para RareGaZz - http://raregazz.cjb.net Argentina, 2002 El articulo aqui traducido, mantiene los derechos de autor.