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:
- netstat - https://gist.github.com/staaldraad/4c4c80800ce15b6bef1c1186eaa8da9f
- id - https://gist.github.com/staaldraad/640b22ac11f8f37872cdddf7c4bdf11f
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!