NPST 12. Pakkestorm: IPv4 evil bit
Task description
Jeg har vært på et temmelig hemmelig oppdrag og fulgt med på en server som har hatt mistenkelig oppførsel tidligere. Nå tok vi den igjen når den begynte å sende masse pakker, men selv om jeg som alle andre alver liker pakker så ble det litt for mye av det gode. Kan du finne de onde for meg?
- Tastefinger
Notes
A bunch of packets sent from 133.7.13.37 to 11,000 different IPs. Fragmented IP protocol (TCP 6). Every TCP conversation contains a flag. Every packet contains some binary data that corresponds to a letter in a fake (or real) flag.
# tshark -r fangede_pakker.pcap -Y "ip.addr==133.7.13.37 && ip.addr==59.224.146.99" -T fields -e data > single-conversation-hex.txt
# for ip in $(cat ips.txt); do tshark -r fangede_pakker.pcap -Y "ip.dst == $ip" -T fields -e data > "./conversations/ip-$ip.txt"; done
tshark -r fangede_pakker.pcap -T fields -e ip.dst -e data > unsorted_conversations.txtWith the script below, we get the flags from all conversations. We need to find the most evil one. It will output all flags and the ip.dst to ./flags.txt, and print:
the most common letters per index:
PST{ztuynnnnzzzzzzzzzz}}}}}}}}}}}}}}}}}}}}}!}the least common letters per index:
PST{HECAAAAAAAAAAAAAA?!!!!!!!!!!!!!!!!!!!!N!}the most common flag lengths:
{23: 2, 24: 40, 25: 112, 26: 328, 27: 596, 28: 895, 29: 1157, 30: 1235, 31: 1317, 32: 1254, 33: 1108, 34: 832, 35: 698, 36: 538, 37: 371, 38: 208, 39: 138,40: 82, 41: 39, 42: 31, 43: 15, 45: 4}
data_lines = open("./unsorted_conversations.txt", "r").read().split("\n")
data_dict = {}
for l in data_lines[:-1]:
[ip, data] = l.split("\t")
if ip not in data_dict:
data_dict[ip] = ""
a = bytes.fromhex(data)
b = int(a[2:], 2)
c = chr(b)
data_dict[ip] += c
# sorted_data_dict = dict(sorted(data_dict.items()))
count_length = {}
count_letters = [{} for _ in range(46)]
with open("flags.txt", "w") as fd:
for ip in data_dict:
fd.write(f"{ip}: {data_dict[ip]}\n")
if len(data_dict[ip]) not in count_length:
count_length[len(data_dict[ip])] = 1
else:
count_length[len(data_dict[ip])] += 1
for i in range(len(data_dict[ip])):
c = data_dict[ip][i]
if c not in count_letters[i]:
count_letters[i][c] = 1
else:
count_letters[i][c] += 1
for x in count_letters:
sorted_list = sorted(x.items())
try:
most_frequent = sorted_list[-1][0]
least_frequent = sorted_list[0][0]
print(most_frequent, least_frequent)
except:
pass
print(dict(sorted(count_length.items())))- “temmelig hemmelig” = 05. Muldvarpjakt
- “mistenkelig oppførsel tidligere”:
- not https://spst.no from 08. Ransomware
- not https://p26e.dev or https://dass.p26e.dev either
- “litt for mye av det gode. Kan du finne de onde for meg?”
- not this one:
grep "evil" flags.txt # 225.120.49.216: PST{WE_CAN_HAZ_evil_owneR!} grep -e "[Ee][Vv][Ii][Ll]" flags.txt # 10.246.184.241: PST{It_Can_HAz_eViL_haLL!} # 27.135.188.185: PST{iT_CAn_HAz_EVIl_deClINe?} # 58.91.252.96: PST{thEy_cAn_HAZ_evIL_AvErAGE!} # 191.239.148.31: PST{thEy_CAn_HAZ_EVIl_TrAFfIc?} # 228.160.2.255: PST{He_CAN_HAZ_Evil_aRt?} # 155.5.122.237: PST{it_cAn_haZ_eVIL_MAStEr!} # 141.199.189.149: PST{I_cAN_HAZ_evIl_DiZtAnce!} # 225.120.49.216: PST{WE_CAN_HAZ_evil_owneR!} # 80.105.1.22: PST{I_Can_HaZ_eVIl_pOuNd?} # 240.35.94.156: PST{I_CAN_HAZ_EViL_EXpREszIoN!}
- not this one:
137.204.107.8 stands out as the IP only sends/receives 19 packets, next fewest is 21. When searching for it in wireshark/tshark, nothing shows up.
Each packet has a field ip.flags.df that seems to be fluctuating. DF means “Don’t fragment”. It almost looks like morse or binary data. This does not yield anything useful though.
Solution
From https://en.wikipedia.org/wiki/Evil_bit
The evil bit is a fictional IPv4 packet header field proposed in a humorous April Fools’ Day RFC from 2003, authored by Steve Bellovin. The Request for Comments recommended that the last remaining unused bit, the “Reserved Bit” in the IPv4 packet header, be used to indicate whether a packet had been sent with malicious intent, thus making computer security engineering an easy problem - simply ignore any messages with the evil bit set and trust the rest.
So, by running this tshark query, we can get all the packets with the “evil” bit set to 1 instead of 0:
tshark -r fangede_pakker.pcap -Y "ip.flags.rb == 1" -T fields -e ip.dst -e ip.flags -e data.text
# 87.194.50.11 0x05 0b1010000
# 87.194.50.11 0x07 0b1010011
# 87.194.50.11 0x07 0b1010100
# 87.194.50.11 0x07 0b1111011
# 87.194.50.11 0x07 0b1001001
# 87.194.50.11 0x05 0b1011111
# 87.194.50.11 0x07 0b1100011
# 87.194.50.11 0x05 0b1000001
# 87.194.50.11 0x05 0b1101110
# ...It seems that all the evil packets come from a single IP. If we only fetch the data.text it should be easy to decode each byte of data as a letter:
tshark -r fangede_pakker.pcap -Y "ip.flags.rb == 1" -T fields -e data.text > evil-bits.txt
# 0b1010000
# 0b1010011
# 0b1010100
# 0b1111011
# 0b1001001
# 0b1011111
# 0b1100011
# 0b1000001
# 0b1101110
# 0b1011111
# 0b1001000
# 0b1100001
# 0b1011010
# 0b1011111
# 0b1110010
# 0b1000101
# 0b1100011
# 0b1101001
# 0b1110000
# 0b1110010
# 0b1001111
# 0b1000011
# 0b1100001
# 0b1010100
# 0b1100101
# 0b1000100
# 0b1011111
# 0b1110100
# 0b1010010
# 0b1110101
# 0b1111010
# 0b1010100
# 0b111111
# 0b1111101lines = open("evil-bits.txt", "r").read().split("\n")
evil = "".join([
chr(int(l[2:], 2))
for l in lines[:-1]
])
print(evil)
# Prints: PST{I_cAn_HaZ_rEciprOCaTeD_tRuzT?}Flag: PST{I_cAn_HaZ_rEciprOCaTeD_tRuzT?}