A blog on Computer Science, Security, Programming, and more...
08
Jun
2014

Redirect STDOUT of Child to Parent Process in C

Written by Matt

Here's some C code for how to do this on Linux... mostly posting it for my own reference. I've commented the code extensively. (compile with -std=c99 or -std=gnu99 if using GCC)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define BUFSIZE 256

void child_process(int pipe_pair[2]){

    // closes the child's STDOUT descriptor and replaces it with
    // the write pipe linked to the parent's read pipe
    if (dup2(pipe_pair[1], STDOUT_FILENO) == -1){ 
      // if dup2 fails, perror writes to stderr so this reports
      // appropriately either way as the child and parent share stderr
      perror("dup2"); 
      // parent will know if the child failed since we return 1
      // (ideally return errno so that calling processes know the error code)
      exit(EXIT_FAILURE); 
    }   

    // duplicated by dup2 above, no longer needed
    close(pipe_pair[1]); 
    // close read end as we will never read from stdout
    close(pipe_pair[0]); 

    // printf writes to stdout by default
    // we could also use fprintf(stdout, ...)
    printf("Hello, parent!\n"); 
    // make sure the write buffer is flushed before we exit
    fflush(stdout); 

    // close to make sure read() returns 0 in the parent
    close(STDOUT_FILENO); 

    // child exits
    exit(EXIT_SUCCESS); 
}

void parent_process(int pipe_pair[2], pid_t cpid){

    // cstatus will store the return of the child process
    // buf will hold the child's writes to stdout --
    // {0} initializes the array elements to 0x00
    int cstatus; 
    char buf[BUFSIZE] = {0}; 

    close(pipe_pair[1]); // we won't write to stdout

    // read until closed, or error (0 or -1, respectively)
    for (int n = 0; (n = read(pipe_pair[0], buf, BUFSIZE)) > 0;){ 
      printf("Received %d bytes from child process: ", n); 
      // (needed otherwise write() may output before 
      // printf since stdio output to stdout is line buffered)
      fflush(stdout); 
      // writes just what we read so no need to reset buf
      write(STDOUT_FILENO, buf, n); 
      printf("\n");
      fflush(stdout);
    }   

    // close read pipe
    close(pipe_pair[0]); 

    // waits for child process with pid 'cpid' to
    // return and stores the exit code in cstatus
    waitpid(cpid, &cstatus, 0); 

    printf("Child exit status was: %d\n", cstatus);

    // terminate parent
    exit(EXIT_SUCCESS); 

}

