class PalevoCcBot(AbuseCHFeedBot): feed_malware = "palevo" feed_type = "c&c" feed_name = "palevo c&c" feeds = bot.ListParam(default=["https://palevotracker.abuse.ch/?rssfeed"]) def parse_link(self, link): # The source seems to provice invalid links, which can # be fixed by changing the URL scheme from http to https. split = urlparse.urlparse(link) if split[0].lower() == "http": link = urlparse.urlunparse(["https"] + list(split[1:])) yield "description url", link def parse_title(self, title): yield host_or_ip(title.split()[0]) def parse_description(self, description): for key, value in split_description(description): if key == "status": yield key, value elif key == "sbl" and value.lower() != "not listed": yield key + " id", value elif key == "ip address": yield "ip", value
class ZeusCcBot(AbuseCHFeedBot): feed_malware = "zeus" feed_type = "c&c" feeds = bot.ListParam(default=["https://zeustracker.abuse.ch/rss.php"]) def parse_title(self, title): pieces = title.split(None, 1) yield host_or_ip(pieces[0]) if len(pieces) > 1: date = pieces[1] date = re.sub("[()]", "", date) yield "source time", date + "Z" def parse_description(self, description): for key, value in split_description(description): if key == "status": yield key, value elif key == "level": yield "description", resolve_level(value) elif key == "sbl" and value.lower() != "not listed": yield key + " id", value elif key == "ip address": yield "ip", value
class CSVArchiveBot(archivebot.ArchiveBot): csv_columns = bot.ListParam() def archive_path(self, *args, **keys): path = archivebot.ArchiveBot.archive_path(self, *args, **keys) path += ".csv" return path def archive_open(self, archive_path): archive = open(archive_path, "ab") try: csvfile = csv.writer(archive) if archive.tell() == 0: csvfile.writerow([x.encode("utf-8") for x in self.csv_columns]) except: archive.close() raise return archive, csvfile def archive_write(self, (_, csvfile), timestamp, room_name, event): row = list() for column in self.csv_columns: value = event.value(column, u"").encode("utf-8") row.append(value) csvfile.writerow(row)
class FeodoCcBot(AbuseCHFeedBot): feed_type = "c&c" feed_name = "feodo c&c" feeds = bot.ListParam( default=["https://feodotracker.abuse.ch/feodotracker.rss"]) # The timestamp in the title appears to be the firstseen timestamp, # skip including it as the "source time". parse_title = None def parse_description(self, description): got_version = False for key, value in split_description(description): if key == "version": yield "malware family", "feodo." + value.strip().lower() got_version = True elif key == "status": yield "status", value elif key == "host": yield host_or_ip(value) if not got_version: yield "malware family", "feodo"
class Malc0deBot(RSSBot): feeds = bot.ListParam(default=["https://malc0de.com/rss/"]) def is_ip(self, string): for addr_type in (socket.AF_INET, socket.AF_INET6): try: socket.inet_pton(addr_type, string) except (ValueError, socket.error): pass else: return True return False def create_event(self, **keys): description = keys.get("description", None) if description is None: return None event = events.Event() event.add("feeder", "malc0de.com") event.add("feed", "malc0de") event.add("type", "malware url") link = keys.get("link", None) if link: event.add("description url", link) for part in description.split(","): pair = part.split(":", 1) if len(pair) < 2: continue key = pair[0].strip() value = pair[1].strip() if not key or not value: continue if key in ["URL", "MD5"]: if key == "URL": value = "hxxp://" + value event.add(key.lower(), value) elif key == "IP Address": event.add("ip", value) host = keys.get("title", None) if not self.is_ip(host): event.add("domain name", host) event.add("description", "This host is most likely hosting a malware URL.") return event
class ZeusDropzoneBot(AbuseCHFeedBot): feed_malware = "zeus" feed_type = "dropzone" feeds = bot.ListParam( default=["https://zeustracker.abuse.ch/monitor.php?urlfeed=dropzones"]) def parse_description(self, description): for key, value in split_description(description): if key == "status": yield key, value elif key == "url": yield "url", value yield host_or_ip_from_url(value)
class SpamhausDropBot(bot.PollingBot): use_cymru_whois = bot.BoolParam() http_headers = bot.ListParam("a list of http header (k, v) tuples", default=[]) @idiokit.stream def poll(self, url="https://www.spamhaus.org/drop/drop.lasso"): request = urllib2.Request(url) for key, value in self.http_headers: request.add_header(key, value) self.log.info("Downloading %s" % url) try: info, fileobj = yield utils.fetch_url(request) except utils.FetchUrlFailed as fuf: self.log.error("Download failed: %r", fuf) idiokit.stop(False) self.log.info("Downloaded") for line in fileobj.readlines(): if line.startswith(';'): continue data = line.split(';') if not data: continue netblock_sbl = [x.strip() for x in data] if len(netblock_sbl) != 2: continue netblock, sbl = netblock_sbl if not len(netblock.split('/')) == 2: continue new = events.Event() new.add('netblock', netblock) new.add('description url', "http://www.spamhaus.org/sbl/query/" + sbl) new.add('feeder', 'spamhaus') new.add('feed', 'spamhaus drop list') new.add('type', 'hijacked network') if self.use_cymru_whois: values = yield cymruwhois.lookup(netblock.split('/')[0]) for key, value in values: new.add(key, value) yield idiokit.send(new)
class ZeusConfigBot(AbuseCHFeedBot): feed_malware = "zeus" feed_type = "malware configuration" feed_name = "zeus malware configurations" feeds = bot.ListParam(default=["https://zeustracker.abuse.ch/monitor.php?urlfeed=configs"]) def parse_description(self, description): for key, value in split_description(description): if key in ["status", "version"]: yield key, value elif key == "md5 hash": yield "md5", value elif key == "url": yield "url", value yield host_or_ip_from_url(value)
class SpyEyeConfigBot(AbuseCHFeedBot): feed_malware = "spyeye" feed_type = "malware configuration" feeds = bot.ListParam(default=[ "https://spyeyetracker.abuse.ch/monitor.php?rssfeed=configurls" ]) def parse_description(self, description): for key, value in split_description(description): if key == "status": yield key, value if key == "spyeye configurl": yield "url", value yield host_or_ip_from_url(value) elif key == "md5 hash": yield "md5", value
class ZeusBinaryBot(AbuseCHFeedBot): feed_malware = "zeus" feed_type = "malware" feeds = bot.ListParam( default=["https://zeustracker.abuse.ch/monitor.php?urlfeed=binaries"]) def parse_description(self, description): for key, value in split_description(description): if key == "url": yield "url", sanitize_url(value) yield host_or_ip_from_url(value) elif key == "virustotal" and value.lower() != "n/a": yield "virustotal", value elif key == "status": yield "status", value elif key == "md5 hash": yield "md5", value
class Info112Bot(RSSBot): feeds = bot.ListParam(default=["http://www.peto-media.fi/tiedotteet/rss.xml"]) def create_event(self, **keys): description = keys.get("description", None) title = keys.get("title", None) if None in [description, title]: return None event = events.Event() event.add("feed", "112Info") url = keys.get("source", None) if url: event.add("source", url) date = " ".join(description.split()[:2]) event.add("date", date) location = title.split(",")[0].split("/")[0] event.add("location", location) latitude, longitude = municipalities.get(location, (None, None)) if latitude and longitude: event.add("latitude", latitude) event.add("longitude", longitude) definition = title.split(",")[1].strip() event.add("description", definition) for event_type in types: if definition.startswith(event_type): event.add("type", event_type) break for severity in severities: if definition.endswith(" %s" % severity): event.add("severity", severities[severity]) break return event
class TrainBot(RSSBot): feeds = bot.ListParam("RSS feed urls.") def create_event(self, **keys): id = keys.get("guid", None) location = keys.get("{http://www.georss.org/georss}point", None) if None in [id, location]: return None event = events.Event() event.add("feed", "VR") event.add("id", id) url = keys.get("source", None) if url: event.add("source", url) location = location.split(" ") latitude = location[0] longitude = location[1] event.add("latitude", latitude) event.add("longitude", longitude) description = "Train %s" % id _from = keys.get("from", None) if _from: event.add("from", _from) description += " from %s" % _from to = keys.get("to", None) if to: event.add("to", to) description += " to %s" % to event.add("description", description) direction = keys.get("dir", None) if direction: event.add("direction", direction) return event
class RSSBot(bot.PollingBot): feeds = bot.ListParam("a list of RSS feed URLs") use_cymru_whois = bot.BoolParam() http_headers = bot.ListParam("a list of http header (k,v) tuples", default=[]) def __init__(self, *args, **keys): bot.PollingBot.__init__(self, *args, **keys) def feed_keys(self, **_): for feed in self.feeds: yield (feed, ) def poll(self, url): if self.use_cymru_whois: return self._poll(url) | cymruwhois.augment() return self._poll(url) @idiokit.stream def _poll(self, url): request = urllib2.Request(url) for key, value in self.http_headers: request.add_header(key, value) try: self.log.info('Downloading feed from: "%s"', url) _, fileobj = yield utils.fetch_url(request) except utils.FetchUrlFailed as e: self.log.error('Failed to download feed "%s": %r', url, e) idiokit.stop(False) self.log.info("Finished downloading the feed.") byte = fileobj.read(1) while byte and byte != "<": byte = fileobj.read(1) if byte == "<": fileobj.seek(-1, 1) try: for _, elem in etree.iterparse(fileobj): for event in self._parse(elem, url): if event: yield idiokit.send(event) except ParseError as e: self.log.error('Invalid format on feed: "%s", "%r"', url, e) def _parse(self, elem, url): items = elem.findall("item") if not items: return for item in items: args = {"source": url} for element in list(item): if element.text and element.tag: args[element.tag] = element.text event = self.create_event(**args) yield event def create_event(self, **keys): event = events.Event() for key, value in keys.iteritems(): if value: event.add(key, value) return event
class Roomreader(bot.XMPPBot): bot_name = "roomreader" xmpp_rooms = bot.ListParam(""" comma separated list of XMPP rooms roomreader should watch. (e.g. [email protected], [email protected]) """) show_events = bot.BoolParam("print out events from channels") @idiokit.stream def main(self): try: xmpp = yield self.xmpp_connect() rooms = list() for name in self.xmpp_rooms: room = yield xmpp.muc.join(name, self.bot_name) rooms.append( room | self.xmpp_to_log(room.jid, room.participants)) yield idiokit.pipe(*rooms) except idiokit.Signal: pass @idiokit.stream def xmpp_to_log(self, own_jid, participants): in_room = set() for participant in participants: in_room.add(participant.name.resource) while True: elements = yield idiokit.next() for message in elements.with_attrs("from"): sender = JID(elements.get_attr("from")) if sender == own_jid or sender.resource is None: continue resource = sender.resource.encode("unicode-escape") bare = unicode(sender.bare()).encode("unicode-escape") type_ = message.get_attr("type", None) if type_ == "unavailable": if sender.resource in in_room: in_room.discard(sender.resource) self.log.info("* {0} left the room {1}.".format( resource, bare)) else: if sender.resource not in in_room: in_room.add(sender.resource) self.log.info("* {0} entered the room {1}.".format( resource, bare)) for body in message.children("body"): self.log.info("<{0}> {1}".format( unicode(sender).encode("unicode-escape"), body.text.encode("unicode-escape"))) if self.show_events: for event in events.Event.from_elements(message): self.log.info("<{0}> {1}".format( unicode(sender).encode("unicode-escape"), event))