/*
 * Jan 18 2003 --solarx
 * Aug 15 2003 - tested with glibc and corrected some typo's
 *
 * It's not authors fault if this sets your computer on fire or
 * something even more nasty. This src is for educational purposes
 * only. Some of the functions used within this program are based in
 * part of article from Phrack 59.
 *
 * I keep hacking these same files and functions together and end up with things
 * that do totally different handy shit like this one does.
 *
 * Tested with: gcc 2.95.3, gcc 3.2.1, gcc-3.2.3
 * Tested with dietlibc but do to some header/name conflicts it failed,
 * however it but could be made to work rather easily.
 *
 * Usage:
 *
 * ntrace 	;# With no options it will try and attach to and print everything in its uid class.
 * ntrace <pid> ;# Attach to a single pid and print all symbols loaded in its memory and there address.
 * ntrace <pid> [function] [func... ;# Attach to a single pid and print location of each symbol given on the cmdline.
 *
 *
 * Compile:
 *
 * cc -o ntrace ntrace.c 
 * cc -o ntrace ntrace.c -DEBUG 
 *
 */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <elf.h>
#include <fcntl.h>
#include <link.h>
#include <linux/user.h>

#include <assert.h>

#ifdef EBUG
#define DBG(a)  a
#else				/* !EBUG */
#define DBG(a)			/* nothing */
#endif				/* EBUG */


unsigned long symtab;
unsigned long strtab;
int nchains;

int task_list[65536];

#undef DBG
#ifdef EBUG
#define DBG(a)  a
#else				/* !EBUG */
#define DBG(a)			/* nothing */
#endif				/* EBUG */


#ifndef PATH_MAX
#define PATH_MAX 255
#endif


char *iota(int num)
{
   static char str[17];
   int x = 0;
   int i = 16;

   str[16] = 0;
   if (!num) {
      str[15] = '0';
      return str + 15;
   }
   if (num < 0) {
      x = 1;
      num *= -1;
   }
   while (num) {
      i--;
      str[i] = '0' + (num % 10);
      num /= 10;
   }
   if (x) {
      i--;
      str[i] = '-';
   }
   return str + i;
}

char *getpidname(int pid)
{
   int fd;
   char *p;
   static char buf[255];
   if (pid < 0 && pid > 65536)
      return NULL;
   strcpy(buf, "/proc/");
   strncat(buf, iota(pid), sizeof(buf));
   strncat(buf, "/stat", sizeof(buf));
#ifndef __linux__
   strncat(buf, "us", sizeof(buf));
#endif

   if ((fd = open(buf, O_RDONLY)) < 0)
      return NULL;
   memset(buf, 0, sizeof(buf));
   if (read(fd, buf, sizeof(buf) - 1) > 0) {
#ifdef __linux__
      if (p = strchr(buf, ' ')) {
	 strcpy(buf, p + 2);
	 if (p = strrchr(buf, ')'))
	    *p = 0;
      }
#else
      if (p = strchr(buf, ' '))
	 *p = 0;
#endif
   }
   close(fd);
   return buf;
}

/* attach to pid */

long ptrace_attach(int pid)
{
   long ret;
   if (((ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL))) < 0) {
      // DBG((perror("ptrace_attach")));
      return ret;
   }
   waitpid(pid, NULL, WUNTRACED);
   DBG((printf("Attached to %d - %s\n", pid, getpidname(pid))));
   return ret;
}


/* continue execution */

void ptrace_cont(int pid)
{
   int s;
   if ((ptrace(PTRACE_CONT, pid, NULL, NULL)) < 0) {
      // DBG((perror("ptrace_cont")));
      return;
   }
   while (!WIFSTOPPED(s))
      waitpid(pid, &s, WNOHANG);
}


/* detach process */

void ptrace_detach(int pid)
{
   if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
      // DBG((perror("ptrace_detach")));
      return;
   }
   DBG((printf("Detached from %d - %s\n", pid, getpidname(pid))));
}

/* read data from location addr */

void *read_data(int pid, unsigned long addr, void *vptr, int len)
{
   int i, count;
   long word;
   unsigned long *ptr = (unsigned long *) vptr;

   count = i = 0;

   while (count < len) {
      word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
      count += 4;
      ptr[i++] = word;
   }
}


/* write data to location addr */

