Name: Michael Nijs Answer 1: 00:25:00:fe:07:c4 Answer 2: User-Agent: AppleTV/2.4 Answer 3a: h Answer 3b: ha Answer 3c: hac Answer 3d: hack Answer 4: Hackers by Iain Softley Answer 5: http://a227.v.phobos.apple.com/us/r1000/008/Video/62/bd/1b/mzm.plqacyqb..640x278.h264lc.d2.p.m4v Answer 6: Sneakers by Phil Alden Robinson Answer 7: $9.99 Answer 8: iknowyourewatchingme Description: The first assignment was to figure out what requests have been made by the AppleTv of Ann. For that I used a self-modified Python program. This basic program (which was found on the net) is able to read PCAP files. I added code that will allow a user to display GET & POST requests. This requests can be broken down to parameter level. The code can be found in the additional text field (md5sum: 2ea74acd07e05ee950cd347f1dc21069). ~/forensics$ python read_pcap.py This program analyses HTTP requests found in a PCAP. The first argument is mandatory and this should be the pcap file. python read_pcap.py [options] The optional arguments of the program: - help show this screen - all show the unfiltered request - all-struct show all parameters in a structured human readable fashion - url show the webpage - show the parameter, this could be any parameter that is specified in the URLs. For example for google: q - supress supress information messages By using the program, it was easy to view which requests were made and when. For example an overview of the queries performed by Ann. ~/forensics$ python read_pcap.py evidence03.pcap GET q supress Struct FileHeader: ------------------ 0000 - magic: 2712847316 (0xA1B2C3D4) 0004 - version_major: 0x2 0006 - version_minor: 0x4 0008 - thiszone(gmt to local correction): 0 (0x0) 000C - sigfigs(accuracy of timestamps): 0 (0x0) 0010 - snaplen(max length saved portion of each pkt): 65535 (0xFFFF) 0014 - linktype(data link type (LINKTYPE_*)): 1 (0x1) Starting to analyse the packages Argument: q ==> h. Argument: q ==> ha. Argument: q ==> hac. Argument: q ==> hack. Argument: q ==> s. Argument: q ==> sn. Argument: q ==> sne. Argument: q ==> sneb. Argument: q ==> snea. Argument: q ==> sneak. Argument: q ==> i. Argument: q ==> ik. Argument: q ==> ikn. Argument: q ==> ikno. Argument: q ==> iknow. Argument: q ==> iknowy. Argument: q ==> iknowyo. Argument: q ==> iknowyou. Argument: q ==> iknowyour. Argument: q ==> iknowyoure. Argument: q ==> iknowyourew. Argument: q ==> iknowyourewa. Argument: q ==> iknowyourewat. Argument: q ==> iknowyourewatc. Argument: q ==> iknowyourewatch. Argument: q ==> iknowyourewatchi. Argument: q ==> iknowyourewatchin. Argument: q ==> iknowyourewatching. Argument: q ==> iknowyourewatchingm. Argument: q ==> iknowyourewatchingme. This enabled me to find the location of the important URLs, prices and more. After using the tool wireshark was fired up to review the HTTP 200 ok received after the GET requests. Since these responses are XML formatted packages, it was easy to use the find function to retrieve the variables. Additional Text: # ****************************************************** # Michael Cohen # # ****************************************************** # Version: FLAG $Version: 0.86RC1 Date: Thu Jan 31 01:21:19 EST 2008$ # ****************************************************** # # * This program is free software; you can redistribute it and/or # * modify it under the terms of the GNU General Public License # * as published by the Free Software Foundation; either version 2 # * of the License, or (at your option) any later version. # * # * This program is distributed in the hope that it will be useful, # * but WITHOUT ANY WARRANTY; without even the implied warranty of # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # * GNU General Public License for more details. # * # * You should have received a copy of the GNU General Public License # * along with this program; if not, write to the Free Software # * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ****************************************************** """ A library for reading PCAP files. PCAP format is really simple so this is a nobrainer. Good example of using the file format library though. This shows how we can handle the endianess issue simply. This has been modified by Michael Nijs. Usage: python read_pcap.py [options] The optional arguments of the program: - help show this screen - all show the unfiltered request - all-struct show all parameters in a structured human readable fashion - url show the webpage - show the parameter, this could be any parameter that is specified in the URLs. For example for google: q - supress supress information messages """ import struct,time,cStringIO import sys ## This is the default size that will be read when not specified DEFAULT_SIZE=600*1024*1024 class Buffer: """ This class looks very much like a string, but in fact uses a file object. The advantage here is that when we do string slicing, we are not duplicating strings all over the place (better performace). Also it is always possible to tell where a particular piece of data came from. """ def __init__(self,fd,offset=0,size=None): """ We can either specify a string, or a fd as the first arg """ self.offset=offset self.fd=fd if size!=None: self.size=size else: self.fd=fd if size!=None: self.size=size else: ## Try to calculate the size by seeking the fd to the end offset = fd.tell() try: fd.seek(0,2) except Exception,e: print "%s: %r" % (e,fd) self.size=fd.tell() fd.seek(offset) # if self.size<0: # raise IOError("Unable to set negative size (%s) for buffer (offset was %s)" % (self.size,self.offset)) def clone(self): return self.__class__(fd=self.fd, offset=self.offset, size=self.size) def __len__(self): return self.size def set_offset(self,offset): """ This sets the absolute offset. It is useful in files which specify an absolute offset into the file within some of the data structures. """ return self.__class__(fd=self.fd,offset=offset) def __getitem__(self,offset): """ Return a single char from the string """ self.fd.seek(offset+self.offset) return self.fd.read(1) ## FIXME: Python slicing will only pass uint_32 using the syntax def __getslice__(self,a=0,b=None): """ Returns another Buffer object which may be manipulated completely independently from this one. Note that the new buffer object references the same fd that we are based on. """ if b: if b>self.size: b=self.size return self.__class__(fd=self.fd,offset=self.offset+a,size=b-a) else: return self.__class__(fd=self.fd,offset=self.offset+a) def __str__(self): self.fd.seek(self.offset) if self.size>=0: data=self.fd.read(self.size) else: data=self.fd.read(DEFAULT_SIZE) # if len(data) < self.size: # raise IOError("Unable to read %s bytes from %s" %(self.size,self.offset)) return data def __nonzero__(self): return 1 def search(self, bytes): """ Searches the buffer for the occurance of bytes. """ data = self.__str__() return data.find(bytes) #### Start of data definitions: class DataType: """ Base class that reads a data type from the file.""" ## Controls if this is visible in the GUI visible = False ## This is the SQL type which is most appropriate for storing the ## results of value() sql_type = "text" data='' def __init__(self,buffer,*args,**kwargs): """ This will force this class to read the data type from data at the specified offset """ if isinstance(buffer,str): fd = cStringIO.StringIO(buffer) self.buffer=Buffer(fd) else: self.buffer=buffer self.parameters = kwargs if self.buffer: self.data=self.read() else: self.buffer=Buffer(cStringIO.StringIO('')) def size(self): """ This is the size of this data type - it returns the number of bytes we consume. """ return 0 def __str__(self): return "%s" % (self.data,) def read(self): """ Abstract method that returns the data type required to be stored internally """ return None def write(self,out): pass def __ne__(self,target): return not self.__eq__(target) def get_value(self): """ In the general case we return ourself as the opaque data type """ return self def set_value(self, data): self.data=data def form(self,prefix, query,result): pass def display(self, result): result.text(self.__str__(), wrap='full', sanitise='full', font='typewriter') def value(self): return self.__str__() class RAW(DataType): """ This data type is simply a data buffer. """ def __init__(self,buffer,*args,**kwargs): DataType.__init__(self,buffer,*args,**kwargs) self.buffer = buffer self.data = buffer.__str__() def size(self): return len(self.data) def get_value(self): return self.buffer.clone() def __repr__(self): if not self.data: self.read() result = ''.join([self.data[a].__str__() for a in range(len(self.data))]) return result def __str__(self): tmp = [] for i in range(len(self.data)): char = "%s" % self.data[i] if char.isalnum() or char in '!@#$%^&*()_+-=[]\\{}|;\':",./<>?': tmp.append(char) else: tmp.append('.') return ''.join(tmp) class BasicType(DataType): """ Base class for basic types that unpack understands """ sql_type = "int" def __init__(self,buffer,*args,**kwargs): try: if kwargs['endianess'].startswith('l'): direction = "<" elif kwargs['endianess'].startswith('b'): direction = ">" ## Enforce the endianess if self.fmt[0] in '<>=@': self.fmt = direction+self.fmt[1:] else: self.fmt = direction+self.fmt except KeyError: pass try: self.data = kwargs['value'] except KeyError: pass DataType.__init__(self,buffer,*args,**kwargs) def size(self): return struct.calcsize(self.fmt) def read(self): try: length = struct.calcsize(self.fmt) if length>0: return struct.unpack(self.fmt,self.buffer[:length].__str__())[0] return '' except struct.error,e: raise IOError("%s. Tried to use format string %s"% (e, self.fmt)) def write(self, output): try: # print "%r" % self,self.data, self.fmt data = struct.pack(self.fmt, self.data) output.write(data) except struct.error,e: raise IOError("%s" % e) def __int__(self): return int(self.data) def get_value(self): return self.data def set_value(self,v): self.data=v def __eq__(self,target): if isinstance(target,int): return self.data==target try: return self.data==target except: return False def form(self,prefix, query,result): result.row("Size", self.size()) class WORD(BasicType): """ Reads a word (short int) from the data in big endian """ fmt = '=H' visible = True sql_type = "int" def __str__(self): return "0x%X" % (self.data,) class USHORT(WORD): pass class LONG(BasicType): fmt='=l' visible = True class ULONG(BasicType): fmt='=L' visible = True def __str__(self): return "%s (0x%X)" % (self.data,self.data) class ULONG_CONSTANT(ULONG): """ This class enforces a condition raising an error otherwise """ def read(self): result = ULONG.read(self) if not result==self.parameters['expected']: raise RuntimeError("Expected value 0x%X, got 0x%X" %( self.parameters['expected'], result)) return result class USHORT_CONSTANT(USHORT): """ This class enforces a condition raising an error otherwise """ def read(self): result = USHORT.read(self) if not result==self.parameters['expected']: raise RuntimeError("Expected value 0x%X, got 0x%X" %( self.parameters['expected'], result)) return result class LEWORD(WORD): fmt = "= self.buffer.size: break return result def write(self,output): for item in self.fields: self.data[item[0]].write(output) def calculate_struct_size(self,struct): """ calculates the total size of struct by summing its individual sizes. struct is a dict of classes. """ size=0 for i in struct.values(): size+=i.size() return size def size(self): return self.calculate_struct_size(self.data) def __str__(self): """ Prints the array nicely """ result='Struct %s:\n' % ("%s" %self.__class__).split('.')[-1] result+='-'*(len(result)-1)+"\n" for i in range(len(self.fields)): item=self.fields[i] try: desc = "%s(%s)" % (item[0],item[3]) except: desc = item[0] try: element=self.data[item[0]] except KeyError: continue tmp = "\n ".join((element.__str__()).splitlines()) result+="%04X - %s: %s\n" % ( element.buffer.offset, desc,tmp) return result def __getitem__(self,attr): return self.data[attr] def __setitem__(self,k,attr): ## print "Setting %s to %s " % (k,attr) self.data[k]=attr def form(self,prefix, query,result): result.row("Size", self.calculate_struct_size(self.data)) class POINTER(LONG): """ This represents a pointer to a struct within the file """ visible = True def __init__(self,buffer,*args,**kwargs): LONG.__init__(self,buffer,*args,**kwargs) try: self.relative_offset = kwargs['relative_offset'] except: self.relative_offset = 0 def calc_offset(self): """ return a buffer object seeked to the correct offset """ offset=self.relative_offset+self.data return self.buffer.set_offset(offset) def get_value(self): data = self.calc_offset() if data==None: return None return self.target_class(data) def __str__(self): result="->%s (0x%08X)" % (self.data,self.data) return result class StructArray(SimpleStruct): def __init__(self,buffer,*args,**kwargs): try: self.count=int(kwargs['count']) # print self.count except: self.count=0 self.fields = [ [i,self.target_class, kwargs] for i in range(self.count)] # if buffer: # print "offset %X %s" % (buffer.offset, buffer.size) SimpleStruct.__init__(self,buffer,*args,**kwargs) def __str__(self): result = "Array %s:" % ("%s" %self.__class__).split('.')[-1] for i in range(self.count): result+="\nMember %s of %s:\n" % (i,self.count) result+="\n ".join(self.data[i].__str__().splitlines()) return result def extend(self,target): self.data[self.count]=target self.count+=1 def __eq__(self,target): for x in range(self.count): try: if not self.data[x]==target[x]: return False except: return False return True def __iter__(self): self.index=0 return self def next(self): try: result=self.data[self.index] except (KeyError, IndexError): raise StopIteration() self.index+=1 return result def get_value(self): return [ self.data[x].get_value() for x in range(self.count) ] class ARRAY(StructArray): def __str__(self): result = ','.join([self.data[a].__str__() for a in range(self.count) if self.data.has_key(a)]) return result class BYTE_ARRAY(ARRAY): target_class=BYTE class UBYTE_ARRAY(ARRAY): target_class=UBYTE class WORD_ARRAY(ARRAY): target_class=WORD class LONG_ARRAY(ARRAY): target_class = LONG class ULONG_ARRAY(ARRAY): target_class = ULONG class STRING(BYTE): visible = True def __init__(self,buffer,*args,**kwargs): try: self.data = kwargs['value'] self.length = len(self.data) self.fmt = "%us" % self.length except KeyError: try: self.length = kwargs['length'].__int__() self.fmt = "%us" % self.length except: raise SystemError("you must specify the length of a STRING") BYTE.__init__(self,buffer,*args,**kwargs) def __str__(self): return "%s" % self.data def substr(self,start,end): """ Truncates the string at a certain point """ self.data=self.data[start:end] def set_value(self, value): self.data = value ## Update our format string to use the length: self.length = len(value) self.fmt = "%ss" % self.length def __len__(self): return self.length def size(self): return self.length def form(self,prefix, query,result): ## print "\nString Form\n" result.textfield("String length","%slength" % prefix) def display(self, result): result.text(self.__str__(), sanitise='full', font='typewriter') class TERMINATED_STRING(DataType): """ This data structure represents a string which is terminated by a terminator. For efficiency we read large blocks and use string finds to locate the terminator """ visible = True terminator='\x00' max_blocksize=1024*1024 initial_blocksize=1024 ## Do we include the terminator? inclusive = True def read(self): blocksize=self.initial_blocksize tmp='' end=-1 while end<0: tmp=self.buffer[0:blocksize].__str__() end=tmp.find(self.terminator) if end>=0: break blocksize*=2 if blocksize>self.max_blocksize: end=self.max_blocksize break ## The size of this string includes the terminator self.raw_size=end+len(self.terminator) return self.buffer[0:self.raw_size].__str__() def size(self): return self.raw_size def get_value(self): if self.inclusive: return self.data else: return self.data[:-len(self.terminator)] def __eq__(self,target): return self.data==target def __getitem__(self,x): return self.data[x] class BYTE_ENUM(UBYTE): types={} def __str__(self): try: return "%s" % (self.types[self.data]) except KeyError: return "Unknown (0x%02X)" % self.data def __eq__(self,target): try: return target==self.types[self.data] except KeyError: return target==self.data def get_value(self): try: return self.types[self.data] except (KeyError,IndexError): return "Unknown (%s)" % self.data class LONG_ENUM(BYTE_ENUM): fmt='=l' class WORD_ENUM(BYTE_ENUM): fmt='=H' class BitField(BYTE): ## This stores the masks masks = {} def __str__(self): result=[ v for k,v in self.masks.items() if k & self.data ] return ','.join(result) class UCS16_STR(STRING): visible = True encoding = "utf_16_le" def read(self): result=STRING.read(self) ## This is the common encoding for windows system: try: return result.decode(self.encoding) except UnicodeDecodeError: if result=='\0': return '' else: return "%r" % result def __str__(self): ## Return up to the first null termination try: result = self.data.__str__() try: return result[:result.index("\0")] except ValueError: return result except UnicodeEncodeError: return "%r" % self.data class CLSID(ULONG_ARRAY): """ A class id - common in windows """ visible = True def __init__(self,buffer,*args,**kwargs): ## Class IDs are 4 uint_32 long kwargs['count']=4 ULONG_ARRAY.__init__(self,buffer,*args,**kwargs) def __str__(self): result=[] for i in self: result.append("%0.8X" % i.get_value()) return "{%s}" % '-'.join(result) class TIMESTAMP(ULONG): """ A standard unix timestamp. Number of seconds since the epoch (1970-1-1) """ visible = True def __str__(self): return time.strftime("%Y/%m/%d %H:%M:%S",time.localtime(self.data)) class WIN_FILETIME(SimpleStruct): """ A FileTime 8 byte time commonly see in windows. This represent the number of 100ns periods since 1601 - how did they come up with that??? """ visible = True sql_type = "int" def init(self): self.fields = [ [ 'low', ULONG ], [ 'high', ULONG ] ] def to_unixtime(self): """ Returns the current time as a unix time """ t=float(self['high'].get_value())* 2**32 +self['low'].get_value() return (t*1e-7 - 11644473600) def get_value(self): """ We just return the unix time here """ return self.to_unixtime() def __str__(self): t = self.to_unixtime() try: return time.strftime("%Y/%m/%d %H:%M:%S",time.localtime(t)) except: return "Invalid Timestamp %X:%X" % (int(self['low']),int(self['high'])) # return "%s" % (time.ctime(t)) class WIN12_FILETIME(WIN_FILETIME): """ A 12 byte variant of above. Last LONG is just all zeros usually so we ignore it """ visible = True def init(self): WIN_FILETIME.init(self) self.fields.append(['pad',ULONG]) class LPSTR(SimpleStruct): """ This is a string with a size before it """ def __init__(self, buffer,*args,**kwargs): SimpleStruct.__init__(self, buffer, *args,**kwargs) try: ## This initialises the LPSTR from kwargs length = len(kwargs['value']) new_string = STRING(kwargs['value'], length=length) self.data = dict(data = new_string, length = ULONG(None, value=length)) except KeyError: pass def init(self): self.fields = [ [ 'length', LONG], [ 'data', STRING, dict(length=lambda x: x['length']) ] ] def set_value(self, value): """ Update our length field automatically """ data = self['data'] data.set_value(value) self['length'].set_value(len(data)) def __str__(self): return self['data'].__str__() class IPAddress(STRING): def __init__(self, buffer,*args,**kwargs): kwargs['length'] = 4 STRING.__init__(self, buffer, *args, **kwargs) def __str__(self): return '.'.join([ord(x).__str__() for x in self.data]) class FileHeader(SimpleStruct): """ The PCAP file header """ fields = [ ['magic', ULONG], ['version_major', WORD], ['version_minor', WORD], ['thiszone', ULONG, None, "gmt to local correction"], ['sigfigs', ULONG, None, "accuracy of timestamps"], ['snaplen', ULONG, None, "max length saved portion of each pkt"], ['linktype', ULONG, None, "data link type (LINKTYPE_*)"], ] def read(self): ## Try to read the file with little endianess self.parameters['endianess']='l' ## Try to find the little endianness magic within the first ## 1000 bytes - There could be some crap at the start of the ## file. tmp = self.buffer[0:1000] off =tmp.search(struct.pack("=0: self.offset = off self.buffer = self.buffer[off:] result=SimpleStruct.read(self) self.start_of_file = off self.start_of_data = self.offset return result off=tmp.search(struct.pack(">L",0xa1b2c3d4)) if off>=0: self.parameters['endianess']='b' self.offset = off self.buffer = self.buffer[off:] result=SimpleStruct.read(self) self.start_of_file = off self.start_of_data = self.offset return result result=SimpleStruct.read(self) ## Dont know the magic raise IOError('This is not a pcap magic (%s) at offset 0x%08X' % (result['magic'], self.buffer.offset)) def __iter__(self): self.offset = self.start_of_data return self def next(self): ## Try to read the next packet and return it: try: b = self.buffer.__getslice__(self.offset) p = Packet(b, endianess=self.parameters['endianess']) self.offset+=p.size() return p except IOError: raise StopIteration class Packet(SimpleStruct): """ Each packet is preceeded by this. """ fields = [ ['ts_sec', TIMESTAMP, None, "time stamp"], ['ts_usec', ULONG, None, "Time in usecs"], ['caplen', ULONG, None, "length of portion present"], ['length', ULONG, None, "length this packet (off wire)"] ] def read(self): result=SimpleStruct.read(self) caplen = int(result['caplen']) if caplen>64000: raise IOError("packet too large at %s, maybe PCAP file is corrupted" % caplen) s=RAW(self.buffer[self.offset:self.offset+caplen]) if s.size()!=caplen: raise IOError("Unable to read the last packet from the file (wanted %s, got %s). Is the file truncated?" % (result['caplen'], s.size())) self.offset+=caplen self.add_element(result, 'data', s) return result def payload(self): return self.data['data'].get_value().__str__() def Usage(): return """ This program analyses HTTP requests found in a PCAP. The first argument is mandatory and this should be the pcap file. python read_pcap.py [options] The optional arguments of the program: - help show this screen - all show the unfiltered request - all-struct show all parameters in a structured human readable fashion - url show the webpage - show the parameter, this could be any parameter that is specified in the URLs. For example for google: q - supress supress information messages """ if __name__ == "__main__": method = "" try: fd=open(sys.argv[1],'rb') method = sys.argv[2] except: print Usage() sys.exit b=Buffer(fd=fd) pcap = FileHeader(b) print pcap import re variables_to_search = [] search = 1 counter = 3 while (search): try: argv_content = sys.argv[counter] counter= counter + 1 variables_to_search.append(argv_content) except: search = 0 try: index = variables_to_search.index("help") print Usage() exit except: print "Starting to analyse the packages" packet_count = 0 for packet in pcap: packet_count = packet_count + 1 datastr = str(packet['data']) if (datastr.find(method) > 0 and method == "GET"): get_url_str = str(datastr.split(method)[1].split("HTTP")[0]) try: for part in get_url_str.split("?")[1].split("&"): for variable_to_search in variables_to_search: if part.split("=")[0] == variable_to_search: print "Argument: " + variable_to_search + "\t==>\t" + part.split("=")[1] if variable_to_search == "all": print get_url_str if variable_to_search == "url": print "Argument: URL\t==>\t" + get_url_str.split("?")[0] if variable_to_search == "all-struct": print "-"*20 + "Start Packet (" + str(packet_count) + ")" + "-"*20 print "URL:\t\t" + get_url_str.split("?")[0] for human_part in get_url_str.split("?")[1].split("&"): print human_part.split("=")[0] + ":\t\t" + human_part.split("=")[1] print "-"*20 + "End Packet" + "-"*20 + "\n" except: try: index = variables_to_search.index("supress") except: print "Packet skipped, no arguments given" elif (datastr.find(method) > 0 and method == "POST"): get_url_str = str(datastr.split(method)[1].split("HTTP")[0]) try: params = datastr.split("Content-Length:")[1].split("...")[1].split("&") for param in params: for variable_to_search in variables_to_search: try: if param.split("=")[0] == variable_to_search: print "Argument: " + variable_to_search + "\t==>\t" + param.split("=")[1] except: print "Argument:\t==>\t" + param if variable_to_search == "all": print get_url_str + "\n" for par in params: print par+"\n" if variable_to_search == "url": print "Argument: URL\t==>\t" + get_url_str.split("?")[0] if variable_to_search == "all-struct": print "-"*20 + "Start Packet (" + str(packet_count) + ")" + "-"*20 print "URL:\t\t" + get_url_str.split("?")[0] for human_part in params: try: print human_part.split("=")[0] + ":\t\t" + human_part.split("=")[1] except: print human_part +":\t\t" print "-"*20 + "End Packet" + "-"*20 + "\n" except: print "Error Reading POST variables"