|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: iso-8859-1 -*- |
| 3 | +################################################################################# |
| 4 | +# Simple FritzCap python port |
| 5 | +# Simplifies generation and examination of traces taken from AVM FritzBox and/or SpeedPort |
| 6 | +# Traces can be examined using WireShark |
| 7 | +# (c) neil.young 2010 (spongebob.squarepants in http://www.ip-phone-forum.de/) |
| 8 | +# based on the Windows GUI exe with same name |
| 9 | +################################################################################## |
| 10 | + |
| 11 | +import urllib, re, timeit, hashlib, sys, datetime, os |
| 12 | + |
| 13 | +sys.path.append('core') |
| 14 | + |
| 15 | +from tracer import Tracer |
| 16 | +from pcap_parse import PcapParser |
| 17 | +from g711_decoder import G711Decoder |
| 18 | + |
| 19 | +# Configuration (just change here) ############################################### |
| 20 | +boxname = 'speedport.ip' # or ip (also fritz.box) |
| 21 | +password = 'yourpassword' # your password, adapt |
| 22 | +protocol = 'https' # or http |
| 23 | +capfolder = 'captures' # plus subfolders according to day, month, year, hour, minute |
| 24 | +capfile = 'capture.cap' # name of capture file |
| 25 | +login_required = True # set to 0 if no login is required |
| 26 | +################################################################################## |
| 27 | + |
| 28 | +# Commands |
| 29 | +default_login = 'getpage=../html/de/menus/menu2.html&errorpage=../html/index.html&var:lang=de&var:pagename=home&var:menu=home&=&login:command/password=%s' |
| 30 | +sid_challenge = 'getpage=../html/login_sid.xml' |
| 31 | +sid_login = 'login:command/response=%s&getpage=../html/login_sid.xml' |
| 32 | +start = '?start=1&start1=Start' |
| 33 | +stop = '?stop=1&stop1=Stop' |
| 34 | + |
| 35 | + |
| 36 | +# Main work horse G.711 extraction/mix |
| 37 | +def runparser(): |
| 38 | + g711 = G711Decoder(capfile, mix=1, linearize=1) |
| 39 | + PcapParser(capfile, g711.decode).parse() |
| 40 | + g711.finalize() |
| 41 | + |
| 42 | +# Main |
| 43 | +def main(): |
| 44 | + |
| 45 | + global capfile |
| 46 | + |
| 47 | + capture = True # Audio debug shortcut |
| 48 | + extract_audio = True # Extract audio if available |
| 49 | + |
| 50 | + SID = '' # Required later |
| 51 | + |
| 52 | + if capture: |
| 53 | + |
| 54 | + # Attempt to login |
| 55 | + if login_required: |
| 56 | + |
| 57 | + try: |
| 58 | + # Try to get a session id SID |
| 59 | + sid = urllib.urlopen(protocol + '://' + boxname + '/cgi-bin/webcm?' + sid_challenge) |
| 60 | + if sid.getcode() == 200: |
| 61 | + # Read and parse the response in order to get the challenge (not a full blown xml parser) |
| 62 | + challenge = re.search('<Challenge>(.*?)</Challenge>', sid.read()).group(1) |
| 63 | + |
| 64 | + # Create a UTF-16LE string from challenge + '-' + password, non ISO-8859-1 characters will except here (e.g. EUR) |
| 65 | + challenge_bf = (challenge + '-' + password).decode('iso-8859-1').encode('utf-16le') |
| 66 | + |
| 67 | + # Calculate the MD5 hash |
| 68 | + m = hashlib.md5() |
| 69 | + m.update(challenge_bf) |
| 70 | + |
| 71 | + # Make a byte response string from challenge + '-' + md5_hex_value |
| 72 | + response_bf = challenge + '-' + m.hexdigest().lower() |
| 73 | + |
| 74 | + # Answer the challenge |
| 75 | + login = urllib.urlopen(protocol + '://' + boxname + '/cgi-bin/webcm', sid_login % response_bf) |
| 76 | + |
| 77 | + if login.getcode() == 200: |
| 78 | + SID = re.search('<SID>(.*?)</SID>', login.read()).group(1) |
| 79 | + print "Login OK, SID %s" % SID |
| 80 | + else: |
| 81 | + print "Could not login" |
| 82 | + return |
| 83 | + except: |
| 84 | + # Legacy login |
| 85 | + command = urllib.urlopen(protocol + '://' + boxname + '/cgi-bin/webcm', default_login % password) |
| 86 | + response = command.read() |
| 87 | + # Right now I don't know how to check the result of a login operation. So I just search for the errorMessage |
| 88 | + if command.getcode() == 200: |
| 89 | + try: |
| 90 | + result = urllib.unquote(re.search('<p class="errorMessage">(.*?)</p>', response).group(1).decode('iso-8859-1')).replace(" "," ") |
| 91 | + except: |
| 92 | + result = '' |
| 93 | + print 'Login attempt was made. %s' % result |
| 94 | + |
| 95 | + |
| 96 | + # Create capfile folder |
| 97 | + folder = capfolder + '/' + (datetime.datetime.now().strftime('%d%m%Y%H%M')) |
| 98 | + capfile = folder + '/' + capfile |
| 99 | + if not os.path.exists(folder): |
| 100 | + os.makedirs(folder) |
| 101 | + |
| 102 | + # Start tracer thread, wait for console input to stop |
| 103 | + if SID != '': |
| 104 | + Tracer(protocol + '://' + boxname + '/cgi-bin/capture_notimeout' + start + "&sid=%s" % SID, capfile).start() |
| 105 | + else: |
| 106 | + Tracer(protocol + '://' + boxname + '/cgi-bin/capture_notimeout' + start, capfile).start() |
| 107 | + |
| 108 | + print 'Trace started, abandon with <ENTER>' |
| 109 | + raw_input() |
| 110 | + # Clean stop |
| 111 | + print 'Stopping trace' |
| 112 | + if SID != '': |
| 113 | + urllib.urlopen(protocol + '://' + boxname + '/cgi-bin/capture_notimeout' + stop + "&sid=%s" % SID) |
| 114 | + else: |
| 115 | + urllib.urlopen(protocol + '://' + boxname + '/cgi-bin/capture_notimeout' + stop) |
| 116 | + print 'Capture done' |
| 117 | + |
| 118 | + # Parse the captured file |
| 119 | + if extract_audio: |
| 120 | + print 'Extracting audio...' |
| 121 | + print timeit.Timer('runparser()', 'from __main__ import runparser').timeit(number=1), "seconds" |
| 122 | +# runparser() |
| 123 | + print 'All done' |
| 124 | + |
| 125 | +if __name__ == '__main__': |
| 126 | + main() |
0 commit comments