Recently I was doing an assessment in a locked down and restricted environment. One of the first actions you tend to do when landing a shell on a [linux] box is to do some reconnaissance. This is both on host and network, as you want to determine what new access this host has given you. Normally you would run netstat , ifconfig , ip route etc to determine if the compromised host is connected to any other hosts and to determine if there are other network segments you do not know about.

In this case I ran netstat and immediately got an error back: netstat: command not found . A quick check and nope, it wasn’t that the PATH was incorrect, but that netstat was not on the box. That is ok though, we can use lsof and see if there are listening ports. And it turns out lsof is also not available. Since I wasn’t root and couldn’t install any tools (guess I could have copied a netstat binary across), I decided to find another way to do netstat . Linux is all about file descriptors and there is ‘always’ a file somewhere containing the information you need.

In this case, if you view /proc/net/tcp you will be able to get information about the current network connections (TCP). The file /proc/net/udp gives you information about UDP connections, and /proc/net/unix about unix sockets. This is probably old news to Linux regulars/grey-beards, but for someone who has always relied on netstat or lsof it was a great discovery.

$ cat /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0 : 00000000 :006F 00000000 :0000 0A 00000000 :00000000 00 :00000000 00000000 0 0 15171 1 ffff8aa6b3c88800 100 0 0 10 0 1 : 017AA8C0:0035 00000000 :0000 0A 00000000 :00000000 00 :00000000 00000000 0 0 34766 1 ffff8aa69bdd1000 100 0 0 10 0 2 : 0100007F:0277 00000000 :0000 0A 00000000 :00000000 00 :00000000 00000000 0 0 46372 1 ffff8aa681653000 100 0 0 10 0 3 : 6501A8C0:9DE0 309C10C7:01BB 01 00000000 :00000000 02 :00000012 00000000 1000 0 1235984 2 ffff8aa669a05800 53 4 26 10 -1 4 : 6501A8C0:AE18 872EF468:01BB 01 00000000 :00000000 02 :0000025B 00000000 1000 0 1221848 2 ffff8aa4448b2800 24 4 28 10 -1 5 : 6501A8C0:DA10 85C06597:01BB 01 00000000 :00000000 02 :00000727 00000000 1000 0 1253983 2 ffff8aa69da76000 20 4 1 10 -1 6 : 6501A8C0:BDFA 85806597 :01BB 01 00000000 :00000000 02 :00000BF4 00000000 1000 0 1253097 2 ffff8aa681652800 20 4 11 10 -1 7 : 6501A8C0:96B8 85406597 :01BB 01 00000000 :00000000 02 :00000727 00000000 1000 0 1253988 2 ffff8aa69da71000 20 4 10 10 -1 8 : 6501A8C0:BDFC 85806597 :01BB 01 00000000 :00000000 02 :00000BF4 00000000 1000 0 1253098 2 ffff8aa681656800 21 4 10 10 -1 9 : 6501A8C0:CE32 A78722B0:01BB 01 00000000 :00000000 02 :00000727 00000000 1000 0 1255776 2 ffff8aa669a01000 26 4 22 10 -1 10 : 6501A8C0:BA08 822AF468:01BB 01 00000000 :00000000 02 :000005EA 00000000 1000 0 1222742 2 ffff8aa669a00800 22 4 24 10 -1 11 : 6501A8C0:EBB6 827C9836:01BB 01 00000000 :00000000 02 :00000248 00000000 1000 0 1239587 2 ffff8aa681655800 31 4 18 10 -1 12 : 6501A8C0:BDF8 85806597 :01BB 01 00000000 :00000000 02 :00000BF4 00000000 1000 0 1253096 2 ffff8aa681654800 20 4 22 10 -1 ...

The biggest downside here is that addresses and ports are hex encoded and not particularly user friendly (unless you do all your network addressing in hex). Enter awk , a swiss army knife for processing files/text.

The first thing I wanted was a list of all remote hosts that this host connects to, or are connected to this host. For this I needed to parse the rem_address column. A bit of messing around with awk on my localhost and I came up with the following:

grep -v "rem_address" /proc/net/tcp | awk '{x=strtonum("0x"substr($3,index($3,":")-2,2)); for (i=5; i>0; i-=2) x = x"."strtonum("0x"substr($3,i,2))}{print x":"strtonum("0x"substr($3,index($3,":")+1,4))}'

Which will give you the following output:

