Skip to content

Commit

Permalink
Added actual source, Makefile and description
Browse files Browse the repository at this point in the history
  • Loading branch information
thoni56 committed Jan 30, 2020
1 parent 9e607bc commit 80257b7
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pipe-spy
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Compile pipe-spy

# This is the target to exec...
TARGET=possibly/relative/path/to/the/actual/target
# ... with the full path...
TARGET_PATH=`python -c "import os,sys; print(os.path.realpath(os.path.expanduser('$(TARGET)')))" "${1}"`

pipe-spy:
$(CC) -o pipe-spy -g -Wall -DTARGET=\"$(TARGET_PATH)\" pipe-spy.c
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
# pipe-spy
A C-program that can be injected between two processes to spy on thier communcation over pipes

A C-program that can be injected between two processes to spy on their communcation over pipes.

## Use case

I had a situation where one program exec:ed another and communicated
bi-directionally over pipes. As I was interested in learning the
details of this communication I wanted to spy in that conversation.

## Alternatives

Actually I could not find any alternatives here. My question on
stackoverflow did not get any answers and googling found none either.

I'm happy to add some alternatives here, if you'd like to share.

(Of course there is always `strace` but that just generates a lot of
extra information that was in the way.)

So I implemented this little utility.

## Usage

Set the TARGET variable (environment or Makefile) to point to the
actual `target` and compile.

Then you have to find a way to get the `caller` to exec this instead of the
actual `target`. The way I used it was to create a link to `pipe-spy`
with the same name as the actual target but in a directory that was
earlier in the PATH. This might not always be possible, so YMMV.

If the `caller` tries to exec the actual `target` this utility will
instead be run. It will in turn exec the `target` and spy and log the
downstream and upstream communication in a file
(`/tmp/pipe-spyPID.log`).

## Implementation

`pipe-spy` forks three processes, two for the up- / down-stream
communication and one that execs the actual `target`.

The complicated part is to set up the pipes correctly.

## Improvements

- The handling of closed pipes is probably not correct, so if your
`caller` closes the communication `pipe-spy` will probably hang.

- The path to the `target` now has to be compiled in. Any ideas on how
to do avoid this are most welcome (note that we can't use command
line arguments, since the requirement is that the `caller` cannot be
changed).

PR:s are welcome.
128 changes: 128 additions & 0 deletions pipe-spy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>

#define READ_END 0
#define WRITE_END 1

FILE *logFile;

/* You need to define TARGET as a compile-time constant using -DTARGET=... */
//#define TARGET "path/to/some/executable"


int main(int argc, char **argv) {
bool trace = false;
char logFileName[100];

sprintf(logFileName, "/tmp/pipespy%d.log", getpid());
logFile = fopen(logFileName, "w");
for (int arg=0; arg<argc; arg++)
fprintf(logFile, "%s ", argv[arg]);
fprintf(logFile, "\n");
fflush(logFile);

/* Create the pipes for the sub-spies to the target */
int downstream_pipe[2];
int upstream_pipe[2];

if (pipe(downstream_pipe) < 0) {
perror("pipe(downstream_pipe)");
_exit(-1);
}

if (pipe(upstream_pipe) < 0) {
close(downstream_pipe[READ_END]);
close(downstream_pipe[WRITE_END]);
perror("pipe(upstream_pipe)");
_exit(-1);
}

/* Fork a sub-spy to listen, log and propagate stdin */

pid_t downstream_pid = fork();
if (downstream_pid == 0) {
char buffer[10000];
if (trace) { fprintf(logFile, "** downstream child to fdopen\n"); fflush(logFile); }
FILE *toTarget = fdopen(downstream_pipe[WRITE_END], "w");
if (trace) { fprintf(logFile, "** downstream child did fdopen\n"); fflush(logFile); }

/* Close the pipe ends we don't use */
close(downstream_pipe[READ_END]);
close(upstream_pipe[READ_END]);
close(upstream_pipe[WRITE_END]);

/* Read from stdin which is the same as the parent has */
if (trace) { fprintf(logFile, "** downstream child to fgets\n"); fflush(logFile); }
while (fgets(buffer, 10000, stdin) != NULL) {
if (trace) { fprintf(logFile, "** downstream child did fgets\n"); fflush(logFile); }
fprintf(logFile, "->:%s", buffer); fflush(logFile);
fputs(buffer, toTarget); fflush(toTarget);
}
fprintf(logFile, "** downstream child got NULL\n");
_exit(1);
}
if (trace) { fprintf(logFile, "** parent forked downstream child to %d\n", downstream_pid); fflush(logFile); }

/* Fork a sub-spy to listen, log and propagate stdout */

pid_t upstream_pid = fork();
if (upstream_pid == 0) {
char buffer[10000];
if (trace) { fprintf(logFile, "** upstream child to fdopen\n"); fflush(logFile); }
FILE *fromTarget = fdopen(upstream_pipe[READ_END], "r");
if (trace) { fprintf(logFile, "** upstream child did fdopen\n"); fflush(logFile); }

/* Close the pipe ends we don't use */
close(downstream_pipe[READ_END]);
close(downstream_pipe[WRITE_END]);
close(upstream_pipe[WRITE_END]);

if (trace) { fprintf(logFile, "** upstream child to fgets\n"); fflush(logFile); }
while (fgets(buffer, 10000, fromTarget) != NULL) {
if (trace) { fprintf(logFile, "** upstream child did fgets\n"); fflush(logFile); }
fprintf(logFile, "<-:%s", buffer); fflush(logFile);
/* Write to stdout which is the same as the parent has */
fputs(buffer, stdout); fflush(stdout);
}
fprintf(logFile, "** upstream child got NULL\n"); fflush(logFile);
_exit(1);
}
if (trace) { fprintf(logFile, "** parent forked upstream child to %d\n", upstream_pid); fflush(logFile); }

/* Fork & exec the target with stdin & stdout pipes connected to
the upstream and downstream pipes */
pid_t target_pid = fork();
if (target_pid == 0) {
/* In the target, so... */
/* ... connect the stdin to the downstream pipes read end... */
if (trace) { fprintf(logFile, "** target child to dup2 on read end\n"); fflush(logFile); }
if (dup2(downstream_pipe[READ_END], STDIN_FILENO) == -1) exit(errno);
if (trace) { fprintf(logFile, "** target child did dup2 on read end\n"); fflush(logFile); }
/* ... the stdout to the upstream pipes write end... */
if (trace) { fprintf(logFile, "** target child to dup2 in write end\n"); fflush(logFile); }
if (dup2(upstream_pipe[WRITE_END], STDOUT_FILENO) == -1) exit(errno);
if (trace) { fprintf(logFile, "** target child did dup2 in write end\n"); fflush(logFile); }
/* ... and exec ... */
if (trace) { fprintf(logFile, "** target child to execv\n"); fflush(logFile); }
execv(TARGET, argv);
/* ... if we get here execv failed... */
perror(TARGET);
_exit(1);
}
if (trace) { fprintf(logFile, "** parent forked target to %d\n", target_pid); fflush(logFile); }

/* Parent need to close all pipes... */
close(downstream_pipe[READ_END]);
close(downstream_pipe[WRITE_END]);
close(upstream_pipe[READ_END]);
close(upstream_pipe[WRITE_END]);

int status;
waitpid(downstream_pid, &status, 0);
waitpid(upstream_pid, &status, 0);
waitpid(target_pid, &status, 0);
}

0 comments on commit 80257b7

Please sign in to comment.