NET100: index.php?-s: Post-attack network log analysis

OMG, Eindbazen got hacked. Can you figure out what this evil hacker did?
http://ebctf.nl/files/da021f41e137fa42501586915d677752/net-100.pcap

For this first networking exercise, we will analyse network logs of an attack against Eindbazen to find what the attacker could do! We are given a clean pcap of the whole attack.

Part1: First look at the pcap file

First thing we can notice, is a long UDP “stream” between the attacker and the target. Just before the attack, a POST was done on the web server hosted by the target. You can find the uploaded php script here. We didn’t spend too much time on it since it looked like a “command receiver on UDP”, which was enough information to continue analysing the logs.

Part2: Interesting HTTP traffic

Apart from the UDP stream, we could notice kerberos and ssh traffic, not that interesting, and a GET from the target to the attacker, of a file named rootkit.zip, quite interesting! We fetched the file but it was password protected. Let’s continue digging.

Part3: Interesting UDP stream commands

Back at the UDP stream, we searched for commands related to rootkit.zip and found this interesting part of the stream where we can see the zip file being unzip’ed. Follows what looks like commands to send the password to unzip command, letter by letter: alongpassword1234.

This password unlocked the zip file, in which we found a file flag.txt, containing “Instead of a rootkit we will just give you a flag: ebCTF{b78dc61ce895a3856f3520e41c07b1be}”.

Done!

NET200: Who’s there

We found this strange website.
http://54.216.81.14/

This website only contains:

112 + 386 + 712 + 1398 + 8771 + 11982 + 15397 + 23984 = 51037

After wondering a while what this addition was supposed to mean (especially since it was wrong and should give the result 62742), we noticed that all these numbers were in the valid port range. That’s when the semantic of this operation struck us: a collection of 8 ports giving a final port, this is exactly the principle of port-knocking.

The idea of this technique is to open a port only for a given client after he knockes to a pre-defined number of ports in the right order, which is only known by the server and the trusted users of the protected service.

So we can execute this first series with a simple netcat:

$ for port in 112 386 712 1398 8771 11982 15397 23984; do
>   netcat -v 54.216.81.14 $port
> done
netcat: unable to connect to address 54.216.81.14, service 112
netcat: unable to connect to address 54.216.81.14, service 386
[...]
netcat: ec2-54-216-81-14.eu-west-1.compute.amazonaws.com (54.216.81.14) 51037 [51037] open
So you are knocking me, how about I return the favor?
Repeat after me and I will open the last port...

Is it knocking us back and expecting we mimic it? We can confirm that with tcpdump:

# tcpdump -n -i eth0 'src host 54.216.81.14'
16:25:22.867635 IP 54.216.81.14.1337 > 163.5.55.17.8112: Flags [S], seq 0, win 8192, length 0
16:25:23.869346 IP 54.216.81.14.1337 > 163.5.55.17.33386: Flags [S], seq 0, win 8192, length 0
16:25:24.874334 IP 54.216.81.14.1337 > 163.5.55.17.14712: Flags [S], seq 0, win 8192, length 0
16:25:25.882108 IP 54.216.81.14.1337 > 163.5.55.17.4398: Flags [S], seq 0, win 8192, length 0
16:25:26.885593 IP 54.216.81.14.1337 > 163.5.55.17.1771: Flags [S], seq 0, win 8192, length 0
16:25:27.889869 IP 54.216.81.14.1337 > 163.5.55.17.52313: Flags [S], seq 0, win 8192, length 0
16:25:28.894443 IP 54.216.81.14.1337 > 163.5.55.17.25697: Flags [S], seq 0, win 8192, length 0
16:25:29.900296 IP 54.216.81.14.1337 > 163.5.55.17.932: Flags [S], seq 0, win 8192, length 0
16:25:30.905643 IP 54.216.81.14.1337 > 163.5.55.17.22222: Flags [S], seq 0, win 8192, length 0

