#!/usr/bin/python
#
# AppleTV Snarf v.01
# Wesley McGrew
# Mississippi State University National Forensics Training Center
# wesley@mcgrewsecurity.com
# http://mcgrewsecurity.com
# http://security.cse.msstate.edu/ftc
# 
# Usage:
#
# ./atvsnarf.py [-q] <pcap file>
#
# -q : Quiet(er) mode.  Suppress output of metrics submission
#      and image requests.  Default is to include these entries
#
#
# The output is to a .csv file named "<pcap file name>.csv", as well 
# as a series of plist files with the naming format:
# 
# <pcap file name>_<num>_<type>.plist
#
# These files contain the plist responses from the iTunes servers
# the AppleTV device connects to.  The number <num> refers to the 
# packet number in the pcap file of the corresponding request from 
# the pcap file.  This number is indexed starting at 1, to allow 
# the investigator to use this number to find the request and TCP
# stream in Wireshark.
#
# The <type> refers to the type of request the server response is
# for.
#
# The "<pcap file name>.csv" file is a comma-delimited file that
# contains the following information for each request:
#
# * Packet number (for reference in Wireshark)
# * Timestamp (extracted from server response for accuracy)
# * Apple TV IP address  (\ Assuming pcap captured on local network)
# * Apple TV MAC address (/         to the Apple TV                )
# * Apple TV User Agent, including Version Number
# * Type of Request.  Supported Types:
#    Metrics Submission
#    Image
#    Viewed Grouping Page
#    Incremental Searches
#    Viewed Movie Page
#    Related Items
#    (Unrecognized requests are logged and indexed, too)
# * Notes (Search terms, movie name)
#    Also contains a reference to a plist with the server's response
#    for most requests where it's useful
# * Full URL of the request

from scapy.all import *
import time
import re
import gzip
import StringIO
import sys

index = 1

def get_response(p,packets):
   resp = ""
   for i in packets[index:]:
      try:
         if i.getlayer(IP).src != p.getlayer(IP).src and i.getlayer(IP).dst != p.getlayer(IP).src:
            continue
         if i.getlayer(IP).src != p.getlayer(IP).dst and i.getlayer(IP).dst != p.getlayer(IP).dst:
            continue
         if i.getlayer(TCP).sport != p.getlayer(TCP).sport and i.getlayer(TCP).dport != p.getlayer(TCP).sport:
            continue
         if i.getlayer(TCP).sport != p.getlayer(TCP).dport and i.getlayer(TCP).dport != p.getlayer(TCP).dport:
            continue
         data = str(i.getlayer(TCP).payload)
         if re.search("User-Agent: AppleTV",data):
            break; 
      except:
         continue 
      resp += str(i.getlayer(TCP).payload)
   return resp

def extract_gzip(str):
   m = re.match(r".*Content-Length: (.+?)\r\n",str,re.DOTALL)
   length = int(m.group(1))
   compressed = str[len(str)-length:]
   sio = StringIO.StringIO(compressed)
   gz = gzip.GzipFile(fileobj=sio)
   uncompressed = gz.read()
   return uncompressed

def get_type(url):
   if re.search("metrics.apple.com",url):
      type = "Submitted Metrics"
   elif re.search(".jpg",url):
      type = "Image"
   elif re.search("viewGrouping",url):
      type = "Viewed Grouping Page"
   elif re.search("incrementalSearch",url):
      type = "Incremental Search"
   elif re.search("viewMovie",url):
      type = "Viewed Movie Page"
   elif re.search("relatedItems",url):
      type = "Related Items"
   else:
      type = "Unknown"
   return type

def usage():
   print "Usage:"
   print sys.argv[0] + " [-q] <pcap file>"
   print ""
   print "-q : Quiet mode - Suppress metrics and image requests"
   print ""
   print "See the comments in the header of the code for more"
   print "documentation"

if (len(sys.argv) != 2) and (len(sys.argv) != 3):
   print "Wrong # of command line arguments"
   usage()
   sys.exit()

quiet = False
if (sys.argv[1] == "-q"):
   quiet = True
   if len(sys.argv) != 3:
      print "Missing pcap filename"
      usage()
      sys.exit()
   filename = sys.argv[2]
else:
   filename = sys.argv[1]

csvfile = open(filename+".csv",'w')
pkt = rdpcap(filename)
for p in pkt:
   try:
      data = str(p.getlayer(TCP).payload)
   except:
      index += 1
      continue
   if re.search("User-Agent: AppleTV",data):
      response = get_response(p,pkt)
      m = re.match(r".*?Date: .+?, (.+?)\r.*",response,re.DOTALL)
      atv_time = m.group(1)
      atv_ip   = p.getlayer(IP).src
      atv_mac  = p.src
      m = re.match(r".*User-Agent: (.+?)\r.*",data,re.DOTALL)
      atv_ua  = m.group(1)
      m = re.match(r"GET (.+) HTTP/1.1",data)
      atv_req = m.group(1)
      m = re.match(r".*Host: (.+)\r\n\r\n",data,re.DOTALL)
      atv_req_host = m.group(1)
      atv_url = "http://" + atv_req_host + atv_req
      atv_type = get_type(atv_url) 
      if atv_type == "Incremental Search":
         m = re.match(r".*q=(.+)",atv_url)
         atv_notes = "Query: " + m.group(1)
         data = extract_gzip(response)
         fp = open(filename+'_'+str(index)+'_incrementalSearch.plist','w')
         fp.write(data)
         fp.close()
         atv_notes += " XML: "+str(index)+"_incrementalSearch.plist"
      elif atv_type == "Viewed Movie Page":
         data = extract_gzip(response)
         fp = open(filename+'_'+str(index)+'_viewedMovie.plist','w')
         fp.write(data)
         fp.close()
         m = re.match(r".*?<key>title</key>.*?<string>(.*?)</string>",data,re.DOTALL)
         title = m.group(1)
         atv_notes = "Movie Title: "+title+" ; XML: "+str(index)+"_viewedMovie.plist"
      elif atv_type == "Viewed Grouping Page":
         data = extract_gzip(response)
         fp = open(filename+'_'+str(index)+'_viewGrouping.plist','w')
         fp.write(data)
         fp.close()
         atv_notes = "XML: "+str(index)+"_viewedGrouping.plist"
      elif atv_type == "Related Items":
         data = extract_gzip(response)
         fp = open(filename+'_'+str(index)+'_relatedItems.plist','w')
         fp.write(data)
         fp.close()
         atv_notes = "XML: "+str(index)+"_relatedItems.plist"
      else:
         atv_notes = ''
      csvfile.write(str(index)+","+atv_time+","+atv_ip+","+atv_mac+","+atv_ua+","+atv_type+","+atv_notes+","+atv_url+"\n")
   index += 1

csvfile.close()

