= Damned Traffic = For this challenge, we were given an archive of a text file similar to a tcpdump output. No one has solved this challenge in 12 hours, so they updated the archive with a new one - would be "easier" they said - but it didn't help and in the end no one has solved this challenge. Here are some notes of our work on this challenge. == Incorrect output == The given tcpdump output is incorrect and seems to have been modified by hand: * missing \n on line 287 * hexdump only for some packets (why so?) * modified IP addresses (why 100.0.192.168 in tcpdump?) * sometimes numbers at the beginning of lines, for instance lines 293 and 294 == Create packet capture file == The first thing we did is to ignore the tcpdump text lines and reconstruct all packets from their hexadecimal dump. You can do that with some shell scripting, text2pcap & mergecap, or you can use the best packet manipulation tool ever: Scapy, in python. reconstruct.py opens the tcpdump file ("at") and creates a standard packet capture file "at.pcap", which we can now open in Wireshark. #!/usr/bin/env python from scapy.all import * packets, packet = [], '' match = '\t0x' for line in open('at','r'): if line[:len(match)]==match: offset = int(line.split(':')[0].strip(),16) if offset==0 and len(packet)>0: packets.append(IP(packet)) packet = '' hex = line.split(':')[1].strip().replace(' ','') packet += "".join([chr(int(hex[i:i+2],16)) for i in range(0,len(hex),2)]) if len(packet)>0: packets.append(IP(packet)) wrpcap('at.pcap', packets) print "Extracted %i packets" % len(packets) == Strange packet capture == In Wireshark, we see [image:at.pcap.png]: * source 192.168.0.100 all the time * destination 10.11.0.20 all the time except twice where it's 77.88.21.3 (www.yandex.ru) * an ICMP ping request (no reply) * some UDPs with TTL incrementing (a UDP traceroute?) * one UDP containing JPEG, that we extract to see "no secret allow" [image:1allow.jpeg] * one UDP to destination 77.88.21.3 containing JPEG, that we extract to see "no secret deny" [image:1allow.jpeg] * another UDP to destination 77.88.21.3, with repeated \x80\c6\31 plus \x81\xff\d9 in the end (\xff\xd9 is jpeg end marker, related?) * many IP fragments containing UDP (according to the first packets), but the fragments are incorrect (duplicate offset and offsets are not consecutive to length by a few bytes) == Strange ICMP "admin prohibited filter" == Among the ICMP "need to frag", we noticed an ICMP "admin prohibited filter" after the last packet to 77.88.21.3. But we didn't find anything interesting with this information. == Focus on the fragments == After extracting the two pictures, visiting www.yandex.ru and found nothing, we decided to work on the IP fragments. First, let's create a pcap with only the fragments, again with Scapy. #!/usr/bin/env python from scapy.all import * pcap=rdpcap('at.pcap') pcap2 = [] for i in range(13,len(pcap)): pcap2.append(pcap[i]) wrpcap('at2.pcap',pcap2) Now, let's view how the fragments get together with their offset & offset+length: #!/usr/bin/env python from scapy.all import * pcap2=rdpcap('at2.pcap') d = [] for i in range(len(pcap2)): p = pcap2[i] offset = p.frag*8 end = len(p.payload)+offset d.append((offset,end,i)) d.sort() for offset,end,packetno in d: print "Packet %i: %i-%i" % (packetno,offset,end) The output is interesting, we see which packets are contributing to the fragmentation: Packet 0: 0-248 Packet 1: 0-468 Packet 3: 240-524 Packet 2: 464-972 Packet 5: 520-680 Packet 8: 680-848 Packet 10: 848-1056 Packet 4: 968-1424 [...] Despite the fact that packet 0 is a duplicate for offset 0 (as well other packets after), it seems the packets are almost together, but in two streams. Stream 1 could be: Packet 0: 0-248 Packet 1: 0-468 Packet 3: 240-524 Packet 5: 520-680 Packet 8: 680-848 Packet 10: 848-1056 [...] While Stream 2 could be: Packet 1: 0-468 Packet 2: 464-972 Packet 4: 968-1424 [...] == Reassemble fragments into possible streams == Let's use Scapy again to reassemble this mess. We're using recursion to reassemble the fragments in a pcap, and everytime we have a conflict (two packets for the same offset), we create another "branch". #!/usr/bin/env python # Tries to reassemble fragments in one pcap according to the fragments offset. # Whenever a choice is possible (two packets have same or near offset), # it creates another "branch" not to eliminate this possible case. from scapy.all import * pcap2 = rdpcap('at2.pcap') def find_packet(offset, threshold=10): # can accept different offset at max negative threshold found = [] for i in range(len(pcap2)): o = pcap2[i].frag*8 if o == offset or (o < offset and o+threshold > offset): found.append(i) return found if len(found)>0 else False def rebuild(packets, next_packet, id=''): global files p = pcap2[next_packet] offset = p.frag*8 end = len(p.payload)+offset print "[%s] Packet #%i: %i-%i" % (id, next_packet, offset, end) packets.append(p) next_packets = find_packet(end) if next_packets is False: # end of recursion files.append('rebuild_'+id+'.pcap') wrpcap('rebuild_'+id+'.pcap',packets) elif len(next_packets)==1: # only one possibility rebuild(packets[:], next_packets[0], id) else: # choice: create a "branch" for next_packet in next_packets: # don't forget to give a *copy* (with [:]) of packets list rebuild(packets[:], next_packet, id+'-'+str(next_packet)) files = [] rebuild([], 0, '0') rebuild([], 1, '1') # we have 8 possibilities (packets no responsible for branching are in filename) # files = ['rebuild_0-53-65.pcap', 'rebuild_0-53-154.pcap', 'rebuild_0-129-65.pcap', # 'rebuild_0-129-154.pcap', 'rebuild_1-53-65.pcap', 'rebuild_1-53-154.pcap', # 'rebuild_1-129-65.pcap', 'rebuild_1-129-154.pcap'] There we are: 2 streams with each 4 possibilities. == Playing with reconstructed streams == We tried several things, with no luck. Here we try to concatenate the payloads of these packets and view it. It ignores the offset. def concat_payloads(pcap_file, out_file): pcap = rdpcap(pcap_file) f = open(out_file,'w') for p in pcap: f.write(str(p.payload)) f.close() Here we reassemble the payload by taking offset into account. def reassemble_payload(pcap_file, out_file): pcap = rdpcap(pcap_file) result = '' for i in range(len(pcap)): data = str(pcap[i].payload) offset = pcap[i].frag*8 if offset>len(result): # never matches with our packets, but still here in case print "inserting %i padding at position %i" % (offset-len(result), offset) result += '\x00'*(offset-len(result))+data else: result = result[:offset] + data + result[offset+len(data):] open(out_file,'w').write(result) Then we said, hey, maybe the secret information is hidden in the bytes involved in the wrong offset! So we made something to extract them. def extract_overlapping_bytes(pcap_file, out_file): pcap = rdpcap(pcap_file) previous_packet = '' length = 0 result = '' for i in range(len(pcap)): p = pcap[i] offset = pcap[i].frag*8 if offset < length: # 240 248 overlap = str(previous_packet.payload)[offset-length:] print "Extracting %i overlap bits: %s" % (length-offset, repr(overlap)) result += overlap length = offset+len(p.payload) previous_packet = p open(out_file,'w').write(result) Or, we could simply ignore these bytes and remove them. def remove_overlaps(pcap_file, out_file): pcap = rdpcap(pcap_file) out = [] for i in range(len(pcap)-1): p = pcap[i] offset = p.frag*8 # 0 length = len(p.payload) # 248 next_offset = pcap[i+1].frag*8 # 240 remove = offset+length-next_offset if remove > 0: p = IP(str(p)[:-remove]) print "Packet %i: removing last %i bytes" % (i, remove) else: print "Packet %i: not removing anything" % (i) out.append(p) if len(pcap)>0: out.append(pcap[len(pcap)-1]) wrpcap(out_file, out) More than removing them, also fix the UDP.length field and clear the MF (More Fragments) of the last packet, to see if we have a correct IP fragmentation. def fix_udp_length_and_MF(pcap_file, out_file): pcap = rdpcap(pcap_file) total_length = 0 for p in pcap: total_length += len(p.payload) print "%s: total length = %i" % (pcap_file, total_length) pcap[0].payload.len=total_length pcap[0].payload.chksum=None pcap[0] = IP(str(pcap[0])) pcap[len(pcap)-1].flags=0x2 pcap[len(pcap)-1].chksum=None pcap[len(pcap)-1] = IP(str(pcap[len(pcap)-1])) wrpcap(out_file, pcap) The code responsible for using all these functions: for fe in files: f = fe[:-5] # remove .pcap extension concat_payloads(fe, f+'.concat') reassemble_payload(fe, f+'.reassembled') extract_overlapping_bytes(fe, f+'.overlaps') remove_overlaps(fe, f+'_nolap.pcap') fix_udp_length_and_MF(f+'_nolap.pcap', f+'_nolap_udp.pcap') == Conclusion == Sadly, even if we had fun with Scapy, we did not found anything interesting no matter what we tried. We are very sad that the CTF staff was not replying to our questions neither giving hints knowing that no one achieved to solve this challenge. We think it would have been great for them to probe the teams to know if they were advancing on this challenge, and if no team was advancing, to give some hints. We are now looking forward the solution of this challenge if the CIT CTF has the time to give one. To know what we missed!