├── screens ├── screen1.png └── screen2.png ├── src ├── exiftransformer.pyc ├── narsil.py ├── narsil.kv └── exiftransformer.py └── README.md /screens/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neo23x0/narsil/master/screens/screen1.png -------------------------------------------------------------------------------- /screens/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neo23x0/narsil/master/screens/screen2.png -------------------------------------------------------------------------------- /src/exiftransformer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neo23x0/narsil/master/src/exiftransformer.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Narsil - Spy Agency Teasing 2 | 3 | Narsil is meant as a defense tool with various functions. 4 | 5 | ## Nasty EXIF 6 | 7 | One function is to trigger alerts or generate errors in the NSA XKeyscore system by generating files with metadata that are highly suspicious. Nasty EXIF changes the EXIF Metadata of JPEG files to high profile GPS locations. You can select a single file or a directory. To make blacklisting of those files nearly impossible, I integrated a random generator for the GPS coordinates. Locations will be 0 to around 700 meters around the NSA Headquarter, Pentagon and Keith Alexanders new "private" work place. 8 | 9 | ## Encrypted ZIP Bomb 10 | 11 | This will be an encrypted ZIP file generator that packs numerous equal characters of an arbitraty symbol into an encrypted ZIP file with an easily guessable password like "abc" and a high profile word in the file name like "civil_rights_demo_confidential.zip". The encrypted file contains more than 50 GB of data. 12 | This will not do harm to any AV system on the transit, as they do not crack simple ZIP passwords. But the NSA and other criminals do. ;) 13 | 14 | ## HoneyDocs 15 | 16 | The HoneyDocs Feature will also be integrated in future release 17 | 18 | # Use 19 | 20 | ## Sources 21 | 22 | To execute narsil.py you'll need a copy of Kivy. Download it from [here](http://kivy.org/#download). Than start kivy.bat and navigate to the narsil directory. Run narsil with "python narsil.py". 23 | 24 | You can build it on your own with PyInstaller. Find tutorials [here](http://kivy.org/docs/guide/packaging-windows.html) 25 | 26 | ## Binaries 27 | 28 | Binaries should run on all Windows Systems with OpenGL 2.x+ - If you want to run it in Virtual Machines, you should consider that. 29 | 30 | # Screenshots 31 | 32 | ![Screenshot 1](./screens/screen1.png) 33 | ![Screenshot 2](./screens/screen2.png) 34 | -------------------------------------------------------------------------------- /src/narsil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | # -*- coding: utf-8 -*- 4 | # 5 | # Florian Roth 6 | 7 | from kivy.app import App 8 | from kivy.uix.tabbedpanel import TabbedPanel 9 | from kivy.uix.floatlayout import FloatLayout 10 | from kivy.factory import Factory 11 | from kivy.properties import ObjectProperty 12 | from kivy.uix.popup import Popup 13 | 14 | import os 15 | import exiftransformer 16 | import traceback 17 | 18 | 19 | class LoadDialog(FloatLayout): 20 | load = ObjectProperty(None) 21 | cancel = ObjectProperty(None) 22 | 23 | 24 | class Error(FloatLayout): 25 | message = ObjectProperty(None) 26 | cancel = ObjectProperty(None) 27 | 28 | 29 | class Root(TabbedPanel): 30 | loadfile = ObjectProperty(None) 31 | savefile = ObjectProperty(None) 32 | txt_filename = ObjectProperty(None) 33 | txt_log = ObjectProperty(None) 34 | target_set = False 35 | location_set = False 36 | locations = [] 37 | 38 | def dismiss_popup(self): 39 | self._popup.dismiss() 40 | 41 | def on_dropfile(self, filename): 42 | print filename 43 | 44 | def load(self, path, filename): 45 | #with open(os.path.join(path, filename[0])) as stream: 46 | # self.text_input.text = stream.read() 47 | if filename: 48 | self.txt_filename.text = filename[0] 49 | self.target_set = True 50 | self.dismiss_popup() 51 | 52 | def show_load(self): 53 | content = LoadDialog(load=self.load, cancel=self.dismiss_popup) 54 | self._popup = Popup(title="Select image file or directory", content=content, size_hint=(0.9, 0.9)) 55 | self._popup.open() 56 | 57 | def set_location(self, location): 58 | if not location in self.locations: 59 | self.log("Adding '%s' to EXIF locations" % location) 60 | self.locations.append(location) 61 | self.location_set = True 62 | else: 63 | self.log("Removing '%s' to EXIF locations" % location) 64 | self.locations.remove(location) 65 | if len(self.locations) < 1: 66 | self.location_set = False 67 | 68 | def run_transform(self): 69 | # Check if target set 70 | if not self.target_set: 71 | self.show_error("Set image file or directory") 72 | return 0 73 | elif not self.location_set: 74 | self.show_error("Set fake EXIF location") 75 | return 0 76 | try: 77 | self.log("Starting transformation ...") 78 | transformer = exiftransformer.EXIFTransformer(self.txt_filename.text, self.locations, self) 79 | transformer.execute() 80 | self.log("Transformation completed.") 81 | self.log("---------------------------------------------") 82 | except Exception,e: 83 | self.log(traceback.format_exc()) 84 | 85 | def show_error(self, message): 86 | content = Error(message=message, cancel=self.dismiss_popup) 87 | self._popup = Popup(title="Error", content=content, size_hint=(0.3, 0.3)) 88 | self._popup.open() 89 | 90 | def log(self, message): 91 | self.txt_log.text += "\n%s" % message 92 | 93 | class Narsil(App): 94 | pass 95 | 96 | Factory.register('Root', cls=Root) 97 | Factory.register('LoadDialog', cls=LoadDialog) 98 | Factory.register('Error', cls=Error) 99 | 100 | if __name__ == '__main__': 101 | Narsil().run() -------------------------------------------------------------------------------- /src/narsil.kv: -------------------------------------------------------------------------------- 1 | #:kivy 1.1.0 2 | 3 | Root: 4 | 5 | txt_filename: txt_filename 6 | txt_log: txt_log 7 | 8 | title: 'Narsil - The Spy Agency Pain in the Ass' 9 | size_hint: 1, 1 10 | pos_hint: {'center_x': .5, 'center_y': .5} 11 | do_default_tab: False 12 | tab_width: 200 13 | 14 | TabbedPanelItem: 15 | text: 'Nasty EXIF' 16 | BoxLayout: 17 | orientation: 'vertical' 18 | 19 | # Title Description 20 | Label: 21 | color: 0.21, 0.67, 0.84, 1 22 | id: txt_filename 23 | text: 'Nasty EXIF modifies EXIF data of an image file or directory to look as if it has been taken on a high profile location.' 24 | size_hint: 1, 0.1 25 | 26 | # Select location bar 27 | BoxLayout: 28 | size_hint: 1, 0.3 29 | orientation: 'horizontal' 30 | Label: 31 | text: 'Select a fake location' 32 | size_hint: 0.3, 1 33 | canvas.before: 34 | Color: 35 | rgba: 0.21, 0.67, 0.84, 1 36 | Rectangle: 37 | pos: self.pos 38 | size: self.size 39 | BoxLayout: 40 | orientation: 'vertical' 41 | ToggleButton: 42 | text: 'Fort Meade' 43 | on_release: root.set_location('Fort Meade') 44 | ToggleButton: 45 | text: 'Pentagon' 46 | on_release: root.set_location('Pentagon') 47 | ToggleButton: 48 | text: 'Keith Alexander\'s Mansion' 49 | on_release: root.set_location('Keith Alexanders Mansion') 50 | 51 | # Select and Run Buttons 52 | BoxLayout: 53 | size_hint: 1, 0.1 54 | orientation: 'horizontal' 55 | Button: 56 | text: 'Select Image File or Directory' 57 | size_hint: 0.7, 1 58 | on_release: root.show_load() 59 | Button: 60 | text: 'Run' 61 | size_hint: 0.3, 1 62 | on_release: root.run_transform() 63 | bold: True 64 | background_color: 0.21, 0.67, 0.84, 1 65 | 66 | # Output panes 67 | Label: 68 | id: txt_filename 69 | text: 'File Name or Directory' 70 | size_hint: 1, 0.1 71 | italic: True 72 | Label: 73 | id: txt_log 74 | text_size: self.width,self.height 75 | valign: 'top' 76 | halign: 'left' 77 | text: 'Nasty EXIF up and ready ...' 78 | color: 0.21, 0.67, 0.84, 1 79 | canvas.before: 80 | Color: 81 | rgba: 0, 0, 0, 1 82 | Rectangle: 83 | pos: self.pos 84 | size: self.size 85 | 86 | TabbedPanelItem: 87 | text: 'Encrypted ZIP Bomb' 88 | BoxLayout: 89 | Label: 90 | text: 'Yes, there is more to come.' 91 | 92 | 93 | : 94 | 95 | txt_error: txt_error 96 | 97 | BoxLayout: 98 | size: root.size 99 | pos: root.pos 100 | orientation: 'vertical' 101 | Label: 102 | id: txt_error 103 | size_hint: 1, 0.7 104 | text: root.message 105 | color: 1,1,1,1 106 | bold: True 107 | canvas.before: 108 | Color: 109 | rgba: 0.8, 0, 0, 1 110 | Rectangle: 111 | pos: self.pos 112 | size: self.size 113 | Button: 114 | text: "Cancel" 115 | size_hint: 1, 0.3 116 | on_release: root.cancel() 117 | 118 | : 119 | BoxLayout: 120 | size: root.size 121 | pos: root.pos 122 | orientation: "vertical" 123 | FileChooserListView: 124 | id: filechooser 125 | dirselect: True 126 | 127 | BoxLayout: 128 | size_hint_y: None 129 | height: 30 130 | Button: 131 | text: "Cancel" 132 | on_release: root.cancel() 133 | 134 | Button: 135 | text: "Load" 136 | on_release: root.load(filechooser.path, filechooser.selection) -------------------------------------------------------------------------------- /src/exiftransformer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | # -*- coding: utf-8 -*- 4 | # 5 | # Florian Roth 6 | 7 | import os 8 | import traceback 9 | import fractions 10 | import scandir 11 | import random 12 | import math 13 | from pyexiv2 import * 14 | 15 | class Fraction(fractions.Fraction): 16 | """Only create Fractions from floats. 17 | 18 | >>> Fraction(0.3) 19 | Fraction(3, 10) 20 | >>> Fraction(1.1) 21 | Fraction(11, 10) 22 | """ 23 | 24 | def __new__(cls, value, ignore=None): 25 | """Should be compatible with Python 2.6, though untested.""" 26 | return fractions.Fraction.from_float(value).limit_denominator(99999) 27 | 28 | def dms_to_decimal(degrees, minutes, seconds, sign=' '): 29 | """Convert degrees, minutes, seconds into decimal degrees. 30 | 31 | >>> dms_to_decimal(10, 10, 10) 32 | 10.169444444444444 33 | >>> dms_to_decimal(8, 9, 10, 'S') 34 | -8.152777777777779 35 | """ 36 | return (-1 if sign[0] in 'SWsw' else 1) * ( 37 | float(degrees) + 38 | float(minutes) / 60 + 39 | float(seconds) / 3600 40 | ) 41 | 42 | 43 | def decimal_to_dms(decimal): 44 | """Convert decimal degrees into degrees, minutes, seconds. 45 | 46 | >>> decimal_to_dms(50.445891) 47 | [Fraction(50, 1), Fraction(26, 1), Fraction(113019, 2500)] 48 | >>> decimal_to_dms(-125.976893) 49 | [Fraction(125, 1), Fraction(58, 1), Fraction(92037, 2500)] 50 | """ 51 | remainder, degrees = math.modf(abs(decimal)) 52 | remainder, minutes = math.modf(remainder * 60) 53 | return [Fraction(n) for n in (degrees, minutes, remainder * 60)] 54 | 55 | 56 | class EXIFTransformer(object): 57 | 58 | file_target = False 59 | dir_target = False 60 | target = "" 61 | HIGH_PROFILE_LOCS = { "Fort Meade": { "lon": -76.7717974, "lat": 39.1079717 }, 62 | "Pentagon": { "lon": -77.0561619, "lat": 38.8707991 }, 63 | "Keith Alexanders Mansion": { "lon": -77.0389819, "lat": 38.90054 } } 64 | 65 | def __init__(self, transform_target, locations, root): 66 | 67 | self.target = transform_target 68 | self.root = root 69 | self.locations = locations 70 | 71 | # File or Directory 72 | if os.path.isfile(transform_target): 73 | self.file_target = True 74 | if os.path.isdir(transform_target): 75 | self.dir_target = True 76 | 77 | # Errors 78 | if not self.file_target and not self.dir_target: 79 | self.root.log("Target is not a file and not a directory") 80 | if not os.path.exists(transform_target): 81 | self.root.log("Target does not exist.") 82 | 83 | def execute(self): 84 | if self.file_target: 85 | self.transform(self.target) 86 | if self.dir_target: 87 | for root, directories, files in scandir.walk(self.target, followlinks=False): 88 | try: 89 | # Files scan 90 | for filename in files: 91 | # Generate composed var 92 | filePath = os.path.join(root, filename) 93 | extension = os.path.splitext(filePath)[1] 94 | 95 | if extension.lower() == ".jpg": 96 | self.transform(filePath) 97 | 98 | except Exception, e: 99 | self.root.log(traceback.format_exc()) 100 | 101 | def transform(self, filePath): 102 | try: 103 | # Selecting new location 104 | rand_loc = random.choice(self.locations) 105 | if rand_loc in self.HIGH_PROFILE_LOCS: 106 | loc = self.HIGH_PROFILE_LOCS[rand_loc] 107 | # Randomize a bit to make blacklisting harder 108 | lat = loc["lat"] + random.uniform(-0.004, 0.004) 109 | lon = loc["lon"] + random.uniform(-0.004, 0.004) 110 | 111 | # Reading EXIF data 112 | self.root.log("Transforming %s - EXIF GPS Location: %s - new LON: %s new LAT: %s" % (filePath, rand_loc, lon, lat)) 113 | GPS = 'Exif.GPSInfo.GPS' 114 | metadata = ImageMetadata(filePath) 115 | metadata.read() 116 | 117 | metadata[GPS + 'Latitude'] = decimal_to_dms(lat) 118 | metadata[GPS + 'LatitudeRef'] = 'N' if lat >= 0 else 'S' 119 | metadata[GPS + 'Longitude'] = decimal_to_dms(lon) 120 | metadata[GPS + 'LongitudeRef'] = 'E' if lon >= 0 else 'W' 121 | 122 | metadata.write() 123 | 124 | except Exception, e: 125 | self.root.log(traceback.format_exc()) --------------------------------------------------------------------------------