from __future__ import with_statement import sys import os import httplib, mimetypes import urllib import hashlib import struct from urlparse import urlparse # script for getting patch blobs for dsi sav # version v1.1 current_version=1110 host='bootmii.org' path_base='/dsiexploits/inject/' script_path='upload.php' outfile='' extra_params='' if (len(sys.argv) < 2) or (len(sys.argv) > 3): print 'need one or two options [extra_params]' sys.exit(1) outfilesplit = sys.argv[1].split('.') outfile=outfilesplit[0] for index in range(1,len(outfilesplit)-1): outfile = '%s.%s' % (outfile, outfilesplit[index]) if (len(outfilesplit[len(outfilesplit)-1].split(os.sep)) > 1): if (len(outfilesplit) > 1): outfile='%s.%shax' % (outfile,outfilesplit[len(outfilesplit)-1]) else: outfile='%shax' % outfile else: outfile='%shax' % outfile if (len(outfilesplit) > 1): outfile='%s.%s' % (outfile,outfilesplit[len(outfilesplit)-1]) if len(sys.argv) == 3: extra_params='&%s' % sys.argv[2] print 'extra_params=%s' % extra_params ## from http://code.activestate.com/recipes/146306/ def post_multipart(host, selector, fields, files): """ Post fields and files to an http host as multipart/form-data. fields is a sequence of (name, value) elements for regular form fields. files is a sequence of (name, filename, value) elements for data to be uploaded as files Return the server's response page. """ content_type, body = encode_multipart_formdata(fields, files) h = httplib.HTTPConnection(host) headers = { 'User-Agent': 'dsisavpatch/1.1', 'Content-Type': content_type } h.request('POST', selector, body, headers) res = h.getresponse() return res.status, res.reason, res.getheaders(), res.read() def encode_multipart_formdata(fields, files): """ fields is a sequence of (name, value) elements for regular form fields. files is a sequence of (name, filename, value) elements for data to be uploaded as files Return (content_type, body) ready for httplib.HTTP instance """ BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for (key, filename, value) in files: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) L.append('Content-Type: %s' % get_content_type(filename)) L.append('') L.append(value) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body def get_content_type(filename): return mimetypes.guess_type(filename)[0] or 'application/octet-stream' ## end of from http://code.activestate.com/recipes/146306/ with open(sys.argv[1], 'rb') as f: contents = f.read() footer = contents[0x40f4:0x4554] print 'opening http://%s%s%s' %(host, path_base, script_path) print 'footerhash: %s' % (hashlib.sha1(footer).hexdigest(),) status, reason, headers, response = post_multipart(host, '%s%s?footerhash=%s%s' % (path_base, script_path, hashlib.sha1(footer).hexdigest(), extra_params), [], [('footer', 'footer.bin', footer)]) if status != 200 and status != 302: print 'error getting: %s' % script_path print status, reason location=''; for a in headers: if a[0] == 'location': location = a[1] if a[0] == 'x-protocolver': server_version = int(a[1]) if server_version > current_version: print 'The client software was updated and this update is required, now aborting.' sys.exit(1) else: print 'ProtocolVer %d is good!' % server_version if location=='': print 'did not get location redirect! %d as status' % status print response sys.exit(0) if location[0:5] != 'http:': print 'not a http redirect, bad luck' sys.exit(0) url = urlparse(location) redirect_params = dict([part.split('=') for part in url[4].split('&')]) redirect_host = url[1] redirect_path = url[2] params = urllib.urlencode(redirect_params) headers = {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "dsisavpatch/1.1"} attempts = 20 payload = '' while attempts > 0: print 'trying %s with hash as %s'%(redirect_path, redirect_params['hash']) h = httplib.HTTPConnection(redirect_host) h.request('POST', redirect_path, params, headers) res = h.getresponse() if res.status == 200: payload = res.read() # print 'got %d, and length %d' %(res.status, len(payload)) if payload[0:5] == 'ERROR': print 'error: %s' % payload[5:] attempts = -1 elif payload[0:7] == 'INFOMSG': print 'info: %s' % payload[7:] else: break attempts = attempts - 1 else: print 'bad times, we got %d reply' % res.status payload = res.read() # print 'got %d, and length %d' %(res.status, len(payload)) if payload[0:5] == 'ERROR': print 'error: %s' % payload[5:] sys.exit(0) if attempts == 0: print 'too many attempts, not sure what happened!' sys.exit(0) # process payload with open('debug.out', 'wb') as f: f.write(payload) if server_version < 1110: start_of_field=0 end_of_field=payload.find('\0', start_of_field) if end_of_field == -1: print 'whoops, end of field fail!' sys.exit(0) notes_size = int(payload[start_of_field:end_of_field], 16) start_of_field = end_of_field+1 end_of_field=payload.find('\0', start_of_field) if end_of_field == -1: print 'whoops, end of field fail!' sys.exit(0) num_objs = int(payload[start_of_field:end_of_field], 16) else: (notes_size,num_objs) = struct.unpack("<2L", payload[0:8]) end_of_field = 7 print 'noteslen: %d' % notes_size print 'found %d objs' % num_objs patch_blobs = [] for i in range(0,num_objs): start_of_field = end_of_field+1 if server_version < 1110: end_of_field = payload.find('\0', start_of_field) if end_of_field == -1: print 'whoops, end of field fail!' sys.exit(0) offset = int(payload[start_of_field:end_of_field], 16) start_of_field = end_of_field+1 end_of_field = payload.find('\0', start_of_field) if end_of_field == -1: print 'whoops, end of field fail!' sys.exit(0) size = int(payload[start_of_field:end_of_field], 16) else: (offset,size) = struct.unpack("<2L", payload[start_of_field:start_of_field+8]) end_of_field = start_of_field+7 patch_blobs.append((offset,size)) print 'queued patch_blob' start_of_field = end_of_field + 1 notes = payload[start_of_field:start_of_field+notes_size] start_of_field = start_of_field + notes_size for (offset,size) in patch_blobs: contents = contents[0:offset] + payload[start_of_field:start_of_field+size] + contents[offset+size:] print 'applied patch_blob!' start_of_field = start_of_field + size with open(outfile, 'wb') as f: f.write(contents) print 'notes: \n%s' % notes print 'done!'