SecurityInside Live: Mundo Hacker Day 2017
Al igual que el año pasado, me voy al evento «Mundo Hacker Day 2017» para asistir en primera persona a las interesantes charlas que grandes expertos en seguridad van a ofrecer.
Al igual que el año pasado, me voy al evento «Mundo Hacker Day 2017» para asistir en primera persona a las interesantes charlas que grandes expertos en seguridad van a ofrecer.
Esta entrada es un poco atípica, realmente no es más que una excusa para presentaros el nuevo repositorio en GitHub de esta web.
Poco a poco quiero ir ofreciendo los scripts que me hacen la vida más sencilla y que pueden ayudaros en ciertas tareas. Para estrenar el repo, he pensado en contar cómo hago la entrada de los Viernes en las que os listo las noticias que me han parecido más interesantes de la semana.
Tras unas cuantas entradas en las que iba recopilando enlaces a mano, decidí que así no podía continuar. Como dice un gran amigo, soy muy vago, pero vago de los buenos. Eso me hace pensar en cómo automatizar absolutamente todo lo que hago para que las tareas repetitivas las haga una CPU y yo pueda centrarme en ideas felices o a imaginar qué isla me compraré cuando me toque la lotería.
Gracias a lo de internet, tiro de Twitter para estar al tanto de las noticias de última hora y suelo hacer retweet de lo que me llama la atención. Noticias, herramientas, análisis, humor… pero todo relacionado con lo que me apasiona, la seguridad de la información.
Como últimamente le he cogido cariño a Python (al pitón como le llamo alegremente por la oficina de Dive), decidí tirar de un poco de crawling vía BeautifulSoup para buscar en twitter ciertos elementos.
Básicamente lo que hago es abrir la página de Twitter de SecurityInside y busco todos los retweets. De cada uno, me quedo con los elementos interesantes (título, autor, imágen y texto) y los pongo en forma de tabla para que se vea de forma decente como una entrada del blog. Básicamente esto:
# Get retweet info # --------------------------------------------------------------------------------------- req = requests.get(url) statusCode = req.status_code if statusCode == 200: html = BeautifulSoup(req.text, "html.parser") for timeline in html.find_all('div', {'data-test-selector':'ProfileTimeline'}): for oltag in timeline.find_all('ol', {'id':'stream-items-id'}): for litag in oltag.find_all('li'): for div in litag.find_all('div', {"class" : "tweet"}): try: if div['data-retweet-id']: for small in litag.find_all('small', {"class" : "time"}): for a in small.find_all('a', {"class" : "tweet-timestamp"}): try: date = a['title'].encode("ascii", "ignore") except Exception as e: None title = div.find('p', {'class':'TweetTextSize'}).getText().split('http')[0].encode("ascii", "ignore") link = div.find('a', {'class':'twitter-timeline-link'}).getText().encode("ascii", "ignore") name = div.find('span', {'class':'username'}).getText().split('@')[1].encode("ascii", "ignore") for img in div.find_all('img'): if 'avatar' in img: image = 'http://securityinside.info/wp-content/uploads/logo.png' else: image = img['src'].encode("ascii", "ignore") print '<tr><td style="vertical-align:middle;border:0px;margin: 0px 0px"><img class="aligncenter" src="' + image + '" alt="' + name + '" width="150"/></td>\n<td style="vertical-align:middle;border:0px;margin: 0px 0px"><strong><a href="https://twitter.com/' + name + '" target="_blank">' + date + ' @' + name + ':<br></a></strong> <a href="' + link + '" target="_blank">' + title + '</a></td></tr>' except Exception as e: None else: print "Status Code %d" %statusCode
El ejemplo es sólo parte del script completo. Si quieres utilizarlo o modificarlo, puedes descargarlo desde nuestro repositorio en GitHub.
Como podéis ver, sólo tengo que copiar la línea que me genera el script e insertarla en la entrada. La tarea se completa añadiendo el texto habitual y configurando las categorías, la imágen y los valores SEO.
Vale, me vais a decir que eso también se puede automatizar. La respuesta es que si, por eso os dejo pendiente la segunda parte del artículo en la que dedicaré otro rato a generar la entrada completa (insertando en base de datos y demás).
Pero poco a poco, que estoy un poco liado gestionando los backups anti malware de la junta directiva… pero eso es otra historia que os voy a contar en breve.
¿Os lo vais a perder?
Las técnicas de para circunscribir un proceso a un espacio aislado dentro de un sistema operativo no es algo nuevo. El primer antecedente, chroot, se remonta a 1979 y fue introducido como un concepto en desarrollo para sistemas Unix que permitía aislar dentro una ruta un proceso y sus hijos de modo que para ellos, esa ruta pareciese ser el directorio raíz. Más adelante, en 1982, chroot se incorpora a sistemas operativos BSD. En 1991 es utilizado por William Cheswick, un programador e investigador en seguridad de red, para implementar una honeypot y monitorizar comportamientos maliciosos.
Estas semillas iniciales supusieron los primeros pasos de lo que se acabaría consolidando como virtualización de sistemas con la aparición de hypervisores, software que permite la ejecución virtual de sistemas completos emulando tanto el hardware como el sistema operativo.
Dejando a un lado lo que a virtualización de hardware e hypervisores se refiere, en este artículo vamos a ofrecer una visión de los contenedores, una tecnología que, partiendo de la idea base de chroot, extiende este concepto para conseguir ejecutar entornos aislados del sistema. Con el uso de contenedores, en realidad no se está virtualizando nada sino que se están manteniendo en un espacio aislado (namespace) los procesos y ficheros necesarios mientras se reutiliza el kernel del sistema anfitrión.
La primera aproximación sólida a los contenedores en sistemas *nix aparece en FreeBSD en el año 2000 con la introducción del comando jail que orienta y amplia las funcionalidades de chroot. A partir de ahí, se empieza a extender a otras plataformas como Solaris, el sistema operativo de Sun Microsystems, que en 2005 incorpora Solaris Zones permitiendo crear subsistemas aislados denominados zonas. De igual forma otros fabricantes como IBM con AIX WPARs, o HP con HP-UX Containers adoptan implementan soluciones similares.
Finalmente, en 2008, llega a Linux con LXC (Linux Containers) que da soporte al kernel para los namespaces. Los namespaces constituyen el elemento base de los contendores y es una funcionalidad del kernel que proporcionan facilidades para crear una abstracción del sistema de modo que, todo lo que sucede fuera del espacio del contenedor sea invisible al interior.
La evolución de los namespaces ha permitido pasar del “enjaulamiento” de rutas de chroot hasta los contenedores que proporcionan espacios aislados en todos los niveles: espacio de usuario, espacio de procesos, de red, puntos de montaje, etc. Esta situación se acerca a estado parecido a la virtualización que quizás podemos entender mejor como una virtualización de sistema operativo o mejor dicho, una “paralelización” puesto que se comparte una arquitectura y un kernel y no se virtualiza ningún elemento hardware o dispositivo lo que contribuye a un mejor rendimiento.
La popularidad y la utilización de los namespaces ha crecido enormemente desde su integración directa, en 2013 en el kernel 3.8 de Linux. Desde entonces se han consolidado y afianzado desarrollos basados en contenedores como por ejemplo Docker o CoreOS Rkt entre otros.
Los contenedores frente a la virtualización de hardware mejoran aspectos como:
Velocidad: Compartir el kernel significa cero latencia para levantar con contenedor.
Gestión de disco: El almacenamiento basado en sistemas de ficheros con características Copy on Write (CoW) permiten reutilizar y compartir segmentos de sólo lectura de datos (imágenes base) entre distintos contenedores y trabajar con deltas (capas de escritura) que cada contenedor puede ir añadiendo y manteniendo de forma independiente. Sistemas de ficheros como Union Filesystems (aufs), OverlayFS o Brtfs permiten este modo de operación.
lobo@SI_$ docker info Containers: 3 Running: 2 Paused: 0 Stopped: 1 Images: 102 Server Version: 1.11.1 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs Dirs: 87 Dirperm1 Supported: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge null host Kernel Version: 4.0.0-kali1-amd64 Operating System: Kali GNU/Linux 2.0 (sana) OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 997.8 MiB
Portabilidad: Un contenedor puede trasladarse a cualquier otro sistema siempre que comparta la misma arquitectura de CPU, sin necesidad de adaptaciones. Esto permite trabajar de forma rápida y cómoda con repositorios que facilitan enormemente tareas de despliegue y desarrollo.
Por otro lado algunos inconvenientes son:
Seguridad: La implementación de los namespaces es relativamente reciente, y esto unido a que el kernel es compartido, incrementa los riesgos de comprometer el host y con ello todos los contenedores que estuviesen en el mismo. Punto único de fallo.
Entornos únicos: La imposibilidad de virtualizar otros sistemas operativos puede ser una desventaja en desarrollos multiplataforma.
Escalado y migración: Aunque existen soluciones de orquestación que permiten la ejecución multicontenedor distribuidos en distintas ubicaciones, la complejidad aumenta notablemente según lo hace el número de hosts. Esto unido a la imposibilidad de migración “en caliente” para mover contenedores supone una desventaja ante soluciones completas de virtualización.
El aislamiento en los contenedores Linux se sustenta principalmente en dos funcionalidades del kernel: los namespaces y cgroups, que haciendo una aproximación sencilla podemos identificar como los mecanismos que proporcionarán la base para el aislamiento a nivel cualitativo y cuantitativo respectivamente. Desde la versión de kernel 2.6, ambas tecnologías se han ido asentando paulatinamente hasta una completa integración en el kernel 3.8. Revisando la configuración del kernel (generalmente existe un fichero de config en /boot) podemos averiguar el grado de soporte en la compilación en uso, como muestran las siguientes imágenes para un kernel 4.0:
lobo@SI_$ grep -E 'NAMESPACES|_NS=y' /boot/config-4.0.0-kali1-amd64 CONFIG_NAMESPACES=y CONFIG_UTS_NS=y CONFIG_IPC_NS=y CONFIG_USER_NS=y CONFIG_PID_NS=y CONFIG_NET_NS=y CONFIG_NCPFS_NFS_NS=y CONFIG_NCPFS_OS2_NS=y
lobo@SI_$ grep CGROUP /boot/config-4.0.0-kali1-amd64 CONFIG_CGROUPS=y # CONFIG_CGROUP_DEBUG is not set CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y # CONFIG_CGROUP_HUGETLB is not set CONFIG_CGROUP_PERF=y CONFIG_CGROUP_SCHED=y CONFIG_BLK_CGROUP=y # CONFIG_DEBUG_BLK_CGROUP is not set CONFIG_NETFILTER_XT_MATCH_CGROUP=m CONFIG_NET_CLS_CGROUP=m CONFIG_CGROUP_NET_PRIO=y CONFIG_CGROUP_NET_CLASSID=y
Desde el punto de vista de la seguridad, los namespaces permitirán espacios aislados para los procesos que contienen, lo que protege el sistema de acciones que suceden en el interior de la sandbox creada. Por otra parte los control groups (cgroups), permiten gestionar los recursos que se atribuyen a ciertos procesos, siendo posible de este modo asignar límites cuantitativos de CPU, memoria, acceso a disco, etc. Esto es muy útil para asegurar que el sistema no pueda ver comprometidos sus recursos.
Por otra parte, el uso de las capabilities del kernel complementaría la seguridad de los namespaces y los cgroups, restringiendo y evitando acciones privilegiadas dentro del contenedor que podrían establecer una vía de escape de la sandbox.
Todo lo anterior unido a la integración de los contenedores con mecanismos de control de acceso como SELinux completa un entorno de seguridad suficientemente robusto que, como siempre, necesita de un ajuste y una configuración apropiada.
Una vez introducidos los mecanismos principales de seguridad de los contenedores: namespaces, cgroups, capabilities y SELinux, vamos a echar un vistazo a su funcionamiento.
Existen 6 tipos básicos de namespaces relacionados con distintos aspectos del sistema:
Así, sobre un kernel con soporte para namespaces es posible aislar procesos utilizando las llamadas al kernel (clone) con los flags necesarios (CLONE_NEWNET, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWNS, CLONE_NEWUSER, CLONE_NEWIPC) según lo deseado. Esta es la base utilizada por frameworks como Docker para crear contenedores.
Utilizando Docker, verifiquemos en un ejemplo como los procesos dentro de un contenedor tienen namespaces distintos, lo que significa que se mueven en espacios independientes y aislados entre sí. En el escenario mostrado en la imagen siguiente, podemos ver la ejecución de una shell (sh) dentro un contenedor utilizando Docker. Con el comando “docker ps” mostramos efectivamente, un contenedor con nombre loving_albatanni que está ejecutando una shell. El pid del contenedor es 11344 desde el punto del host que, correspondería al proceso 1 dentro del mismo y que identifica al proceso “sh”, el primer comando ejecutado en el contenedor.
Podemos verificar los cambios de namespaces con el comando pstree con el flag –S, que nos mostrará la jerarquía de procesos, indicándonos entre paréntesis los cambios de contexto en los namespaces. De forma más directa, inspeccionando /proc identificamos claramente identificadores distintos en los namespaces de proceso 1 del host y el proceso 11344 del contenedor (pid 1 dentro).
lobo@SI_$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c1c14004ad6d debian "bash" 6 days ago Up 6 days nostalgic_leavitt lobo@SI_$ docker inspect loving_albatanni | grep -i pid "Pid": 11344, "PidMode": "", "PidsLimit": 0, lobo@SI_$ pstree -gsS 11344 systemd(1)───docker(751,mnt)───docker-containe(828)───docker-containe(13340)───bash(11344,ipc,mnt,net,pid,uts)───s lobo@SI_$ ls -l /proc/1/ns total 0 lrwxrwxrwx 1 root root 0 jun 8 11:22 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 jun 8 11:22 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 jun 8 11:22 net -> net:[4026531957] lrwxrwxrwx 1 root root 0 jun 8 11:22 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 jun 8 11:22 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 jun 8 11:22 uts -> uts:[4026531838] lobo@SI_$ lobo@SI_$ ls -l /proc/11344/ns total 0 lrwxrwxrwx 1 root root 0 jun 6 08:34 ipc -> ipc:[4026532153] lrwxrwxrwx 1 root root 0 jun 6 08:34 mnt -> mnt:[4026532151] lrwxrwxrwx 1 root root 0 jun 6 08:34 net -> net:[4026532156] lrwxrwxrwx 1 root root 0 jun 6 08:34 pid -> pid:[4026532154] lrwxrwxrwx 1 root root 0 jun 8 11:22 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 jun 6 08:34 uts -> uts:[4026532152] lobo@SI_$ lobo@SI_$ docker attach loving_albatanni root@c1c14004ad6d:/# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 20244 1960 ? Ss Jun01 0:00 bash root 17 0.0 0.1 17500 1828 ? R+ 09:26 0:00 ps aux
NOTA: Nótese que no hay cambio en el namespace de usuario ya que este es opcional y debe fijarse al arrancar docker. Al no haber cambio en el namespace de usuario, el usuario dentro del contenedor es el mismo usuario fuera de él. Esto quiere decir que root dentro del contenedor es root fuera, lo que entraña un riesgo de seguridad que conviene evitar configurando oportunamente la ejecución.
Los grupos de control o cgroups son una excelente herramienta para controlar la asignación de recursos hardware. Para ello se definen jerarquías en árbol en las que se agrupan los procesos del sistema apoyándose en una ruta del sistema de ficheros, generalmente ubicado en /sys/fs/cgroup (debian) o /cgroup (redhat). Simplificando, podemos imaginar como el tradicional ulimit pero mucho más extenso y granular. No es objeto de este artículo profundizar en la descripción funcional de los grupos de control, pero si es interesante ver como gracias a ello es posible recortar recursos a un contenedor y por tanto contribuir a aislar su impacto en el sistema.
De nuevo utilizando docker arrancamos dos contenedores uno sin especificar límite de memoria y un segundo aplicando un límite de 64mb utilizando el flag –m 64mb. Para demostrar la restricción de memoria corremos el programa stress para consumir 100Mb de memoria ( stress –vm 1 –vm-bytes 100M )
lobo@SI_$ docker run -it -m 64mb debian bash WARNING: Your kernel does not support swap limit capabilities, memory limited without swap. root@39769040071c:/# stress --vm 1 --vm-bytes 100M stress: info: [6] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd lobo@SI_$ docker run -it debian bash WARNING: Your kernel does not support swap limit capabilities, memory limited without swap. root@21e5fd19b14963:/# stress --vm 1 --vm-bytes 100M stress: info: [6] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
Con el comando systemd-cgtop podemos ver el consumo de recursos por distintos grupos de control y verificamos efectivamente que, uno de los contenedores no es capaz de consumir más de 64Mb:
lobo@SI_$ systemd-cgtop
Las propiedades del contenedor y sus límites podemos comprobarlas con docker inspect:
Las capabilities proporcionan un buen mecanismo para restringir privilegios. En el caso de los contenedores es especialmente útil y por ejemplo en Docker, por defecto cualquier contenedor sufre un recorte de capabilities que impiden numerosas operaciones privilegiadas tales como el montaje de sistemas de ficheros o administración de interfaces de red entre otras.
Verifiquemos con un ejemplo real como se produce este recorte de capabilities comparando una shell en el host y una shell en el contenedor.
Una shell de root en el host tiene todas las capabilities:
lobo@SI_$ echo $$ 26599 lobo@SI_$ cat /proc/26599/status | grep ^Cap CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff lobo@SI_$ capsh --decode=0000003fffffffff 0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner, cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable, cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock, cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace, cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource, cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write, cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog, cap_wake_alarm,cap_block_suspend,37 lobo@SI_$
Sin embargo, por defecto en un contenedor se restringen capabilities y como se puede ver en la siguiente imagen de un contenedor docker se eliminan todas menos unas pocas:
lobo@SI_$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 39769040071c debian "bash" 15 minutes ago Up 15 minutes condescending_lumiere 21e5fd19b149 debian "bash" 17 minutes ago Up 17 minutes sad_blackwell lobo@SI_$ docker inspect condescending_lumiere | grep -i pid "Pid": 26579, "PidMode": "", "PidsLimit": 0, lobo@SI_$ cat /proc/26579/status | grep ^Cap CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb lobo@SI_$ capsh --decode=00000000a80425fb 0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill, cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw, cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Esto se traduce en restricciones dentro del contenedor. Para ilustrar con un ejemplo: obsérvese que se han quitado las capabilities cap_net_admin y cap_sys_time, de modo quelo el kernel denegaría operaciones de cambio de fecha del sistema o manipulación de interfaces de red dentro del contenedor (aun siendo root):
lobo@SI_$ docker exec -it condescending_lumiere bash root@39769040071c:/# root@39769040071c:/# date Wed Jun 8 10:20:25 UTC 2016 root@39769040071c:/# date +Y%m%d -s "20160609" date: cannot set date: Operation not permitted root@39769040071c:/# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:03 inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:65 errors:0 dropped:0 overruns:0 frame:0 TX packets:43 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:234083 (228.5 KiB) TX bytes:3295 (3.2 KiB) root@39769040071c:/# ifconfig eth0:1 172.17.0.5 SIOCSIFADDR: Operation not permitted SIOCSIFFLAGS: Operation not permitted root@39769040071c:/#
Las capabilities necesarias pueden otorgarse o denegarse al arrancar el contenedor según se necesite.
Así por ejemplo podemos comprobar el efecto de quitar la capability NET_RAW necesaria para ejecutar ping:
lobo@SI_$ docker run -it --cap-drop net_raw alpine sh / # ping localhost PING localhost (127.0.0.1): 56 data bytes ping: permission denied (are you root?) / # id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(t ape),27(video)
Comprobamos que en las capabilities del contenedor ya no tenemos CAP_NET_RAW:
lobo@SI_$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a57f71180937 alpine "sh" 24 seconds ago Up 22 seconds focused_stallman lobo@SI_$ docker inspect focused_stallman | grep -i pid "Pid": 27070, "PidMode": "", "PidsLimit": 0, lobo@SI_$ cat /proc/27070/status | grep ^Cap CapInh: 00000000a80405fb CapPrm: 00000000a80405fb CapEff: 00000000a80405fb CapBnd: 00000000a80405fb lobo@SI_$ capsh --decode=00000000a80405fb 0x00000000a80405fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid, cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service, cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
El comando ping necesita de la capability NET_RAW como podemos comprobar con el comando getcap: lobo@SI_$ getcap /bin/ping /bin/ping = cap_net_raw+ep
Los contenedores de Linux y las tecnologías subyacentes tienen un desarrollo suficientemente sólido que han propiciado que su utilización se vaya asentando y teniendo en cuenta cada vez más. No obstante, ha de tenerse en cuenta que la falta de rodaje que conlleva esta relativa inmadurez se hace extensiva a la seguridad se echa en falta aún una base de metodologías para configurar, fortalecer y verificar aspectos que sin la debida atención pueden poner en entredicho la seguridad de sistemas “containerizados”.
Frida es un framework de instrumentación de binarios multiplataforma: en el momento de escribir este artículo, Frida puede ser usado en Windows, Mac, Linux, iOS y Android.
Frida funciona inyectando el motor de JavaScript V8 en aplicaciones nativas. De esta manera, Frida puede ejecutar código JavaScript en el contexto de la aplicación en la que está inyectado, pudiendo así acceder a la memoria, hookear funciones, llamarlas, etc.
Frida no es sólo una librería. Frida trae consigo un conjunto de herramientas que te pueden ayudar a la hora de realizar una análisis de un aplicación.
En este post os mostraré algunas de las capacidades de Frida mediante la realización de una serie de ejercicios prácticos en los que analizaremos un binario compilado para OSx (Mach-O 64-bit executable x86_64).
include #include #include #include #include char * encrypt_arg(char *data) { int key; srand(time(NULL)); key = '='; for (int i = 0; i < strlen(data); i++) { data[i] = data[i] ^ key; } return data; } int main(int argc, char *argv[]) { int i = 0; char *data; char buffer[10]; char password[10]; while(1){ printf("Password: "); scanf("%10s", password); buffer[7] = 'd'; buffer[3] = 'I'; buffer[8] = 'e'; buffer[0] = 'S'; buffer[9] = '\0'; buffer[1] = 'e'; buffer[4] = 'n'; buffer[6] = 'i'; buffer[2] = 'c'; buffer[5] = 's'; data = encrypt_arg(password); if (strcmp(data,buffer) != 0) { printf("Nice try, but nope\n"); } else { printf("Yey!\n"); } } return 0; }
Analizando el binario con radare2 (o haciendo trampa y mirando el código) se puede ver que el programa pide una contraseña y luego comprueba si esta es buena
o no.
Si la contraseña es correcta devuelve el mensaje «Yey!», y si no es buena «Nice try, but nope».
$ r2 poc -- Experts agree, security holes suck, and we fixed some of them! [0x100000e60]> pd 60 @ main ;-- main: ;-- entry0: ;-- _main: ;-- func.100000e60: 0x100000e60 55 push rbp 0x100000e61 4889e5 mov rbp, rsp 0x100000e64 4883ec50 sub rsp, 0x50 0x100000e68 c745fc000000. mov dword [rbp - 4], 0 0x100000e6f 897df8 mov dword [rbp - 8], edi 0x100000e72 488975f0 mov qword [rbp - 0x10], rsi 0x100000e76 c745ec000000. mov dword [rbp - 0x14], 0 .-> 0x100000e7d 488d3d080100. lea rdi, [rip + 0x108] ; 0x100000f8c ; section.3.__cstring ; "Password: " @ 0x100000f8c | 0x100000e84 b000 mov al, 0 | 0x100000e86 e88f000000 call sym.imp.printf | 0x100000e8b 488d3d050100. lea rdi, [rip + 0x105] ; 0x100000f97 ; str._10s ; "%10s" @ 0x100000f97 | 0x100000e92 488d75cc lea rsi, [rbp - 0x34] | 0x100000e96 8945c8 mov dword [rbp - 0x38], eax | 0x100000e99 b000 mov al, 0 | 0x100000e9b e880000000 call sym.imp.scanf | 0x100000ea0 488d7dcc lea rdi, [rbp - 0x34] | 0x100000ea4 c645dd64 mov byte [rbp - 0x23], 0x64 ; [0x64:1]=0 ; 'd' | 0x100000ea8 c645d949 mov byte [rbp - 0x27], 0x49 ; [0x49:1]=0 ; 'I' | 0x100000eac c645de65 mov byte [rbp - 0x22], 0x65 ; [0x65:1]=0 ; 'e' | 0x100000eb0 c645d653 mov byte [rbp - 0x2a], 0x53 ; [0x53:1]=0 ; 'S' | 0x100000eb4 c645df00 mov byte [rbp - 0x21], 0 | 0x100000eb8 c645d765 mov byte [rbp - 0x29], 0x65 ; [0x65:1]=0 ; 'e' | 0x100000ebc c645da6e mov byte [rbp - 0x26], 0x6e ; [0x6e:1]=0 ; 'n' | 0x100000ec0 c645dc69 mov byte [rbp - 0x24], 0x69 ; [0x69:1]=0 ; 'i' | 0x100000ec4 c645d863 mov byte [rbp - 0x28], 0x63 ; [0x63:1]=0 ; 'c' | 0x100000ec8 c645db73 mov byte [rbp - 0x25], 0x73 ; [0x73:1]=69 ; 's' ; "EXT" @ 0x73 | 0x100000ecc 8945c4 mov dword [rbp - 0x3c], eax | 0x100000ecf e8fcfeffff call sym._encrypt_arg | 0x100000ed4 488d75d6 lea rsi, [rbp - 0x2a] | 0x100000ed8 488945e0 mov qword [rbp - 0x20], rax | 0x100000edc 488b7de0 mov rdi, qword [rbp - 0x20] | 0x100000ee0 e847000000 call sym.imp.strcmp | 0x100000ee5 83f800 cmp eax, 0 ,==< 0x100000ee8 0f8416000000 je 0x100000f04 || 0x100000eee 488d3da70000. lea rdi, [rip + 0xa7] ; 0x100000f9c ; str.Nice_try__but_nope_n ; "Nice try, but nope." @ 0x100000f9c || 0x100000ef5 b000 mov al, 0 || 0x100000ef7 e81e000000 call sym.imp.printf || 0x100000efc 8945c0 mov dword [rbp - 0x40], eax ,===< 0x100000eff e911000000 jmp 0x100000f15 |`--> 0x100000f04 488d3da50000. lea rdi, [rip + 0xa5] ; 0x100000fb0 ; str.Yey__n ; "Yey!." @ 0x100000fb0 | | 0x100000f0b b000 mov al, 0 | | 0x100000f0d e808000000 call sym.imp.printf | | 0x100000f12 8945bc mov dword [rbp - 0x44], eax `-`=< 0x100000f15 e963ffffff jmp 0x100000e7d
El mensaje «Yey!» se puede ver en la linea 46 y «Nice try, buy nope.»en la linea 42.
Analizando cómo llegar a «Yey!», vemos la siguiente parte del código, dónde se llama a strcmp y si las dos cadenas son iguales, salta a la parte del código que hará que se escriba «Yey!»:
| 0x100000ee0 e847000000 call sym.imp.strcmp | 0x100000ee5 83f800 cmp eax, 0 ,==< 0x100000ee8 0f8416000000 je 0x100000f04
Analicemos el resto de llamadas que ocurren en el main del programa:
Primero hay una llamada a printf y luego una a scanf. Sin saber nada de ingeniería inversa, sólo ejecutando el binario podemos imaginar que se trata de la petición de contraseña que aparece al ejecutar el programa:
Después de eso se llama a la función encrypt_arg y por ultimo el resultado de esa función se pasa a strcmp, que lo compara con la cadena que se forma con la ejecución de las siguientes instrucciones:
| 0x100000ea4 c645dd64 mov byte [rbp - 0x23], 0x64 ; [0x64:1]=0 ; 'd' | 0x100000ea8 c645d949 mov byte [rbp - 0x27], 0x49 ; [0x49:1]=0 ; 'I' | 0x100000eac c645de65 mov byte [rbp - 0x22], 0x65 ; [0x65:1]=0 ; 'e' | 0x100000eb0 c645d653 mov byte [rbp - 0x2a], 0x53 ; [0x53:1]=0 ; 'S' | 0x100000eb4 c645df00 mov byte [rbp - 0x21], 0 | 0x100000eb8 c645d765 mov byte [rbp - 0x29], 0x65 ; [0x65:1]=0 ; 'e' | 0x100000ebc c645da6e mov byte [rbp - 0x26], 0x6e ; [0x6e:1]=0 ; 'n' | 0x100000ec0 c645dc69 mov byte [rbp - 0x24], 0x69 ; [0x69:1]=0 ; 'i' | 0x100000ec4 c645d863 mov byte [rbp - 0x28], 0x63 ; [0x63:1]=0 ; 'c' | 0x100000ec8 c645db73 mov byte [rbp - 0x25], 0x73 ; [0x73:1]=69 ; 's' ; "EXT" @ 0x73
Dependiendo del resultado de esa comparación, podremos saltaremos a «Yey!», o no.
Para resolver este ejercicio, necesitamos primero saber cual es la cadena contra la compara strcmp y luego saber que es lo que hace la función encrypt_arg con la password que introducimos antes de pasársela a strcmp. En ese momento podremos generar una cadena que tras ser procesada por encrypt_arg sea igual que el segundo argumento de strcmp.
Normalmente este tipo de cosas se pueden solucionar leyendo ensamblador y reformando la cadena, este es el caso de nuestro binario. Pero otras veces, esta cadena se forma dinámicamente en tiempo de ejecución hay que ir paso a paso con el debugger mirando la cadena que se forma.
O, si lo que queremos es sólo llegar a «Yey!» podríamos parchear la instrucción je 0x100000f04
para convertirla en un salto incondicional. Pero esto se trata de utilizar Frida, así que veamos que podemos hacer con Frida.
Frida nos permite interceptar la función y mandarnos un mensaje con el contenido de los argumentos de la función.
Para ello basta con escribir un modulo en python de este estilo:
import frida import sys session = frida.attach(int(sys.argv[2])) script = session.create_script(""" Interceptor.attach(ptr("%s"), { onEnter: function (args) { send("arg[0]: " + Memory.readCString(args[0])); send("arg[1]: " + Memory.readCString(args[1])); } }); """ % int(sys.argv[1], 16)) def on_message(message, data): print message['payload'] script.on('message', on_message) script.load() sys.stdin.read()
Este script va a interceptar la llamada en la dirección que le pasemos como primer parámetro, del proceso cuyo PID pasemos como segundo parámetro.
La dirección de la llamada función se puede sacar de muchas maneras, pero en el video de demostración que podéis ver a continuación, he usado radare2 para depurar el proceso y encontrar la dirección.
Ya sabemos entonces que la cadena que introducimos se procesa de alguna manera y se compara con SecInside. Ahora podríamos analizar la función que «cifra» la cadena (que en este caso es muy fácil, o usar Frida para hacer que strcmp siempre se cumpla.
Para eso tenemos dos opciones: hookear la llamada y modificar los argumentos, o modificar el valor de retorno. Para ambos casos la API de Frida nos lo pone fácil con sus «onEnter» y «onLeave».
En el primer caso querremos que al entrar el primer argumento sea igual que el segundo. Este se consigue muy fácil modificando un poco el script anterior:
import frida import sys session = frida.attach(int(sys.argv[2])) script = session.create_script(""" Interceptor.attach(ptr("%s"), { onEnter: function (args) { args[0] = args[1] send("arg[0]: " + Memory.readCString(args[0])); send("arg[1]: " + Memory.readCString(args[1])); } }); """ % int(sys.argv[1], 16)) def on_message(message, data): print message['payload'] script.on('message', on_message) script.load() sys.stdin.read()
En este caso la parte de los mensajes no es necesaria, pero la he dejado para que se vea claramente lo que está pasando. La clave está en la linea 8, dónde le decimos que al entrar en la función args[0] = args[1]
.
En el segundo caso, lo que haremos será aprovechar onLeave para modificar el valor de salida de strcmp que lo modificaremos para que siempre diga que las cadenas son iguales. Es decir, que siempre devuelva 0:
import frida import sys session = frida.attach(int(sys.argv[2])) script = session.create_script(""" Interceptor.attach(ptr("%s"), { onEnter: function (args) { send("arg[0]: " + Memory.readCString(args[0])); send("arg[1]: " + Memory.readCString(args[1])); }, onLeave: function (retval) { retval.replace(0); } }); """ % int(sys.argv[1], 16)) def on_message(message, data): print message['payload'] script.on('message', on_message) script.load() sys.stdin.read()
Como podéis ver, he utilizado también onEnter, pero la única razón por la que aparece es para registrar la actividad y que se vea un poco mejor lo que está pasando.
A continuación un video con estos dos scripts en acción:
Esto son sólo unos ejemplos simples de las cosas que se pueden hacer con Frida, pero vale que para lo que no lo conocíais, tengáis una idea general de las cosas que permite automatizar con unas pocas lines de JavaScript. Si te ha llamado la atención, pruébalo y cuéntanos que tal te fue en los comentarios. ¡Hasta la próxima!
Supongo que todos conocéis Netflix a estas alturas, el servicio VOD con suscripción mensual que aterrizó en España hace unos meses y ya es todo un éxito.
Pero, además de ser una forma estupenda de ver películas y series, puede ofrecernos mucho que aprender gracias a lo que algunos (yo no :D) llaman pornografía tecnológica.
Netflix es una de esas empresas que se rodea de los mejores ingenieros para poder ofrecer un servicio con calidad por encima de lo normal. En ese sentido, leer de forma regular «The Netflix Tech Blog» es altamente recomendable.
Que profesionales tan altamente cualificados como los que trabajan en Neflix dediquen una parte de su tiempo a contarnos cómo hacen las cosas, es algo de agradecer. Vivimos en un mundo en el que estamos obligados a aprender de forma contínua y este tipo de lecturas nos abren la mente a nuevas formas de hacer las cosas.
Puedes encontrar temas tan interesantes como:
Y sobre todo, en general de elementos de Amazon WS (unos servicios con los que suelo trabajar tal y como os conté en Controla lo que hacen tus usuarios con Amazon WS IAM (parte 1) y Controla lo que hacen tus usuarios con Amazon WS IAM (parte 2)).
Revisando esto, me he encontrado con una solución desarrollada por el equipo Netflix que tiene por objetivo monitorizar y analizar la seguridad de los servicios de AWS.
Se trata de un desarrollo propio que ofrecen como open source y que nos permite monitorizar la configuración de los servicios EC2, RDS, S3 e IAM. Cualquier cambio que se realice sobre ellos quedará registrado para que tengamos el control de todo lo que sucede.
Está pensada para correr sobre una máquina Linux y almacenar la información en una PostgreSQL. A nivel tecnológico está escrita en Python utilizando el framework Flask, utilizando datos JSON servidos mediante una API REST.
Casos de uso típicos serían:
Para conseguir esto, Security Monkey cuenta con tres componentes principales:
Una vez lo tengamos funcionando podremos acceder mediante su interfaz web, configurar los elementos que queremos monitorizar y ver todo en detalle.
¿Te ha gustado tanto como a mi? Entonces descarga el proyecto desde su repositorio y coméntanos qué tal tu experiencia. Yo lo haré en un post en el que os mostraré el proceso de instalación, configuración y uso. De momento, esto es todo, espero que os esté resultando interesante.
Como siempre digo, si ves algún error, no estás de acuerdo con lo que cuento o quieres hacer alguna aportación, no dudes en pasarte por los comentarios.