void write_data(int pid, unsigned long addr, void *vptr, int len)
{
   int i, count;
   long word;

   i = count = 0;

   while (count < len) {
      memcpy(&word, vptr + count, sizeof(word));
      word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
      count += 4;
   }
}

void free_link_map(struct link_map *map)
{
   if (map != NULL)
      free(map);
}

struct link_map *locate_linkmap(int pid)
{
   Elf32_Ehdr *ehdr = malloc(sizeof(Elf32_Ehdr));
   Elf32_Phdr *phdr = malloc(sizeof(Elf32_Phdr));
   Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
   Elf32_Word got;
   struct link_map *l = malloc(sizeof(struct link_map));
   unsigned long phdr_addr, dyn_addr, map_addr;


   /* first we check from elf header , mapped at 0x08048000 , offset to
    * program header table from where we try to locate PT_DYNAMIC section.
    */

   read_data(pid, 0x08048000, ehdr, sizeof(Elf32_Ehdr));

   phdr_addr = 0x08048000 + ehdr->e_phoff;

   read_data(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));

   while (phdr->p_type != PT_DYNAMIC) {
      read_data(pid, phdr_addr +=
		sizeof(Elf32_Phdr), phdr, sizeof(Elf32_Phdr));
   }


   /* now go through dynamic section until we find address of GOT */

   read_data(pid, phdr->p_vaddr, dyn, sizeof(Elf32_Dyn));

   dyn_addr = phdr->p_vaddr;

   while (dyn->d_tag != DT_PLTGOT) {
      read_data(pid, dyn_addr +=
		sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
   }

   got = (Elf32_Word) dyn->d_un.d_ptr;

   got += 4;			/* second GOT entry, remember? */

   /* now just read first link_map item and return it */

   read_data(pid, (unsigned long) got, &map_addr, 4);
   read_data(pid, map_addr, l, sizeof(struct link_map));


   return l;
}

void resolv_tables(int pid, struct link_map *map)
{
   Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
   unsigned long addr;


   addr = (unsigned long) map->l_ld;
   read_data(pid, addr, dyn, sizeof(Elf32_Dyn));

   while (dyn->d_tag) {
      switch (dyn->d_tag) {
	 case DT_HASH:
	    read_data(pid, dyn->d_un.d_ptr + 4 + map->l_addr, &nchains,
		      sizeof(nchains));
	    break;

	 case DT_STRTAB:
	    strtab = dyn->d_un.d_ptr;
	    break;

	 case DT_SYMTAB:
	    symtab = dyn->d_un.d_ptr;
	    break;

	 default:
	    break;
      }

      addr += sizeof(Elf32_Dyn);
      read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
   }

   free(dyn);
}

char *read_str(int pid, unsigned long addr)
{
   long ptr[128];
   static char string[128];
   int len;


   len = 128;
   read_data(pid, addr, ptr, len);

   bzero(string, sizeof(string));

   strncpy(string, (char *) ptr, strlen((char *) ptr));
   return string;
}


unsigned long find_sym_in_tables(int pid, struct link_map *map, char *sym_name)
{
   Elf32_Sym *sym = malloc(sizeof(Elf32_Sym));
   char *str;
   int i;

   i = 0;

   while (i < nchains) {
      read_data(pid, symtab + (i * sizeof(Elf32_Sym)), sym,
		sizeof(Elf32_Sym));
      i++;

      if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC)
	 continue;

      str = read_str(pid, strtab + sym->st_name);
      if (strncmp(str, sym_name, strlen(sym_name)) == 0)
	 return (map->l_addr + sym->st_value);
   }
   /* no symbol found, return 0 */
   return 0;
}

unsigned long find_sym_in_lib(int pid, char *sym_name, char *lib)
{
   struct link_map *map, *l_prev;
   unsigned long sym;
   char libname[256];

   sym = 0;
   map = locate_linkmap(pid);

   while (!sym && map->l_next) {
      read_data(pid, (unsigned long) map->l_next, map,
		sizeof(struct link_map));
      l_prev = map->l_next;

      read_data(pid, (unsigned long) map->l_name, libname,
		sizeof(libname));

      /* compare libname if its not NULL */
      if (lib)
	 if (strncmp(libname, lib, strlen(lib)) != 0)
	    continue;

      resolv_tables(pid, map);
      sym = find_sym_in_tables(pid, map, sym_name);
   }
   return sym;
}

