Team

Network Traffic Analysis

Attack (Easy)

An employee recently came under attack. What can you find out from this capture?

  1. What is the name for the attack that is shown in this capture? (10 points)

    According to this site, the PCAP looks a lot like ARP poisoning, due to 192.168.1.1 and 192.168.1.101 having the same MAC address.

    Answer: DNS Spoofing

  2. What is the IPv4 address of the target of the attack? (10 points)

  3. What is the attacker's MAC address? (50 points)

  4. What is the name of the web server that the attacker was redirecting traffic to? (20 points)

    It looks like the attacker was redirecting traffic to ns-1053.awsdns-03.org.

    Answer: ``

  5. What is the domain name of the site originally supposed to receive the redirected traffic? (10 points)

    The answer could be httpbin.org, but that seems too easy.

Chunked (Easy)

It looks like there is some sort of hidden flag in this wifi capture. See if you can find it.

  1. What MAC address was generating fake beacons? (10 points)

    There is only one MAC address visible in the file, there were no results for the filter !(wlan.ta == de:ad:be:ef:fe:ed) so the answer can only be one thing.

    Answer: de:ad:be:ef:fe:ed

  2. On what numbered wifi channel are the non-junk pieces of data being leaked? (15 points)

    We can look at different channels individually by doing wlan_radio.channel == 1 for each number. Going through all of these, the only one that wasn't empty or filled with just "Junk" was with wlan_radio.channel == 13.

    Answer: 13

  3. What is the hidden flag that is being broadcasted? (40 points)

    I got a hunch that the data was in the broadcast SSIDs, so I used the following tshark command to compile that data and only look for what was after the colon:

    tshark -r Chunked.pcap -Y "wlan_radio.channel == 13 && wlan.fc.type_subtype == 0x0008" | grep -oE '[^ ]+$' | cut -d':' -f2- | tr -d '\n'
    

    That gave me the following:

    NkN=JVk=xkkTVk==TYkkLxkV0w=YxNdddNwkLtx0tTJ=w00J=dxLLRNY=JtTkt=t=Jd0k==tV=NRVRUkk=xRRdNNdTtxU=TtkLtNkttkYYt=tYkVkRktYZN=NwkZttwZJLVRt00Z=JLVTtNN=Jk=V=twNd=tk0VTkN=TRJNNwk0RtdNkYkR
    

    I couldn't make any sense of this, so I tried only getting the numbers before the colon instead:

    $ tshark -r Chunked.pcap -Y "wlan_radio.channel == 13 && wlan.fc.type_subtype == 0x0008" | grep -oE '[^ ]+$' | cut -d'=' -f2- | cut -d':' -f1 | tr -d '\n'
    161016187591815910135918181314101041510511719141516666121794215111137191711719615448121418711131011192187619191811519128580991815886121261321501913210411129221014141118214105989214312181217103112173745811113197451311161618710195192171261829151310121913871612179182612914108
    

    Nothing useful there either. One of my teammates suggested grouping by SSID, so I tried that:

    tshark -r Chunked.pcap -Y "wlan_radio.channel == 13 && wlan.fc.type_subtype == 0x0008" | grep -oE '[^ ]+$' | cut -d'=' -f2- | awk -F':' '{print $1,$0}' | sort | awk '{print substr($0, index($0,$2))}' | uniq | sort -n
    

    And once I decoded all the characters in order with base64, I got the flag!

    Answer: SKY-WIFI-5617

Lightning (Medium)

Smart home devices are super prevalent these days, we have captured some network traffic of a smarthome system that controls lighting, but it looks like part of the data got corrupted. Can you find out the following information from the pcap?

  1. What is the company that made these smart home devices? (10 points)

    We can tell this from the protocol being used, TPLINK-SMARTHOME/JSON.

    Answer: TP-Link

  2. There's a dimmer that got switched up, what percentage is the dimmer set to? (15 points)

    This can be found in #84:

    {"smartlife.iot.dimmer":{"set_brightness":{"brightness":29}}}
    

    Answer: 29

  3. How many different smarthome devices are connected? (15 points)

    Using the filter tplink-smarthome, we can see a total of 10 communications. Once there, we can see that there are 3 different IP addresses starting with 192.168. Unfortunately, this was the wrong answer.

    I decided to use the UDP packets at the start of the capture, 22 of them were reached out to on port 3879 with the same UDP traffic: d0f281f88bff9af7d5ef94b6d1b4c09fec95e68fe187e8caf08bf68bf6

    Answer: 22

  4. How many different models of smarthome devices are connected? (20 points)

    The set_relay_state command indicated a TP-Link HS110 Smart Plug, and the smartlife.iot.dimmer command indicates a Smart Dimmer Switch HS220. It looks like get_realtime is also tied to the HS110. Unfortunately, 2 was not the correct answer.

    I did a random guess of 3 as well, just for shits n giggles, but that was wrong as well.

    Answer: ``

  5. How many different smarthome devices are powered off? (20 points)

    Search for tplink-smarthome, sort by Time, and observe the commands. We can see that .30 is turned on then turned off ({"system":{"set_relay_state":{"state":0}}}). According to Google, seeing {"system":{"set_relay_state":{"err_code":0}}} as a response means that the command executed successfully.

    Whenever the status for .31 is queried, we can see that the current power is 0, which means that it is off. The final command is turning on .21, so we know that it is on.

    Answer: 2

  6. What city are these devices located in? (20 points)

    Answer: ``

Covert Exfiltration (Hard)

