123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-"
- # vim: set expandtab tabstop=4 shiftwidth=4:
- """
- This file is part of the XSSer project, https://xsser.03c8.net
- Copyright (c) 2010/2019 | psy <epsylon@riseup.net>
- xsser 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 version 3 of the License.
- xsser 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 xsser; if not, write to the Free Software Foundation, Inc., 51
- Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- """
- import os
- from pathlib import Path
- import gi
- gi.require_version('Gtk', '3.0')
- gi.require_version('Gdk', '3.0')
- gi.require_version('PangoCairo', '1.0')
- from gi.repository import Gtk as gtk
- from gi.repository import Gdk as gdk
- gdk.threads_init()
- from gi.repository import GObject as gobject
- from gi.repository import PangoCairo as pangocairo
- from gi.repository import GdkPixbuf
- from core.reporter import XSSerReporter
- from core.curlcontrol import Curl
- from collections import defaultdict
- from threading import Thread
- import traceback
- import urllib.request, urllib.parse, urllib.error
- import math
- import cairo
- import cairocffi
- import gzip
- import time
- from PIL import Image
- import array
- import GeoIP
- class PointType(object):
- checked = 15
- success = 10
- failed = 5
- crawled = 0
- crashsite = -1
- crash_color = [0.1,0.1,0.1]
- checked_color = [0,0.8,0.8]
- failed_color = [0.8,0.0,0.0]
- success_color = [0.0,0.0,0.8]
- crawl_color = [0.0,0.0,0.0]
- def gtkcol(col):
- return [int(col[0]*65535),int(col[1]*65535),int(col[2]*65535)]
- class MapPoint(object):
- def __init__(self, lat, lng, ptype, size, text): # 0, 5, 10, 15, 20 -> 20==checked
- self.latitude = lat
- self.longitude = lng
- self.size = size
- self.text = text
- self.reports = defaultdict(list)
- self.reports[ptype].append(text)
- self.type = ptype
- if ptype == PointType.crawled:
- self.color = crawl_color
- elif ptype == PointType.failed:
- self.color = failed_color
- elif ptype == PointType.success:
- self.color = success_color
- elif ptype == PointType.checked:
- self.color = checked_color
- else:
- self.color = crawl_color
- self.gtkcolor = gtkcol(self.color)
- def add_reports(self, report_type, reports):
- for report_type in set(reports.keys() + self.reports.keys()):
- self.reports[report_type].extend(reports[report_type])
- class CrashSite(MapPoint):
- def __init__(self, lat, lng, size, desturl):
- MapPoint.__init__(self, lat, lng, PointType.crashsite, size, desturl)
-
- class DownloadThread(Thread):
- def __init__(self, geomap, parent):
- Thread.__init__(self)
- self.daemon = True
- self._map = geomap
- self._parent = parent
- def run(self):
- geo_db_path = self._map.get_geodb_path()
- def reportfunc(current, blocksize, filesize):
- percent = min(float(current)/(filesize/float(blocksize)),1.0)
- self._parent.report_state('downloading map', percent)
- if not os.path.exists(os.path.dirname(geo_db_path)):
- os.makedirs(os.path.dirname(geo_db_path))
- self._parent.report_state('getting city database', 0.0)
- try:
- urllib.request.urlretrieve('https://xsser.03c8.net/map/GeoLite2-City.dat.gz',
- geo_db_path+'.gz', reportfunc)
- except:
- try:
- urllib.request.urlretrieve('https://xsser.sf.net/map/GeoLite2-City.dat.gz',
- geo_db_path+'.gz', reportfunc)
- except:
- self._parent.report_state('error downloading map', 0.0)
- self._map.geomap_failed()
- else:
- self._parent.report_state('map downloaded (restart XSSer!!!!)', 0.0)
- f_in = gzip.open(geo_db_path+'.gz', 'rb')
- f_out = open(geo_db_path, 'wb')
- f_out.write(f_in.read())
- f_in.close()
- print('deleting gzipped file')
- os.remove(geo_db_path+'.gz')
- self._map.geomap_ready()
- class GlobalMap(gtk.DrawingArea, XSSerReporter):
- def __init__(self, parent, pixbuf, onattack=False):
- gtk.DrawingArea.__init__(self)
- geo_db_path = self.get_geodb_path()
- self._parent = parent
- self._pixbuf = pixbuf
- self._cache_geo = {}
- self.geo = None
- self._onattack = onattack
- if not os.path.exists(geo_db_path):
- self._t = DownloadThread(self, parent)
- self._t.start()
- else:
- self.finish_init()
- def geomap_ready(self):
- gdk.threads_enter()
- gobject.timeout_add(0, self.finish_init)
- gdk.threads_leave()
- def geomap_failed(self):
- gdk.threads_enter()
- gobject.timeout_add(0, self.failed_init)
- gdk.threads_leave()
- def failed_init(self):
- if hasattr(self, '_t'):
- self._t.join()
- delattr(self, '_t')
- def finish_init(self):
- if hasattr(self, '_t'):
- self._t.join()
- delattr(self, '_t')
- parent = self._parent
- geo_db_path = self.get_geodb_path()
- Geo = GeoIP.open(geo_db_path, GeoIP.GEOIP_STANDARD)
- self.geo = Geo
- self.set_has_tooltip(True)
- self._max_points = 200
- self._lasttime = 0.0
- self.context = None
- self.mapcontext = None
- self._mappixbuf = None
- self._selected = []
- self._current_text = ["", 0.0]
- self._stats = [0,0,0,0,0,0,0]
- self.width = self._pixbuf.get_width()
- self.height = self._pixbuf.get_height()
- self._min_x = 0
- self._max_x = self.width
- self._drawn_points = []
- self._lines = []
- self._frozenlines = []
- self._points = []
- self._crosses = []
- self.connect('draw', self.expose)
- self.connect("query-tooltip", self.on_query_tooltip)
- self.queue_draw()
- if not self._onattack:
- self.add_test_points()
- def get_geodb_path(self):
- ownpath = os.path.dirname(os.path.dirname(__file__))
- gtkpath = os.path.join(ownpath, 'gtk')
- if os.path.exists(os.path.join(gtkpath, 'map/GeoIP.dat')):
- return os.path.join(gtkpath, 'map/GeoIP.dat')
- else:
- home = str(Path.home())
- return os.path.join(home, '.xsser', 'GeoIP.dat')
- def find_points(self, x, y, distance=9.0):
- points = []
- self._selected = []
- for idx, point in enumerate(self._drawn_points):
- d_x = x-point[0]
- d_y = y-point[1]
- if d_y*d_y+d_x*d_x < distance:
- self._points[point[2]].size = 4.0
- points.append(self._points[point[2]])
- self._selected.append(point[2])
- if points:
- rect = gdk.Rectangle()
- self.queue_draw()
- return points
- def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
- if not self.geo:
- return False
- points = self.find_points(x, y)
- if points:
- text = ""
- success = []
- finalsuccess = []
- failures = []
- crawls = []
- for point in points:
- finalsuccess.extend(point.reports[PointType.checked])
- success.extend(point.reports[PointType.success])
- failures.extend(point.reports[PointType.failed])
- crawls.extend(point.reports[PointType.crawled])
- if finalsuccess:
- text += "<b>browser checked sucesses:</b>\n"
- text += "\n".join(map(lambda s: gobject.markup_escape_text(s), finalsuccess))
- if failures or success:
- text += "\n"
- if success:
- text += "<b>sucesses:</b>\n"
- text += "\n".join(map(lambda s: gobject.markup_escape_text(s), success))
- if failures:
- text += "\n"
- if failures:
- text += "<b>failures:</b>\n"
- text += "\n".join(map(lambda s: gobject.markup_escape_text(s), failures))
- if crawls and not failures and not success:
- text += "<b>crawls:</b>\n"
- text += "\n".join(map(lambda s: gobject.markup_escape_text(s), crawls))
- tooltip.set_markup(str(text))
- return True
- return False
- def add_test_points(self):
- self.add_point(0.0, 0.0)
- self.add_point(0.0, 5.0)
- self.add_point(0.0, 10.0)
- self.add_point(0.0, 15.0)
- self.add_point(5.0, 0.0)
- self.add_point(10.0, 0.0)
- self.add_point(15.0, 0.0)
- def clear(self):
- self._points = []
- self._lines = []
- self.mapcontext = None
- self._frozenlines = []
- self._crosses = []
- self._stats = [0,0,0,0,0,0,0]
- def expose(self, widget, cr):
- if not self.mapcontext:
- self._mappixbuf = self._pixbuf.copy()
- self.mapsurface = cairo.ImageSurface.create_from_png('gtk/images/world.png')
- self.mapcontext = cairo.Context(self.mapsurface)
- self.context = cr
- self.draw_frozen_lines()
- self.context.set_source_surface(self.mapsurface)
- self.context.rectangle(self._min_x, self._max_x, self.width, self.height)
- self.context.fill()
- self.context.rectangle(self._min_x, self._max_x, self.width, self.height)
- self.context.fill()
- self.context.paint()
- self.context.set_source_rgb(0,0,0)
- self._min_x = 5 # we have the scale at the left for now
- self._max_x = 0
- if self.geo:
- self.draw(self.context)
- return False
- def add_point(self, lng, lat, point_type=PointType.crawled, desturl="testpoint"):
- map_point = MapPoint(lat, lng, point_type, 5.0, desturl)
- map_point.x, map_point.y = self.plot_point(lat, lng)
- self._points.append(map_point)
- def add_cross(self, lng, lat, col=[0,0,0], desturl="testpoint"):
- for a in self._crosses:
- if a.latitude == lat and a.longitude == lng:
- return
- crash_site = CrashSite(lat, lng, 5.0, desturl)
- crash_site.x, crash_site.y = self.plot_point(lat, lng)
- self.adjust_bounds(crash_site.x, crash_site.y)
- self._crosses.append(crash_site)
- self.queue_redraw()
- def insert_point(self, lng, lat, col=[0,0,0], desturl="testpoint"):
- self._points.insert(0, MapPoint(lat, lng, point_type, 5.0, desturl))
- def _preprocess_points(self):
- newpoints = defaultdict(list)
- for point in self._points:
- key = (point.latitude, point.longitude)
- newpoints[key].append(point)
- self._points = []
- for points in newpoints.values():
- win_type = points[0]
- win_size = points[0]
- for point in points[1:]:
- if point.type > win_type.type:
- win_type = point
- if point.size > win_type.size:
- win_size = point
- self._points.append(win_type)
- if win_type != win_size:
- self._points.append(win_size)
- for point in points:
- if not point in [win_size, win_type]:
- win_type.add_reports(point.type, point.reports)
- if len(self._points) > self._max_points:
- self._points = self._points[:self._max_points]
- def draw_frozen_lines(self):
- for line in self._lines[len(self._frozenlines):]:
- if line[4] <= 0.5:
- self.draw_line(self.mapcontext, line)
- self._frozenlines.append(line)
- def draw(self, context, failures=True):
- self._preprocess_points()
- if self._lasttime == 0:
- self._lasttime = time.time()-0.04
- currtime = time.time()
- timepassed = currtime - self._lasttime
- redraw = False
- if failures:
- self._drawn_points = []
- for cross in reversed(self._crosses):
- if cross.size > 0.1:
- cross.size -= timepassed*2
- else:
- self._crosses.remove(cross)
- if cross.size > 0.1:
- redraw = True
- self.draw_cross(cross)
- for line in reversed(self._lines[len(self._frozenlines):]):
- if line[4] > 0.5:
- line[4] -= timepassed*2
- if line[4] > 0.5:
- redraw = True
- self.draw_line(self.context, line)
- for idx, point in enumerate(self._points):
- if point.type >= PointType.success:
- if failures:
- continue
- else:
- if not failures:
- continue
- if point.size > 1.0 and not idx in self._selected:
- point.size -= timepassed*2
- redraw = True
- elif point.size < 1.0:
- point.size = 1.0
- self.draw_point(point)
- x = point.x
- y = point.y
- self.adjust_bounds(x, y)
- self._drawn_points.append([x, y, idx])
- stat_f = 1.0
- if failures:
- mp = self._max_points
- self.draw_bar((-45,-160,crawl_color,(self._stats[0]%mp)*stat_f))
- self.draw_bar((-45,-155,failed_color,(self._stats[1]%mp)*stat_f))
- self.draw_bar((-45,-150,success_color,(self._stats[2]%mp)*stat_f))
- self.draw_bar((-45,-145,checked_color,(self._stats[3]%mp)*stat_f))
- if int(self._stats[0] / mp):
- self.draw_bar((-46,-160,crawl_color,-2-(self._stats[0]/mp)*stat_f))
- if int(self._stats[1] / mp):
- self.draw_bar((-46,-155,failed_color,-2-(self._stats[1]/mp)*stat_f))
- if int(self._stats[2] / mp):
- self.draw_bar((-46,-150,success_color,-2-(self._stats[2]/mp)*stat_f))
- if int(self._stats[3] / mp):
- self.draw_bar((-46,-145,checked_color,-2-(self._stats[3]/mp)*stat_f))
- self.draw(context, False)
- else:
- if self._current_text[1] > 0.0:
- self.draw_text(100, self.height-50, self._current_text[0])
- self._current_text[1] -= timepassed*4
- self._lasttime = currtime
- if redraw:
- self.queue_redraw()
- def adjust_bounds(self, x, y):
- if x-20 < self._min_x:
- self._min_x = x-20
- elif x+20 > self._max_x:
- self._max_x = x+20
- def draw_text(self, x, y, text):
- self.context.save()
- self.context.move_to(x, y)
- v = (5.0-self._current_text[1])/5.0
- self.context.scale(0.1+max(v, 1.0), 0.1+max(v, 1.0))
- self.context.set_source_rgb(*gtkcol((v,)*3))
- u = urllib.parse.urlparse(text)
- self.context.show_text(u.netloc)
- self.context.restore()
- def draw_bar(self, point):
- if point[3]:
- self.context.save()
- x, y = self.plot_point(point[0], point[1])
- self.context.set_source_rgb(*point[2])
- self.context.rectangle(x, y, 5, -(2.0+point[3]))
- self.context.fill()
- self.context.restore()
- return x, y
- def draw_line(self, context, line):
- if line[4]:
- context.save()
- x, y = self.plot_point(line[0], line[1])
- x2, y2 = self.plot_point(line[2], line[3])
- self.adjust_bounds(x, y)
- self.adjust_bounds(x2, y2)
- context.set_line_width(1.0)
- context.set_source_rgba(0.0, 0.0, 0.0, float(line[4])/5.0)
- context.move_to(x, y)
- context.rel_line_to(x2-x, y2-y)
- context.stroke()
- context.restore()
- def draw_point(self, point):
- if point.size:
- self.context.save()
- self.context.set_source_rgb(*point.gtkcolor)
- self.context.translate(point.x, point.y)
- self.context.arc(0.0, 0.0, 2.4*point.size, 0, 2*math.pi)
- self.context.close_path()
- self.context.fill()
- self.context.restore()
- def draw_cross(self, point):
- if point.size:
- self.context.save()
- self.context.translate(point.x, point.y)
- self.context.rotate(point.size)
- self.context.set_line_width(0.8*point.size)
- self.context.set_source_rgb(*point.gtkcolor)
- self.context.move_to(-3*point.size, -3*point.size)
- self.context.rel_line_to(6*point.size, 6*point.size)
- self.context.stroke()
- self.context.move_to(-3*point.size, +3*point.size)
- self.context.rel_line_to(6*point.size, -6*point.size)
- self.context.stroke()
- self.context.restore()
- def get_latlon_fromurl(self, url):
- parsed_url = urlparse.urlparse(url)
- split_netloc = parsed_url.netloc.split(":")
- if len(split_netloc) == 2:
- server_name, port = split_netloc
- else:
- server_name = parsed_url.netloc
- port = None
- if server_name in self._cache_geo:
- return self._cache_geo[server_name]
- Geodata = self.geo.record_by_name(server_name)
- if Geodata:
- country_name = Geodata['country_name']
- longitude = Geodata['longitude']
- latitude = Geodata['latitude']
- self._cache_geo[server_name] = (latitude, longitude)
- return latitude, longitude
- def start_attack(self):
- self.clear()
- def queue_redraw(self):
- rect = gdk.Rectangle()
- self.queue_draw()
- def mosquito_crashed(self, dest_url, reason):
- self._current_text = [dest_url, 5.0]
- self._stats[4] += 1
- try:
- lat, lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- self.add_cross(lon, lat, crash_color, dest_url)
- gdk.threads_enter()
- self.queue_redraw()
- gdk.threads_leave()
- def add_checked(self, dest_url):
- self._current_text = [dest_url, 5.0]
- self._stats[3] += 1
- try:
- lat, lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- self.add_point(lon, lat, PointType.checked, dest_url)
- gdk.threads_enter()
- self.queue_redraw()
- gdk.threads_leave()
- def add_success(self, dest_url):
- self._current_text = [dest_url, 5.0]
- self._stats[2] += 1
- try:
- lat, lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- self.add_point(lon, lat, PointType.success, dest_url)
- gdk.threads_enter()
- self.queue_redraw()
- gdk.threads_leave()
- def add_failure(self, dest_url):
- self._current_text = [dest_url, 5.0]
- self._stats[1] += 1
- try:
- lat, lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- self.add_point(lon, lat, PointType.failed, dest_url)
- gdk.threads_enter()
- self.queue_redraw()
- gdk.threads_leave()
- def add_link(self, orig_url, dest_url):
- try:
- lat, lon = self.get_latlon_fromurl(orig_url)
- except:
- return
- try:
- d_lat, d_lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- if lat == d_lat and lon == d_lon:
- return
- for a in self._lines:
- if a[0] == lat and a[1] == lon and a[2] == d_lat and a[3] == d_lon:
- return
- self._lines.append([lat, lon, d_lat, d_lon, 0.5])
- def start_crawl(self, dest_url):
- self._current_text = [dest_url, 5.0]
- self._stats[0] += 1
- try:
- lat, lon = self.get_latlon_fromurl(dest_url)
- except:
- return
- self.add_point(lon, lat, PointType.crawled, dest_url)
- gdk.threads_enter()
- self.queue_redraw()
- gdk.threads_leave()
- def plot_point_mercator(self, lat, lng):
- longitude_shift = -23
- map_width = self.width
- map_height = self.height
- y_pos = -1
- x = int((map_width * (180.0 + lng) / 360.0) + longitude_shift) % map_width
- lat = lat * math.pi / 180; # convert from degrees to radians
- y = math.log(math.tan((lat/2.0) + (math.pi/4.0)))
- y = (map_height / 2.0) - (map_width * y / (2.0*math.pi)) + y_pos
- return x, y
- def plot_point_mercatormiller(self, lat, lng):
- longitude_shift = 0
- map_width = self.width
- map_height = self.height
- y_pos = 70
- x = int((map_width * (180.0 + lng) / 360.0) + longitude_shift) % map_width
- lat = lat * math.pi / 180.0; # convert from degrees to radians
- y = 1.25*math.log(math.tan((lat/2.5) + (math.pi/4.0)))
- y = (map_height / 2.0) - (map_width * y / (2.0*math.pi)) + y_pos
- return x, y
- def plot_point_equirectangular(self, lat, lng):
- longitude_shift = -23
- map_width = self.width
- map_height = self.height
- y_pos = 0
- magic_factor = 1.1
- x = int((map_width * (180.0 + lng) / 360.0) + longitude_shift) % map_width
- y = int((map_height / 2.0) - int((map_height * (lat) / 180.0)*magic_factor))
- return x,y
- def plot_point(self, lat, lng):
- x, y = self.plot_point_equirectangular(lat, lng)
- if x-20 < self._min_x:
- self._min_x = x-20
- if x+20 > self._max_x:
- self._max_x = x+20
- return x, y
|