4

I'm messing with TIOCSTI which shoves data into the terminal's input buffer. I want to be able to capture this data before it arrives at the shell or redirects it to a file.

To better illustrate what I'm trying to do:

gcc -x c -o pusher.bin - <<PUSHER
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>

int main() {
  char *c = "echo 'Catch me if you can'\n";
  while(*c) ioctl(0, TIOCSTI, c++);
}
PUSHER
./pusher.bin

If running in my terminal, ./pusher.bin will inject echo 'Catch me if you can'\n in my tty which my shell would immediately execute. If I run setsid ./pusher.bin, echo won't be injected in my terminal but I also won't be able to capture it.

I want to wrap ./pusher.bin with something that allows me to inspect what pusher would have injected in my tty's input buffer if it was run bare.

Clarification: I'm aware that injected input can be captured after it arrives at my shell's stdin. This approach while effective at capturing the injected input will also capture normal user input. Furthermore, this approach would not work if stdin is closed or if the process is not attached to a tty. These downsides alone make capturing stdin unviable as a general solution.

3
  • Hi, @A.B. I'm afraid I don't understand how your link helps me wrap the program in a sub tty or capture what's in its input buffer. Can you please elaborate further? Dec 20, 2020 at 13:44
  • That's why it was just a comment. I'll remove it because it brought only confusion
    – A.B
    Dec 20, 2020 at 13:45
  • 1
    else, just use the script command. It will do what you're asking, but I'm not sure you'll get your program behave as you intend then.
    – A.B
    Dec 20, 2020 at 13:46

3 Answers 3

2

It seems script is the solution, as mentioned by A.B. With -e you even get the return code of the program. cat -vet shows more explicitly the carriage return ^M and newline $.

$ script -q -e out -c ./pusher.bin >/dev/null; echo $?
0
$ cat -vet out
Script started on Mon Dec 21 10:54:40 2020$
echo 'Catch me if you can'^M$
1

You need to read the terminal after you inject. For example, for one line:

$ ./pusher.bin;read a
$ echo "Intercepted: $a"

or generally:

$ ./pusher.bin;cat

You might have to inject EOF so that cat ends.

An alternative, if you want to see just the injected characters:

$ stty -echo;./pusher.bin;cat;stty echo

You will not be able to capture those injected strings and tell them apart from normal input

1
  • Thank you, but capturing the injected characters this way would imply that they reached my terminal's input buffer which is what I want to avoid (See the clarification I just added). I don't think there is anyway for me to reliably capture injected input by itself without having a separate input buffer for the process (and hence wanting a separate tty) Dec 21, 2020 at 5:05
0

I came up with this functional but flawed solution:

RESULT="$(bash -c 'ID="s$(head -c 8 /dev/urandom | base64 | sed "s/[^0-9a-zA-Z]//g")"; F="$(mktemp)";  screen -d -m -S "$ID" bash -c "./escape.bin; cat >/$F"; sleep 1s; screen -S "$ID" -X quit; cat <$F; rm "$F"')"

Breaking the important bits down:

# Generate a unique session name for screen
ID="s$(head -c 8 /dev/urandom | base64 | sed "s/[^0-9a-zA-Z]//g")";

# Get a temporary file
F="$(mktemp)";  

# Run pusher in a detached (`-d -m`) screen session 
# with its own tty and read back the tty's buffer 
# using cat and write it to the temporary file
screen -d -m -S "$ID" bash -c "./pusher.bin; cat >/$F"; 

# Sleep for one second for pusher to finish (I'm a 
# aware that I can poll screen using `-list`, but 
# exec fails for some reason, help here would be 
# appreciated)
sleep 1s; 

# Kill screen session manually (again, `exec`ing 
# fails for some reason )
screen -S "$ID" -X quit; 

# Print the contents of F to stdout so that it can 
# be captured by the outermost `$(...)`
cat <$F; 

# Removes the temporary file
rm "$F"'

This method is reliable enough for my usecase, but it suffers from obvious problems:

  1. Exit status of pusher.bin is lost.
  2. Doesn't wait for pusher.bin to exit but assumes that it will be finished after 1 second.
  3. Uses screen sessions, a shared and user-facing resource, to get a tty.
  4. Doesn't unlink the temporary file early on, which can lead to leaving behind temporary files if anything fails.

Some of these are easy to fix while others are more involved.

1
  • I'll leave this question open for a day or two to give someone else a chance at submitting a better solution. I think the four problems listed are far too significant for my solution to be generally useful. Dec 21, 2020 at 6:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .