├── README.md ├── screens └── skelscan.png ├── skeletonkey-scanner.exe ├── skeletonkey-scanner.py └── skeletonkey_rules.yar /README.md: -------------------------------------------------------------------------------- 1 | # Obsolete 2 | 3 | The two specialiced scanners [Regin Scanner](https://github.com/Neo23x0/ReginScanner) and [Skeleton Key Scanner](https://github.com/Neo23x0/SkeletonKeyScanner) have been merged into a new generic IOC scanner called [LOKI](https://github.com/Neo23x0/Loki). 4 | 5 | LOKI features a completely free IOC signature definition via different configuration files. It includes signatures for Regin, Skeleton Key and the recently published [FiveEyes QUERTY malware](http://www.spiegel.de/media/media-35668.pdf) mentioned in the [Spiegel report](http://www.spiegel.de/international/world/new-snowden-docs-indicate-scope-of-nsa-preparations-for-cyber-battle-a-1013409.html) released on 17.01.2015. 6 | 7 | LOKI is free for private and commercial use and published under the GPL. He is the little brother of [THOR](http://www.bsk-consulting.de/apt-scanner-thor/), our full featured corporate APT Scanner. 8 | 9 | # Skeleton Key Malware Scanner 10 | 11 | Scanner for Skeleton Key Malware 12 | 13 | Detection is based on four detection methods: 14 | 15 | 1. File Name IOC 16 | 17 | 2. Yara Ruleset 18 | 19 | 3. SHA1 hash check 20 | Compares known malicious SHA1 hashes with scanned files 21 | 22 | 4. Process parameter check 23 | Detecting a PsExec.exe with NTLM Hash as parameter (as described in report) 24 | 25 | All included IOCs are extracted from [this report](http://goo.gl/aAk3lN). 26 | 27 | The Windows binary is compiled with PyInstaller 2.1 and should run as x86 application on both x86 and x64 based systems. 28 | 29 | ## Antivirus - False Positives 30 | 31 | The compiled scanner is [falsely detected](https://www.virustotal.com/en/file/7f855a1e66339f00464abe89559b56c6a0559310761af4f22f7d567f8c461226/analysis/1421234417/) as a Virus by McAfee and some other second-class scanners. This may be caused by the fact that the scanner is a compiled python script that implement some file system and process scanning features that are also used in compiled malware code. 32 | 33 | If you don't trust the compiled executable, please compile it yourself. 34 | 35 | ### Compile the Scanner 36 | 37 | Download PyInstaller, switch to the pyinstaller program directory and execute: 38 | 39 | python ./pyinstaller.py -F C:\path\to\skeletonkey-scanner.py 40 | 41 | This will create a "skeletonkey-scanner.exe" in the subfolder "./skeletonkey-scanner/dist". 42 | 43 | ### Pro Tip (optional) 44 | 45 | To include the msvcr100.dll to improve the target os compatibility change the line in the file "./skeletonkey-scanner/skeletonkey-scanner.spec" that contains `a.bianries,` to the following: 46 | 47 | a.binaries + [('msvcr100.dll', 'C:\Windows\System32\msvcr100.dll', 'BINARY')], 48 | 49 | ## Requirements 50 | 51 | No requirements if you use the compiled EXE. 52 | 53 | If you want to build it yourself: 54 | 55 | - [yara](http://goo.gl/PQjmsf) : It's recommended to use the most recent version of the compiled packages for Windows (x86) - Download it from here: http://goo.gl/PQjmsf 56 | - [scandir](https://github.com/benhoyt/scandir) : faster alternative to os.walk() 57 | - [colorama](https://pypi.python.org/pypi/colorama) : to color it up 58 | 59 | ## Usage 60 | 61 | usage: skeletonkey-scanner.py [-h] [-p path] [--printAll] [--noprocscan] 62 | [--nofilescan] [--dots] [--debug] 63 | 64 | SKELETONKEY Scanner 65 | 66 | optional arguments: 67 | -h, --help show this help message and exit 68 | -p path Path to scan 69 | --printAll Print all files that are scanned 70 | --noprocscan Skip the process scan 71 | --nofilescan Skip the file scan 72 | --dots Print a dot for every scanned file to see the progress 73 | --debug Debug output 74 | 75 | ## Screenshots 76 | 77 | ![Screen](/screens/skelscan.png?raw=true) 78 | 79 | ## Notice 80 | 81 | IOCs are based on the report by the Dell SecureWorks Counter Threat Unit(TM) (CTU) researchers. Scanner has not been tested on one of the samples. 82 | 83 | ## Contact 84 | 85 | Profile on Company Homepage 86 | http://www.bsk-consulting.de/author/froth/ 87 | 88 | Twitter 89 | @MalwrSignatures 90 | 91 | If you are interested in a corporate solution for APT scanning, check: 92 | http://www.bsk-consulting.de/apt-scanner-thor/ 93 | -------------------------------------------------------------------------------- /screens/skelscan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neo23x0/SkeletonKeyScanner/5dde41076c401f6cbe4d20688f823464fd7d83a6/screens/skelscan.png -------------------------------------------------------------------------------- /skeletonkey-scanner.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neo23x0/SkeletonKeyScanner/5dde41076c401f6cbe4d20688f823464fd7d83a6/skeletonkey-scanner.exe -------------------------------------------------------------------------------- /skeletonkey-scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | # -*- coding: utf-8 -*- 4 | # 5 | # Skeleton Key Scanner 6 | # 7 | # Detection is based on three detection methods: 8 | # 9 | # 1. File Name IOC 10 | # 11 | # 2. Yara Ruleset 12 | # 13 | # 3. Hash Check 14 | # Compares known malicious SHA1 hashes with scanned files 15 | # 16 | # If you like Skeleton Key Scanner you'll love THOR our full-featured APT Scanner 17 | # 18 | # Florian Roth 19 | # BSK Consulting GmbH 20 | # January 2015 21 | # v0.1 22 | # 23 | # DISCLAIMER - USE AT YOUR OWN RISK. 24 | 25 | import sys 26 | import os 27 | import argparse 28 | import scandir 29 | import traceback 30 | import yara 31 | import hashlib 32 | import wmi 33 | import re 34 | from colorama import Fore, Back, Style 35 | from colorama import init 36 | 37 | EVIL_FILES = [ 'msuta64.dll' ] 38 | 39 | EVIL_HASHES = [ 'ad61e8daeeba43e442514b177a1b41ad4b7c6727', '5083b17ccc50dd0557dfc544f84e2ab55d6acd92' ] 40 | FALSE_POSITIVES = [ ] 41 | 42 | def scanPath(path, rules): 43 | 44 | # Startup 45 | print "Scanning %s ... " % path , 46 | # Compromised marker 47 | compromised = False 48 | c = 0 49 | 50 | for root, directories, files in scandir.walk(path, onerror=walkError, followlinks=False): 51 | 52 | # Loop through files 53 | for filename in files: 54 | try: 55 | 56 | # Get the file and path 57 | filePath = os.path.join(root,filename) 58 | 59 | # Print files 60 | if args.printAll: 61 | print "[SCANNING] %s" % filePath 62 | 63 | # Counter 64 | c += 1 65 | 66 | printProgress(c) 67 | 68 | if args.dots: 69 | sys.stdout.write(".") 70 | 71 | file_size = os.stat(filePath).st_size 72 | # print file_size 73 | 74 | # File Name Checks ------------------------------------------------- 75 | for file in EVIL_FILES: 76 | if file in filePath: 77 | print Fore.RED, "\bSKELETONKEY File Name MATCH: %s" % filePath, Fore.WHITE 78 | compromised = True 79 | 80 | # Hash Check ------------------------------------------------------- 81 | if file_size > 200000: 82 | continue 83 | 84 | sha1hash = sha1(filePath) 85 | if sha1hash in EVIL_HASHES: 86 | print Fore.RED, "\bSKELETON KEY SHA16 Hash MATCH: %s FILE: %s" % ( sha1hash, filePath), Fore.WHITE 87 | compromised = True 88 | if sha1hash in FALSE_POSITIVES: 89 | compromised = False 90 | continue 91 | 92 | # Yara Check ------------------------------------------------------- 93 | if 'rules' in locals(): 94 | try: 95 | matches = rules.match(filePath) 96 | if matches: 97 | for match in matches: 98 | print Fore.RED, "\bSKELETONKEY Yara Rule MATCH: %s FILE: %s" % ( match, filePath), Fore.WHITE 99 | compromised = True 100 | except Exception, e: 101 | if args.debug: 102 | traceback.print_exc() 103 | 104 | except Exception, e: 105 | if args.debug: 106 | traceback.print_exc() 107 | 108 | # Return result 109 | return compromised 110 | 111 | 112 | def scanProcesses(rules): 113 | # WMI Handler 114 | c = wmi.WMI() 115 | processes = c.Win32_Process() 116 | 117 | compromised = False 118 | 119 | for process in processes: 120 | 121 | try: 122 | pid = process.ProcessId 123 | name = process.Name 124 | cmd = process.CommandLine 125 | if not cmd: 126 | cmd = "N/A" 127 | if not name: 128 | name = "N/A" 129 | except Exception, e: 130 | print Fore.MAGENTA, "Error getting all process information. Did you run the scanner 'As Administrator'?", Fore.WHITE 131 | continue 132 | 133 | if pid == 0 or pid == 4: 134 | print Fore.CYAN, "Skipping Process - PID: %s NAME: %s CMD: %s" % ( pid, name, cmd ), Fore.WHITE 135 | continue 136 | 137 | print Fore.GREEN, "Scanning Process - PID: %s NAME: %s CMD: %s" % ( pid, name, cmd ), Fore.WHITE 138 | 139 | # Psexec command check 140 | # Skeleton Key Malware Process 141 | if re.search(r'psexec .* [a-fA-F0-9]{32}', cmd, re.IGNORECASE): 142 | print Fore.RED, "\bProcess that looks liks SKELETON KEY psexec execution detected PID: %s NAME: %s CMD: %s" % ( pid, name, cmd), Fore.WHITE 143 | compromised = True 144 | 145 | # Yara rule match 146 | try: 147 | matches = rules.match(pid=pid) 148 | if matches: 149 | for match in matches: 150 | print Fore.RED, "\bSKELETONKEY Yara Rule MATCH: %s PID: %s NAME: %s CMD: %s" % ( match, pid, name, cmd), Fore.WHITE 151 | compromised = True 152 | except Exception, e: 153 | print Fore.MAGENTA, "Error while process memory Yara check (maybe the process doesn't exist anymore or access denied). PID: %s NAME: %s" % ( pid, name), Fore.WHITE 154 | 155 | return compromised 156 | 157 | 158 | def sha1(filePath): 159 | try: 160 | with open(filePath, 'rb') as file: 161 | file_data = file.read() 162 | return hashlib.sha1(file_data).hexdigest() 163 | except Exception, e: 164 | if args.debug: 165 | traceback.print_exc() 166 | return 0 167 | 168 | 169 | def walkError(err): 170 | if args.debug: 171 | traceback.print_exc() 172 | 173 | 174 | def printProgress(i): 175 | if (i%4) == 0: 176 | sys.stdout.write('\b/') 177 | elif (i%4) == 1: 178 | sys.stdout.write('\b-') 179 | elif (i%4) == 2: 180 | sys.stdout.write('\b\\') 181 | elif (i%4) == 3: 182 | sys.stdout.write('\b|') 183 | sys.stdout.flush() 184 | 185 | 186 | def printWelcome(): 187 | print Back.CYAN, " ", Back.BLACK 188 | print Fore.CYAN 189 | print " SKELETON KEY SCANNER" 190 | print " " 191 | print " by Florian Roth - BSK Consulting GmbH" 192 | print " Jan 2015" 193 | print " Version 0.1" 194 | print " " 195 | print " DISCLAIMER - USE AT YOUR OWN RISK" 196 | print " " 197 | print Back.CYAN, " ", Back.BLACK 198 | print Fore.WHITE+''+Back.BLACK 199 | 200 | 201 | # MAIN ################################################################ 202 | if __name__ == '__main__': 203 | 204 | # Parse Arguments 205 | parser = argparse.ArgumentParser(description='SKELETONKEY Scanner') 206 | parser.add_argument('-p', help='Path to scan', metavar='path', default='C:\\') 207 | parser.add_argument('--printAll', action='store_true', help='Print all files that are scanned', default=False) 208 | parser.add_argument('--noprocscan', action='store_true', help='Skip the process scan', default=False) 209 | parser.add_argument('--nofilescan', action='store_true', help='Skip the file scan', default=False) 210 | parser.add_argument('--dots', action='store_true', help='Print a dot for every scanned file to see the progress', default=False) 211 | parser.add_argument('--debug', action='store_true', default=False, help='Debug output') 212 | 213 | args = parser.parse_args() 214 | 215 | # Colorization 216 | init() 217 | 218 | # Print Welcome 219 | printWelcome() 220 | 221 | # Compiling yara rules 222 | if os.path.exists('skeletonkey_rules.yar'): 223 | rules = yara.compile('skeletonkey_rules.yar') 224 | else: 225 | print "Place the yara rule file 'skeletonkey_rules.yar' in the program folder to enable Yara scanning." 226 | 227 | # Scan Processes 228 | if not args.noprocscan: 229 | result_proc = scanProcesses(rules) 230 | 231 | # Scan Path 232 | if not args.nofilescan: 233 | result_path = scanPath(args.p, rules) 234 | 235 | if result_path or result_proc: 236 | print Fore.RED+''+Back.BLACK 237 | print "\bRESULT: SKELETONKEY INDICATORS DETECTED!" 238 | print Fore.WHITE+''+Back.BLACK 239 | else: 240 | print Fore.GREEN+''+Back.BLACK 241 | print "\bRESULT: SYSTEM SEEMS TO BE CLEAN. :)" 242 | print Fore.WHITE+''+Back.BLACK 243 | 244 | raw_input("Press Enter to exit ...") -------------------------------------------------------------------------------- /skeletonkey_rules.yar: -------------------------------------------------------------------------------- 1 | rule skeleton_key_patcher 2 | { 3 | meta: 4 | description = "Skeleton Key Patcher from Dell SecureWorks Report http://goo.gl/aAk3lN" 5 | author = "Dell SecureWorks Counter Threat Unit" 6 | reference = "http://goo.gl/aAk3lN" 7 | date = "2015/01/13" 8 | score = 70 9 | strings: 10 | $target_process = "lsass.exe" wide 11 | $dll1 = "cryptdll.dll" 12 | $dll2 = "samsrv.dll" 13 | 14 | $name = "HookDC.dll" 15 | 16 | $patched1 = "CDLocateCSystem" 17 | $patched2 = "SamIRetrievePrimaryCredentials" 18 | $patched3 = "SamIRetrieveMultiplePrimaryCredentials" 19 | condition: 20 | all of them 21 | } 22 | 23 | rule skeleton_key_injected_code 24 | { 25 | meta: 26 | description = "Skeleton Key injected Code http://goo.gl/aAk3lN" 27 | author = "Dell SecureWorks Counter Threat Unit" 28 | reference = "http://goo.gl/aAk3lN" 29 | date = "2015/01/13" 30 | score = 70 31 | strings: 32 | $injected = { 33 C0 85 C9 0F 95 C0 48 8B 8C 24 40 01 00 00 48 33 CC E8 4D 02 00 33 | 00 48 81 C4 58 01 00 00 C3 } 34 | 35 | $patch_CDLocateCSystem = { 48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 20 48 8B FA 36 | 8B F1 E8 ?? ?? ?? ?? 48 8B D7 8B CE 48 8B D8 FF 50 10 44 8B D8 85 C0 0F 88 A5 00 37 | 00 00 48 85 FF 0F 84 9C 00 00 00 83 FE 17 0F 85 93 00 00 00 48 8B 07 48 85 C0 0F 38 | 84 84 00 00 00 48 83 BB 48 01 00 00 00 75 73 48 89 83 48 01 00 00 33 D2 } 39 | 40 | $patch_SamIRetrievePrimaryCredential = { 48 89 5C 24 08 48 89 6C 24 10 48 89 74 41 | 24 18 57 48 83 EC 20 49 8B F9 49 8B F0 48 8B DA 48 8B E9 48 85 D2 74 2A 48 8B 42 42 | 08 48 85 C0 74 21 66 83 3A 26 75 1B 66 83 38 4B 75 15 66 83 78 0E 73 75 0E 66 83 43 | 78 1E 4B 75 07 B8 A1 02 00 C0 EB 14 E8 ?? ?? ?? ?? 4C 8B CF 4C 8B C6 48 8B D3 48 44 | 8B CD FF 50 18 48 8B 5C 24 30 48 8B 6C 24 38 48 8B 74 24 40 48 83 C4 20 5F C3 } 45 | 46 | $patch_SamIRetrieveMultiplePrimaryCredential = { 48 89 5C 24 08 48 89 6C 24 10 47 | 48 89 74 24 18 57 48 83 EC 20 41 8B F9 49 8B D8 8B F2 8B E9 4D 85 C0 74 2B 49 8B 48 | 40 08 48 85 C0 74 22 66 41 83 38 26 75 1B 66 83 38 4B 75 15 66 83 78 0E 73 75 0E 49 | 66 83 78 1E 4B 75 07 B8 A1 02 00 C0 EB 12 E8 ?? ?? ?? ?? 44 8B CF 4C 8B C3 8B D6 50 | 8B CD FF 50 20 48 8B 5C 24 30 48 8B 6C 24 38 48 8B 74 24 40 48 83 C4 20 5F C3 } 51 | 52 | condition: 53 | any of them 54 | } --------------------------------------------------------------------------------