Hacking Team: el cazador cazado

hackinteamSegún un informe de Reporteros sin Fronteras, Hacking Team es una empresa con base en Milán que vende herramientas software de intrusión y vigilancia a gobiernos y agencias de seguridad gubernamentales. Sus sistemas de control remoto permiten a los gobiernos monitorizar las comunicacones de los usuarios de Internet, descifrar sus ficheros y mensajes de correo cifrados, grabar conversaciones de Skype y otras comunicaciones de VoIP activar remotamente micrófonos y cámaras en los ordenadores bajo control.

No debemos creernos todo lo que se dice 😉 (eso de descifrar ficheros y mensajes cifrados no termina de cuadrarme), pero claramente, la empresa Hacking Team no era precisamente una empresa bien vista en los círculos de seguridad informática. Entre otras cosas ha sido criticada varias veces por vender sus servicios a gobiernos bajo sospecha de violación de derechos humanos (Sudán).

Hace un par de días (el 5 de julio), Hacking Team fue hackeada (el cazador cazado). Su cuenta en Twitter fue secuestrada y se publicó un torrent con unos 400GB de datos (incluyendo emails, facturas, código fuente de los exploits y herramientas usadas…).

Uno de los exploits era un 0day para Flash, aunque entre las herramientas se encuentran también herramientas para monitorizar Windows Phone, exploits para Win32, Android, etc.

Curiosamente, ayer mismo alguien clonó los repos git (tras extraerlos del torrent de 400GB) en GitHub, https://github.com/hackedteam/, pero hoy – como siempre ocurre en estos casos – ya no está disponible. Sin embargo, Internet se mueve a un ritmo endiablado, y hubo gente que no sólo hizo un mirror de los repos Git, sino de todo el material publicado en el torrent http://ht.transparencytoolkit.org/.

La cuenta Twitter de Christian Pozzi (https://twitter.com/christian_pozzi), uno de los miembros del Hacking Team, indicó ayer que el material contenido en el torrent contenía un virus e información falsa. Poco tiempo después, la cuenta de Pozzi fue (tras ser supuestamente crackeada, yo no lo llegué a ver) cerrada.

También Anonymous (@YourAnonGlobal) hace su trabajo, y ya ha detectado, dentro del módulo RCS (Remote Control System, el módulo principal de la empresa Hacking Team) código que parece implantar «evidencias» ilegales en el ordenador bajo control.
evidencias

La cuenta Twitter de la Confederación de Partidos Pirata (@partidos_pirata) informa sobre una hoja de cálculo dentro de los archivos del torrent donde se pueden ver supuestos clientes de la empresa: Policía Nacional y CNI (ES) entre ellos.

Al Hacking Team, lógicamente, no le gustaba el proyecto Tor. Desde allí le dedican un mensaje.

Parcheando GHOST

GHOST es un nuevo bug de seguridad (esta vez en glibc, más en concreto en la función gehostbyname que se usa en casi cualquier aplicación que trabaje con la red, como por ejemplo, apache, mysql, ssh…) que permite tomar el control remoto de una máquina Linux. Se hizo público ayer. Dada la peligrosidad del bug, estás tardando en aplicar el parche.

¿No sabes si eres vulnerable? Bien, prueba esto:

Copia este código

Compílalo

gcc -o ghost gistfile1.c

Ejecútalo:

./ghost

Y verás si tu kernel es vulnerable o no. Ubuntu 14.01 está libre. Ubuntu 12.04 es vulnerable. CentOS 5.11 es vulnerable, CentOS 6.6 es vulnerable, CentOS 7.0.1406 es vulnerable.

Update: un vídeo del ISC (Internet Storm Center) que explica con más detalle el bug.

HackIt! 2014 : epílogo

Tras mi insistencia a @ramonechavarri y @abeaumont de w0pr para que nos echara un cable con el writeup de alguno de los dos levels del HackIt! 2014 que se nos quedaron en el tintero, nos pasaron hace unos días un extenso y completo texto que explica paso a paso en qué consiste y cómo superar el último nivel, la marcanada del año (TM) 🙂

Captura de pantalla 2014-09-28 a la(s) 18.55.05

¡Disfrutadlo!

HackIt! 2014: final

Hubo otra prueba que nos tuvo a todos los participantes danzando al lado de la «caseta» de redes y sistemas. @marcan42 puso una Raspberry Pi enviando pings extraños vía wifi. El título del level era «Raspberry Pi(ng)» y el enunciado decía:

«Hemos montado una Raspberry Pi de router wifi (¡no se lo digas a Iban!), pero le ha poseído algo raro al kernel y nos hace cosas muy raras. ¿Podéis mirar a ver qué pasa? La contraseña es 0hvoit6e y el SSID lo he puesto yo».

Una vez encontrado el SSID (como digo, cerca de «control»), tuvimos algunos problemas porque de vez en cuando la señal desaparecía. Cuando conseguimos encontrarla de forma estable (por el camino desvirtualizamos a la gente de amnesia, como @L0ngin0s y @thePoPe, ¡kudos! y hablamos con @tatai, que lógicamente no soltó prenda – tenían la prueba pasada – entre otras cosas porque había 2 equipos justo detrás de NavarParty a un level) lanzamos Wireshark para esnifar y analizar el tráfico. Lo primero que nos sorprendió fue el ver paquetes IPv4 con contenido IPv6 (¿WTF?) como éstos:

eth1_wireshark_scapy

La IP 192.168.42.1 estaba emitiendo en broadcast mensajes de ping con el texto «I’m so lonely here :-(» La idea era responderle a esos pings (con empaquetamiento travestido). Había que afinar con el id de respuesta y el id de secuencia. El checksum… por ahora mejor no ponerlo, y si luego lo pide, ya nos estrujaremos un poco más el bolo 🙂

Bien, ¿y cómo demonios generamos un paquete ping con esas características tan «al detalle»? Con Scapy, una potente herramienta de manipulación de paquetes desarrollada en Python.

Construimos un paquete como el siguiente:

scapy_2
(UPDATE: añadir data=»I’m so lonely here :-(» como opción a ICMPv6EchoReply)

y vimos que se enviaba, pero no a la dirección MAC correcta sino en broadcast… Mmmh… momento para sacar la MAC de la Raspi de Wireshark y fijarla a manivela:

sudo arp -s 192.168.42.1  78:54:2e:25:4c:23

Ahora debería funcionar… lanzamos de nuevo el paquete Scapy y… no vemos respuesta en Wireshark. ¿Qué demonios? Aquí es cuando @marcan42 nos comunicó que quedaban 3 minutos… 2 minutos… 1 minuto… Ya sabéis, con calma 🙂
@ochoto fue a quien se le ocurrió: igual es que el filtro de Wireshark no era correcto y estábamos «escondiendo» la respuesta de la Raspi… ¿por qué no filtrar por la MAC en lugar de por el protocolo? Veamos…

scapy_3

¡Bingo! (PD: hay que ser maléfico para devolver la respuesta justo con un empaquetamiento inverso al que se nos presentó originalmente, IPv6 sobre IPv4). «Pero… ¿qué hora es? Las 14:01… oh, ¡shit! perdimos por 1 minuto…»

Acabada la prueba nos fuimos a hablar con la gente de @w0pr y @tatai que andaban por allí. @abeaumont y su equipo estuvo luchando con el último level, «la marcanada del año», durante muchas horas y -también sobre la campana de las 14:00- parece ser que lo resolvieron. Hubo algún revuelo porque llamaron a @marcan para que revisara la prueba… No sé si finalmente entró su solución o no, pero lo que sí sé es que terminaron todos los niveles y eso, señoras y señores, es para quitarse el sombrero.

HackIt! 2014 _ Level 6

¿Level 6? ¿Y qué ha pasado con el level 5? El nivel 5 está cocinándose en el server de @marcan42 (prepararlo sobre un server con arquitectura Big Endian requiere su tiempo ;-). Así que vamos a por el level 6. No pudimos superarlo en la competición. Tras la Euskal, con ayuda de Timosoft, supimos por dónde tirar. El título del reto es «A null is a null» y reza así: ‘Hemos recibido una imagen forense de una tarjeta SD, pero no encontramos nada interesante…’. La imagen es un fichero llamado image:

$ file image
image: Linux rev 1.0 ext4 filesystem data (extents) (huge files)

Intentamos montarlo en modo lectura:

$ sudo losetup -r -o 0 /dev/loop0 image
$ sudo mount -o ro,noexec,noload /dev/loop0 /tmp/s
$ ls -al /tmp/s
-rw-r--r--  1 root root 179201 jul 24 18:55 data.bin
drwx------  2 root root  12288 jul 24 18:55 lost+found

El directorio lost+found está vacío. El fichero data.bin está compuesto de 0’s.

$ file /tmp/s/data.bin
/tmp/s/data.bin: data
 
$ strings /tmp/s/data.bin
$

Pues qué bien… aquí nos quedamos clavados. @acuartango le dedicó unas cuantas horas a salir del atolladero, pero no conseguimos ver la solución.

La cuestión es que el fichero image es un fichero disperso (sparse). Algunos bloques están ocupados – con 0’s – y otros no. Podemos verlo con este comando:

 
$ debugfs -R "stat data.bin" image
 
Inode: 12   Type: regular    Mode:  0644   Flags: 0x80000
Generation: 2845516278    Version: 0x00000001
User:     0   Group:     0   Size: 179201
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 166
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x53d13a76 -- Thu Jul 24 12:55:18 2014
atime: 0x53d13a76 -- Thu Jul 24 12:55:18 2014
mtime: 0x53d13a76 -- Thu Jul 24 12:55:18 2014
EXTENTS:
(ETB0):1594, (1-3):1082-1084, (6):1085, (10-11):1086-1087, (14-15):1088-1089, (18-19):2019-2020, (21):2022, (25-26):2026-2027, (29):2030, (34-35):1987-1988, (39):1992, (41):1994, (44-46):1997-1999, (49):2002, (53-55):2006-2008, (57):2010, (62):2015, (66-67):1923-1924, (70-71):1927-1928, (73-75):1930-1932, (77):1934, (81-83):1938-1940, (85-87):1942-1944, (90-91):1947-1948, (94-95):1951-1952, (98-99):1955-1956, (102-103):1959-1960, (105):1962, (108-110):1965-1967, (113-115):1970-1972, (117):1974, (121-122):1978-1979, (124):1981, (130-131):1795-1796, (134-135):1799-1800, (137):1802, (142):1807, (145-148):1810-1813, (151):1816, (153):1818, (155):1820, (157):1822, (161):1826, (165):1830, (167):1832, (169-171):1834-1836, (174-175):1839-1840

A null is a null… Si no hay bloque ocupado, tendremos un 0. Si está ocupado, tendremos un 1. Así,

0 --> 0
1-3 --> 111
4 --> 0
5 --> 0
6 --> 1

Si agrupamos esa ristra de bits (0’s y 1’s) de 8 en 8, y los interpretamos como caracteres ASCII, tendremos la solución 😉

Un script quick&dirty en Python que lo hace por nosotros:

import re
import sys
 
# recuerda ejecutar antes debugfs -R "stat data.bin" image > stats.txt
 
fname = './stats.txt'
 
with open(fname) as f:
    content = f.readlines()  
 
res = []
for line in content:
    if re.search('(', line):
       res.append(line)
 
cadena = ''
last = 0
for i in res[0].split(',')[1:] :
   m = re.search('(?P<num>(.*))', i)
   par = m.group('num')
   n = re.search('(d+)(-(d+))?', par)
   inicio = int(n.group(1))
   if len(n.groups()) < 2 or n.group(3) is None:
      fin = inicio
   else:
      fin = int(n.group(3))
   for j in range( inicio, fin+1):
      if last < fin:
        for k in range (last, inicio):
            cadena +='0'
      cadena +='1'
      last = fin+1
 
string_blocks = (cadena[i:i+8] for i in range(0, len(cadena), 8)) 
string = ''.join(chr(int(char, 2)) for char in string_blocks)
 
print string