OK, so let’s ping it on these exact same ports in that order. But this time, while the service was rejecting instantly all of our SYN TCP packets in the first series with a RST, for this new series, it seems to drop half of the packets and to reject the other half with RST. Thus, our previous super cool for-loop got stuck in the middle and caused the whole series to fail. So we just changed it to launch the netcat in background and it worked perfectly. This time the 22222 port replied with this message:

[Advanced]
    sequence    = 234,781,983,2411,9781,14954,23112,63991
    seq_timeout = 15
    command     = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 32154 -j ACCEPT
    tcpflags    = fin,urg,!ack
    cmd_timeout = 30
    stop_command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 32154 -j ACCEPT

We recognized it was a chunk of configuration for the knockd daemon, which can be used to setup port-knocking on a UNIX host. It is easy to read, we just have to knock to another series of ports given by the sequence option with the appropriate TCP flags, specified by the tcpflags option, and we will be given access to the port 32154.

This time, we could not use netcat because it does not allow us to specify arbitrary TCP flags, but, since we already had a script ready for the NET300 challenge using Scapy, we also used it for this new series:

from scapy.all import *

ports = [234,781,983,2411,9781,14954,23112,63991]
for p in ports:
        print(send(IP(dst="54.216.81.14")/TCP(dport=p,flags="FU")))

And just connected normally to the final port which gave us the flag of this challenge:

$ netcat -v 54.216.81.14 32154
netcat: ec2-54-216-81-14.eu-west-1.compute.amazonaws.com (54.216.81.14) 32154 [32154] open
ebCTF{32c64f2542ba4566acff750196ca2e13}


NET300: Hop on a plane!

We found this website which uses a location based access control system.
Hop on a plane and hit all target zones!
http://54.212.115.245/

The content of the website

What we understood was that this service tries to locate us by pinging our IP from three servers located in the US, in Brazil and in Japan and display our approximate location on the map. The goal is to make that location change by delaying the ping replies we send back to these three servers and make it hop in each of the three circles on the map.

A few of us tried to look for ways to do that using iptables or the traffic control in the kernel but it was impossible with the first one and it took them a long time with the second one.

Meanwhile, we tried to use the scapy Python module to reply to the pings instead of the kernel. We first tried to prevent the kernel from answering, but dropping the ICMP packets with iptables didn’t work, apparently because the answering part is lower than iptables in the network stack of the Linux kernel in order to make these replies fast. So we decided to disable these replies globally by enabling the net.ipv4.icmp_echo_ignore_all.

Then, we wrote the Scapy script to respond to ping requests with fine adjustment of time.sleep() before our replies in function of which of the three servers we were replying. This script did work great but the results were really random due to network latency and probably our strange solution of replying to pings in userland. So now we had a plane that randomly wandered all over the map… ok great…

We tried to adjust the time.sleep parameter but the result was just too random to be useful. Another problem was that the sleeps accumulated over our replies because scapy queues the requests so we were accumulating requests too much and, after a while, were answering with more than one minute of delay.

So to fix these problems, we decided to modify the script to spawn threads for the replies, to avoid the accumulation of sleeps, so we could have big delay time (30 seconds or so) that would allow us to compensate the random network delay and finely tune the delays to reach exactly the appropriate locations. But we first modified the first script to make the delays totally random and launched it in background, just in case…

from scapy.all import *
import time, random

SRC = ["54.212.115.245", "54.232.216.98", "54.250.176.246"]

def callback(pkt):
        if pkt[IP].proto == 1 and pkt[IP].src in SRC:
                if pkt[IP].src in SRC:
                        time.sleep(random.randint(0,400)/1000.0)
                send(IP(dst=pkt[IP].src)/ICMP(type=0, id=0, seq=0)/Raw(load=pkt[Raw].load))

sniff(prn=callback, filter="(src host 54.212.115.245 or src host 54.232.216.98 or src host 54.250.176.246) and icmp", store=0)

And it worked, before we could finish the new script, the first one made us reach the three circles successfully, giving us the flag: ebCTF{9bd26cbffa30c0ea32c425df220f06b9}.