pipe


Python 3 program that accepts a path to a binary file executes it, captures all output, and kills it once a part of the output matches a pattern or a timeout occurs

import argparse
import subprocess
import shlex
import signal


# Function to execute a binary and kill it when a needle is found in its output or after a timeout
# Parameters:
#   binary_path: Path to the binary to execute
#   binary_parameters: Parameters to pass to the binary
#   needle: Needle to match in the output
#   timeout: Timeout after which to kill the process
# Returns:
#   The return code of the binary
# Notes:
#   This function uses subprocess.Popen to execute the binary in a sandbox and capture its output.
#   It then loops through the output lines and checks if the needle is found in the output.
#   If the needle is found, the process is killed and the function returns. If the needle is not found,
#   the function waits for the process to finish and returns its return code.
#   Since popen is used, it does not accept a timeout parameter.
#   Instead, the timeout is implemented by using the timeout command to run the binary.
#   We could not use the timeout parameter of subprocess.run because it blocks the execution of the script
#   until the process finishes.
#   This means that we would not be able to capture the output of the process.
def using_popen(binary_path, binary_parameters, needle, timeout):
    # Define the command to run the binary file
    command = f"timeout {timeout} {binary_path} {binary_parameters}"
    # Use subprocess to execute the command in a sandbox and capture its output
    process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # Loop through the output lines and process them
    while True:
        output = process.stderr.readline().decode().strip()
        if output == "" and process.poll() is not None:
            break
        if output != "":
            # Process the output here
            print(output)
            # Check if the output contains the keyword
            if needle in output:
                # Kill the process if the keyword is found
                process.send_signal(signal.SIGINT)
                break
    return process.returncode


if __name__ == "__main__":
    # Parse command line arguments
    parser = argparse.ArgumentParser(
        description='Execute a binary and kill it either when a needle is found in its output or after a timeout')
    parser.add_argument('-e', '--executable', type=str, help='Path to the executable to run')
    parser.add_argument('-p', '--parameters', type=str, help='Parameters to pass to the executable')
    parser.add_argument('-n', '--needle', type=str, help='Needle to match in the output')
    parser.add_argument('-t', '--timeout', type=str, help='Timeout after which to kill the process')
    # Example usage
    # python main.py -e "python" -p "-u -m http.server" -n "404" -t "15s"

    args = parser.parse_args()
    # Execute the binary and capture its output
    return_code = using_popen(args.executable, args.parameters, args.needle, args.timeout)

    # Print the http server's return code
    print("Http server returned with code:", return_code)

The given code is a Python script that executes a binary and kills it when a specified string is found in its output or after a specified timeout. Let’s break down the code into its constituent parts.

First, the script imports several Python modules – argparse, subprocess, shlex, and signal.

argparse is a module that makes it easy to write user-friendly command-line interfaces. It parses the command-line arguments and options specified by the user and returns them in a convenient format.

subprocess is a module that allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.

shlex is a module that provides a simple way to parse command-line strings into a list of tokens, which can then be passed to subprocess.Popen().

signal is a module that provides mechanisms to handle asynchronous events such as interrupts and signals.

Next, the script defines a function called using_popen() that takes four parameters: binary_path, binary_parameters, needle, and timeout. The function first constructs a command string that combines the binary_path and binary_parameters arguments and adds a timeout command to limit the execution time of the process.

The subprocess.Popen() function is then called to create a new process with the constructed command. The stdout and stderr arguments are set to subprocess.PIPE so that the output of the process can be captured.

A while loop is then entered to read the process’s stderr output line by line. The decode() method is used to convert the byte string output to a regular string, and the strip() method is used to remove any whitespace characters from the beginning and end of the string.

If the output string is empty and the process has finished running (process.poll() is not None), the loop is terminated. Otherwise, if the output string is not empty, it is printed to the console.

If the specified needle string is found in the output, the process is terminated by sending a signal.SIGINT signal to it.

After the loop has completed, the return code of the process is retrieved using process.returncode and returned by the function.

Finally, the script checks if it is being run as the main program using the __name__ attribute. If it is, it uses the argparse module to parse the command-line arguments specified by the user. The using_popen() function is then called with the parsed arguments, and its return code is printed to the console along with a message indicating the completion of the script.

In summary, this script provides a convenient way to execute a binary with specified parameters, limit its execution time, and terminate it early if a specified string is found in its output. It also provides a user-friendly command-line interface for specifying these parameters.


How to process tcpdump live data stream from a remote machine on a local WireShark 1

