Sinkholing Suspicious Scripts or Executables on Linux
When you need to analyze some suspicious pieces of code, it's interesting to detonate them in a sandbox. If you don't have a complete sandbox environment available or you just want to avoid generatin noise on your network, why not route the traffic to a sinkhole or NULL-route (read: packets won't be sent across the normal network and default gateway).
When you inspect a process using the /proc[1] virtual filesystem, there is a "route" file:
remnux@remnux:~$ cat /proc/1180/net/route
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
ens19 00000000 01FEA8C0 0003 0 0 100 00000000 0 0 0
ens18 004A10AC 00000000 0001 0 0 0 00FFFFFF 0 0 0
ens19 00FEA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
ens19 01FEA8C0 00000000 0005 0 0 100 FFFFFFFF 0 0 0
It displays the IP routing table assigned to this process. Typically, IP addresses are encoded in little-endian hexadecimal values. They can be easily decoded using a few lines of Python:
gw = "01FEA8C0" octets = [gw[i:i+2] for i in range(0, len(gw), 2)] ip = '.'.join(str(int(o, 16)) for o in octets) print(ip) # Will return: 1.254.168.192
Does it mean that we could apply a specific routing table to a process? Yes and no... In /proc, the "route" file is read-only.
But, Linux is full of features that many people aren't aware of. One of them are namespaces[2]. It's a kernel feature (introduced around 2016 if I remember well) that provides isolation of system resources between processes (a bit like containers). Each namespace type—such as PID, mount, UTS, network, IPC, and user—isolates a specific aspect of the operating system environment. For example, the network namespace gives processes their own network stack, including interfaces and routing tables. Very interesting!
Let's try this and run our suscipious script in a dedicated namespace. My suspicious script will be super simple:
remnux@remnux:~$ cat sample.sh #!/bin/bash echo "Am I bad?" curl https://isc.sans.edu
First example, no network connectivity at all!
remnux@remnux:~$ sudo unshare --net bash root@remnux:/home/remnux# ./sample.sh Am I bad? curl: (6) Could not resolve host: isc.sans.edu root@remnux:/home/remnux# ip a 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 root@remnux:/home/remnux# ip r Error: ipv4: FIB table does not exist. Dump terminated root@remnux:/home/remnux# exit remnux@remnux:~$
The unshare command (executed as root) will create a new shell in a new namespace with dropped network settings. When curl is executed, it can't resolve isc.sans.edu nor connect to it. We have a complete network isolation.
Second example, let's build a dedicated IP stack that will route packets to another IP address, our synchole. A pair of virtial Ethernet interfaces must be added. In this case, 10.0.0.1 will be the new namespace and 10.0.0.2 the main one.
(Note: I'll change the bash prompt to make it clearer)
remnux@remnux:~$ sudo unshare --net bash root@remnux:/home/remnux# export PS1="namespace> " namespace> ip link set lo up namespace> ip link add veth0 type veth peer name veth1 namespace> ip link set veth0 up namespace> ip addr add 10.0.0.1/24 dev veth0 namespace> ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: veth1@veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether b6:5c:6e:ed:c3:62 brd ff:ff:ff:ff:ff:ff 3: veth0@veth1: <NO-CARRIER,BROADCAST,MULTICAST,UP,M-DOWN> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000 link/ether 66:72:35:1f:9f:9e brd ff:ff:ff:ff:ff:ff inet 10.0.0.1/24 scope global veth0 valid_lft forever preferred_lft forever namespace> ip link set veth1 netns 1
On the main namespace (your original shell), create the virtual NIC:
root@remnux:/home/remnux# ip addr add 10.0.0.2/24 dev veth1 root@remnux:/home/remnux# ip link set veth1 up
Back in the new namespace:
namespace> ping 10.0.0.2 PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.020 ms 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.034 ms ^C --- 10.0.0.2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1023ms rtt min/avg/max/mdev = 0.020/0.027/0.034/0.007 ms
Let's add a default route to the IP in the main namespace:
namespace> ip route add default via 10.0.0.2 namespace> ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. ^C --- 8.8.8.8 ping statistics --- 13 packets transmitted, 0 received, 100% packet loss, time 12293ms
If we run a tcpdump on veth1, we can now capture all the network connection attempts from the namespace:
root@remnux:/home/remnux# tcpdump -i veth1 -n tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes 11:02:32.122380 ARP, Request who-has 10.0.0.2 tell 10.0.0.1, length 28 11:02:32.122408 ARP, Reply 10.0.0.2 is-at b6:5c:6e:ed:c3:62, length 28 11:02:32.154271 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 18547, seq 6, length 64 11:02:33.178401 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 18547, seq 7, length 64 11:02:34.202411 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 18547, seq 8, length 64 ^C 5 packets captured 5 packets received by filter 0 packets dropped by kernel
Finally, let's verify the routing table of the shell running in the new namespace:
namespace> echo $$ 149522
On the main namespace:
root@remnux:/home/remnux# cat /proc/149522/net/route
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
veth0 00000000 0200000A 0003 0 0 0 00000000 0 0 0
veth0 0000000A 00000000 0001 0 0 0 00FFFFFF 0 0 0
(0x0200000A = 10.0.0.2)
Done! The current configuration is very basic and does not provide, amongst others, a DNS. Your sinkholed sample won't be able to resolve FQDN. Also, you could really route the packets by enabling ip_forward and NAT the traffic.
WARNING: This is not a bullet-proof solution to perform malware analysis: Only the network traffic was isolated!
[1] https://docs.kernel.org/filesystems/proc.html
[2] https://en.wikipedia.org/wiki/Linux_namespaces
Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Comments