=-|================================================-{ www.enye-sec.org }-====| =-[ Jugando con los sockets (port scanning) ]-===============================| =-|==========================================================================| =-[ por Pepelux ]-=====================-[13/11/2007]-=| ------[ 0.- Indice ] 0.- Indice 1.- Introduccion rapida 1.1.- Escaneo con conexion abierta (Open Scan) 1.2.- Escaneo con conexion a medias (Half Open Scan) 1.3.- Otras tecnicas (Stealth Scan) 2.- Juando con los sockets 2.1.- Conexiones TCP 2.2.- Conexiones UDP / ICMP 3.- Deteccion de sistemas operativos 3.1.- Realizando algunas pruebas 4.- Archivos 5.- Referencias 6.- Despedida ------[ 1.- Introduccion ] Queda ya muy lejos aquella epoca en la que los escaneos a los diferentes servicios consistian en hacer telnet manualmente a los diferentes puertos. Segun va pasando el tiempo la gente tiende a utilizar programas mas sofisticados usandolos de manera masiva y escaneando rangos de IPs en busca de numerosos puertos. Voy a comentar muy por encima los diferentes tipos de escaneo que hay, ya que en Internet puedes encontrar mucha informacion. Ademas, la finalidad de este articulo es divertirnos un poco viendo el comportamiento de los sockets. ------[ 1.1.- Escaneo con conexion abierta (Open Scan) ] Conocida como TCP Scan y utilizada normalmente al programar sockets, esta tecnica es la mas antigua y consiste en establecer una conexion completa con el servidor. Es similar a realizar un telnet a cada servicio pero de forma automatica. Para ello se realiza una autentificacion mediante el envio de 3 paquetes: Para los puertos abiertos: Cliente ----> SYN ----> <---- SYN/ACK <---- Servidor Cliente ----> ACK ----> Para los puertos cerrados: Cliente ----> SYN ----> <---- RST <---- Servidor Ventajas : muy facil de programar. Inconvenientes: es muy facil de detectar y deja un registro en los logs por cada conexion. ------[ 1.2.- Escaneo con conexion a medias (Half Open Scan) ] Esta tecnica consiste en realizar unicamente el inicio de conexion. Lo que antes veiamos como el envio de un SYN, pero sin mandar el ACK final en caso de encontrarnos el puerto abierto. Usando RAW sockets podemos programar a mano las cabeceras de los sockets y enviar unicamente los paquetes que queramos. Mas adelante lo veremos mejor con los ejemplos. ------[ 1.3.- Otras tecnicas (Stealth Scan) ] Es similar al anterior pero en lugar de mandar un SYN se manda otro flag activo, o una combinacion de varios de ellos. Los mas conocidos y que normalmente chequea el nmap son: SYN SYN+ACK FIN ACK NULL (todos los flags a 0) XMAS (FIN=URG=PSH=1) En los ejemplos podremos verlo con mas claridad. ------[ 2.- Jugando con los sockets ] Ahora llega la parte divertida. Lo que vamos a hacer es enviar diferentes paquetes modificando los flags y estudiando los resultados. Para las pruebas he usando dos equipos: Cliente : 192.168.2.5 (Linux Debian - kernel 2.6.18.1) Servidor: 192.168.2.7 (Linux Debian - kernel 2.6.21.2) Puertos abiertos en el servidor: 22(TCP), 80(TCP), 111(UDP) ------[ 2.1.- Conexiones TCP ] Para comprobar los puertos TCP vamos a usar un programa que envia y recibe RAW sockets, permitiendonos escoger el valor de los flags: - sendsock.c -> Usage: ./sendsocket [s|a|r|p|u|f] [-x host_source] -d host_destination -c port -s SYN flag enabled -a ACK flag enabled -r RST flag enabled -p PSH flag enabled -u URG flag enabled -f FIN flag enabled Envio SYN (conexion half open) ------------------------------ Consiste en establecer una comunicacion a medias, es decir, enviar un SYN y tras obtener respuesta (bien SYN+ACK, bien RST) se manda un RST para finalizar la comunicacion (al utilizar RAW sockets, este envio del RST lo hace automaticamente el kernel). La idea original consistia en solicitar una conexion con un SYN y si el servidor devuelve un SYN+ACK aceptando la comunicacion, en lugar de mandar el acuse para iniciar la comunicacion, le enviamos un RST indicando que nosotros no queremos comunicarnos ... todo queda en un 'malentendido' y no se guarda registro de la conexion. Hoy en dia muchos ordenadores si que son capaces de guardar registro de este tipo de conexiones o incluso bloquear estos ataques con un firewall. Ventajas : funciona en todos los sistemas y puertos, a no ser que esten tras un firewall. Inconvenientes: es facilmente detectable y logeable. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -s SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=39553 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 53270 seq=-1378930670 - ack_seq=863708102 - doff=6 - check=8049 - ID=0 Puerto abierto. Obtenemos SYN+ACK y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -s SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=54145 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=863708102 - doff=5 - check=49281 - ID=0 Puerto cerrado. Obtenemos ACK+RST y Window=0 Envio ACK --------- Este tipo de ataque solo afecta a ciertos sistemas antiguos de BSD y Unix y consiste en analizar el campo Window de manera que si obtenemos un valor de Window=0 el puerto esta abierto y si obtenemos un valor de Window<>0 el puerto esta cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: no funciona en todos los sistemas operativos. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -a SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=35969 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=0 - doff=5 - check=61122 - ID=0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -a SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=50561 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=0 - doff=5 - check=10179 - ID=0 En este caso el Linux no es vulnerable y obtenemos el mismo resultado en puertos abiertos y cerrados. Envio RST --------- Este ataque afecta tambien a ciertos sistemas no siendo siempre efectivo. En caso de obtener solo un ACK o no obtener respuesta, el puerto esta abierto y en caso de obtener un RST, esta cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: no funciona en todos los sistemas ni con todos los puertos. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -r SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=39041 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 0 | 53 | 37920 seq=1193401395 - ack_seq=908914171 - doff=5 - check=43532 - ID=4979 Puerto abierto. Obtenemos ACK+PSH, TTL<64 y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -r SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=53633 - ID=27032 Sin respuesta ... Puerto cerrado flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -r SENT DATA | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=53889 - ID=27032 Sin respuesta ... Resultado engañoso dado que el puerto esta abierto realmente Como podemos ver, si se obtiene respuesta podemos verificar que esta abierto pero si no se obtiene ninguna respuesta no sabemos si esta abierto o no. Envio PSH --------- Este ataque afecta tambien a ciertos sistemas no siendo siempre efectivo. En caso de obtener ACK+PSH y un valor de Window<>0 o no obtener respuesta, el puerto esta abierto y en caso de obtener un RST, esta cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: no funciona en todos los sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=38017 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 0 | 53 | 17447 seq=1710733151 - ack_seq=1317887646 - doff=5 - check=32634 - ID=4439 Puerto abierto. Obtenemos ACK+PSH, TTL<64 y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=52609 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=846930886 - doff=5 - check=49537 - ID=0 Puerto cerrado. Obtenemos PSH, TTL=64 y Window=0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=52865 - ID=27032 Sin respuesta ... puerto abierto Envio URG --------- Este ataque afecta a ciertos sistemas no siendo siempre efectivo. En caso de obtener un ACK sin RST o no obtener respuesta, el puerto esta abierto y en caso de obtener un RST, esta cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: funciona en mi linux pero no he probado con otros sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -u SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=31873 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 0 | 53 | 37920 seq=-716127216 - ack_seq=1741636632 - doff=5 - check=41524 - ID=59663 Puerto abierto. Obtenemos ACK+PSH, TTL<64 y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -u SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=46465 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=846930886 - doff=5 - check=49537 - ID=0 Puerto cerrado. Obtenemos ACK+RST, TTL=64 y Window=0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -u SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=3968 - ID=27032 Sin respuesta ... puerto abierto Envio FIN --------- Este ataque afecta a ciertos sistemas no siendo siempre efectivo. En caso de obtener un ACK o no obtener resultado, el puerto esta abierto y en caso de obtener un RST, el puerto se encuentra cerrado. En otros sistemas operativos se puede diferenciar el valor de RST de forma que si esta activo el flag el puerto esta cerrado, estando abierto en caso contrario. Ventajas : es mas dificil de detectar. Inconvenientes: no funciona en todos los sistemas ya que se basa en un fallo del sistema operativo. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=39809 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 1 | 53 | 37920 seq=-1722694640 - ack_seq=1741636632 - doff=5 - check=39751 - ID=56001 Puerto abierto. Obtenemos ACK+PSH+FIN, TTL<64 y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=54401 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=863708102 - doff=5 - check=49281 - ID=0 Puerto cerrado. Obtenemos ACK+RST, TTL=64 y Window=0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=54657 - ID=27032 Sin respuesta ... puerto abierto Envio SYN+ACK ------------- Vamos ahora a usar combinaciones de flags. Este ataque solo funciona en algunos sistemas. Cuando funciona, en caso de no obtener respuesta, el puerto esta abierto y en caso de obtener un RST, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: no funciona en todos los sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -s -a SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=35457 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=0 - doff=5 - check=61122 - ID=0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -s -a SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=50049 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=0 - doff=5 - check=10179 - ID=0 En este caso obtenemos el mismo resultado por lo que no podemos deducir nada. Como he comentado antes, solo afecta a algunos sistemas :) Envio SYN+PSH ------------- Cuando no obtenemos un RST y el valor de Window<>0, el puerto esta abierto y con un RST y un valor de Window=0, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: funciona en mi linux pero desconozco la respuesta en otros sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -s -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=37505 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 53270 seq=1874969709 - ack_seq=863708102 - doff=6 - check=51491 - ID=0 Puerto abierto. Obtenemos SYN+ACK y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -s -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=52353 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 53270 seq=1445991790 - ack_seq=863708102 - doff=6 - check=52148 - ID=0 Puerto abierto. Obtenemos SYN+ACK y Window<>0 flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -s -p SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 1 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=52097 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=863708102 - doff=5 - check=49281 - ID=0 Puerto cerrado. Obtenemos ACK+RST y Window=0 Envio URG+FIN ------------- En este caso, cuando no obtenemos respuesta, el puerto esta abierto y con un RST, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: funciona en mi linux pero desconozco la respuesta en otros sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=31617 - ID=27032 Sin respuesta ... puerto abierto flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=46209 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=863708102 - doff=5 - check=49281 - ID=0 Puerto cerrado. Obtenemos ACK+RST flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=46465 - ID=27032 Sin respuesta ... puerto abierto Envio URG+FIN+PSH ----------------- Tambien conocido por XMAS scan, cuando no obtenemos respuesta o bien un RST el puerto esta abierto y con un RST, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: funciona solo en algunos sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -p -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=23938 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 47 | 10262 seq=128745367 - ack_seq=338264191 - doff=10 - check=49414 - ID=0 Puerto abierto. Obtenemos SYN+ACK flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -p -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=38786 - ID=27032 Sin respuesta ... puerto abierto flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=38530 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 56 | 0 seq=0 - ack_seq=863708102 - doff=5 - check=43650 - ID=56815 Puerto cerrado. Obtenemos ACK+RST Envio nulo ---------- Tambien llamado null scan consiste en enviar todos los flags a 0. En este caso, cuando la respuesta es un ACK o no obtenemos respuesta, el puerto esta abierto y con un RST, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: solo para algunos sistemas. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=40065 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 0 | 0 | 0 | 53 | 22560 seq=-515721121 - ack_seq=1706581918 - doff=5 - check=55050 - ID=61453 Puerto abierto. Obtenemos ACK flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=54657 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 1 | 0 | 0 | 0 | 64 | 0 seq=0 - ack_seq=846930886 - doff=5 - check=49537 - ID=0 Puerto cerrado. Obtenemos ACK+RST flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 0 | 0 | 0 | 0 | 0 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=54913 - ID=27032 Sin respuesta ... puerto abierto Envio SYN+ACK+RST+PSH+URG+FIN ----------------------------- Consiste en enviar todos los flags a 1. En este caso, cuando la respuesta es un ACK o no obtenemos respuesta, el puerto esta abierto y con un RST, el puerto se encuentra cerrado. Ventajas : es mas dificil de detectar. Inconvenientes: solo para Unix. flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -s -a -r -p -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 1 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=23937 - ID=27032 RECEIVED DATA ------------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 1 | 53 | 37920 seq=2033962504 - ack_seq=-1093275500 - doff=5 - check=8061 - ID=35468 Puerto abierto. Obtenemos ACK+PSH flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 23 -s -a -r -p -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 1 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=38529 - ID=27032 Sin resultado ... resultado engañoso dado que el puerto esta cerrado realmente flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 22 -s -a -r -p -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 1 | 1 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=38785 - ID=27032 Sin resultado ... resultado engañoso dado que el puerto esta abierto realmente En este caso no nos sirve esta tecnica ya que da resultados que no son fiables. Como he dicho antes, solo funciona en Unix. ------[ 2.2.- Conexiones UDP / ICMP ] Para escanear los puertos UDP vamos a seguir jugando un poco con el envio de paquetes. El protocolo UDP no esta orientado a conexion por lo que, al contrario que TCP, no se espera una respuesta tras enviar un paquete, de manera que con el envio de UDP solamente, no podemos nunca saber si el servicio esta o no disponible. Para poder realizar el escaneo nos vamos a ayudar de los paquetes ICMP. Por que? pues porque cuando intentamos realizar una conexion a un puerto UDP el sistema nos respondera con un paquete ICMP en caso de error; concretamente un paquete de tipo 3 / codigo 3, que si mirais los RFCs significa: tipo 3 = mensaje de destino inaccesible / codigo 3 = puerto inaccesible. Como nos gusta hacer las cosas manualmente, vamos a usar dos programas: - checkicmp.c -> queda a la escucha de conexiones ICMP con nuestro equipo - sendudp.c -> envia un paquete UDP a un host / puerto determinado flashgordon# ./checkicmp Waiting data ... Mientras queda el programa a la escucha, vamos a otro terminal y realizamos un envio a un puerto cerrado: flashgordon# ./sendudp 192.168.2.7 100 Sending UDP packet to 192.168.2.7:100 Si miramos el otro terminal vemos: flashgordon# ./checkicmp Waiting data ... Received: type 3 code 3 Esto quiere decir que el puerto esta cerrado ... probemos ahora con uno abierto a ver que ocurre: flashgordon# ./sendudp 192.168.2.7 111 Sending UDP packet to 192.168.2.7:111 Y en el otro terminal: flashgordon# ./checkicmp Waiting data ... Received: type 3 code 3 Sigue todo como antes (el mensaje que hay es del paquete anterior :P) ... por lo que al no recibir ningun paquete nuevo deducimos que el puerto esta abierto. Probemos de nuevo con otro puerto cerrado: flashgordon# ./sendudp 192.168.2.7 101 Sending UDP packet to 192.168.2.7:101 Y en la otra ventana: flashgordon# ./checkicmp Waiting data ... Received: type 3 code 3 Received: type 3 code 3 Ha llegado otro paquete ICMP indicando que el 101 tambien esta cerrado. ------[ 3.- Deteccion de sistemas operativos ] Podemos detectar el sistema operativo que corre en un servidor sin realizar una conexion TCP. Al igual que ocurre en los escaneos anteriormente descritos para lo que un servidor no tiene una respuesta predecible (hemos visto que lo normal en una conexion TCP es mandar un SYN y recibir un SYN/ACK o un RST) cada sistema puede responder de una forma diferente. Lo mismo ocurre al enviar paquetes UDP o IMCP que no son los tipicos para realizar una comunicacion estandar. No voy a poner las diferencias entre un sistema y otro porque requiere muchas pruebas con diferentes sistemas operativos y sobre todo mucho tiempo libre :) ... ademas, en nmap ya tiene una buena base de datos de sistemas detectables, basado en el famoso QueSO de Savage. Pero si que vamos a jugar un poco mas con los sockets para ver algun ejemplo y poder entender todo esto. Para las pruebas he usado cuatro equipos: Cliente : 192.168.2.5 (Linux Debian - kernel 2.6.18.1) Servidor1: 192.168.2.7 (Linux Debian) Servidor2: 192.168.2.6 (Windows 2000 Server) Servidor3: 192.168.2.8 (Solaris) Puertos abiertos en los tres servidores servidores: 80(TCP) Antes de nada aclarar que para la deteccion de sistemas operativos se puede jugar con mas campos de las cabeceras de los paquetes, pero para no tener que modificar los programas y dado que esto solo son unas pruebas, vamos a usar los mismos flags que antes. ------[ 3.1.- Realizando algunas pruebas ] Para hacer una prueba vamos a enviar un paquete TCP al puerto 80 (abierto) con los flags SYN+URG+FIN activos. Primer servidor, bajo Linux: flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.7 -c 80 -s -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=31617 - ID=27032 RECEIVED DATA | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 64 | 6272 seq=24764339 - ack_seq=863708102 - doff=6 - check=32130 - ID=0 Segundo servidor, bajo Windows: flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.6 -c 80 -s -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=17292 - ID=27032 RECEIVED DATA | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 0 | 1 | 0 | 1 | 0 | 0 | 48 | 33820 seq=2043592525 - ack_seq=772288166 - doff=5 - check=30285 - ID=15717 Tercer servidor, bajo Solaris: flashgordon# ./sendsocket -x 192.168.2.5 -d 192.168.2.8 -c 80 -s -u -f SENT DATA --------- | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 0 | 0 | 0 | 1 | 1 | 64 | 65535 seq=846930886 - ack_seq=0 - doff=5 - check=50815 - ID=27032 RECEIVED DATA | SYN | ACK | RST | PSH | URG | FIN | TTL | Window | 1 | 1 | 0 | 0 | 0 | 0 | 41 | 65535 seq=1684159920 - ack_seq=1278904408 - doff=6 - check=34271 - ID=62243 Si analizamos las diferencias: - Linux : SYN+ACK - TTL=64 - Window= 6272 - Windows: ACK+PSH - TTL=48 - Window=33820 - Solaris: SYN+ACK - TTL=41 - Window=65535 Como he comentado antes, esto solo es una prueba para comprender como se hace esto de la deteccion de sistemas operativos. Realmente hay que afinar un poco mas y jugar con mas campos e incluso combinar con paquetes UDP e ICMP. Pues la diferencia en este caso entre un Linux, un Windows y un Solaris esta clara, pero si usamos mas comprobaciones podremos ver como varia la respuesta entre las diferentes versiones de un sistema operativo y otro, por ejemplo entre Windows 98 y Windows 2000 o entre diferentes distribuciones y/o versiones de kernel de un Linux. ------[ 4.- Archivos ] Programas usados para realizar estas pruebas: --- sendsocket.c --------------------------8<--------------------------------- // sendsocket.c // By Pepelux // Change DEFAULT_HOST writing your private IP if you want to use always the // same IP address #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define Error(msg) { perror(msg); exit -1; } #define DEFAULT_HOST "PUT HERE YOUR PRIVATE IP" #define PBUFFER 10000 #define BUFFER_LONG 65536 #define DEFAULT_LEN (sizeof(struct tcphdr)+sizeof(struct iphdr)) void usage(char *nom); unsigned short cksum(unsigned short *, int); void SendPacket(struct sockaddr_in saddr, struct sockaddr_in daddr, int dport, int syn, int ack, int psh, int rst, int urg, int fin); int main(int argc, char *argv[]) { int dport; char *host_dest, *host_source; struct sockaddr_in saddr, daddr; struct hostent *hostentry; int i, c; int h = 0; // destination flag int x = 0; // Source flag int syn = 0; // SYN flag int ack = 0; // ACK flag int rst = 0; // RST flag int urg = 0; // URG flag int fin = 0; // FIN flag int psh = 0; // PSH flag if (geteuid() != 0) { printf("You must be root to use RAW Sockets\n"); exit(0); } // Check params while((c = getopt(argc, argv, "saprufd:x:c:")) != -1) { switch(c) { case 'd': // destination host if(strlen(optarg) == 0) usage(argv[0]); host_dest = optarg; h++; break; case 's': // SYN syn = 1; break; case 'a': // ACK ack = 1; break; case 'r': // RST rst = 1; break; case 'p': // PUSH psh = 1; break; case 'u': // URG urg = 1; break; case 'f': // FIN fin = 1; break; case 'x': // source host if(strlen(optarg) == 0) usage(argv[0]); host_source = optarg; x++; break; case 'c': // destination port if(strlen(optarg) == 0) usage(argv[0]); dport = atoi(optarg); break; default: usage(argv[0]); break; } } if (x == 0) host_source = DEFAULT_HOST; if (h == 0) usage(argv[0]); // you must write destination host. Error // IP source if((hostentry = gethostbyname(host_source)) == NULL) Error("Error solving source address"); bzero(&saddr, sizeof(struct sockaddr)); saddr.sin_family = AF_INET; saddr.sin_addr = *((struct in_addr *)hostentry->h_addr); // IP destination if((hostentry = gethostbyname(host_dest)) == NULL) Error("Error solving destination address"); bzero(&daddr, sizeof(struct sockaddr)); daddr.sin_family = AF_INET; daddr.sin_addr = *((struct in_addr *)hostentry->h_addr); // Send data SendPacket(saddr, daddr, dport, syn, ack, psh, rst, urg, fin); } void SendPacket(struct sockaddr_in saddr, struct sockaddr_in daddr, int dport, int syn, int ack, int psh, int rst, int urg, int fin) { int destination_port, source_port, on, s, rs, pid; int status, i; char buffer[BUFFER_LONG], rbuffer[BUFFER_LONG]; char string[BUFFER_LONG]; struct iphdr *iphdr, *riphdr; struct tcphdr *tcphdr, *rtcphdr; struct sockaddr from; int fromlen, ethlen; struct pseudohdr { struct in_addr saddr; struct in_addr daddr; unsigned char zero; unsigned char protocol; unsigned short length; } *pseudoheader; ethlen = sizeof(struct ethhdr); on = 1; source_port = htons(random()); destination_port = htons(dport); setvbuf(stdout, NULL, _IONBF, 0); fflush(stdout); if((pid=fork()) == -1) Error("fork"); if(pid) { if((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) Error("socket"); if(setsockopt(s, IPPROTO_IP, IP_HDRINCL,(char *)&on, sizeof(on)) < 0) Error("setsockopt"); bzero(buffer, BUFFER_LONG); // TCP header tcphdr = (struct tcphdr *)(buffer+ sizeof(struct iphdr)); tcphdr->source = htons(source_port); // puerto origen tcphdr->dest = destination_port; // puerto destino tcphdr->window = htons(65535); // ventana tcphdr->seq = random(); // numero de secuencia aleatorio tcphdr->syn = syn; // flag SYN tcphdr->ack = ack; // flag ACK tcphdr->rst = rst; // flag RST tcphdr->psh = psh; // flag PSH tcphdr->urg = urg; // flag URG tcphdr->fin = fin; // flag FIN tcphdr->doff = sizeof(struct tcphdr) / 4; // TCP pseudoheader pseudoheader = (struct pseudohdr *) ((unsigned char *)tcphdr-sizeof(struct pseudohdr)); pseudoheader->saddr = saddr.sin_addr; // direccion origen pseudoheader->daddr = daddr.sin_addr; // direccion destino pseudoheader->protocol = IPPROTO_TCP; // protocolo pseudoheader->length = htons(sizeof(struct tcphdr)); tcphdr->check = cksum((unsigned short *) pseudoheader, sizeof(struct pseudohdr)+sizeof(struct tcphdr)); // IP header bzero(buffer, sizeof(struct iphdr)); iphdr = (struct iphdr *)buffer; iphdr->ihl = 5; // IHL (longitud de cabecera) iphdr->version = 4; // version iphdr->tot_len = htons(DEFAULT_LEN); // longitud del datagrama iphdr->id = htons(random()); // numero de identifiacion (aleatorio) iphdr->ttl = IPDEFTTL; // tiempo de vida iphdr->protocol = IPPROTO_TCP; // protocolo iphdr->daddr = daddr.sin_addr.s_addr; // direccion origen iphdr->saddr = saddr.sin_addr.s_addr; // direccion destino printf(" SENT DATA\n"); printf(" ---------\n"); printf("| SYN | ACK | RST | PSH | URG | FIN | TTL | Window\n"); printf("| %d | %d | %d | %d | %d | %d | %d | %d\n", syn, ack, rst, psh, urg, fin, iphdr->ttl, tcphdr->window); printf("seq=%d - ack_seq=%d - doff=%d - check=%d - ID=%d\n\n", tcphdr->seq, tcphdr->ack_seq, tcphdr->doff, tcphdr->check, iphdr->id); if(sendto(s, buffer, DEFAULT_LEN, 0x0, (struct sockaddr *) &daddr, sizeof(struct sockaddr) ) != DEFAULT_LEN) Error("sendto"); wait(&status); close(s); exit(0); } else { if((rs = socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP))) < 0) Error("socket input"); if((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) Error("socket"); if(setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) Error("setsockopt"); while(1) { if(recvfrom(rs, rbuffer, BUFFER_LONG, 0x0, (struct sockaddr *)&from, &fromlen) <= 0) Error("recvfrom"); riphdr = (struct iphdr *)(rbuffer+ethlen); if(riphdr->protocol == IPPROTO_TCP) { rtcphdr = (struct tcphdr *)(rbuffer+ethlen+ sizeof(struct iphdr)); if(rtcphdr->source == destination_port) { bzero(buffer, BUFFER_LONG); printf(" RECEIVED DATA\n"); printf(" -------------\n"); printf("| SYN | ACK | RST | PSH | URG | FIN | TTL | Window\n"); printf("| %d | %d | %d | %d | %d | %d | %d | %d\n", rtcphdr->syn, rtcphdr->ack, rtcphdr->rst, rtcphdr->psh, rtcphdr->urg, rtcphdr->fin, riphdr->ttl, rtcphdr->window); printf("seq=%d - ack_seq=%d - doff=%d - check=%d - ID=%d\n\n", rtcphdr->seq, rtcphdr->ack_seq, rtcphdr->doff, rtcphdr->check, riphdr->id); return; } } } close(rs); close(s); } return; } unsigned short cksum(unsigned short *ptr,int nbytes) { register long sum; unsigned short oddbyte; register unsigned short anwser; sum = 0; while(nbytes>1) { sum += *ptr++; nbytes -= 2; } if(nbytes==1) { oddbyte = 0; *((unsigned char *) & oddbyte) = *(unsigned char *)ptr; sum += oddbyte; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); anwser = ~sum; return(anwser); } void usage(char *nom) { printf("Usage: %s [s|a|r|p|u|f] [-x host_source] -d host_destination -c port\n", nom); printf("\t-s SYN flag enabled\n"); printf("\t-a ACK flag enabled\n"); printf("\t-r RST flag enabled\n"); printf("\t-p PSH flag enabled\n"); printf("\t-u URG flag enabled\n"); printf("\t-f FIN flag enabled\n"); exit(-1); } -------------------------------------------8<--------------------------------- --- checkicmp.c ---------------------------8<--------------------------------- // checkicmp.c // By Pepelux #include #include #include #include #include #define Error(msg) { perror(msg); exit -1; } int main(void) { int s; struct sockaddr_in dir = {AF_INET, 0, 0 }; char buff[1024]; int len = sizeof(dir); struct icmphdr *rec = (struct icmphdr*) (buff + sizeof(struct iphdr)); if (geteuid() != 0) { printf("You must be root\n"); exit(0); } if ((s = socket(AF_INET, SOCK_RAW, 1)) < 0) Error("socket"); printf("Waiting data ...\n"); while (1) { bzero(buff, 1024); while (recvfrom(s, buff, 1024, 0, (struct sockaddr_in*) &dir, &len) > 0) printf("Received:\ttype %d\tcode %d\n", rec->type, rec->code); } } -------------------------------------------8<--------------------------------- --- sendudp.c -----------------------------8<--------------------------------- // sendudp.c // By Pepelux #include #include #include #include #include #include #include #include #include #define Error(msg) { perror(msg); exit -1; } main (int argc, char *argv[]) { int s; int dport; struct sockaddr_in addr_dest; if (argc!=3) { printf ("Usage: %s ip port\n", argv[0]); exit(0); } dport = atoi(argv[2]); printf ("Sending UDP packet to %s:%d\n",argv[1], dport); if ((s=socket(AF_INET,SOCK_DGRAM,0)) < 0) Error("socket"); addr_dest.sin_addr.s_addr=inet_addr(argv[1]); addr_dest.sin_family=AF_INET; addr_dest.sin_port=htons(dport); if (sendto(s,"\n",1,0,(struct sockaddr*)&addr_dest,sizeof(struct sockaddr_in)) < 0) Error("sendto"); close (s); } -------------------------------------------8<--------------------------------- ------[ 5.- Referencias ] Pongo aqui un listado de paginas de interes y sobre las que he basado este articulo: Estandares (RFCs): Protocolo TCP : http://www.rfc-es.org/rfc/rfc0793-es.txt Protocolo IP : http://www.rfc-es.org/rfc/rfc0791-es.txt Protocolo UDP : http://www.rfc-es.org/rfc/rfc0768-es.txt Protocolo ICMP: http://www.rfc-es.org/rfc/rfc0792-es.txt Documentacion de nmap: http://insecure.org/nmap/man/es/ ------[ 6.- Despedida ] Bueno, si, todo esto lo hace el nmap, mas bonito, mas rapido y mejor ... pero, a que nos hemos divertido? :P El nmap tiene muchos modos de escaneo pero hay veces en los que la respuesta es imprevisible, dependiendo del sistema que corra al otro lado. Para eso es necesario hacer los escaneos a mano, o utilizar un metodo tipo SYN, conocido y facilmente detectable. Ademas asi se dejan menos huellas y se aprende mucho mas. Saludos!!! Pepelux http://www.enye-sec.org =-|================================================================ EOF =====|