Recently we needed to process the results of a tcpdump command using the GUI version of WireShark on machine that did not have a window manager installed. That device was an embedded device, for which it did not make sense to even consider installing a window manager on it. So, in order to process the results of the tcpdump command we decided to use another machine that had a full working window manager installed and was able to operate the GUI version of WireShark.

For our solution to work some requirements were expected to be met by the embedded device (a.k.a. remote machine).

  1. tcpdump was installed on the remote machine
  2. ssh server was installed on the remote machine and allowed us to connect to it remotely
  3. there was a user that had remote ssh rights on the remote machine that also had the rights to execute tcpdump on the needed interfaces

Synopsis of our solution:

Just execute the following on the machine with the GUI (a.k.a. local machine)


mkfifo /tmp/board;
wireshark -k -i /tmp/board &
ssh [email protected] "tcpdump -s 0 -U -n -w - -i lo not port 22" > /tmp/board;

Explanation of our solution:

Following are the steps that we performed on the local machine to pipe the results of tcpdump on the remote machine on the wireshark on the local machine.

  1. First we created a named pipe as follows:
    mkfifo /tmp/board;
    You can name your pipe anyway you like and place it in any folder you wish. We used /tmp as our pipe is a temporary construct that we do not care to preserve across time/restarts.
  2. Then we started wireshark from a terminal so that we could pass as capture interface the named pipe we just created using the -i /tmp/board parameter. The -k parameter instructs wireshark to start the capture session immediately.
    wireshark -k -i /tmp/board &
    Since this operation was going to execute for a long time, we sent it to the background to release the terminal for further use by placing the & symbol at the end of the command.
  3. Finally, we started tcpdump over ssh on a board and redirected its output to our named pipe.
    ssh [email protected] "tcpdump -s 0 -U -n -w - -i lo not port 22" > /tmp/board;
    The parameters we used on tcpdump have the following effects:
    -s 0 instructs tcpdump to set the snapshot length of data from each packet to the default value of 262144 bytes.
    -U Since the -w option is not specified, make the printed packet output packet-buffered. Which means that it will print the description of the contents of each packet without waiting for the output buffer to get full.
    -n Does not convert host addresses to names. This can be used to avoid DNS lookups.
    -w - Write the raw packets to Standard Output rather than parsing them.
    -i lo Defines which interface to listen on. We wanted the loopback interface to listen to everything.
    not port 22 Since we used ssh to start this command, we do not want to listen to the data that we produce as well and flood the inputs.

 


C/C++: Pass random value from parent to child after fork() via a pipe()

The following code will create a pipe for each child, fork the process as many times as it is needed and send from the parent to each child a random int value, finally the children will read the value and terminate.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char *argv[]) {
    int count = 3;
    int fd[count][2];
    int pid[count];
    srand(time(NULL));

    // create pipe descriptors
    for (int i = 0; i < count; i++) {
        pipe(fd[i]);
        // fork() returns 0 for child process, child-pid for parent process.
        pid[i] = fork();
        if (pid[i] != 0) {
            // parent: writing only, so close read-descriptor.
            close(fd[i][0]);

            // send the value on the write-descriptor.
            int r = rand();
            write(fd[i][1], &r, sizeof(r));
            printf("Parent(%d) send value: %d\n", getpid(), r);

            // close the write descriptor
            close(fd[i][1]);
        } else {
            // child: reading only, so close the write-descriptor
            close(fd[i][1]);

            // now read the data (will block)
            int id;
            read(fd[i][0], &id, sizeof(id));
            printf("%d Child(%d) received value: %d\n", i, getpid(), id);

            // close the read-descriptor
            close(fd[i][0]);
            //TODO cleanup fd that are not needed
            break;
        }
    }
    return 0;
}

C/C++: Pass value from parent to child after fork() via a pipe()

The following code will create a pipe, fork the process and then send from the parent to the child an int value (the id we want to give to the child), finally the child will read the value and terminate.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
	int fd[2];
	int childID = 0;

	// create pipe descriptors
	pipe(fd);

	// fork() returns 0 for child process, child-pid for parent process.
	if (fork() != 0) {
		// parent: writing only, so close read-descriptor.
		close(fd[0]);

		// send the childID on the write-descriptor.
		childID = 1;
		write(fd[1], &childID, sizeof(childID));
		printf("Parent(%d) send childID: %d\n", getpid(), childID);

		// close the write descriptor
		close(fd[1]);
	} else {
		// child: reading only, so close the write-descriptor
		close(fd[1]);

		// now read the data (will block until it succeeds)
		read(fd[0], &childID, sizeof(childID));
		printf("Child(%d) received childID: %d\n", getpid(), childID);

		// close the read-descriptor
		close(fd[0]);
	}
	return 0;
}