├── LICENSE ├── NtFileSins-v2.2.jpg ├── NtFileSins.py └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 hyp3rlinx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NtFileSins-v2.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyp3rlinx/NtFileSins/cc53a08ab9491d8adcb0b78cfa2edfb0f61a0ac6/NtFileSins-v2.2.jpg -------------------------------------------------------------------------------- /NtFileSins.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | import sys,argparse,re 3 | 4 | #MIT License 5 | #Copyright (c) 2020 John Page (aka hyp3rlinx) 6 | #Permission is hereby granted, free of charge, to any person obtaining a copy 7 | #of this software and associated documentation files (the "Software"), to deal 8 | #in the Software without restriction, including without limitation the rights 9 | #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | #copies of the Software, and to permit persons to whom the Software is 11 | #furnished to do so, subject to the following conditions: 12 | 13 | #The above copyright notice and this permission notice shall be included in all 14 | #copies or substantial portions of the Software. 15 | 16 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | #SOFTWARE. 23 | 24 | #Permission is also explicitly given for insertion in vulnerability databases and similar, 25 | #provided that due credit is given to the author John Page (aka hyp3rlinx). 26 | # 27 | # 28 | # NtFileSins v2.2 (c) 29 | # By John Page (aka hyp3rlinx) 30 | # Python v3 compatible 31 | # Enhancements: search target user dir on first pass, unless the -d flag is used, added .dat, .tmp file ext checks. 32 | # TODO: Alternate Data Streams (ADS) check e.g. abc.txt:test.txt:$DATA 33 | # Original advisory: http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-NTFS-PRIVILEGED-FILE-ACCESS-ENUMERATION.txt 34 | # 35 | # NtFileSins is a Windows File Enumeration Intel Gathering Tool. 36 | # Standard users can prove existence of privileged user artifacts. 37 | # 38 | # Typically, the Windows commands DIR or TYPE hand out a default "Access Denied" error message, 39 | # when a file exists or doesn't exist, when restricted access is attempted by another user. 40 | # 41 | # However, accessing files directly by attempting to "open" them from cmd.exe shell, 42 | # we can determine existence by compare inconsistent Windows error messages. 43 | # 44 | # Requirements: 1) target users with >= privileges (not admin to admin). 45 | # 2) artifacts must contain a dot "." or returns false positives. 46 | # 47 | # Windows message "Access Denied" = Exists 48 | # Windows message "The system cannot find the file" = Not exists 49 | # Windows returns "no message" OR "c:\victim\artifact is not recognized as an internal or external command, 50 | # operable program or batch file" = Admin to Admin so this script is not required. 51 | # 52 | # Profile other users by compare ntfs error messages to potentially learn their activities or machines purpose. 53 | # For evil or maybe check for basic malware IOC existence on disk with user-only rights. 54 | 55 | #From a defensive perspective we can leverage this to try to detect basic IOC and malware artifacts like .tmp, .ini, .dll, .exe 56 | #or related config files on disk with user-only rights, instead of authenticating with admin rights as a quick paranoid first pass. 57 | 58 | #Example, if malware hides itself by unlinking themselves from the EPROCESS list in memory or using programs like WinRAP to hide 59 | #processess from Windows TaskMgr, we may not discover them even if using tasklist command. The EPROCESS structure and flink/blink is 60 | #how Windows TaskMgr shows all running processes. However, we may possibly detect them by testing for the correct IOC name if the 61 | #malicious code happens to reside on disk and not only in memory. Whats cool is we can be do this without the need for admin rights. 62 | # 63 | #Other Windows commands that will also let us confirm file existence by comparing error messages are start, call, copy, icalcs, and cd. 64 | #However, Windows commands rename, ren, cacls, type, dir, erase, move or del commands will issue flat out "Access is denied" messages. 65 | 66 | # 67 | #==========================================================================# 68 | # NtFileSins.py - Windows File Enumeration Intel Gathering Tool v2.2 (c) # 69 | # By John Page (aka hyp3rlinx) # 70 | # Apparition Security # 71 | #==========================================================================# 72 | 73 | BANNER=''' 74 | _ _______________ __ _____ _ 75 | / | / /_ __/ ____(_) /__ / ___/(_)___ _____ 76 | / |/ / / / / /_ / / / _ \\__ \ / / __ \/ ___/ 77 | / /| / / / / __/ / / / __/__/ / / / / (__ ) 78 | /_/ |_/ /_/ /_/ /_/_/\___/____/_/_/ /_/____/ v2.2 (c) 79 | 80 | By hyp3rlinx 81 | ApparitionSec 82 | ''' 83 | 84 | sin_cnt=0 85 | internet_sin_cnt=0 86 | found_set=set() 87 | zone_set=set() 88 | ARTIFACTS_SET=set() 89 | ROOTDIR = "c:/Users/" 90 | ZONE_IDENTIFIER=":Zone.Identifier:$DATA" 91 | 92 | USER_DIRS=["Contacts","Desktop","Downloads","Favorites","My Documents","Searches","Videos/Captures", 93 | "Pictures","Music","OneDrive","OneDrive/Attachments","OneDrive/Documents"] 94 | 95 | APPDATA_DIR=["AppData/Local/Temp"] 96 | 97 | EXTS = set([".contact",".url",".lnk",".search-ms",".exe",".csv",".txt",".ini",".conf",".config",".log",".pcap",".zip",".mp4",".mp3", ".bat",".tmp", 98 | ".wav",".docx",".pptx",".reg",".vcf",".avi",".mpg",".jpg",".jpeg",".png",".rtf",".pdf",".dll",".xml",".doc",".gif",".xls",".wmv",".dat"]) 99 | 100 | REPORT="NtFileSins_Log.txt" 101 | 102 | def usage(): 103 | print("NtFileSins is a privileged file access enumeration tool to search multi-account artifacts without admin rights.\n") 104 | print('-u victim -d Searches -a "MS17-020 - Google Search.url"') 105 | print('-u victim -a ""') 106 | print("-u victim -d Downloads -a -s") 107 | print('-u victim -d Contacts -a "Mike N.contact"') 108 | print("-u victim -a APT -b -n") 109 | print("-u victim -d -z Desktop/MyFiles -a <.name>") 110 | print("-u victim -d Searches -a .search-ms") 111 | print("-u victim -d . -a ") 112 | print("-u victim -d desktop -a inverted-crosses.mp3 -b") 113 | print("-u victim -d Downloads -a APT.exe -b") 114 | print("-u victim -f list_of_files.txt") 115 | print("-u victim -f list_of_files.txt -b -s") 116 | print("-u victim -f list_of_files.txt -x .txt") 117 | print("-u victim -d desktop -f list_of_files.txt -b") 118 | print("-u victim -d desktop -f list_of_files.txt -x .rar") 119 | print("-u victim -z -s -f list_of_files.txt") 120 | 121 | def parse_args(): 122 | parser.add_argument("-u", "--user", help="Privileged user target") 123 | parser.add_argument("-d", "--directory", nargs="?", help="Specific directory to search .") 124 | parser.add_argument("-a", "--artifact", help="Single artifact we want to verify exists.") 125 | parser.add_argument("-t", "--appdata", nargs="?", const="1", help="Searches the AppData/Local/Temp directory.") 126 | parser.add_argument("-f", "--artifacts_from_file", nargs="?", help="Enumerate a list of supplied artifacts from a file.") 127 | parser.add_argument("-n", "--notfound", nargs="?", const="1", help="Display unfound artifacts.") 128 | parser.add_argument("-b", "--built_in_ext", nargs="?", const="1", help="Enumerate files using NtFileSin built-in ext types.") 129 | parser.add_argument("-x", "--specific_ext", nargs="?", help="Enumerate using specific ext, e.g. <.exe> using a supplied list of artifacts, a supplied ext will override any in the supplied artifact list.") 130 | parser.add_argument("-z", "--zone_identifier", nargs="?", const="1", help="Identifies artifacts downloaded from the internet by checking for Zone.Identifier:$DATA.") 131 | #parser.add_argument("-r", "--ads_streams", nargs="?", const="1", help="Locate ADS hidden file streams (artifact name required).") 132 | parser.add_argument("-s", "--save", nargs="?", const="1", help="Saves successfully enumerated artifacts, will log to "+REPORT) 133 | parser.add_argument("-v", "--verbose", nargs="?", const="1", help="Displays the file access error messages.") 134 | parser.add_argument("-e", "--examples", nargs="?", const="1", help="Show example usage.") 135 | return parser.parse_args() 136 | 137 | 138 | def access(j): 139 | result="" 140 | try: 141 | p = Popen([j], stdout=PIPE, stderr=PIPE, shell=True) 142 | stderr,stdout = p.communicate() 143 | result = stdout.strip() 144 | res = result.decode("utf-8") 145 | except Exception as e: 146 | #print(str(e)) 147 | pass 148 | return res 149 | 150 | 151 | def artifacts_from_file(artifacts_file, bflag, specific_ext): 152 | try: 153 | f=open(artifacts_file, "r") 154 | for a in f: 155 | idx = a.rfind(".") 156 | a = a.strip() 157 | if a != "": 158 | if specific_ext: 159 | if idx==-1: 160 | a = a + specific_ext 161 | else: 162 | #replace existing ext 163 | a = a[:idx] + specific_ext 164 | if bflag: 165 | ARTIFACTS_SET.add(a) 166 | else: 167 | ARTIFACTS_SET.add(a) 168 | f.close() 169 | except Exception as e: 170 | print(str(e)) 171 | exit() 172 | 173 | 174 | def save(): 175 | try: 176 | f=open(REPORT, "w") 177 | for j in found_set: 178 | f.write(j+"\n") 179 | f.close() 180 | except Exception as e: 181 | print(str(e)) 182 | 183 | 184 | def recon_msg(s): 185 | if s == 0: 186 | return "Access is denied." 187 | else: 188 | return "\t[*] Artifact exists ==>" 189 | 190 | 191 | def echo_results(args, res, x, i): 192 | global sin_cnt 193 | if res=="": 194 | print("\t[!] No NTFS message, you must already be admin, then this script is not required.") 195 | exit() 196 | if "not recognized as an internal or external command" in res: 197 | print("\t[!] You must target users with higher privileges than yours.") 198 | exit() 199 | if res != recon_msg(0): 200 | if args.verbose: 201 | print("\t"+res) 202 | else: 203 | if args.notfound: 204 | print("\t[-] not found: " + x +"/"+ i) 205 | else: 206 | sin_cnt += 1 207 | if args.save or args.zone_identifier: 208 | found_set.add(x+"/"+i) 209 | if args.verbose: 210 | print(recon_msg(1)+ x+"/"+i) 211 | print("\t"+res) 212 | else: 213 | print(recon_msg(1)+ x+"/"+i) 214 | 215 | 216 | def valid_artifact_name(sin,args): 217 | idx = "." in sin 218 | if re.findall(r"[/\\*?:<>|]", sin): 219 | print("\t[!] Skipping: disallowed file name character.") 220 | return False 221 | if not idx and not args.built_in_ext and not args.specific_ext: 222 | print("\t[!] Warning: '"+ sin +"' has no '.' in the artifact name, this can result in false positives.") 223 | print("\t[+] Searching for '"+ sin +"' using built-in ext list to prevent false positives.") 224 | if not args.built_in_ext: 225 | if sin[-1] == ".": 226 | print("\t[!] Skipping: "+sin+" non valid file name.") 227 | return False 228 | return True 229 | 230 | 231 | def search_missing_ext(path,args,i): 232 | res="" 233 | for x in path: 234 | for e in EXTS: 235 | res = access(ROOTDIR+"/"+x+"/"+i+e) 236 | if res=="": 237 | res = access(ROOTDIR+args.user+"/"+x+"/"+i+e) 238 | if res: 239 | echo_results(args, res, x, i+e) 240 | 241 | 242 | #Check if the found artifact was downloaded from internet 243 | def zone_identifier_check(args): 244 | 245 | global ROOTDIR, internet_sin_cnt 246 | zone_set.update(found_set) 247 | 248 | for c in found_set: 249 | c = c + ZONE_IDENTIFIER 250 | res = access(ROOTDIR+args.user+"/"+c) 251 | if res == "Access is denied.": 252 | internet_sin_cnt += 1 253 | print("\t[$] Zone Identifier found: "+c+" this file was downloaded over the internet!.") 254 | zone_set.add(c) 255 | 256 | 257 | #@TODO: Find ADS 258 | def alternate_data_dreams(): 259 | pass 260 | 261 | 262 | def ntsins(path,args,i): 263 | res="" 264 | if i.rfind(".")==-1: 265 | search_missing_ext(path,args,i) 266 | i="" 267 | for x in path: 268 | if i != "": 269 | if args.built_in_ext=="1": 270 | for e in EXTS: 271 | #Search current targets user dir first. 272 | res = access(ROOTDIR+"/"+x+"/"+i+e) 273 | if res=="": 274 | res = access(ROOTDIR+args.user+"/"+x+"/"+i+e) 275 | if res: 276 | echo_results(args, res, x, i+e) 277 | elif args.specific_ext: 278 | idx = i.rfind(".") 279 | if idx == -1: 280 | i = i + "." 281 | else: 282 | i = i[:idx] + args.specific_ext 283 | #Search current targets user dir first. 284 | res = access(ROOTDIR+"/"+x+"/"+i) 285 | if res=="": 286 | res = access(ROOTDIR+args.user+"/"+x+"/"+i) 287 | if res: 288 | echo_results(args, res, x, i) 289 | 290 | 291 | def search(args): 292 | print("\tSearching...\n") 293 | global ROOTDIR, USER_DIRS, ARTIFACTS_SET 294 | 295 | if args.artifact: 296 | ARTIFACTS_SET = set([args.artifact]) 297 | 298 | for i in ARTIFACTS_SET: 299 | idx = i.rfind(".") + 1 300 | if idx and args.built_in_ext: 301 | i = i[:idx -1:None] 302 | if len(i) > 0 and i != None: 303 | if valid_artifact_name(i,args): 304 | #specific user dir search 305 | if args.directory: 306 | single_dir=[args.directory] 307 | ntsins(single_dir,args,i) 308 | #search appdata dirs 309 | elif args.appdata: 310 | ntsins(APPDATA_DIR,args,i) 311 | #all default user dirs 312 | else: 313 | ntsins(USER_DIRS,args,i) 314 | 315 | 316 | def check_dir_input(_dir): 317 | if len(re.findall(r":", _dir)) != 0: 318 | print("[!] Check the directory arg, NtFileSins searches under c:/Users/target by default see Help -h.") 319 | return False 320 | return True 321 | 322 | 323 | def main(args): 324 | 325 | global USER_DIRS 326 | 327 | if len(sys.argv)==1: 328 | parser.print_help(sys.stderr) 329 | sys.exit(1) 330 | 331 | if args.examples: 332 | usage() 333 | exit() 334 | 335 | if not args.user: 336 | print("[!] No target user specified see Help -h") 337 | exit() 338 | 339 | if args.appdata and args.directory: 340 | print("[!] Multiple search directories supplied see Help -h") 341 | exit() 342 | 343 | if args.specific_ext: 344 | if "." not in args.specific_ext: 345 | print("[!] Must use full extension e.g. -x ."+args.specific_ext+", dot in filenames mandatory to prevent false positives.") 346 | exit() 347 | 348 | if args.artifact and args.artifacts_from_file: 349 | print("[!] Multiple artifacts specified, use just -f or -a see Help -h") 350 | exit() 351 | 352 | if args.built_in_ext and args.specific_ext: 353 | print("\t[!] Both specific and built-in extensions supplied, use only one.") 354 | exit() 355 | 356 | if args.specific_ext and not args.artifacts_from_file: 357 | print("\t[!] -x to be used with -f flag only see Help -h.") 358 | exit() 359 | 360 | if args.artifact: 361 | if args.artifact.rfind(".")==-1 and not args.built_in_ext: 362 | print("\t[!] Artifacts must contain a .ext or will result in false positives, use -b flag (built-in ext checks).") 363 | exit() 364 | 365 | if args.directory: 366 | if not check_dir_input(args.directory): 367 | exit() 368 | 369 | if args.artifacts_from_file: 370 | artifacts_from_file(args.artifacts_from_file, args.built_in_ext, args.specific_ext) 371 | 372 | #TODO: 373 | #if args.ads_streams: 374 | #alternate_data_dreams() 375 | 376 | if not args.artifact and not args.artifacts_from_file: 377 | print("[!] Exiting, no artifacts supplied see Help -h") 378 | exit() 379 | else: 380 | #Search targets user dir by default, instead of require -d flag to specify the dir. 381 | USER_DIRS.append(args.user) 382 | search(args) 383 | 384 | if sin_cnt >= 1 and args.zone_identifier: 385 | zone_identifier_check(args) 386 | 387 | if args.save and len(found_set) != 0 and not args.zone_identifier: 388 | save() 389 | 390 | if args.save and len(zone_set) != 0: 391 | found_set.update(zone_set) 392 | save() 393 | 394 | print("\n\tNtFileSins Detected "+str(sin_cnt)+ " out of %s" % str(len(ARTIFACTS_SET)) + " Sins.\n") 395 | 396 | if args.zone_identifier and internet_sin_cnt >= 1: 397 | print("\t"+str(internet_sin_cnt) + " of the sins were internet downloaded.\n") 398 | 399 | if not args.notfound: 400 | print("\tuse -n to display unfound enumerated files.") 401 | if not args.built_in_ext: 402 | print("\tfor extra search coverage try -b flag or targeted artifact search -a.") 403 | 404 | 405 | if __name__ == "__main__": 406 | print(BANNER) 407 | parser = argparse.ArgumentParser() 408 | main(parse_args()) 409 | 410 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NtFileSins 2 | 3 | Windows File Enumeration Intel Gathering Tool. 4 | 5 | Python v3 compatible, with few enhancements made. 6 | 7 | Original source: 8 | http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-NTFS-PRIVILEGED-FILE-ACCESS-ENUMERATION.txt 9 | 10 | ![ScreenShot](NtFileSins-v2.2.jpg) 11 | --------------------------------------------------------------------------------