0 .0.0.0:0 0 .0.0.0:0 0 .0.0.0:0 216 .58.210.170:443 199 .16.156.48:443 104 .244.46.135:443 151 .101.192.133:443 151 .101.128.133:443 ...

Much better! I ran it on the remote host and it failed… Darn, it turns out my awk above is for gawk (GNU Awk) and functions like strtonum aren’t available in standard awk . Back to the drawing board and some more awk was born, in this version I define a new function, hextodec which does the same as strtonum .

grep -v "rem_address" /proc/net/tcp | awk 'function hextodec(str,ret,n,i,k,c){ ret = 0 n = length(str) for (i = 1; i <= n; i++) { c = tolower(substr(str, i, 1)) k = index("123456789abcdef", c) ret = ret * 16 + k } return ret } {x=hextodec(substr($2,index($2,":")-2,2)); for (i=5; i>0; i-=2) x = x"."hextodec(substr($2,i,2))}{print x":"hextodec(substr($2,index($2,":")+1,4))}'

Great success! This worked brilliantly and I got much needed insight into the network. To get information about the local ports, for example which ports are we listening on, we parse the local_address column, so replace $2 with $1 throughout the script and you’ll get the correct output.

Nice, but how about an all-in-one solution?

This was great, but I got annoyed having to match the output from rem_address and local_address. Being a sucker for punishment I decided to try and do it all in awk . So no pipelining commands, just one long awk function.

awk 'function hextodec(str,ret,n,i,k,c){ ret = 0 n = length(str) for (i = 1; i <= n; i++) { c = tolower(substr(str, i, 1)) k = index("123456789abcdef", c) ret = ret * 16 + k } return ret } function getIP(str,ret){ ret=hextodec(substr(str,index(str,":")-2,2)); for (i=5; i>0; i-=2) { ret = ret"."hextodec(substr(str,i,2)) } ret = ret":"hextodec(substr(str,index(str,":")+1,4)) return ret } NR > 1 {{if(NR==2)print "Local - Remote";local=getIP($2);remote=getIP($3)}{print local" - "remote}}' /proc/net/tcp

This is the raw awk version, so it will work in both awk and gawk . When running this, you will get output similar to netstat , it is still up to you to figure out which are ‘listening’ connections and which are outgoing. This should be relatively straight forward, anything connecting to a remote of 0.0.0.0:0 is a listening port. Or since listening connections tend to be on the low-range ports, and outgoing connections will be a high-range port connecting to a low-range port.

Local - Remote 0 .0.0.0:111 - 0 .0.0.0:0 192 .168.122.1:53 - 0 .0.0.0:0 127 .0.0.1:631 - 0 .0.0.0:0 192 .168.1.101:40416 - 199 .16.156.48:443 192 .168.1.101:44568 - 104 .244.46.135:443 192 .168.1.101:54108 - 151 .101.65.69:443 192 .168.1.101:47624 - 104 .244.42.130:443 192 .168.1.101:34728 - 104 .16.110.18:443 192 .168.1.101:60342 - 54 .152.124.130:443 ...

Success - what else can we do?

This was great, I could now do some network recon and had some go to scripts for the next time this happens. You can also parse various other files in /proc/net such as /unix , /arp , /route to get unix sockets, the arp table and the route table respectively. The route table needs the same hex to dotted notation translation as the /tcp and /udp files. It also got me thinking, could we do this for all the common hacker commands (actually just admin commands), that we run when first compromising a box? The thinking being that monitoring may be setup for instances of id , netstat , ifconfig etc being run. If we run one of those, we would be alerting our target that they have just been popped. Unless of course they are Equifax.

My first attempt at this was a tame copy of the id command. In this case we query the /proc/self/status file to find out which user we are running as.

awk -F: 'END {print "uid:"u" gid:"g" groups:"gg}{if($1=="Uid"){split($2,a," ");u=a[1]}if($1=="Gid"){split($2,a," ");g=a[1]}if($1=="Groups"){gg=$2}}' /proc/self/status

The output is pretty rudimentary and I’m sure this can still be improved, but I was trying to avoid the common action of cat /etc/passwd (or reading /etc/passwd in any other way):

uid:1000 gid:1000 groups: 10 1000 1001

Scripts

I’ve hosted all versions of the netstat awk script as well as the id script on gist.gitlab.com:

In time I’ll hopefully add a few more of these. If you got some go to scripts that help you avoid detection or accomplish the same but in a better way, please let me know!