We have identified someone covertly exfiltrating information from the network, can you find out what they stole?

  1. What is the filename of the stolen file? (20 points)

    I see GET requests for /report.pdf on the server's port 1337, so I assume that the exploitation is taking place there. Going to File -> Export Objects -> HTTP, we can see that there is a file report.pdf that was transferred a lot of times. When we "Save All", the report.pdf file is downloaded 1000 times.

    Answer: report.pdf

  2. What is the MD5 digest of the stolen file? (50 points)

    Looking at the conversations, we can see that there was likely a file transferred from 10.0.0.3 to 172.25.123.187, as it's a size of several megabytes.

    There is an extraordinary amount of TCP data that is just 58 repeated over and over, I'm not sure if that has any significance or if it is just meant as a distractor. Filtering it out brings us down to 40% of the original data (99699/248590). If we also ignore the associated SYN-ACKs with !(tcp.flags == 0x012), that brings us down to 27% (67319/248590). Combining with ignoring RST packets (!(tcp.flags == 0x004)) brings us down to 15% (37077/248590).

    Using a script to assemble the 1000 files, we get one massive 1.3MB file with no discernable file type. Running strings Wireshark_Objects/combined_output -n 8 shows us a few goodies, including the following:

    pdf="http://ns.adobe.com/pdf/1.3/">
    <xmp:ModifyDate>2023-04-11T01:03:13-04:00</xmp:ModifyDate>
    <xmp:CreateDate>2023-04-11T01:02:47-04:00</xmp:CreateDate>
    <xmp:MetadataDate>2023-04-11T01:03:13-04:00</xmp:MetadataDate>
    <xmp:CreatorTool>Adobe Acrobat 22.2</xmp:CreatorTool>
    <dc:format>application/pdf</dc:format>
    <xmpMM:DocumentID>uuid:c8222c26-16bd-e44e-9dab-010874887568</xmpMM:DocumentID>
    <xmpMM:InstanceID>uuid:c7d3f0bc-4793-684e-a671-8149a286f46e</xmpMM:InstanceID>
    <pdf:Producer>Adobe Acrobat 22.2 Image Conversion Plug-in</pdf:Producer>
    </rdf:Description>
    </rdf:RDF>
    </x:xmpmeta>
    
    7.1-c000 79.cb7c5a1, 2022/04/14-05:22:35        ">
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
    xmlns:xmp="http://ns.adobe.com/xap/1.0/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
    xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
    <xmp:ModifyDate>2023-04-11T01:03:13-04:00</xmp:ModifyDate>
    <xmp:CreateDate>2023-04-11T01:02:47-04:00</xmp:CreateDate>
    <xmp:MetadataDf
    
    <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.1-c000 79.cb7c5a1, 2022/04/14-05:22:35        ">
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
    xmlns:xmp="http://ns.adobe.com/xap/1.0/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
    xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
    <xmp:ModifyDate>2023-04-11T01:03:13-04:00</xmp:ModifyDate>
    <xmp:CreateDate>2023-04-11T01:02:47-04:00</xmp:CreateDate>
    <xmp:Meta
    

    I tried using binwalk, which indicates that there's a PDF document v1.6 in there, but wasn't able to extract anything. I tried binwalk -dd='.*' ./Wireshark_Objects/combined_output and binwalk -e ./Wireshark_Objects/combined_output, but neither of those worked.

    I found one valid PDF out of the 1000:

    $ python3 check_pdf_validity.py | grep VALID -a2 -b2
    incorrect startxref pointer(3)
    22732-  Not a real PDF file :(
    22756-File #654:
    22767:  VALID!!!!
    22778-File #655:
    22789-  Not a real PDF file :(
    

    The tool wouldn't open normally, but I can see it has some streams in it, so I'm going to check it for useful data:

    $ sudo apt install mupdf-tools
    $ mutool clean -d ./Wireshark_Objects/report\(654\).pdf ./decompressed.pdf
    

    Unfortunately, the output didn't seem useful.

    tshark -r covert_exfiltration.pcapng -Y "http.response.code == 206 && http.content_type contains \"application/pdf\"" -T json > packet_json.json
    
    tshark -r covert_exfiltration.pcapng --export-objects http,Wireshark_Objects
    
    tshark -r covert_exfiltration.pcapng -Y "http.response.code == 206 && http.content_type contains \"application/pdf\"" -T fields -e http.file_data | tr -d '\n' > binary_data
    
    http.response.code == 206 && http.content_type contains "application/pdf"
    

    Answer: ``

  3. What is the flag in the stolen file? (30 points)

    One idea from valinkrai#1138:

    from scapy.all import *
    import re
    
    file_length=855474
    file_bytes = [0]*file_length
    file_bytes_found = [0]*file_length
    
    load_layer("http")
    #pkts = rdpcap('partial-content.pcapng')
    pkts = sniff(offline="partial-content.pcapng", session=TCPSession)  # pcap
    #print(pkts.sessions())
    load_layer("http")
    #print(len(pkts))
    
    sessions = pkts.sessions()
    for session in sessions:
        http_payload = b""
        http_header = b""
        http_header_raw = b""
        http_header_parsed = b""
    
        for packet in sessions[session]:
            try:
                payload = bytes(packet[TCP].payload)
                http_header_exists = False
                try:
                    http_header = payload[payload.index(b"HTTP/1.1"):payload.index(b"\r\n\r\n")+2]
                    if http_header:
                        http_header_exists = True
                except:
                    pass
                if not http_header_exists and http_payload:
                    http_payload += payload
                elif http_header_exists and http_payload:
                    http_payload = payload
                elif http_header_exists and not http_payload:
                    http_payload = payload
            except:
                pass
    
        http_header_raw = http_payload[:http_payload.index(b"\r\n\r\n")+2]
        http_header_parsed = dict(re.findall(r"(?P<name>.*?): (?P<value>.*?)\r\n", http_header_raw.decode("utf8")))
        if "Content-Type" in http_header_parsed.keys():
            if "application/pdf" in http_header_parsed["Content-Type"]:
                pdf_payload = http_payload[http_payload.index(b"\r\n\r\n")+4:]
    
        header_string = http_header.decode('utf-8')
        #print(header_string)
        range_regex=r'bytes\ ([0-9]+)\-([0-9]+)\/855474'
        result = re.search(range_regex, header_string)
        #print(result)
        low=int(result.group(1))
        #print(low)
        high=int(result.group(2))
    
        #print(http_payload)
        if low == 0:
    
            print(f"Has {low} - {high + 1}")
            print(f"calc len: {high-low}")
            print(f"actual len: {len(pdf_payload)}")
            print(type(pdf_payload))
            print(pdf_payload)
            print()
    
        for i in range(low, high + 1):
            file_bytes_found[i] = True
            j = i - low
            #if low==0:
    #       # print(j)
            #    print(f"i:{i} j:{j}")
            file_bytes[i] = pdf_payload[j]
    
    for file_byte_found in file_bytes_found:
        if not file_byte_found:
            print(f"Missing byte  :(")
            exit(1)
    
    print(file_bytes[0])
    print("All bytes should be here!")
    
    byte_array = bytearray(file_bytes)
    try:
        with open("bytes_array.pdf", 'wb') as f:
            f.write(byte_array)
            print(str(byte_array) + " successfully stored in a file...")
    except Exception as e:
        print(e)
    

    Another from VFMADDSUBPD#0618:

    import glob
    from itertools import count
    
    pdfs = glob.glob("*/*-HTTPBODY-001.pdf")
    
    JUNK = 17
    
    finished = bytearray(855474)
    
    for pdf in pdfs:
        with open(pdf[:-JUNK],"rb") as f:
            data = f.read()
    
        range_line = next(x for x in data.splitlines() if x.startswith(b"Content-Range"))
    
        start_bytes, _, rest = range_line[21:].partition(b"-")
        end_bytes, _, length = rest.partition(b"/")
    
        start = int(start_bytes.decode("ascii"))
        end= int(end_bytes.decode("ascii"))+1
    
        with open(pdf, "rb") as f:
            data = f.read()
    
        for i, b in zip(range(start, end), data):
            finished[i] = b
    
    with open("hope.pdf", "wb") as f:
        f.write(finished)
    

    Answer: ``

Web Application Exploitation

Never Winter Bank (Easy)

You have been hired to run a pentest on this bank's old out of date system. Can you find the vulnerability in the code and completely drain the other user's account?

Note: Your scope is limited to HTTPS. You may use automated tools that make educated guesses for this challenge, blind automated brute force is not permitted.

  1. What is the path of the leaked file? (10 points)

    We can change the account where the money is being sent to in static/app.js:

    function transfer() {
      var amount = $('#transferAmnt').val();
      superagent.post('/transfer')
        .send({
          amount,
          account : 'ForgeOfNeverWinter',
        })
        .then(function () {
          location.reload();
        }).catch(console.error)
      ;
    }
    
    function reset() {
      superagent.get('/reset').then(function() {
        location.reload();
      }).catch(console.error);
    }
    

    But that doesn't help us in this case. Using the input -100 for the amount changed the Forge amount from 500 to 110, and my account from 500 to 890, but that doesn't help answer the question.

    Looking for the robots.txt, we find the following:

    Disallow: *
    /dev/rel.js
    

    Answer: /dev/rel.js

  2. What is the flag? (90 points)

    Looking at the forbidden file, we see the following code:

    // TODO auditor says something is wrong with this code....
    if (parseInt(amount) < account.amount) {
      if ((account.amount - parseInt(amount)) < account.minimum) {
        return res.status(400).send('Error: Account is not allowed to have a balance lower than 10');
      }
      var transferAmount = parseInt(amount, 10);
      account.amount -= transferAmount;
    
    }
    

    The issue was that one instance of parseInt uses a radix and one does not, meaning we can trick JS. Sending the value "0500" twice completely drains the other account and gets us the flag!

    Answer: SKY-DJUP-3502

WebAuthn (Medium)

There is a server that is handing out flags but only to certain users, but we've seen a lot of people having gotten flags from the server so it seems like there's potentially a misconfiguration with the different authentication endpoints that people are exploiting.

You may want to open the page in a new tab as WebAuthn may be blocked when embedded.

  1. What is the username you need to get the flag? (20 points)

    From Taldan#0074 in the Discord:

    https://cdn.discordapp.com/attachments/1097323084492587170/1097325604224897154/image.png

    Answer: ``

  2. What is the flag? (80 points)

    Answer: ``

File Server v2 (Hard)

The Liber8tion hacking group is at it again and seem to have revamped their security on their file server website. Can you find the flag?

  1. What is the email address of the person who created the server? (30 points)

    Our first approach was to check /.git/head, but the server has a block that tells us "Warning: Secured Directory" when attempting to access it. Undeterred, we decided to use some tradecraft to get around this:

    bash gitdumper.sh https://000c05fed5d1685bb9afc22064383654-fileserver-v2.web.cityinthe.cloud/.git/ dest_folder
    

    And it worked! The email address was found in .git/logs/HEAD.

    Answer: tomf@cityinthe.cloud

  2. What is the flag? (70 points)

    Using the data we got in the last step, we can see what was changed from the individual challenge in the COMMIT_EDITMSG file:

    Version 2!
    
  • Removed .bak file vulnerability!

  • Moved to an in-memory storage device to be blazingly fast!

  • Changed permission structure so I can upload secure files without being logged in.

  • Limited file size for uploading

    Please enter the commit message for your changes. Lines starting

    with '#' will be ignored, and an empty message aborts the commit.

    On branch master

    Initial commit

    Changes to be committed:

    new file: httpserver.js

    
    This is one method, suggested by `tsuto#1337`:
    
    ```py
    import requests
    import time
    import threading
    
    def post_data():
    
        yield b'------WebKitFormBoundary0o869BnsDYfoxkHu\r\nContent-Disposition: form-data; name="file"; filename="flag.txt"\r\nContent-Type: text/plain"\r\n\r\n'
    
        for i in range(2):
            yield b'a'
            time.sleep(1)
    
    def do_requests(tid):
        headers = {"Content-Type":"multipart/form-data; boundary=----WebKitFormBoundary0o869BnsDYfoxkHu"}
        i = 0
        while True:
            R = requests.post("https://000c9f67fb3cb35bab3ace0364383654-fileserver-v2.web.cityinthe.cloud/upload",data=post_data(),headers=headers)
    
            print("Thread %d: %d" % (tid,i))
            i += 1
    
    for i in range(5):
        t = threading.Thread(target=do_requests,args=(i,))
        t.start()
    

    Answer: ``

Scanning & Reconnaissance

Docker (Easy)

We have found a base container used by the Liber8tion hacking group that seems to hold some stolen flags. Can you find them?

  1. What is flag 1? (20 points)

    Looking on Docker Hub, we can see Flag 1 being set initially.

    Answer: SKY-SCYD-6476

  2. What is flag 2? (20 points)

    sudo docker run -t -i --rm cityinthecloud/metadata:latest bash
    

    Running env just gave us FLAG1=SKY-SCYD-6476 again, no sign of FLAG2. Running grep -r "SKY-" / 2>/dev/null to search all files for the flag pattern gave us nothing.

    My teammate was able to find it with the following approach:

    docker pull cityinthecloud/metadata -a
    docker save cityinthecloud/metadata > metadata.tar
    

    Then just looked through the JSON files in the tarball for the flag.

    Answer: SKY-WSZZ-8369

  3. What is flag 3? (20 points)

    In the old tag, we can see flag 3.

    Answer: SKY-ZBDX-4860

  4. What is flag 4? (20 points)

    sudo docker run -t -i --rm cityinthecloud/metadata:old bash
    

    Running env just gave us FLAG3=SKY-ZBDX-4860 again, no sign of FLAG4. Running grep -r "SKY-" / 2>/dev/null to search all files for the flag pattern took FOREVER, but it worked!

    /root/.bash_history:echo "Flag 4: SKY-IYHL-4359" > /flag.txt
    

    Answer: SKY-IYHL-4359

  5. What is flag 5? (20 points)

    Answer: SKY-XBIP-9314

Call to Action (Medium)

Can you extract the flag from the redis server at redis.services.cityinthe.cloud:18321?

  1. What channel is the flag being published to? (30 points)

    Can make the initial server connection with redis-cli -h redis.services.cityinthe.cloud -p 18321. Running SELECT 0 gives the following error:

    (error) NOPERM this user has no permissions to run the 'select' command
    

    Ran into the same problem with info. Running KEYS * gives us an empty list. Running PUBSUB CHANNELS gives us stream-chat, which is the only one, so that's our answer.

    Answer: stream-chat

  2. What is the flag? (70 points)

    We can subscribe to the channel with SUBSCRIBE stream-chat:

    1) "subscribe"
    2) "stream-chat"
    3) (integer) 1
    

    But that doesn't appear to have any messages. Running PUBSUB NUMSUB stream-chat gives us a permission error, so we can't see how many subscribers there are. Running PUBSUB NUMPAT also doesn't work, so we can't see how many patterns there are.

    Turns out, I just needed to wait a bit for the message to be published! The subscribe command worked.

    Answer: SKY-RDIS-8399

Database (Hard)

Cryptography

Decoding 4 (Medium)

This file was encrypted using securepassword1! as the password, can you decrypt the file?

  1. What is the flag? (50 points)

    Firs thing I tried was this:

    openssl enc -aes-256-cbc -d -in file.enc -out flag.txt -pass pass:securepassword1!
    

    But that gave the error bad magic number. I also tried with -aes256, then wrote a script to try all the different cipher algorithms, but none of them worked. Next I tried GPG with gpg -d file.enc, but that threw a different error:

    gpg: [don't know]: invalid packet (ctb=65)
    

    We didn't get an error when doing openssl enc -d -aes-128-cfb1 -nosalt -in file.enc -out flag.txt, but the output was just gibberish. Apparently my teammate's Kali instance detected it as an OpenPGP Public Key with the file command, but mine did not.

    Answer: SKY-OPEN-1234

PGP (Medium)

We're sending you a secure communication, please decrypt the message. Please then respond to the encrypted message using the same encrypted protocol here.

  1. Free Response. (50 points)

    We can paste the PGP message into CyberChef and decrypt with the private key of the recipient, which gives this:

    Please reply in encrypted form that "the flag is SKY-CSPG-5104"
    
    As long as you include the flag in the encrypted message, it'll be accepted.
    

    So we can encrypt the message with the public key of the recipient.

    Answer:

    -----BEGIN PGP MESSAGE-----
    Version: Keybase OpenPGP v2.1.15
    Comment: https://keybase.io/crypto
    
    wYwDP788M89ptnwBA/9VRxi7H++xILXN75zD1vtX8L4zR0CzWH4e02T9+LuVnEEc
    8i1Fa3iCa/Prtn+9Um5/mk2uGfz14VYRJSvJhSw8UUBqaULH2/xeIB57q5J78OQy
    2hWXfxFaKbur9uEmiywNRMgQFuTvIZmuKAlqfrcFznwyxiGNo95L90RpHvxkUdJV
    AbibXsffoWQd8OZb4uqHbIQamNaZJpV86g4sYIvnuRp7qzQHTd2gSK96vNcra6f5
    bB7ntDLq7wDf6vGYSUIFn9UhM9VpbBxJcQW4HNRRjvowvM2S6A==
    =bqqB
    -----END PGP MESSAGE-----
    

AutoCrypt (Hard)

We've intercepted this message that was encrypted using some kind of simple autokey cipher where the ciphertext is used as the subsequent encryption key. We don't have the initial encryption key but we know that the initial encryption key is only 1 byte, so it should be easily breakable. Can you help us crack the code?

d2badfff99f594f3d3bac9e9baf1a885c491c58aa790a692a6
  1. What is the initial encryption key (in hexadecimal representation)? (20 points)

    ciphertext = "d2badfff99f594f3d3bac9e9baf1a885c491c58aa790a692a6"
    
    for key in range(256):
        plaintext = ""
        prev = key
        for i in range(0, len(ciphertext), 2):
            # convert two hex digits to a byte
            byte = int(ciphertext[i:i+2], 16)
            # xor with previous byte or key
            xored = byte ^ prev
            # add decrypted byte to plaintext
            plaintext += chr(xored)
            # update previous byte
            prev = byte
        print(f"Key: {key:02X}, Plaintext: {plaintext}")
    

    Answer: 86

  2. What is the flag? (80 points)

    Answer: SKY-AUTO-7644

Forensics

Hidden (Medium)

What information can you extract from the file inside the archive? (The questions reference the file inside the archive)

  1. What OS is this file from? (10 points)

    In the ._COPY file.

    Answer: MAC OS X

  2. What app has a size of 16732160 bytes? (25 points)

    Download the tool from https://github.com/hanwenzhu/.DS_Store-parser and use the script to parse the COPY file:

    python3 parse.py COPY
    

    Answer: Muzzle

  3. What is the scrollPositionY of the Src entry? (25 points)

    Answer: 39.0

  4. What is the icon size of Src? (40 points)

    Answer: 16.0

Enumeration & Exploitation

Vault (Medium)

We can't seem to run this on our operating system, but we should still be able to extract the information.

  1. What is the binary format? (10 points)

    Untar -> MacOs Folder -> Challenge -> File or Open in IDA

    Answer: Mach-O

  2. What is the flag? (90 points)

    Answer: ``

Log Analysis

Iptables (Medium)

One of our machines stores a log of the iptables activity. What information can you gather from it?

  1. How many packets sent were UDP packets? (10 points)

    cat iptable.log | grep PROTO=UDP | wc -l
    

    Answer: 6445

  2. How many packets sent were TCP packets? (10 points)

    cat iptable.log | grep PROTO=TCP | wc -l
    

    Answer: 6834

  3. How many unique source address were in the capture? (10 points)

    Answer: 236

  4. What address generated the most entries? (20 points)

    Answer: 169.254.169.123

  5. How many times did the above address appear in the log? (10 points)

    Answer: 5341

  6. How large was the longest length? (10 points)

    Answer: 47836

  7. What is the average amount of bytes transferred per IP (Round to the nearest whole)? (30 points)

    This one was intersting, because the log had several entries with more than one LEN that we had to find the meaning of. According to this forum post, the only one that should matter is the first, because that's the size of the whole IP packet.

    The first thing we tried was calculating the average of the LEN field, but that didn't work:

    cat iptable.log| cut -d " " -f 12 | cut -d "=" -f 2 | awk '{a+=$1} END {print "mean = " a/NR}'
    

    That looked for the overall average, not the average per IP. Once a second person read the prompt, we just replaced NR with 236 (the number of unique IPs). That gave us 20328.5, which rounds up to 20329. Unfortunatley, that was wrong as well.

    My third method was to take the average of the averages, which I did with ip_average_bytes.py. It came out to 126 after some tweaks to make sure the regex is non-greedy and gets the right occurrence of LEN. Unfortunately, that ALSO wasn't right!

    We decided to re-evaluate the last method, and used the following command to calculate the total number of bytes:

    $  cat iptable.log | awk '{print $12}' | cut -d '=' -f2 | awk '{s+=$1} END {print s}'
    4797523
    

    Then we divided that by the number of unique IPs, which gives us 20328.4872881, indicating that we rounded wrong before!

    Answer: 20328

Flight Record (Hard)

We have a flight record of an aircraft but it's in a strange log format that we can't read, can you identify it?

  1. What is the manufacturer of the aircraft? (5 points)

    Answer: DJI

  2. What is the model of the aircraft? (5 points)

    Running strings shows us ZXL-MAVIC AIR in the file, which is the model of the aircraft.

    Answer: Mavic Air

  3. What city is the aircraft recorded flying in? (10 points)

    Running strings shows us Naples and Campania in the file, which is the city and region of the aircraft.

    Answer: Naples

  4. How many seconds was the aircraft in the air? (10 points)

    Uploading the log file to PhantomHelp, our copy can be seen here: https://www.phantomhelp.com/LogViewer/8SXVS106QKMAQRLXCG9P

    TODO: Describe how to sum up the time in the air!

    Answer: 911.4

  5. What is the name of the castle that this aircraft flies over? (20 points)

    Can be seen on Google Maps here, it's on the flight path.

    Answer: Ovo Castle

  6. How many seconds into the flight does the aircraft make a U-turn at the castle? (20 points)

    (The entire range from when the aircraft enters the U-turn to completing the U-turn will be accepted)

    We just used the timestamp for right when the aircraft hits the turnaround point, which is approximately 9 minutes and 13 seconds into the flight.

    Answer: 553.4

  7. While the aircraft was flying to the aircraft's own left, how many meters in the air was the aircraft? (20 points)

    The aircraft began flying to its left at 6 minutes 36.1 seconds into the flight, and stopped at 7 minutes 38.9 seconds into the flight. I used the following Python script to calculate the average height throughout this timespan:

    import pandas as pd
    
    csv_file = "flight_record.csv"
    
    # Read the CSV file with pandas, skipping the first row
    data = pd.read_csv(csv_file, sep=',', skiprows=1)
    
    # Filter data based on specified time range
    start_time = 6 * 60 + 36.1
    end_time = 7 * 60 + 38.9
    filtered_data = data[(data['OSD.flyTime [s]'] >= start_time) & (data['OSD.flyTime [s]'] <= end_time)]
    
    # Calculate the average altitude in feet and meters
    average_altitude_ft = filtered_data["OSD.height [ft]"].mean()
    average_altitude_m = average_altitude_ft * 0.3048
    
    print(f"Average altitude between {start_time}s and {end_time}s: {average_altitude_ft} feet ({average_altitude_m} meters)")
    

    Answer: 38.55986433566434

  8. How many total meters did the aircraft fly? (10 points)

    We tried two different methods here, using both the OSD.mileage [ft] column and the DETAILS.totalDistance [ft] column in the following script:

    import pandas as pd
    
    csv_file = "flight_record.csv"
    
    # Read the CSV file with pandas, skipping the first row
    data = pd.read_csv(csv_file, sep=',', skiprows=1)
    
    total_distance_ft = data["DETAILS.totalDistance [ft]"].iloc[-1]
    
    total_distance_m = total_distance_ft * 0.3048  # Convert feet to meters
    print(f"Total distance travelled by the aircraft: {total_distance_m} meters")
    

    For the mileage we get 4316.51664 meters, and for the total distance we get 4321.302000000001 meters. We decided to go with the mileage, since it's more accurate.

    Answer: 4316.51664