Cross Compiling tcpdump for Cisco SPA112/122/232D.

This is one "blog post" in a series of blog posts about developing an exploit, and post-exploitation toolkit, for a number of Cisco devices. These are notes written while solving the problems, so its pretty rough work. I might clean 'em up before publishing, or I might not.

I'll be speaking about this work at BSides Basingstoke, this blog post is part of the series of "supporting documents" published automatically before the event.

One of the tools I needed on-target was tcpdump. These devices claim to be armv5l, so we need to cross compile it to run. This is often a pain in the arse.

We want tcpdump on these targets because in some circumstances, they are "upstream" of user workstations (acting as a router), and having a upstream packet capture capability is cool as fuck.

# cat /proc/cpuinfo
Processor       : ARM926EJ-S rev 5 (v5l)
<snip>

Here are my notes on cross compiling it. Includes the mistakes I made, and how I solved them.

First, make a working directory and cd into it.

mkdir tcpdump_cross
cd tcpdump_cross

Next, we grab the uclibc compilers, tcpdump source code, and libpcap source. You also need to install "flex" and "bison" using your package manager.

I chose an older libpcap for some reason, newer ones had issues compiling...

wget https://www.tcpdump.org/release/tcpdump-4.99.4.tar.gz
wget https://www.tcpdump.org/release/libpcap-1.3.0.tar.gz
wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv5l.tar.bz2

Unpack these.

tar -xf tcpdump-4.99.4.tar.gz
tar -xf libpcap-1.3.0.tar.gz
tar -xf cross-compiler-armv5l.tar.bz2 

Now, we cd into libpcap and try run configure. It took me some trial and error to work this shit out.

CC=$HOME/tcpdump_cross/cross-compiler-armv5l/bin/armv5l-gcc ./configure --host=arm-linux-gnueabi --with-pcap=linux

This worked. So we try run 'make', and it seems to work. I think.

We now go into the tcpdump directory and do the whole mess all over again.

cd ../tcpdump-4.99.4
CC=$HOME/tcpdump_cross/cross-compiler-armv5l/bin/armv5l-gcc CFLAGS="-static" ./configure --host=arm-linux-gnueabi
make

We now have a tcpdump binary.

$ ls -lah tcpdump
-rwxr-xr-x 1 user user 2.0M Jun 22 10:46 tcpdump
$ file tcpdump
tcpdump: ELF 32-bit LSB executable, ARM, version 1 (ARM), statically linked, with debug_info, not stripped

Now we try debiggen it a little.

~/tcpdump_cross/cross-compiler-armv5l/bin/armv5l-strip ./tcpdump

This cuts 500kb off the size, but its still big... So we recompile.

CC=$HOME/tcpdump_cross/cross-compiler-armv5l/bin/armv5l-gcc CFLAGS="-static -Os" ./configure --host=arm-linux-gnueabi
make
~/tcpdump_cross/cross-compiler-armv5l/bin/armv5l-strip ./tcpdump

This gets us down to 1.1mb, UPX gets us down to just under 500kb, which I can live with for now. We probably can further optimise, but thats a matter for another blog entry I think. Now we have to check the fucking thing works on target.

# ./tcpdump -h
tcpdump version 4.99.4
libpcap version 1.3.0
Usage: tcpdump [-AbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ] [--count]
                [ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ]
                [ -i interface ] [ -j tstamptype ]
                [ -M secret ] [ --number ] [ --print ] [ -Q in|out|inout ]
                [ -r file ] [ -s snaplen ] [ -T type ] [ --version ]
                [ -V file ] [ -w file ] [ -W filecount ] [ -y datalinktype ]
                [ -z postrotate-command ] [ -Z user ] [ expression ]
# ./tcpdump        
Illegal instruction
# 

Fuck.

After some thought, I wondered if trying the armv4l compiler would work better, so we recompile libpcap...

wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv4l.tar.bz2
tar -xf cross-compiler-armv4l.tar.bz2
(cd to libpcap....)
libpcap-1.3.0$ CC=$HOME/tcpdump_cross/cross-compiler-armv4l/bin/armv4l-gcc CFLAGS='-static -Os' ./configure --host=arm-linux-gnueabi --with-pcap=linux
make

Now we recompile tcpdump...

tcpdump-4.99.4$ CC=$HOME/tcpdump_cross/cross-compiler-armv4l/bin/armv4l-gcc CFLAGS='-static -Os' ./configure --host=arm-linux-gnueabi;make

And we test this binary on target.

It works!

# ./tcpdump -h
tcpdump version 4.99.4
libpcap version 1.3.0
Usage: tcpdump [-AbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ] [--count]
                [ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ]
                [ -i interface ] [ -j tstamptype ]
                [ -M secret ] [ --number ] [ --print ] [ -Q in|out|inout ]
                [ -r file ] [ -s snaplen ] [ -T type ] [ --version ]
                [ -V file ] [ -w file ] [ -W filecount ] [ -y datalinktype ]
                [ -z postrotate-command ] [ -Z user ] [ expression ]
# ./tcpdump -i br0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on br0, link-type EN10MB (Ethernet), snapshot length 65535 bytes
02:10:47.057948 IP 192.168.0.150.23000 > 192.168.0.54.59996: Flags [P.], seq 3505544835:3505544842, ack 116819468, win 2896, options [nop,nop,TS val 171120 ecr 2833405978], length 7
02:10:47.061658 IP 192.168.0.54.59996 > 192.168.0.150.23000: Flags [.], ack 7, win 2047, options [nop,nop,TS val 2833406102 ecr 171120], length 0

Ok, now lets try make the binary smaller by stripping.

$ ls -lah tcpdump
-rwxr-xr-x 1 user user 1.2M Jun 22 11:09 tcpdump
$ ~/tcpdump_cross/cross-compiler-armv4l/bin/armv4l-strip ./tcpdump
$ ls -lah tcpdump
-rwxr-xr-x 1 user user 1.1M Jun 22 11:12 tcpdump

This bin works on target, but its still pretty big, we try UPX.

$ upx -9 tcpdump
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   1049452 ->    456452   43.49%    linux/arm    tcpdump                       

Packed 1 file.
$ ls -lah tcpdump
-rwxr-xr-x 1 user user 446K Jun 22 11:12 tcpdump

And it works on our target!

# ls -lah tcpdump
-rwxr-xr-x    1 admin    admin      445.8k Jun 22 02:14 tcpdump
# ./tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on br0, link-type EN10MB (Ethernet), snapshot length 65535 bytes
02:14:53.684913 IP 192.168.0.150.23000 > 192.168.0.54.59996: Flags [P.], seq 3505678683:3505678690, ack 116820134, win 2896, options [nop,nop,TS val 232777 ecr 2833652385], length 7
02:14:53.873981 IP 192.168.0.54.59996 > 192.168.0.150.23000: Flags [.], ack 7, win 2047, options [nop,nop,TS val 2833652916 ecr 232777], length 0
working!

So we have now achieved one of our goals for our "on target toolkit": a packet sniffer.

I guess the next step is to write a Dockerfile or a script that just emits the binary, but that is a problem for later me.

A future blog post will cover operational use of tcpdump on embedded device targets for surveillance of networks.