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

fangede_pakker.pcap

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.txt

With 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”:
  • “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!}

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
# 0b1111101
lines = 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?}