int main(int argc, char **argv){

  // cpid stores the process id of the child process
  // stdout_pipe array = pipe descriptor pair -- 
  // [0] is the read end, [1] is the write end
  pid_t cpid; 
  int stdout_pipe[2]; 

  // call that creates the two unidirectional pipe streams 
  // and stores the descriptors in the array
  if (pipe(stdout_pipe) == -1){ 
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  // fork happens here, cpid will have the child's
  // process id or -1 if the call fails
  cpid = fork(); 
  if (cpid == -1){
    perror("fork");
    exit(EXIT_FAILURE);
  }

  // child (fork returns 0 in the child and the child ID for the parent)
  if (cpid == 0) 
    child_process(stdout_pipe);
  // else when cpid is not 0 or -1 we're in the parent
  else 
    parent_process(stdout_pipe, cpid);

  // we shouldn't get here, but return int from main for correctness
  return 0;
}

This is a cheap and easy way to do interprocess communication, but also useful if you have code that outputs to stdout / takes from stdin and you want to integrate it into your program without rewriting all the output code to use a different write mechanism, or you just don't want to maintain two separate codebases. Simply fork the process and replace STDIN/STDOUT/STDERR in the child before executing the code you need to execute.

Topic: Programming tags: linux, snippet, c, code, system
07
Jun
2014

Allow Traffic Only To VPN in Linux with iptables

Written by Matt

iptables is a little awkward if you're not used to it. The way that it works is that it attempts to match events against an ordered list of rules, and if none match then the default policy is used for that chain. iptables stops processing the list once there's a match, so if you have a very general rule at the top it will frustrate you when everything is allowed/blocked for seemingly no reason.

Here's a script to allow traffic from a physical interface (wlan0 or eth0) to just one IP (which could be a VPN IP).

#!/bin/bash

# Set your LAN subnet here
LANIP='192.168.1.0/24'

# Set the allowed destination IP (or IP range), port and protocol
DSTIP='8.8.8.8/32'
DSTPORT='1194'
DSTPROT='udp'

# flush all previous rules (reset iptables)
iptables -F

# drop INPUT and FORWARD if not matched (-P stands for policy, we're setting the default policy to DROP the packet if nothing below is matched)
iptables -P INPUT DROP 
iptables -P FORWARD DROP

# allow established sessions and localhost traffic (many programs rely on localhost connections)
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -s 127.0.0.0/8 -d 127.0.0.0/8 -i lo -j ACCEPT

# allow LAN traffic and traffic going to the VPN
iptables -A OUTPUT -d $LANIP -j ACCEPT
iptables -A OUTPUT -d $DSTIP -p $DSTPROT -m $DSTPROT --dport $DSTPORT -j ACCEPT

# drop everything else (on the real devices)
iptables -A OUTPUT -o wlan0 -j DROP
iptables -A OUTPUT -o eth0 -j DROP

Basically, the above says that anything to/from localhost, or anything that has either a LAN IP or the DST IP as its destination in the packet header will be allowed. Everything else going out of the wlan0 and eth0 devices is caught by the last two rules, and is dropped. Be careful about the order that you write these rules in, if I had put the last two rules at the top (after the -F rule), then ALL traffic would get instantly dropped, because those are two very general rules and they will match every single packet that leaves either interface. Once a rule is matched, later rules are not processed, and along with the two INPUT and FORWARD DROP policies at the top, I'd have isolated the OS from the network completely.

-A in the rules means append, so append to the chain OUTPUT or INPUT (this puts the rule at the bottom, as opposed to -I which stands for insert, and places the rule at the top, to be evaluated before any other rule). The line -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT is important otherwise we cannot get input responses from connections we open to the LAN, since the default INPUT policy is to drop. In the rest, -s stands for source (the source IP in the packet header), -d stands for destination (again, destination IP in the packet header), -p specifies the protocol (UDP or TCP, most of the time), and -j specifies the action to be taken if the rule matches, either ACCEPT (allow the packet) or DROP it. In the last two lines, -o signifies the output interface, the interface that the packets are being sent from, just as -i in the line for localhost means input interface. --dport is of course just destination port.

19
May
2014

Using Tshark To View Raw Socket Streams

Written by Matt

Why do all packet capture tools do things you never ask them to do? It took me a while to figure out how to get clean streams using just tshark from pcap files. Here's the script:

#!/bin/bash

if [ "$#" -lt 1 ]; then
        echo "Usage: tshark_strams.sh <pcap file> [filter rules]"
        exit
fi

if [ ! -z "$2" ]; then
        STREAMS=$(tshark -r "$1" -R "$2" -T fields -e tcp.stream | sort -n | uniq)
else
        STREAMS=$(tshark -r "$1" -T fields -e tcp.stream | sort -n | uniq)
fi


for i in $STREAMS
do 
        INDEX=`printf '%.5d' $i`

        echo "Processing stream $INDEX ..."

        tshark -r "$1" -T fields -e data -qz follow,tcp,raw,$i | tail -n +7 | tr -d '=\r\n\t' | xxd -r -p > "$1"_stream-$INDEX.bin
        tshark -r "$1" -qz follow,tcp,ascii,$i > "$1"_stream-$INDEX.txt
done

This takes a pcap file generated from wireshark, tshark, tcpdump (or anything that outputs libpcap files) and creates two files for each socket stream. First, a .txt file that contains an ASCII representation of the packet, so that non-printable characters are substituted -- unfortunately it also seems to like mixing in packet byte size numbers prefix by a tab before each packet, and it seems this is impossible to disable, so I've also fixed it to generate a .bin file that stores just the raw stream and nothing else. Don't dump the .bin files to the console if you've captured binary data, use xxd, hexdump, hexedit, or even something like vim.

There is also an optional argument which is the filter, so if you run it as ./tshark_streams.sh capture.pcap "http" it dumps only HTTP streams. See Capture Filter Examples for what you can use.

Here's an example run on wget packet capture for this blog, along with a cat for each generated file:

$ ./tshark_streams.sh example.pcap "http"
Processing stream 00000 ...
$ cat example.pcap_stream-00000.txt

=================================================================== Follow: tcp,ascii Filter: tcp.stream eq 0 Node 0: 10.9.0.26:40667 Node 1: 174.136.97.90:80 94 GET / HTTP/1.1 User-Agent: wget Accept: */* Host: heapspray.net Connection: Keep-Alive

1324 HTTP/1.1 200 OK Server: Anonymous Date: Mon, 19 May 2014 06:41:40 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding [...]
$ cat example.pcap_stream-00000.bin | head -n 20
GET / HTTP/1.1
User-Agent: wget
Accept: */*
Host: heapspray.net
Connection: Keep-Alive

HTTP/1.1 200 OK Server: Anonymous Date: Mon, 19 May 2014 06:41:40 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding

3fa0

As can be seen, the .txt has a header and random packet size numbers mixed in throughout, whereas the .bin file is purely the raw stream. Since wget uses HTTP 1.1 by default the server responds in with chunked mode transfer encoding, so the hexadecimal numbers are the chunk headers and sizes, but the offset decimal numbers (i.e., the "94" and " 1324" in the first file) represent the sizes of the received TCP packets. Useful for debugging your networking applications, but they get in the way if you're just trying to analyze the raw stream, so I've had it generate the raw .bin file as well.

For completeness, the command I used to capture the wget request was:

$ tshark -i tun0 -w example.pcap
Capturing on tun0
53 ^C

Simply CTRL+C when you are finished capturing, tshark ends the session gracefully. The -i option specifies the interface. On most systems it will be either eth0 if you use a wired, ethernet connection, or wlan0 if you use a wireless connection. Use ifconfig to check. I use a VPN so therefore my device is tun0, for network TUNnel.

18
May
2014

Prevent Program From Accessing The Network in Linux

Written by Matt

I've just recently found out Linux supports namespaces on recent kernels, through the program unshare. As the name implies, it unshares namespaces from the parent and allows you to run programs with restricted access.

The program can simply be used as follows:

# unshare -n -- ping 127.0.0.1
connect: Network is unreachable

The '--' signifies that all arguments beyond that point are no longer arguments to 'unshare' but to the program you want to execute and its arguments. As you can see, ping fails to reach even localhost, because this creates a new network namespace (option -n in unshare) that has no devices and no networking setup - it's totally blank.

One problem with this is that you need to run the command itself as root, because to run 'unshare' you need CAP_SYS_ADMIN capabilities, which is basically equivalent to root, but you may not want to run the restricted process as root and certainly not with CAP_SYS_ADMIN capabilities, for obvious reasons (as it will just be able to choose another namespace itself if it wants to). Therefore, you can run the command as follows:

$ su -c 'unshare -n -- su - YOUR_USER -c "ping 127.0.0.1"'

A little obtuse, but this uses 'su' to run 'unshare' as root, then uses 'su' within unshare's created namespace to demote the privileges down to YOUR_USER, and then runs the command "ping 127.0.0.1" as YOUR_USER. It can be turned into a script for convenience:

#!/bin/bash

if [ "$#" -ne 2 ]; then
    echo "Usage: nonetwork.sh <user> <command>"
    exit
fi

su -c "unshare -n -- su - $1 -c \"$2\""

Save this as nonetwork.sh and place it in /usr/local/bin, and then you can run it as follows:

$ nonetwork.sh USER "ping 127.0.0.1"
Password: [Enter root password]
connect: Network is unreachable

Do note that you need a relatively recent kernel, and your kernel needs to be compiled with CONFIG_NET_NS=y -- from a little research it seems "full" support for these operations was added after kernel version 3.4.x, so it might not work on server distributions like CentOS which still use the 2.6.x series kernel.

Glad to see Linux is getting namespace support. I wonder when it happened?

21
Apr
2014

Getting Raw HTTP Request in PHP

Written by Matt

In case you ever want to get a raw HTTP request for logging, analysis or filtering reasons, the code is simply:

function http_raw_request(){
  
  $http_request = ""; 

  foreach (getallheaders() as $key => $value){
    $http_request .= $key . ': ' . $value . "\n";
  }

  $http_request .= "\n";
  $http_request .= file_get_contents("php://input");

  return $http_request;

}

If, like me, you use php-fastcgi and lack the function getallheaders, it can be implemented as followslink:

if (!function_exists('getallheaders')){ 
  function getallheaders(){ 
    $headers = ''; 
    foreach ($_SERVER as $name => $value){ 
      if (substr($name, 0, 5) == 'HTTP_'){ 
        $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 
      } 
    } 
    return $headers; 
  } 
}

Example capture on a comment for this blog:

 Host: heapspray.net                                                               +
 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1+
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8           +
 Accept-Language: en-us,en;q=0.5                                                   +
 Accept-Encoding: gzip, deflate                                                    +
 Connection: keep-alive                                                            +
 Referer: http://heapspray.net/post/added-comments/                                +
 Content-Type: application/x-www-form-urlencoded                                   +
 Content-Length: 38                                                                +
                                                                                   +
 id=46&poster=test&email=test&text=test

Very useful for anything from site statistics to debugging and filtering spam (most spambots have very different request headers than most browsers).