void print_sym_tables(int pid, struct link_map *map)
{
   Elf32_Sym *sym = malloc(sizeof(Elf32_Sym));
   char *str, *pidname;
   int i, x;

   i = x = 0;
   pidname = getpidname(pid);

   while (i < nchains) {
      read_data(pid, symtab + (i * sizeof(Elf32_Sym)), sym,
		sizeof(Elf32_Sym));
      i++;

      if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC)
	 continue;

      str = read_str(pid, strtab + sym->st_name);
      for (x = 0; str[x]; x++)
	 if (!isprint(str[x]))
	    str[x] = '?';
      printf("%d/%s]\t%p\t%s\n", pid, pidname, map->l_addr + sym->st_value, str);
   }
}

ptrace_cleanup()
{
//       ptrace_cont(task_list[i]);
//       kill(task_list[i], SIGCONT);

   int i;
   for (i = 0; i < 65536; i++)
      if (task_list[i] != 0) {
	 wait((int *) 0);
	 ptrace_detach(task_list[i]);
	 task_list[i] = 0;
      }
}
void signal_handler(int sig)
{
#define SIG_NAME(x) x == SIGURG ? "SIGURG" : \
                    x == SIGPIPE ? "SIGPIPE" : \
                    x == SIGQUIT ? "SIGQUIT" : \
                    x == SIGINT ? "SIGINT" : \
                    x == SIGTERM ? "SIGTERM" : \
                    x == SIGHUP ? "SIGHUP" : \
                    x == SIGSEGV ? "SIGSEGV" : \
                    x == SIGBUS ? "SIGBUS" : "UNKNOWN"

   printf("Got signal %s\n", SIG_NAME(sig));
   ptrace_cleanup();
   exit(0);
}

void print_lib_tables(int pid, int argc, char **argv)
{
   struct link_map *map;
   unsigned long dlopen_loc, o_func;
   char *name;
   int i;

   /* locate _dl_open() */
   if (!(dlopen_loc = find_sym_in_lib(pid, "_dl_open", "/lib/libc."))) {
      DBG((fprintf(stderr, "Error! couldn't locate _dl_open()!\n")));
   }
   map = (struct link_map *) locate_linkmap(pid);
   resolv_tables(pid, map);
   DBG((printf("_dl_open at %p\n", dlopen_loc)));
   if (argc > 2) {
      for (i = 2; i < argc; i++) {
	 o_func = 0x0;
	 name = argv[i];
	 o_func = find_sym_in_tables(pid, map, name);
	 if (o_func)
	    printf("Found %s at %p\n", name, o_func);
      }
   } else
      print_sym_tables(pid, map);

   free_link_map(map);

   ptrace_detach(pid);
   ptrace_cont(pid);
}

int main(int argc, char **argv)
{
   int i, ret;

   int pid, ppid, mypid;
   struct user_regs_struct regs;

   memset(&task_list, 0, sizeof(task_list));

   mypid = getpid();
   ppid = getppid();
   assert(PTRACE_GETREGS != PTRACE_GETFPREGS);


   signal(SIGHUP, SIG_IGN);
   signal(SIGURG, SIG_IGN);
   signal(SIGQUIT, &signal_handler);
   signal(SIGTERM, &signal_handler);
   signal(SIGINT, &signal_handler);

   if (argc > 1) {
      pid = atoi(argv[1]);

      if (ptrace_attach(pid) != (-1)) {
	 task_list[0] = pid;
	 print_lib_tables(pid, argc, argv);
      }
   } else {
      struct stat st;
      char buf[255];
      int uid;

      uid = getuid();
      for (pid = 0; pid != 65536; pid++) {
	 snprintf(buf, sizeof(buf), "/proc/%d", pid);
	 if ((stat(buf, &st)) != (-1)) {
	    if ((st.st_uid == uid) && (pid != ppid) && (pid != mypid)) {
	       if (ptrace_attach(pid) != (-1)) {
			task_list[pid] = pid;
			print_lib_tables(pid, argc, argv);
			wait((int *) 0);
			ptrace_detach(task_list[pid]);
			task_list[pid] = 0;
		} else {
			printf("Cant Attach to pid %d %s\n", pid, getpidname(pid));
		}
	    }
	 }
      }
   }
   exit(EXIT_SUCCESS);
}
