/* honoriak 2000.12.26 Ante cualquier duda, critica, pregunta, etc. no dudes en escribirme a una de las direcciones de e-mail que acabas de ver. Las fuentes que me ayudaron en la elaboracion de este reducido 'paper' figuran al final de este documento. Este paper ha sido elaborado con emacs y joe. */ -----------------[ Analisis remoto de sistemas --------------------------[ honoriak de HeliSec [ Seccion de I+D de networking-center.org ] [ Version Final ] Indice ====== 1. Introduccion: - Localizacion. - NS de la maquina. - Informacion del registro del dominio. 2. Analisis: - Sistema operativo: Analisis sin conocimiento de la pila TCP/IP. Analisis basado en la pila TCP/IP. Fingerprinting pasivo - Servicios: Software de escaneo de puertos y vulnerabilidades: panorama actual. Tecnicas usadas en el escaneo de puertos. - Relacion de principales servicios con puertos. Daemons. - CGIs 3. Bibliografia y agradecimientos |---------------------------------------| 1. Introduccion ============ Localizacion: ~~~~~~~~~~~~~ En este manual se tratara unicamente el caso de un servidor con una ip fija y un dominio/s asociado, ya que creo que el analisis de sistemas se aplica a este tipo de configuraciones y no me parece logico el ocuparse de ordenadores de usuarios domesticos ya que normalmente no son los que necesitan este tipo de comprobaciones. Lo unico a resaltar es que las IPs de las maquinas que vamos a analizar no pueden estar en ningun caso entre: ============================================ | Clase Networks | | A de 10.0.0.0 a 10.255.255.255 | | B de 172.16.0.0 a 172.31.0.0 | | C de 192.168.0.0 a 192.168.255.0 | ============================================ Ya que estas son de uso privado (para LANs, intranets) y estamos tratando el caso de maquinas conectadas a internet. La version del Internet Protocol utilizada mayormente en la actualidad es la 4 pero es cierto que los esfuerzos porque este sea reemplazado en un futuro no muy lejano por IPv6 es notable y en este cambiara el esquema de direcciones y las direcciones seran mas largas. Dos herramientas de uso muy comun entre los usuarios de cualquier sistema operativo serio son ping y traceroute. Me parece que es obvio su uso y sino, siempre puedes acudir al man para saber todas sus opciones de sintaxis. La ultima de ellas, muchas veces es infravalorada en un analisis y realmente puede dar una idea de la situacion fisica del servidor y maquinas cercanas a este. Actualmente hay bastantes frontends y utilidades basadas en traceroute para x-windows e incluso alguna de ellas representa en un mapa el camino que sigue un paquete desde nuestro sistema hasta la maquina a analizar. Mas adelante, comentaré el uso de traceroute para conocer mejor el tipo de firewall que protege a una máquina. NS de la maquina ~~~~~~~~~~~~~~~~ Otra herramienta muy util en el analisis es el nslookup, gracias a ella podremos saber el servidor de nombres (NS) que ofrece el dominio a nuestro servidor, es decir, el NS que hace que w.x.y.z sea dddd.com. Para obtener esta informacion, haremos uso de nuestro DNS (es decir, el servidor de nombres que nos ofrece nuestro ISP). Asi por ejemplo, suponiendo que mi NS es ns1.worldonline.es y queremos saber cual es el NS de insflug.org, se actuaria de la siguiente forma: $ nslookup insflug.org Server: ns1.worldonline.es Address: 212.7.33.3 Name: insflug.org Address: 209.197.122.174 $ nslookup Default Server: ns1.worldonline.es Address: 212.7.33.3 > set q=ns > insflug.org Server: ns1.worldonline.es Address: 212.7.33.3 Non-authoritative answer: insflug.org nameserver = NS0.NS0.COM insflug.org nameserver = NS84.PAIR.COM Authoritative answers can be found from: NS0.NS0.COM internet address = 209.197.64.1 NS84.PAIR.COM internet address = 209.68.1.177 Como puedes observar, hemos obtenido los NS tanto primario como secundario que hace que insflug.org este asociado a 209.197.122.174 siendo: NS0.NS0.COM y NS84.PAIR.COM. Esta informacion nos puede ser de gran utilidad para cierto tipo de cosas. Lo que si que puede ser de cierta utilidad es saber que en los NS hay unas zone files en las que se encuentra la informacion sobre el dominio a analizar, de esta forma encontrariamos zone "insflug.org"{ type master; file "insflug.org.zone"; }; en el fichero en el que se encontrase la informacion sobre las secciones de zona (algunas veces /var/named/), siendo la zone file para insflug.org /var/named/insflug.org.zone, en el supuesto de estar en /var/named/. Alli encontrariamos @ IN NS NS0.NS0.COM. www IN A 209.197.122.174 ftp IN CNAME www ..... CNAME significa canonical name y quiere decir que en realidad la ip a la que se refiere ftp.insflug.org es la misma que www.insflug.org y que en este caso es la misma que insflug.org, como podemos comprobar haciendo: $ nslookup Default Server: ns1.worldonline.es Address: 212.7.33.3 > set q=ns > www.insflug.org Server: ns1.worldonline.es Address: 212.7.33.3 Non-authoritative answer: www.insflug.org canonical name = insflug.org ... > ftp.insflug.org Server: ns1.worldonline.es Address: 212.7.33.3 ftp.insflug.org canonical name = insflug.org ... De esta forma, podremos saber si los demonios de ftp, www... de un dominio se encuentran en una misma maquina o maquinas diferentes; muy util para tener una vision global del host a estudiar, ya que lo que en principio se podria pensar que era un servidor en particular son varios. Ademas, www.insflug.org por ejemplo puede estar asociado a varias IPs y viceversa. Pese a que para saber el servidor de nombres del servidor a estudiar hemos utilizado nslookup, que se supone que es el metodo en el cual utilizamos un poco "nuestros propios medios", estos NSs se podrian saber haciendo uso del comando que se utiliza en lo que viene a continuacion: whois. Informacion del registro del dominio ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Para obtener informacion sobre el registro de un dominio, entiendase por dominio ddd.xxx y no pr.ddd.xxx pr2.ddd.xxx... que serian considerados subdominios del primero, se puede hacer uso de la herramienta ya implementada en la mayoria de los unix whois. Asi, de esta forma: $ whois insflug.org [whois.internic.net] Whois Server Version 1.3 Domain names in the .com, .net, and .org domains can now be registered with many different competing registrars. Go to http://www.internic.net for detailed information. Domain Name: INSFLUG.ORG Registrar: NETWORK SOLUTIONS, INC. Whois Server: whois.networksolutions.com Referral URL: www.networksolutions.com Name Server: NS0.NS0.COM Name Server: NS84.PAIR.COM Updated Date: 24-jun-2000 >>> Last update of whois database: Mon, 25 Dec 2000 11:16:57 EST <<< The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and Registrars. Puedes observar como se han obtenido tambien los servidores de nombres que contienen la entrada insflug.org (por esto lo comentado anteriormente). Pero, en realidad, esto la mayoria de las veces no es de mucha utilidad ya que actualmente los registros de dominios no son directos y en realidad no figura el nombre del que lo quiso registrar sino de la empresa intermediaria que hizo efectivo el registro. Lo que si que nos proporciona una informacion mucho mas completa es hacer un whois al Whois Server que nos ha proporcionado este primer whois insflug.org que es whois.networksolutions.com, asi de esta forma: $ whois insflug.org@whois.networksolutions.com [whois.networksolutions.com] The Data in Network Solutions' WHOIS database is provided by Network Solutions for information purposes, and to assist persons in obtaining information about or related to a domain name registration record. Network Solutions does not guarantee its accuracy. By submitting a WHOIS query, you agree that you will use this Data only for lawful purposes and that, under no circumstances will you use this Data to: (1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations via e-mail (spam); or (2) enable high volume, automated, electronic processes that apply to Network Solutions (or its systems). Network Solutions reserves the right to modify these terms at any time. By submitting this query, you agree to abide by this policy. Registrant: Impatient & 'Novatous' Spanish FidoNet Linux Users Group (INSFLUG-DOM) Avda. Pablo VI, 11 - 4C Dos Hermanas, Sevilla 41700 ES Domain Name: INSFLUG.ORG Administrative Contact, Billing Contact: Montilla, Francisco J (FJM43) pacopepe@INSFLUG.ORG Impatient & 'Novatous' Spanish FidoNet Linux Users Group Avda. Pablo VI, 11 - 4C Dos Hermanas, Sevilla 41700 ES +34 955679066 (FAX) +34 955679066 Technical Contact: Administrator, Domain (DA550) domain@PAIR.COM pair Networks, Inc 2403 Sidney St, Suite 510 Pittsburgh, PA 15203 +1 412 681 6932 (FAX) +1 412 381 9997 Record last updated on 25-Jul-2000. Record expires on 24-Jun-2001. Record created on 24-Jun-1998. Database last updated on 25-Dec-2000 20:18:04 EST. Domain servers in listed order: NS84.PAIR.COM 209.68.1.177 NS0.NS0.COM 209.197.64.1 Vemos pues, una informacion mucho mas completa =) Para obtener informacion sobre dominios que no sean .com, .net, .org, .edu tendremos que saber el servidor que nos permite hacer un whois de dicho dominio, ya que con el whois.internic.net no nos permitira esa busqueda, $ whois ctv.es [whois.internic.net] Whois Server Version 1.3 Domain names in the .com, .net, and .org domains can now be registered with many different competing registrars. Go to http://www.internic.net for detailed information. No match for "CTV.ES". >>> Last update of whois database: Mon, 25 Dec 2000 11:16:57 EST <<< The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and Registrars. 2. Analisis ======== 2.1 Sistema operativo ~~~~~~~~~~~~~~~~~~~~~ I. Analisis sin conocimientos de la pila TCP/IP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ De paquete, algunos sistemas operativos (quizas versiones antiguas), tenian o incluso tienen "por costumbre" darnos dicha informacion (so y version) al telnetear al servidor y los administradores no se preocupan de modificarlo. Asi que siempre puedes probar haber si hay suerte y por ejemplo te encuentras con: $ telnet jeropa.com Trying 64.60.1.66... Connected to jeropa.com. Escape character is '^]'. Cobalt Linux release 4.0 (Fargo) Kernel 2.0.34C53_SK on a mips login: ... Lo que es cierto, es que cualquier sysadmin serio debe preocuparse de cambiar esto, ya que tampoco hay que dar tantas facilidades. Pero, en la actualidad si que es cierto que cada vez son mas los sysadmins que cambian esto e incluso ponen un so o version falsa. Asi que esta tampoco va a ser una muy buena solucion para saber el sistema operativo de la maquina que tratamos. (El escaner ISS, de pago, utiliza esta "fiable" tecnica, asi que te recomiendo usar queso o nmap). Aun asi, podemos seguir obteniendo informacion sobre el SO de la maquina a estudiar de forma mas o menos parecida ya que, por ejemplo, si tiene operativo www, ftp o snmp, a lo mejor se puede hacer una peticion al servidor web, ejecutar SYST en una sesion de FTP o simplemente ver la version del cliente de FTP o usar snmpwalk (de las utilidades CMU SNMP) para conseguir cierta informacion respectivamente y saber en algunos casos el SO; de esta forma, por ejemplo: $ telnet www.microsoft.com 80 Trying 207.46.230.229... Connected to www.microsoft.akadns.net. Escape character is '^]'. probando? HTTP/1.1 400 Bad Request Server: Microsoft-IIS/5.0 Date: Wed, 27 Dec 2000 00:03:18 GMT ... Te suena de algo lo de IIS/5.0? Pues ya sabes hablamos de un win*. __ $ telnet ftp.ciudadfutura.com 21 Trying 216.35.70.14... Connected to ftp.ciudadfutura.com. Escape character is '^]'. 220 Serv-U FTP-Server v2.5e for WinSock ready... ... Y por tanto si revisamos las caracteristicas del Serv-U FTP-Server, | "FTP Serv-U from is a full-featured | FTP server that allows you to turn almost any | MS Windows (9x, NT, 2000) computer into an | Internet FTP Server." nos damos cuenta de que estamos hablando de una maquina win*. II Analisis basado en la pila TCP/IP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Antes de pasar a enumerar los programas que han hecho posible el reconocimiento del sistema operativo de un host de forma remota me parece logico explicar, a grandes rasgos, cual es su funcionamiento, sin entrar de momento en particularidades. Dichos programas basan su funcionamiento en analizar las diferentes respuestas que ofrecen distintos sistemas ante ciertos envios (he aqui las singularidades y la variedad de metodos). Por tanto, dichas respuestas, que son comunmente conocidas como TCP/IP fingerprints, son las que permiten distinguir un sistema operativo de otro. Muchas veces, recurren dichos programas a distintos tipos de envios ya que, en muchas ocasiones, las diferencias en la pila TCP/IP de un sistema operativo a otro no son muy marcadas y ante ciertos envios actuan de igual forma, diferenciandose, a veces, solo en uno o incluso no habiendo diferencia (como en el caso de Windows 95/98/NT, en los que increiblemente no se observa un comportamiento diferente en sus pilas TCP/IP; unicamente probando nukes contra dichos hosts y viendo si se caen o no, para asi distinguir por ejemplo entre un 95 y un 98 (ej. WinNuke)). Entre los programas disponibles que utilizan dicha tecnica de fingerprinting destacan: -spoofer para IRC sirc (Johan) -checkos (shok) -nmap (fyodor) -nsat (mixter) -p0f (Michal Zalewski) -SS (Su1d) -queso (savage) Ya entrando mas a fondo en el funcionamiento a mas bajo nivel de dichos programas encontramos cierta diferencia entre ellos, ya que mientras unos usan un fichero externo con fingerprints de diferentes sistemas tipo, como el queso, otros incluyen en el codigo dicha comparacion, como checkos por ejemplo. En checkos encontramos: ... if ((tcp.hrc & CF_SYN) && (tcp.hrc & CF_FIN)) { type=OS_LINUX; done=1; } ... if ((tcp.hrc & CF_ACK) && (tcp.hrc & CF_RST)) { if (flags & OSD_WIN95WAIT) { done=1; type=OS_WIN95; } En ss encontramos: /* fragmento codigo de ss de Remote OS Detection via TCP/IP Fingerprinting de Fyodor */ ... if ((flagsfour & TH_RST) && (flagsfour & TH_ACK) && (winfour == 0) && (flagsthree & TH_ACK)) reportos(argv[2],argv[3],"Livingston Portmaster ComOS"); ... Mientras que en queso encontramos un fichero de configuracion en el que se distingue por ejemplo: $ cat /etc/queso.conf ... * AS/400 OS/400 V4R2 (by rodneybrown@pmsc.com) 0 1 1 1 SA 1 0 1 0 R 2 0 1 0 RA 3 0 1 0 R 4 1 1 1 SA 5 0 1 0 RA 6 1 1 1 SA ... Se observa, pues, que savage ha implementado de forma bastante mas inteligente dicha idea. Este metodo ha sido heredado por fyodor para su nmap, y por ejemplo, en ciertas versiones de nmap encontramos: $ cat /usr/local/lib/nmap/nmap-os-fingerprints ... # Thanks to Juan Cespedes FingerPrint AGE Logic, Inc. IBM XStation TSeq(Class=64K) T1(DF=N%W=2000%ACK=S++%Flags=AS%Ops=M) T2(Resp=N) T3(Resp=Y%DF=N%W=2000%ACK=O%Flags=A%Ops=) T4(DF=N%W=2000%ACK=O%Flags=R%Ops=) T5(DF=N%W=0%ACK=S++%Flags=AR%Ops=) T6(DF=N%W=0%ACK=O%Flags=R%Ops=) T7(DF=N%W=0%ACK=S%Flags=AR%Ops=) PU(DF=N%TOS=0%IPLEN=38%RIPTL=148%RID=F%RIPCK=0%UCK=E%ULEN=134%DAT=E) ... Y tambien ha sido usado por mixter en su NSAT, destacando la distincion que hace entre diferentes configuraciones de windows: $ cat /usr/local/bin/nsat.os ... Windows (Firewall-1) 1 1 1 0 1 18 1 0 1 0 0 4 1 0 1 0 1 21 1 0 1 0 1 21 1 1 1 0 1 18 1 0 1 0 1 28 0 0 0 0 0 0 ... En lo que se refiere al tipo de tecnicas usadas para diferenciar unos OSs se debe puntualizar que en realidad, estas pruebas se combinan, para asi conseguir aislar cada sistema operativo. Un muy buen programa para hacer este tipo de pruebas es el hping2 (antirez@invece.org, http://www.kyuzz.org/antirez/hping2.html) o sing (aandres@mfom.es, http://sourceforge.net/projects/sing/) combinandolo con el analisis mediante tcpdump o ethereal (un magnifico frontend), ya que aunque puedes realizar tu propio codigo (en C, por ejemplo) esta claro que esto conlleva unos conocimientos de unix network programing bastante importantes, asi que en este paper analizare los resultados obtenidos con hping2 y no presentare codes especificos para cada prueba, además utilizare mi propia maquina para dichas pruebas y no lo hare de forma remota para asi tener un mayor control de los resultados. Los metodos que conozco son: (si conoces otras tecnicas utilizadas para esto no dudes en decirmelo - honoriak@mail.ru) - TCP ISN: Cuando el host a analizar responde a solicitudes de conexion, genera unos numeros en la secuencia inicial (ISN) que no siempre se producen de la misma forma; esto, es aprovechado para distinguir unos sistemas de otros. Estos ISNs pueden ser constantes (hubs de 3com, etc.), 64K (UNIX antiguos), aleatorios (linux >2.0, AIX modernos, OpenVMS), incremento en funcion del tiempo (windows), de incremento aleatorio (freebsd, digital unix, cray, solaris modernos...) siendo estos ultimos incrementos basados en diferentes cosas como por ejemplo maximos comunes divisores. Si enviamos varios paquetes, por ejemplo, de la forma: $ hping2 localhost -p 80 default routing not present HPING localhost (lo 127.0.0.1): NO FLAGS are set, 40 headers + 0 data bytes 40 bytes from 127.0.0.1: flags=RA seq=0 ttl=255 id=5 win=0 rtt=0.4 ms 40 bytes from 127.0.0.1: flags=RA seq=1 ttl=255 id=6 win=0 rtt=24.9 ms --- localhost hping statistic --- 2 packets tramitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.4/12.6/24.9 ms Y ahora analizamos dichos paquetes por ejemplo con el tcpdump (mas claro son los resultados que ofrece ethereal, pero para copiar aqui es mas comoda la salida del tcpdump; solo copiare las respuestas, no las peticiones) ... 14:12:47.774380 lo < honorato.2485 > honorato.www: . 7200421:72 00421(0) win 512 ... 14:12:48.771779 lo < honorato.2486 > honorato.www: . 2002659674:200 2659674(0) win 512 ... Se observa, pues, una variacion en la seq inicial del paquete TCP, en el primer paquete vemos 7200421 y en el segundo 2002659674 siendo en este caso completamente aleatorios ya que estoy trabajando en: $ uname -a Linux honorato.com 2.2.16 #14 SMP Sat Jun 10 15:51:08 CEST 2000 i86 unknown - Opciones de TCP: esta tecnica se basa en el diferenciar sistemas operativos segun el numero de opciones TCP que admiten, los valores de dichas opciones y el orden en que las opciones se nos presentan. Esto, que yo sepa, solo es utilizado por Nmap (si sabes de otros programas que lo usen, no dudes en decirmelo y modificare esto). Fyodor en su nmap hace prueba las siguientes opciones: Window Scale=10; NOP; Max Segment Size = 265; Timestamp; End of Ops; El hping2 no implementa esta posibilidad (o eso creo) asi que no lo he llevado a la practica. Siempre puedes analizar el codigo del nmap que realiza esto y heredar dicha tecnica. - FIN: Se basa en el envio a un puerto abierto del host a estudio de un paquete FIN o cualquiera que no tenga un flag ACK o SYN. Segun el RFC793 el host no tendria que responder pero algunos OSs responden con un RESET como Windows, HP/UX, IRIX, MVS, BSDI, CISCO. Para hacer una prueba practica usare el puerto 80, con apache arrancado: $ /usr/bin/httpd $ hping2 localhost -p 80 -F default routing not present HPING localhost (lo 127.0.0.1): F set, 40 headers + 0 data bytes --- localhost hping statistic --- 4 packets tramitted, 0 packets received, 100% packet loss round-trip min/avg/max = 0.0/0.0/0.0 ms Se observa pues, como mi linux si que cumple el RFC793 y no responde a dichos paquetes. - ACK recibido: El valor de ACK que nos envia el servidor a analizar cuando por ejemplo enviamos un SYN|FIN|URG|PSH a un puerto abierto o un FIN|PSH|URG a un puerto cerrado puede variar respecto al numero de secuencia inicial que envia este. Para probar, inicialmente mandare un paquete normal a un puerto cerrado, y se comprueba que el valor de ACK no cambia y despues uno FIN|PSH|URG tambien a un puerto cerrado y se vera como cambia: $ killall httpd $ hping2 localhost -p 80 ... y en la salida del tcpdump se ve 15:59:37.442157 lo > honorato.1676 > honorato.www: . 1752870898:1752 870898(0) win 512 15:59:37.442157 lo < honorato.1676 > honorato.www: . 1752870898:1752 870898(0) win 512 15:59:37.442259 lo > honorato.www > honorato.1676: R 0:0(0) ack 1752 870898 win 0 vemos como 1752870898 se mantiene en el ack, pero en cambio: $ hping2 localhost -p 80 -S -F -U -P ... y en la salida del tcpdump ahora vemos 16:00:48.480252 lo > honorato.2669 > honorato.www: SFP 1376153753:13 76153753(0) win 512 urg 0 16:00:48.480252 lo < honorato.2669 > honorato.www: SFP 1376153753:13 76153753(0) win 512 urg 0 16:00:48.480334 lo > honorato.www > honorato.2669: R 0:0(0) ack 1376 153754 win 0 Se ve pues como ha cambiado el valor de seq respecto al de ack de 1376153753 a 1376153754. De la misma forma, haciendo dicha prueba para un puerto abierto se puede ver que hay una variacion. En estas pruebas he usado linux, pero de un sistema a otro esa variacion puede ser diferente (lo que permite diferenciarlos, claro esta). - Flag TCP (64/128) en el encabezado TCP de un paquete SYN: Haciendo esto, por lo que yo he probado/leido unicamente el linux 2.0.35 mantiene dicha flag en la respuesta y el resto cancela la conexion. Esto, de estudiarse a fondo, puede servir para diferenciar OSs. No he hecho una demostracion practica de dicho metodo, ya que en este momento no tengo instalado el kernel 2.0.35, pero simplemente se haria: hping2 localhost -p 80 -S y se analizarian los resultados vertidos por el tcpdump. - ICMP: 1) Esta tecnica se basaria en el control del numero de mensajes de destination unreachable que envia un host por ejemplo al mandar un gran numero de paquetes a un puerto UDP. En linux, encontramos como limita dicha cantidad de mensajes, y por ejemplo: $ cat /usr/src/linux/net/ipv4/icmp.c ... * 4.3.2.8 (Rate Limiting) * SHOULD be able to limit error message rate (OK) * SHOULD allow setting of rate limits (OK, in the source) ... Pero, esta tecnica es de dificil implementacion, ya que habria que considerar la posibilidad de que los paquetes se perdiesen. $ hping2 localhost --udp -i u[intervalo_en_microsegundos] ... --- localhost hping statistic --- *** packets tramitted, * packets received, ***% packet loss round-trip min/avg/max = *.*/*.*/*.* ms Y se analizaria si limita o no el numero de paquetes de ICMP Port Unreachable. Pero, no hago la prueba con mi localhost ya que las condiciones son completamente diferentes a las condiciones que te encontrarias en internet. Aun asi, veo de dificil implementacion esta tecnica por lo dicho anteriormente. 2) Basandose en los mensajes de error ICMP, y centrandose en los mensajes que se refieren a que no se pudo alcanzar un puerto casi todos los OSs mandan simplemente el encabezado ip y ocho bytes; pero, tanto solaris como linux mandan una respuesta un poco mas larga siendo este ultimo el que responde con mayor numero de bytes. Esto, claro esta, puede ser utilizado para distinguir unos OSs de otros. $ hping2 localhost --udp -p 21 y si analizamos uno de los paquetes ICMP de Destination unreachable observamos: Header length: 20 bytes Protocol: ICMP (0x01) Data (28 bytes) Type: 3 (Destination unreachable) Code: 3 (Port unreachable) se observa pues como en sistemas linux ademas del encabezado ip se retornan bastante mas de 8 bytes, 28 bytes. 3) Fijandose nuevamente en los mensajes de error ICMP debido a que no se pudo alcanzar un puerto, se observa que todos los OSs a excepcion de linux usa como valor de TOS (tipo de servicio) 0, pero linux en cambio, usa 0xc0 siendo esto parte del AFAIK, el campo de referencia, que no es usado. $ hping2 localhost --udp -p 21 y en el tcpdump, por ejemplo, observamos la siguiente salida: 16:27:57.052282 lo > honorato > honorato: icmp: honorato udp port fs p unreachable [tos 0xc0] 16:27:57.052282 lo < honorato > honorato: icmp: honorato udp port fs p unreachable [tos 0xc0] siendo el tos 0xc0 como he expuesto anteriormente, ya que se trata de un linux, a diferencia de los demas sistemas operativos. 4) Basandose en los encabezados de los paquetes ICMP de error vemos como diferentes OSs lo utilizan como 'scratch space'. Es decir, lo modifican; y asi por ejemplo encontramos como freebsd, openbsd, ultrix... cambian el ID de la IP del mensaje original en su respuesta, bsdi aumenta en 20 bytes la longitud total del campo de IP... (y hay mas diferencias, que estan por analizar, asi que ya sabes). En mi linux, por ejemplo, el un paquete udp al puerto 0 es: 0000 00 00 08 00 45 00 00 1c a4 c8 00 00 40 11 d8 06 ....E... ....@... 0010 7f 00 00 01 7f 00 00 01 0a e5 00 00 00 08 f6 f6 ........ ........ y su el paquete ICMP de Destination unreachable es: 0000 00 00 08 00 45 c0 00 38 22 de 00 00 ff 01 9a 24 ....E..8 "......$ 0010 7f 00 00 01 7f 00 00 01 03 03 fb 18 00 00 00 00 ........ ........ 0020 45 00 00 1c a4 c8 00 00 40 11 d8 06 7f 00 00 01 E....... @....... 0030 7f 00 00 01 0a e5 00 00 00 08 f6 f6 ........ .... En linux, el campo de la IP, no varia del paquete udp al icmp de error a diferencia de otros SOs pero pasa de tener id: 0xa4c8 a tener id: 0x22de. Este metodo no lo he estudiado a fondo y veo que puede tener bastantes particularidades. Si quieres tener una vision un poco mas completa del escaneo de puertos mediante metodos basados en ICMP puedes leer ICMP usage in scanning o tambien llamado Understanding some of the ICMP Protocol's Hazards de Ofir Arkin de Sys-security Group en http://www.sys-security.com. 5) Esta tecnica solo puede ser usada, en plataformas unix/linux/bsd y no en win* ya que win no responde a las queries que seran usadas, que son de ICMP tipo 13 o tambien conocidas como ICMP Timestamp Request. En el caso del sistema operativo linux, que es el que poseo podemos observar la siguiente prueba: $ sing -vv -tstamp 127.0.0.1 ... del que se obtendra un tiempo de respuesta de timestamp que puede ser utilizado para diferenciar unos OSs de otros. 6) Esta tecnica se basa en el funcinamiento especifico de los routers. En particular, se basa en ICMP Router Solicitation (ICMP de tipo 10). Cada router 'multicastea' cada cierto tiempo un anuncio de ruta (ICMP de tipo 9) desde cada una de sus interfaces de 'multicast', y de esta forma anuncia la direccion IP del interfaz. Si vemos que el host remoto responde con ICMP de tipo 9 frente a un ICMP de tipo 10, entonces nos encontramos ante un router. Pero, los routers que tengan suprimida esta caracteristica no seran detectados. Las pruebas para este metodo las puedes realizar tanto con hping2 como con sing (antiguo icmpush), pero el ultimo fue el primero en implementarla, y asi encontramos: $ sing -rts 127.0.0.1 ... $ hping2 -C 10 127.0.0.1 ... - Bit no fragmentado: esta tecnica se basa en que ciertos sistemas operativos ponen un bit no fragmentado de IP en algunos de los paquetes que envian. Pero lo que es cierto es que no todos lo hacen, y de hacerlo no lo hacen de la misma forma; lo que puede ser aprovechado para averiguar el OS. en mi linux (del que ya he copiado un uname -a antes, para saber el kernel que uso): $ hping2 localhost ... al analizar uno de los paquetes tcp mandados con ethereal se comprueba que: Flags: 0x04 .1.. = Don't Fragment: Set ..0. = More fragments: Not set pero, tampoco he hecho un gran numero de pruebas para asegurar que en algun caso y con cierto tipo de paquetes no se adjunte dicho bit. Aun asi, hay OSs que nunca lo usan como SCO o OpenBSD. - La ventana inicial de TCP: se basa en la comprobacion de las dimensiones de la ventana de los paquetes que nos devuelve el host a estudiar. El valor que toma es casi siempre igual para cada sistema operativo, he incluso hay sistemas que se pueden identificar por medio de este metodo, ya que son los unicos que le asignan cierto valor a dicha ventana (ej. AIX, 0x3F25). En lo que se refiere a sistemas linux, freebsd o solaris tienden a mantener el mismo tamaño de ventana para cada sesion. En cambio, cisco o Microsoft Windows/NT cambia constantemente. $ hping2 localhost ... y al analizar, por ejemplo dos de los paquetes con ethereal vemos: Window Size: 512 (0x0200) ... Window Size: 512 (0x0200) - Tratamiento de fragmentacion: Se basa en el hecho de que los sitemas operativos tratan de diferente forman los fragmentos de IP solapados; mientras algunos mantienen el material inicial, otros sobreescriben la porciones antiguas con las nuevas. De dificil implementacion puesto que hay sistemas operativos que no permiten mandar fragmentos de IP (lease Solaris), pero si que es cierto que tendria bastante utilidad. No lo he analizado en la practica, ya que no encontre la forma de hacerlo con hping2 y el hacer un codigo que lo haga no me parece materia para cubrir en este manual por tener bastante dificultad. - Synflood: una tecnica que no me parece aplicable, por razones bien marcadas. Hay ciertos OSs que llega un momento en que no aceptan nuevas conexiones si has mandado demasiados paquetes SYN y por ejemplo algunos sistemas operativos solo admiten 8 paquetes. Linux, evita esto por medio de las SYN cookies. - Nukes: Como ya he dicho anteriormente, la pila de Win95, WinNT o Win98 parece identica. Para distinguir entre una u otra el metodo que propongo es el aplicar nukes de forma cronologica (es decir, de mas antiguos a mas nuevos) e ir viendo si el servidor se cuelga o no; de esta forma sabremos la version ya que si sabemos que un nuke (por ejemplo, Winnuke) solo funciona con Win95 pues ya tendremos el OS. Aun asi, no recomiendo este metodo por razones obvias. Actualmente, estoy a la espera de que mixter me aclare si el ha conseguido alguna forma de distinguir una pila en win* en su nsat. De decirme como, lo incluire en este texto. Fingerprinting pasivo ~~~~~~~~~~~~~~~~~~~~~ El fingerprinting pasivo, en realidad, se basa en lo mismo que el fingerprinting tradicional pero la implementacion es distinta. Esta, se hace mediante un sniffer que tracea el host remoto. Como ves, en realidad, no somos nosotros los que enviamos paquetes sino que simplemente nos dedicamos a recoger los paquetes que son enviados por otros. Por tanto, se ve aqui una primera diferencia, tenemos que tener acceso a una de las maquinas que este en la red interna del host remoto o del host remoto en si, aunque esta ultima posibilidad en la mayoria de los casos ya implicaria el conocimiento del OS del host. Las cuatro cosas que comprobare en este tipo de fingerprinting son la TTL, el tamaño de ventana (window size), el Don't Fragment bit y el TOS. Pero aun esta por estudiar la posibilidad de fijarse en otras cosas que podrian servir en ciertos casos para distinguir unos OSs de otros; pero, en este manual, me centrare en unicamente en estos cuatro aspectos. Para diferenciar unos sistemas operativos de otros, habra que combinar estas cuatro pruebas. Otras de las cosas que se podrian estudiar seria el id, ISN, opciones de TCP/IP... Este sistema no es infalible y funcionara con unos OSs mejor que con otros y claro esta. Por ejemplo, mediante el uso de ethereal, se loggea una peticion www mediante el puerto 80. Si seleccionamos uno de los paquetes vemos lo siguiente: 183-BARC-X45.libre.retevision.es -> 97-VIGO-X12.libre.retevision.es Arrival Time: Jan 17, 2001 21:54:36.2724 Internet Protocol -> version: 4 Type of service: 0x00 (TOS) Flags: 0x04 -> .1.. = Don't fragment: Set Time to live: 58 (TTL) Window size: 15928 en decimal (0x3E38) Observas aqui, pues, los valores del TOS, DF bit, TTL y WS. Inicialmente nos fijaremos en el valor del TTL: El valor que podemos ver en el log del ethereal es 58. Lo mas probable es que el valor sea 64 pero haya saltado 6 veces hasta llegar a nosotros, y en este caso se trata de un linux. Pero, los saltos que hace hasta llegar a nuestro host lo podemos comprobar con la ayuda de traceroute; claro que sino quieres que sea reconocido por el host a estudio dicho traceroute sera mejor que este pare uno o dos hops antes del host, siendo esto posible gracias a poder especificar el time-to-live y asi podremos hacer: $ /usr/sbin/traceroute -m 7 183-BARC-X45.libre.retevision.es traceroute to 183-BARC-X45.libre.retevision.es (62.82.15.183), 7 hops max, 38 byte packets 1 VIGO-X12.red.retevision.es (62.81.45.44) 135.048 ms 122.210 ms 129.345 ms 2 VIGO-R1.red.retevision.es (62.81.45.28) 129.757 ms 119.371 ms VIGO-R3.red.retevision.es (62.81.45.27) 139.679 ms 3 VIGO-R15.red.retevision.es (62.81.44.133) 127.784 ms 129.119 ms 119.800 ms 4 BARC-R15.red.retevision.es (62.81.125.2) 159.456 ms 219.433 ms 214.197 ms 5 BARC-R11.red.retevision.es (62.81.24.5) 214.997 ms 219.233 ms 219.758 ms 6 BARC-X45.red.retevision.es (62.81.17.131) 210.725 ms 219.183 ms 219.693 ms Pero, para que se vea que realmente esto funciona, en este caso te copiare aqui el 7º host para que veas que ya seria el host remoto: 7 183-BARC-X45.libre.retevision.es (62.82.15.183) 339.842 ms BARC-X45 .red.retevision.es (62.81.17.131) 199.385 ms 179.089 ms A continuacion adjunto una tabla con los TTL de diversos sistemas operativos, especificando dicha ttl para tcp y udp: Sistema operativo ttl-tcp ttl-udp linux 64 64 MacOS/MacTCP 2.0.x 60 60 OS/2 TCP/IP 3.0 64 64 OSF/1 V3.2A 60 30 MS WfW 32 32 MS Windows 95 32 32 MS Windows NT 3.51 32 32 MS Windows NT 4.0 128 128 Solaris 2.x 255 255 Sun OS 4.1.3/4.1.4 60 60 Ultrix V4.1/V4.2A 60 30 VMS/Multinet 64 64 VMS/TCPware 60 64 VMS/Wollongong 1.1.1.1 128 30 VMS/UCX (ultimas ver.) 128 128 AIX 60 30 DEC Pathworks V5 30 30 FreeBSD 2.1R 64 64 HP/UX 9.0x 30 30 HP/UX 10.01 64 64 Irix 5.3 60 60 Irix 6.x 60 60 Lo que hay que tener en cuenta, es que existen ciertas utilidades que permiten cambiar este valor de TTL y asi por ejemplo: - HP/UX cuenta con una utilidad que cambia el valor del TTL en los kernels de HP/UX llamada set_ttl hecha por el HP Support Center. - En solaris se puede cambiar haciendo: ndd -set /dev/ip ip_def_ttl 'number' - En linux: echo 'number' > /proc/sys/net/ipv4/ip_default_ttl - En windows: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Paramete rs Pero aun asi, se puede incluir este valor en el fingerprinting, para diferenciar algunos OSs de otros. El siguiente valor a tener en cuenta es el tamaño de la ventana (Window Size): Como se ha comentado anteriormente en los analisis basados en la pila TCP/IP tradicionales ya no volvere a repetir la informacion. Pero, cabe destacar que en la prueba especifica que se hizo para fingerprinting pasivo, los valores no cambiaron y asi vemos: Arrival Time: Jan 17, 2001 21:54:37.5323 Window size: 15928 en decimal Arrival Time: Jan 17, 2001 21:54:38.1424 Window size: 15928 A continuacion, se analiza el bit DF: Es de valor unico, haciento esto mas facil el distinguir algunos sistemas que no lo usan, como por ejemplo SCO o OpenBSD como se ha especificado en la seccion anterior. Y se observa como en el ejemplo especifico usado para el fingerprinting pasivo: Flags: 0x04 -> .1.. = Don't fragment: Set El ultimo de los campos a estudiar es el TOS: De valor tambien limitado, actualmente no esta muy estudiado en funcion de que varia, pero se piensa que depende de la sesion y del protocolo usado en la misma. Este valor de TOS ha sido utilizado en uno de los metodos basados en ICMP de fingerprinting tradicional. 2.2 Servicios ~~~~~~~~~~~~~ I Software de escaneo de puertos y vulnerabilidades: panorama actual ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lo que quiero con esta reducida seccion es simplemente dar mi opinion acerca del software de escaneo de puertos que hay en este momento en la scene. Es preciso destacar, que mientras algunos de los programas que detallo a continuacion simplemente son escaneadores de puertos otros tambien pueden servir para detectar vulnerabilidades en el sistema, debidas a daemons (escuchando en puertos abiertos) que tienen bugs conocidos. Nmap: lo puedes encontrar en http://www.insecure.org/nmap/index.html, se trata de uno de los escaneadores de puertos mas completos. Desarrollado por Fyodor. Admite tanto un escaneo normal como "silencioso". Strobe-classb: lo podras encontrar en http://www.luyer.net/software/strobe-classb/ . Sirve para escanear redes grandes en poco tiempo pero no es updateado. Vetescan: esta en http://www.self-evident.com/sploits.html. Es normalmente una herramienta de "jaker", ya que con ella se puede escanear a gran velocidad grandes redes e incluye los exploits para las vulnerabilidades que detecta en el propio tar.gz Satan: para bajarlo vete a http://www.porcupine.org/satan/ . Usa una interface basada en web y su modelo de actuacion ha sido heredado por programas como Nessus, Saint o SARA. A lo mejor para hacerlo funcionar en las mas modernas distribuciones de linux tienes problemas. Nessus: bajatelo de http://www.nessus.org/ . Es muy util. Hay tanto cliente como servidor; hay clientes para X11, Java y Windows pero servidor unicamente para Unix. Es muy facil agregar nuevos chequeos para vulnerabilidades que inicialmente no estaba preparado y su equipo de desarrolladores suele updatearlo frecuentemente. Utiliza el Nmap para hacer un analisis preliminar de los puertos. Mas que recomendable. Saint: lo puedes encontrar en http://www.wwdsi.com/saint/ . Como ya he comentado se basa en Satan y como este funciona a traves de web. Las nuevas funcionalidades no son agregadas de una forma muy rapida pero esto trae consigo un mejor funcionamiento del programa que destaca por clasificar en niveles el problema encontrado. SARA: se encuentra en http://home.arc.com/sara/index.html. Hereda su funcionamiento de Saint y Satan. Incluye una herramienta para crear informes de las vulnerabilidades, etc. NSAT: te lo puedes bajar de http://mixter.void.ru/progs.html. Su creador es el mixter, reconocido profesional de la seguridad informatica. Al igual que nessus se le pueden hacer reglas nuevas de chequeo para nuevas vulnerabilidades no existentes en el momento de codear el programa. La pega es que no se puede utilizar desde una maquina remota y solo funciona bajo linux/unix. Messala: bajalo en http://www.securityfocus.com/tools/1228. Este programa me ha sorprendido gratamente ya que analiza un gran numero de vulnerabilidades conocidas. Ademas, sus desarrolladores lo updatean frecuentemente. Mns: pillalo en alguna web de seguridad informatica ya que los enlaces que van a la page de dicho programa no funcionan. Tiene capacidad de escanear "silenciosamente" y muestra vulnerabilidades. Hay gran numero de escaneadores de puertos de nivel bastante basico, tanto en C como perl que tampoco me voy a poner a analizar por separado; siempre puedes buscarlos en freshmeat.net o packetstorm.securify.com. En algun caso puede ser interesante bajarte alguno de ellos ya que te sera mas facil analizar el codigo usado para este tipo de utilidades. Por otra parte, cabe resaltar que puedes encontrar reducidos .c que unicamente comprueban la existencia de una vulnerabilidad en concreto. Incluso, te puede ser util, el hacerte algun escaner especifico de cierta vulnerabilidad, en caso de que esta no haya sido hecha publica. II Tecnicas usadas en el escaneo de puertos ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ En un escaneo de puertos, se han ido incluyendo tecnicas, que en la mayoria de los casos lo que buscan es que el escaneo de puertos no sea detectado por el host remoto. Actualmente hay un cierto vacio legal en lo que se refiere a este tipo de acciones ya que no esta muy claro si es legal o ilegal hacer dichos escaneos. Segun una sentencia reciente (12-2000) en USA, el escaneo de puertos no es ilegal, mientras no se perjudique al host remoto. Escaneando TCP con connect(): es el metodo basico que es usado en los escaners de puertos. El problema es que abre una conexion a un puerto de forma que puede ser detectado dicho intento y loggeado. La parte positiva es que destaca por su rapidez y facilidad de implementacion en codigo C. Puede ser utilizado con varios sockets en paralelo para asi no tener que usar un bucle que haria mas largo el proceso. Por ejemplo en el PortScanner-1.2, encontramos: ... while (((base_port + current_port) <= end_port) || !finished) { sock = socket(PF_INET, SOCK_STREAM, 0); ... if (connect(sock, (struct sockaddr *)&address2, sizeof(address2)) == 0) ... y a continuacion en el code simplemente encontramos como intenta averiguar el nombre de servicio asignado a cada puerto que va encontrando abierto, pero no lo voy a copiar aqui porque es un poco largo aunque facil. Vemos pues, como el funcionamiento de este escaner es sumamente sencillo y su archivo portscanner.c es de facil comprension para cualquier persona con ciertos conocimientos de C y unix networking programming. Escaneando TCP con SYN: Este metodo es un poco mejor que el clasico expuesto anteriormente ya que no abre una conexion TCP por completo, por eso el apelativo "half-open" en ingles. Se basa en enviar un paquete SYN a un puerto y si se obtiene un SYN|ACK es inequivocamente porque el puerto esta abierto y si se obtiene un RST es indicacion de que el puerto esta cerrado. De estar abierto se envia un RST para cerrar la conexion, pero esto lo hace automaticamente el kernel. Esta tecnica seguramente hay en servidores en los que no es detectada pero actualmente ya hay herramientas que permiten su deteccion como iplog, ademas, necesitas privilegios de root para construir dichos paquetes. Por ejemplo, en el portscanner hecho por Uriel Maimon (lifesux@cox.org) para su articulo en phrack 49 (Volume Seven, Issue Forty-Nine), Port Scanning without the SYN flag, vemos como define: ... 0: half-open scanning (type 0, SYN) /* se observa que admite este tipo de escaneo */ ... inline int tcpip_send(int socket, struct sockaddr_in *address, unsigned long s_addr, unsigned long t_addr, unsigned s_port, unsigned t_port, unsigned char tcpflags, unsigned long seq, unsigned long ack, unsigned win, char *datagram, unsigned datasize) /* para poder enviar paquetes configurables */ ... tcp->th_sport = htons(s_port); tcp->th_dport = htons(t_port); tcp->th_off = 5; /* 20 bytes, (no options) */ tcp->th_flags = tcpflags; tcp->th_seq = htonl(seq); tcp->th_ack = htonl(ack); tcp->th_win = htons(win); /* we don't need any bigger, I guess. */ /* opciones tcp */ ... struct tcphdr *tcp = (struct tcphdr *)(packet+IPHDRSIZE); ... if (tcp->th_flags & (TH_ACK | TH_SYN)) { readport->state = 1; printf(" (SYN+ACK)"); tcpip_send(rawsock,&destaddr, spoof_addr,destaddr.sin_addr.s_addr STCP_PORT,readport->n, TH_RST, readport->seq++, 0, 512, NULL, 0); } /* se observa aqui el corte despues con RST despues de recibir respuesta */ ... Pero, aun asi, te recomiendo que revises el codigo por completo si quieres entender bien este metodo aplicado a codes en C, ya que tampoco he pasteado todo lo importante sino lo que he encontrado interesante segun repasaba el codigo, y quizas para entenderlo hay que verlo integramente. Escaneando TCP con FIN: si piensas que el servidor que esta analizando puede detectar un escaner basado en la tecnica de envio de paquetes SYN, siempre se puede recurrir a escaners basados en este metodo. El hecho es que los puertos abiertos ante el envio de paquetes FIN no hacen nada, los ignoran, en cambio los puertos cerrados responden con un RST|ACK. Este metodo, pues, se basa en un bug de la implementacion TCP en ciertos sistemas operativos pero hay en ciertos sistemas que esto no funciona, como en el caso de las maquinas Microsoft). Pero, en las ultimas releases de ciertos programas ya se agrega la opcion incluso de detectar este tipo de scaneos. Asi por ejemplo snort: Fri 29 03:25:58 honorato snort[565]: SCAN-SYN FIN: w.x.y.z:0 -> z.y.w.98:53 Si quieres ver que realmente hay empresas que se preocupan hasta de este tipo de escaners puedes revisar el gran numero de logs de este tipo que hay en (por ejemplo): http://www.sans.org/y2k/070200-2000.htm Y en nmap encontramos (he saltado partes del code de la func., cuidado ): ... portlist fin_scan(struct hoststruct *target, unsigned short *portarray) { /* la funcion, a continuacion de esto, define variables, no lo he copiado, porque ocuparia demasiado.. */ ... timeout = (target->rtt)? target->rtt + 10000 : 1e5; bzero(&stranger, sockaddr_in_size); bzero(portno, o.max_sockets * sizeof(unsigned short)); bzero(trynum, o.max_sockets * sizeof(unsigned short)); starttime = time(NULL); /* preliminares */ ... if (o.debugging || o.verbose) printf("Initiating FIN stealth scan against %s (%s), sleep delay: %ld usecond s\n", target->name, inet_ntoa(target->host), timeout); /* se observa que indica que empieza el escaneo.. saca en pantalla datos del scan */ ... if (!target->source_ip.s_addr) { if (gethostname(myname, MAXHOSTNAMELEN) || !(myhostent = gethostbyname(myname))) fatal("Your system is fucked up.\n"); memcpy(&target->source_ip, myhostent->h_addr_list[0], sizeof(struct in_addr)) ; if (o.debugging || o.verbose) printf("We skillfully deduced that your address is %s\n", inet_ntoa(target->source_ip)); } /* comprobaciones de que localhost va bien y saca en pantala nuestra direccion local */ ... if (!(pd = pcap_open_live(target->device, 92, 0, 1500, err0r))) fatal("pcap_open_live: %s", err0r); if (pcap_lookupnet(target->device, &localnet, &netmask, err0r) < 0) fatal("Failed to lookup device subnet/netmask: %s", err0r); p = strdup(inet_ntoa(target->host)); #ifdef HAVE_SNPRINTF snprintf(filter, sizeof(filter), "tcp and src host %s and dst host %s and dst port %d", p, inet_ntoa(target->source_ip), MAGIC_PORT ); #else sprintf(filter, "tcp and src host %s and dst host %s and dst port %d", p, inet_ntoa(target->source_ip), MAGIC_PORT ); #endif free(p); if (o.debugging) printf("Packet capture filter: %s\n", filter); if (pcap_compile(pd, &fcode, filter, 0, netmask) < 0) fatal("Error compiling our pcap filter: %s\n", pcap_geterr(pd)); if (pcap_setfilter(pd, &fcode) < 0 ) fatal("Failed to set the pcap filter: %s\n", pcap_geterr(pd)); /* vemos como Fyodor hace usa de las librerias pcap y despues de dos comprobaciones con pcap_open_live() y pcap_lookupnet(), te recomiendo que si no estas familiarizado con la implementacion de pcap en C que leas algo sobre el tema. La funcion strdup devuelve un puntero a una nueva cadena que en realidad es duplicacion de la variable que tiene la direccion remota. Despues puedes observar que hace uso de snprintf de estar permitido su uso, y sino usa sprintf, donde puedes ver el host local, host remoto y puerto. A continuacion libera p con un free() y ves como monta el Packet capture filter con pcap */ ... if ((rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0 ) perror("socket trobles in fin_scan"); /* creacion de socket, y comprobacion de que funciona */ ... while(!done) { for(i=0; i < o.max_sockets; i++) { if (!portno[i] && portarray[j]) { portno[i] = portarray[j++]; } if (portno[i]) { if (o.fragscan) send_small_fragz(rawsd, &target->source_ip, &target->host, MAGIC_PORT, po rtno[i], TH_FIN); else send_tcp_raw(rawsd, &target->source_ip , &target->host, MAGIC_PORT, portno[i], 0, 0, TH_FIN, 0, 0, 0); usleep(10000); /* *WE* normally do not need this, but the target lamer often does */ } } /* interesante :), bueno, puedes observar un bucle de uso obvio y fijate en send_small_fragz() y send_tcp_raw(). Tambien interesante el uso del temporizador, con comentario del propio fyodor incluido */ ... { if (bytes < (4 * ip->ip_hl) + 4) continue; if (ip->ip_src.s_addr == target->host.s_addr) { tcp = (struct tcphdr *) (((char *) ip) + 4 * ip->ip_hl); if (tcp->th_flags & TH_RST) { badport = ntohs(tcp->th_sport); if (o.debugging > 1) printf("Nothing open on port %d\n", badport) ; /* delete the port from active scanning */ for(i=0; i < o.max_sockets; i++) if (portno[i] == badport) { if (o.debugging && trynum[i] > 0) printf("Bad port %d caught on fin scan, try number %d\n", badport, trynum[i] + 1); trynum[i] = 0; portno[i] = 0; break; } if (i == o.max_sockets) { if (o.debugging) printf("Late packet or dupe, deleting port %d.\n", badport); dupesinarow++; if (target->ports) deleteport(&target->ports, badport, IPPROTO_TCP); } } else if (o.debugging > 1) { printf("Strange packet from target%d! Here it is:\n", ntohs(tcp->th_sport)); if (bytes >= 40) readtcppacket(response,1); else hdump(response,bytes); } } } /* fijate dentro de if (tcp->th_flags & TH_RST) que si se cumple comprueba if (o.debugging > 1) y abre un bucle, con dos if's en su interior en los que tambien conviene fijarse. if (portno[i] == badport)...if (i == o.max_sockets)... y en el interior de los mismos imprime los Bad port y borra puerto del escaneo por ser "Late packet or dupe" respectivamente. Si no se cumple que (tcp->th_flags & TH_RST) entonces comprueba si o.debuffing > 1 y de serlo mira lo que pasa en el code */ ... /* adjust waiting time if neccessary */ if (dupesinarow > 6) { if (o.debugging || o.verbose) printf("Slowing down send frequency due to multiple late packets.\n"); if (timeout < 10 * (target->rtt + 20000)) timeout *= 1.5; else { printf("Too many late packets despite send frequency decreases, skipping scan.\n"); return target->ports; } } /* mas comprobaciones, para diferentes tipos de problemas, tampoco veo necesario detallarlo otra vez ya que creo que se entiende bastante bien de analizar todo el code */ ... someleft = 0; for(i=0; i < o.max_sockets; i++) if (portno[i]) { if (++trynum[i] >= retries) { if (o.verbose || o.debugging) printf("Good port %d detected by fin_scan!\n", portno[i]); addport(&target->ports, portno[i], IPPROTO_TCP, NULL); send_tcp_raw( rawsd, &target->source_ip, &target->host, MAGIC_PORT, por tno[i], 0, 0, TH_FIN, 0, 0, 0); portno[i] = trynum[i] = 0; } else someleft = 1; } if (!portarray[j] && (!someleft || --waiting_period <= 0)) done++; } /* voila, me parece que lo deja bien claro el printf, fijate en addport() y send_tcp_raw(). */ ... if (o.debugging || o.verbose) printf("The TCP stealth FIN scan took %ld seconds to scan %d ports.\n", (long) time(NULL) - starttime, o.numports); pcap_close(pd); close(rawsd); return target->ports; } /* bueno.. se acabo, curioso, eh? */ Escaneando TCP con 'reverse ident': esta tecnica se basa en que el protocolo ident (lee rfc1413) te permite descubrir el usuario propietario de un proceso conectado via TCP incluso si no ha sido el proceso el que ha iniciado la conexion. Esto, permite saber los propietarios de cada daemon que escucha en puertos abiertos. El problema es que se necesita abrir una conexion TCP completa y por lo tanto es facilmente detectable. Un ejemplo de codigo que aplica esto lo encontramos en el nmap de Fyodor: ... int getidentinfoz(struct in_addr target, int localport, int remoteport, char *owner) { /* inicio de la funcion en el que se aplica dicho metodo, no voy a copiar las definiciones de variables. */ ... owner[0] = '\0'; if ((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { perror("Socket troubles"); exit(1); } sock.sin_family = AF_INET; sock.sin_addr.s_addr = target.s_addr; sock.sin_port = htons(113); usleep(50000); /* me parece que esta muy claro la creacion de un socket() y la definicion de la familia, addr y puerto en lo referente a sock. */ ... res = connect(sd, (struct sockaddr *) &sock, sizeof(struct sockaddr_in)); if (res < 0 ) { if (o.debugging || o.verbose) printf("identd port not active now for some reason ... hope we didn't break it!\n"); close(sd); return 0; } /* en el supuesto de que el connect falle, es decir < 0 entonces comprueba if (o.debugging || o.verbose) y ya ves tu.. */ ... sprintf(request,"%hi,%hi\r\n", remoteport, localport); if (o.debugging > 1) printf("Connected to identd, sending request: %s", request ); if (write(sd, request, strlen(request) + 1) == -1) { perror("identd write"); close(sd); return 0; } else if ((res = read(sd, response, 1024)) == -1) { perror("reading from identd"); close(sd); return 0; } else { close(sd); if (o.debugging > 1) printf("Read %d bytes from identd: %s\n", res, response) ; /* se observa como si la o.debugging > 1 entonces es que que se ha conseguido conexion identd. Despues vemos el intento de write() y de read() con identd y la salida del numero de bytes leidos desde identd en caso de llevarse a cabo */ ... if ((p = strchr(response, ':'))) { p++; if ((q = strtok(p, " :"))) { if (!strcasecmp( q, "error")) { if (strstr(response, "HIDDEN-USER") || strstr(response, "hidden-user")) { printf("identd returning HIDDEN-USER, giving up on it\n"); return -1; } if (o.debugging) printf("ERROR returned from identd for port %d\n", rem oteport); return 0; } if ((os = strtok(NULL, " :"))) { if ((p = strtok(NULL, " :"))) { if ((q = strchr(p, '\r'))) *q = '\0'; if ((q = strchr(p, '\n'))) *q = '\0'; strncpy(owner, p, 512); owner[512] = '\0'; } } } } } return 1; } /* se observa como despues si localiza : en la cadena response (la enviada por identd). Despues, se observa el intento de averiguar el usuario, cuya salida comprueba que no sea HIDDEN-USER o hidden-user ya que esto querria decir que no se puede saber */ Fragmentation scanning: Este metodo se basa en una tecnica no totalmente nueva sino que es una variacion de otras tecnicas, ya que en realidad, y como mostrare en el ejemplo de codigo es un scan basado en SYN o FIN pero con pequeños paquetes fragmentados. En realidad, se mandan una pareja de pequeños fragmentos IP al host remoto a estudiar. El principal problema es que algunos programas tienen problemas para tratar este tipo de paquetes. La ventaja es que este metodo de escaneo es mas dificil de detectar y filtrar por los IDS. A continuacion, detallo un ejemplo en C de dicha tecnica: ... int send_small_fragz(int sd, struct in_addr *source, struct in_addr *victim, int sport, int dport, int flags) { /* inicio de la funcion en la que se ve un ejemplo practico de uso de este metodo */ ... struct pseudo_header { /* for computing TCP checksum, see TCP/IP Illustrated p. 145 */ unsigned long s_addy; unsigned long d_addr; char zer0; unsigned char protocol; unsigned short length; }; /* haz lo que dice fyodor en su comentario y te enteraras un poco mas de lo que significa esta estructura. Ademas, te recomiendo no solo que veas la p. 145 sino que leas TCP/IP Illustrated vol. 1 y 2 si quieres realmente tener un control del funcionamiento de redes TCP/IP */ ... char packet[sizeof(struct ip) + sizeof(struct tcphdr) + 100]; struct ip *ip = (struct ip *) packet; struct tcphdr *tcp = (struct tcphdr *) (packet + sizeof(struct ip)); struct pseudo_header *pseudo = (struct pseudo_header *) (packet + sizeof(struct ip) - sizeof(struct pseudo_header)); char *frag2 = packet + sizeof(struct ip) + 16; struct ip *ip2 = (struct ip *) (frag2 - sizeof(struct ip)); int res; struct sockaddr_in sock; int id; /* definiciones de estructuras y variables, fijate en las estructuras de tipo ip, tcphdr y pseudo_header. Realmente necesitas conceptos de programacion de sockets bajo C si quieres entenderlo; te recomiendo Unix Networking Programming vol. 1 y 2 de R. Stevens. Tampoco me parece el proposito de este manual explicar en si lo que es la programacion de sockets en C, unicamente mostrar el codigo usado para las tecnicas de escaneo especificadas. */ ... sock.sin_family = AF_INET; sock.sin_port = htons(dport); sock.sin_addr.s_addr = victim->s_addr; bzero((char *)packet, sizeof(struct ip) + sizeof(struct tcphdr)); /* definicion de familia, puerto y direccion de sock.. */ ... pseudo->s_addy = source->s_addr; pseudo->d_addr = victim->s_addr; pseudo->protocol = IPPROTO_TCP; pseudo->length = htons(sizeof(struct tcphdr)); tcp->th_sport = htons(sport); tcp->th_dport = htons(dport); tcp->th_seq = rand() + rand(); tcp->th_off = 5 /*words*/; tcp->th_flags = flags; tcp->th_win = htons(2048); /* Who cares */ tcp->th_sum = in_cksum((unsigned short *)pseudo, sizeof(struct tcphdr) + sizeof(struct pseudo_header)); /* Estamos hablando de raw sockets. Vemos pues, la definicion de variables de las estructuras pseudo (pseudo_header) y tcp (tcphdr) */ ... bzero((char *) packet, sizeof(struct ip)); ip->ip_v = 4; ip->ip_hl = 5; ip->ip_len = htons(sizeof(struct ip) + 16); id = ip->ip_id = rand(); ip->ip_off = htons(MORE_FRAGMENTS); ip->ip_ttl = 255; ip->ip_p = IPPROTO_TCP; ip->ip_src.s_addr = source->s_addr; ip->ip_dst.s_addr = victim->s_addr; #if HAVE_IP_IP_SUM ip->ip_sum= in_cksum((unsigned short *)ip, sizeof(struct ip)); #endif if (o.debugging > 1) { printf("Raw TCP packet fragment #1 creation completed! Here it is:\n"); hdump(packet,20); } if (o.debugging > 1) printf("\nTrying sendto(%d , packet, %d, 0 , %s , %d)\n", sd, ntohs(ip->ip_len), inet_ntoa(*victim), (int) sizeof(struct sockaddr_in)); if ((res = sendto(sd, packet, ntohs(ip->ip_len), 0, (struct sockaddr *)&sock, sizeof(struct sockaddr_in))) == -1) { perror("sendto in send_syn_fragz"); return -1; } if (o.debugging > 1) printf("successfully sent %d bytes of raw_tcp!\n", res); /* Vemos como inicialmente se prepara la cabecera ip del primer frag que se envia al host remoto; puedes ver como se rellenan las variables de la estructura ip. Despues, se ve como comprueba si la creacion del paquete ha sido satisfactoria y lo intenta enviar. */ ... bzero((char *) ip2, sizeof(struct ip)); ip2->ip_v= 4; ip2->ip_hl = 5; ip2->ip_len = htons(sizeof(struct ip) + 4); ip2->ip_id = id; ip2->ip_off = htons(2); ip2->ip_ttl = 255; ip2->ip_p = IPPROTO_TCP; ip2->ip_src.s_addr = source->s_addr; ip2->ip_dst.s_addr= victim->s_addr; #if HAVE_IP_IP_SUM ip2->ip_sum = in_cksum((unsigned short *)ip2, sizeof(struct ip)); #endif if (o.debugging > 1) { printf("Raw TCP packet fragment creation completed! Here it is:\n"); hdump(packet,20); } if (o.debugging > 1) printf("\nTrying sendto(%d , ip2, %d, 0 , %s , %d)\n", sd, ntohs(ip2->ip_len), inet_ntoa(*victim), (int) sizeof(struct sockaddr_i n)); if ((res = sendto(sd, (void *)ip2, ntohs(ip2->ip_len), 0, (struct sockaddr *)&sock, (int) sizeof(struct sockaddr_in))) == -1) { perror("sendto in send_tcp_raw frag #2"); return -1; } return 1; } /* se ve en esta ultima parte de codigo la creacion del segundo paquete, comprobacion de que todo va bien e intento de envio al host remoto. */ FTP bouncer: bueno, este metodo de escaneo se basa en la caracteristica de algunos servidores de ftp que permiten usarlo como "proxy", es decir, crear una server-DTP activo que te permita enviar cualquier fichero a cualquier otro server. La tecnica en si para el proposito de escaneo de puertos consiste en conectar por ftp al server y mediante el comando PORT declarar el "User-DTP" pasivo que escucha en el puerto que queremos saber si esta abierto. Despues, se actua de la siguiente forma: se hace un LIST del directorio actual y el resultado sera enviado al canal Server-DTP. Si el puerto que comprobamos esta abierto todo ocurre con normalidad generando las respuestas 150 y 226 pero si el puerto esta cerrado obtendremos "425 Can't build data connection: Connection refused.". Este metodo, es en parte no lo suficientemente rapido pero aun asi puede ser util ya que es dificil de tracear por parte del server remoto. Un ejemplo de implementacion de esta tecnica en codigo C: (nmap) ... portlist bounce_scan(struct hoststruct *target, unsigned short *portarray, struct ftpinfo *ftp) { /* vemos el inicio de la funcion que aplica esta tecnica */ ... int starttime, res , sd = ftp->sd, i=0; char *t = (char *)&target->host; int retriesleft = FTP_RETRIES; char recvbuf[2048]; char targetstr[20]; char command[512]; #ifndef HAVE_SNPRINTF sprintf(targetstr, "%d,%d,%d,%d,0,", UC(t[0]), UC(t[1]), UC(t[2]), UC(t[3])); #else snprintf(targetstr, 20, "%d,%d,%d,%d,0,", UC(t[0]), UC(t[1]), UC(t[2]), UC(t[ 3])); #endif /* simplemente definicion de variables y usa snprintf o sprintf segun si se tiene o no */ ... starttime = time(NULL); if (o.verbose || o.debugging) printf("Initiating TCP ftp bounce scan against %s (%s)\n", target->name, inet_ntoa(target->host)); for(i=0; portarray[i]; i++) { #ifndef HAVE_SNPRINTF sprintf(command, "PORT %s%i\r\n", targetstr, portarray[i]); #else snprintf(command, 512, "PORT %s%i\r\n", targetstr, portarray[i]); #endif /* inicio del escaneo, definicion de bucle para ir cambiando puerto (portarray) */ ... if (send(sd, command, strlen(command), 0) < 0 ) { perror("send in bounce_scan"); if (retriesleft) { if (o.verbose || o.debugging) printf("Our ftp proxy server hung up on us! retrying\n"); retriesleft--; close(sd); ftp->sd = ftp_anon_connect(ftp); if (ftp->sd < 0) return target->ports; sd = ftp->sd; i--; } else { fprintf(stderr, "Our socket descriptor is dead and we are out of retries. Giving up.\n"); close(sd); ftp->sd = -1; return target->ports; } } else { res = recvtime(sd, recvbuf, 2048,15); if (res <= 0) perror("recv problem from ftp bounce server\n"); else { recvbuf[res] = '\0'; if (o.debugging) printf("result of port query on port %i: %s", portarray[i], recvbuf); if (recvbuf[0] == '5') { if (portarray[i] > 1023) { fprintf(stderr, "Your ftp bounce server sucks, it won't let us feed bog us ports!\n"); exit(1); } /* en esta parte de code mira que envia con send() y comprueba que el envio ha sido correcto y la recepcion tambien, comprueba que hace uso de ftp_anon_connect(), funcion que podras encontrar en el codigo fuente de nmap en nmap.c, pero que yo no he copiado aqui por no alargar mas la explicacion, el nombre ya indica obviamente para que sirve. Puedes ver que cuando recibe bien y "if (o.debugging) entonces saca en pantalla el resultado del query al puerto. */ ... else { fprintf(stderr, "Your ftp bounce server doesn't allow priviliged ports, skipping them.\n"); while(portarray[i] && portarray[i] < 1024) i++; if (!portarray[i]) { fprintf(stderr, "And you didn't want to scan any unpriviliged ports. Giving up.\n"); /* close(sd); ftp->sd = -1; return *ports;*/ /* screw this gentle return crap! This is an emergency! */ exit(1); } } } /* en caso de que no se consiga query a ningun puerto */ ... else if (send(sd, "LIST\r\n", 6, 0) > 0 ) { res = recvtime(sd, recvbuf, 2048,12); if (res <= 0) perror("recv problem from ftp bounce server\n"); else { recvbuf[res] = '\0'; if (o.debugging) printf("result of LIST: %s", recvbuf); if (!strncmp(recvbuf, "500", 3)) { /* fuck, we are not aligned properly */ if (o.verbose || o.debugging) printf("misalignment detected ... correcting.\n"); res = recvtime(sd, recvbuf, 2048,10); } if (recvbuf[0] == '1' || recvbuf[0] == '2') { if (o.verbose || o.debugging) printf("Port number %i appears good.\ n", portarray[i]); addport(&target->ports, portarray[i], IPPROTO_TCP, NULL); if (recvbuf[0] == '1') { res = recvtime(sd, recvbuf, 2048,5); recvbuf[res] = '\0'; if (res > 0) { if (o.debugging) printf("nxt line: %s", recvbuf); if (recvbuf[0] == '4' && recvbuf[1] == '2' && recvbuf[2] == '6') { deleteport(&target->ports, portarray[i], IPPROTO_TCP); if (o.debugging || o.verbose) printf("Changed my mind about port %i\n", portarray[i]); } } } } } } } } } /* empieza con el LIST al server. Ademas vemos que comprueba si no hay una alineacion correcta y la corrige. Finalmente añade los puertos que comprueba que estan abiertos y.. (ver siguiente comentario) */ if (o.debugging || o.verbose) printf("Scanned %d ports in %ld seconds via the Bounce scan.\n", o.numports, (long) time(NULL) - starttime); return target->ports; } /* final de la funcion, devuelve los puertos abiertos, final del bounce scan */ Envio de ACKs: este metodo se basa en el envio de un paquete ACK. El metodo de analisis de la respuesta RST puede ser: 1. fijarse en el valor del TTL 2. fijarse en el valor de win 1. Si el puerto esta abierto entonces encontramos en la respuesta un valor de ttl menor de 64. 2. Si el puerto esta abierto encontramos un valor de win en la respuesta distinto de 0. Pero, esta tecnica de escaneo no se cumple en todo tipo de sistemas y su implementacion no esta muy clara. Si quieres saber mas sobre este metodo puedes leer Phrack 49; articulo 15 de Uriel Maimon. Null scan: este metodo se basa en el envio de paquetes sin ningun flag en la cabecera TCP, pero, el incluir en los bits reservados (RES1, RES2) no influye. En caso de que el puerto este abierto, no se recibe respuesta del host remoto pero en el caso de estar cerrado se recibe un RST|ACK. Este tipo de escaneo solo funciona en caso de que el host remoto sea unix (BSD sockets). En la version 1.49 del Nmap aun no estaba implementado, actualmente si; pero no tengo las fuentes de la ultima version asi que tendras que buscarlo. Para realizar este tipo de pruebas con el hping2 puedes hacer: $ hping2 127.0.0.1 -c 1 -p 80 y en caso de estar cerrado el puerto 80 se recibira un RST|ACK. Pero, este metodo, puede no ser valido desde el momento en que los hosts remotos pueden chequear paquetes sin flags. Xmas scan: este metodo es digamos antonimo al NULL ya que en el todas los flags estan activados, es decir, con SYN, ACK, FIN, RST, URG, PSH.y de nuevo los bits reservados no influyen en el resultado del escaneo y de nuevo solo funcionara contra host remotos que sean unix, ya que se basa en la implementacion de la pila TCP/IP que hacen sistemas unix/linux/bsd. En caso de que el puerto este abierto, no se recibe respuesta y en caso de que este cerrado se recibe un RST|ACK. En lo que se refiere a las fuentes pasa lo mismo que con el null scan. Para realizar esta prueba con el hping2 tendras que hacer, por ejemplo: $ hping2 127.0.0.1 -c 1 -p 80 -F -S -R -P -A -U -X -Y Spoofed scan a traves de un 'host dormido': este metodo de escaneo destaca, claro está, porque aunque sea detectable, no detecta al que está escaneando sino al 'host dormido' (considerado A) (de actividad 0) que es utilizado para el escaneo. Para esta tecnica se puede usar hping2 y se basa en la variacion del id de los paquetes que envia A (host dormido). Para que se entienda: Se usara hping2 para enviar paquetes TCP con unos flags determinados. Exactamente lo que se hace es monitorizar la actividad de A para asi obtener el incremento del id que inicialmente es de +1 ya que no tiene actividad. A continuacion, se enviaran un paquete SYN spoofeado con la ip del host A al puerto que queramos saber si esta abierto del host remoto (considerado B). Si el puerto de B al que enviamos dichos paquetes esta abierto devolvera un paquete SYN y un ACK a A (host dormido), forzando a B a mandar un RST, ya que A no inicio dicha conexion y no quiere continuar la comunicacion. Esto, hace que A (host silencioso), tenga actividad y por tanto que cambie drasticamente su id, por tanto, sabremos de esta forma que el puerto esta abierto. Lo que hay que puntualizar es que es no es muy sencillo encontrar un host remoto en el que realmente no haya actividad y ademas no todos los citados servidores sirven puesto que no incrementas su numero inicial de secuencia de la misma forma. Para obtener una monitorizacion de la actividad de A (host dormido) se tendra que: $ hping2 A -r HPING B (ppp0 xxx.yyy.zzz.jjj): no flags are set, 40 headers + 0 data bytes 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=0 ttl=64 id=xxx win=0 time=1.3 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=1 ttl=64 id=+1 win=0 time=80 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=2 ttl=64 id=+1 win=0 time=89 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=3 ttl=64 id=+1 win=0 time=90 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=4 ttl=64 id=+1 win=0 time=91 ms ... Para enviar un paquete SYN spoofeado: $ hping2 B -a A -S -p puerto ppp0 default routing interface selected (according to /proc) HPING 127.0.0.1 (ppp0 127.0.0.1): S set, 40 headers + 0 data bytes ... Y entonces si el puerto al que hemos enviado los paquetes está abierto, vemos como en la monitorizacion de A se observa: ... 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=17 ttl=64 id=+1 win=0 time=92 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=18 ttl=64 id=+1 win=0 time=84 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=19 ttl=64 id=+2 win=0 time=83 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=20 ttl=64 id=+3 win=0 time=92 ms 60 bytes from xxx.yyy.zzz.jjj: flags=RA seq=21 ttl=64 id=+1 win=0 time=91 ms ... Escaneando UDP mediante error de ICMP port unreachable: En este metodo encontramos una primera diferencia, se usa el protocolo UDP, no TCP. Aunque dicho protocolo es mas simple, es mas dificil usarlo para escanear, debido a que los puertos abiertos no usan digamos la funcion fatica y los puertos cerrados no tienen porque enviar un mensaje de error ante nuestros envios. Pero, la mayoria de los hosts mandan un mensaje de error ICMP_PORT_UNREACH cuando envias un paquete (UDP) a un puerto UDP cerrado. Esto puede ser usado para, por exclusion, saber los puertos que estan abiertos pero no es muy viable, porque tampoco se tiene la seguridad de que el error de ICMP llegue a nosotros y es ciertamente lento, e incluso mas cuando nos encontramos con un host que limita el numero de mensajes de ICMP a enviar, como se ha comentado anteriormente en los metodos para averiguar el tipo de OS que presenta una maquina. Ademas, necesitas acceso de root para poder construir raw ICMP socket para leer los puertos inalcanzables. Un ejemplo de aplicacion de esta tecnica en C: (nmap) portlist udp_scan(struct hoststruct *target, unsigned short *portarray) { /* inicio de la funcion que pone en practica este metodo */ ... int icmpsock, udpsock, tmp, done=0, retries, bytes = 0, res, num_out = 0; int i=0,j=0, k=0, icmperrlimittime, max_tries = UDP_MAX_PORT_RETRIES; unsigned short outports[MAX_SOCKETS_ALLOWED]; unsigned short numtries[MAX_SOCKETS_ALLOWED]; struct sockaddr_in her; char senddata[] = "blah\n"; unsigned long starttime, sleeptime; struct timeval shortwait = {1, 0 }; fd_set fds_read, fds_write; bzero( (char *) outports, o.max_sockets * sizeof(unsigned short)); bzero( (char *) numtries, o.max_sockets * sizeof(unsigned short)); /* como puedes ver, preliminares, pero importantes, puesto que sino no entenderas el resto del code. */ icmperrlimittime = 60000; sleeptime = (target->rtt)? ( target->rtt) + 30000 : 1e5; if (o.wait) icmperrlimittime = o.wait; starttime = time(NULL); FD_ZERO(&fds_read); FD_ZERO(&fds_write); if (o.verbose || o.debugging) printf("Initiating UDP (raw ICMP version) scan against %s (%s) using wait dela y of %li usecs.\n", target->name, inet_ntoa(target->host), sleeptime); if ((icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) perror("Opening ICMP RAW socket"); if ((udpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) perror("Opening datagram socket"); unblock_socket(icmpsock); her.sin_addr = target->host; her.sin_family = AF_INET; /* se observa como icmperrlimittime hace que no estropee el scan el hecho de que algunos SOs pongan limites al numero de mensajes de error ICMP por tiempo. Abre raw socket y dgram socket. */ ... while(!done) { tmp = num_out; for(i=0; (i < o.max_sockets && portarray[j]) || i < tmp; i++) { close(udpsock); if ((udpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) perror("Opening datagram socket"); if ((i > tmp && portarray[j]) || numtries[i] > 1) { if (i > tmp) her.sin_port = htons(portarray[j++]); else her.sin_port = htons(outports[i]); FD_SET(udpsock, &fds_write); FD_SET(icmpsock, &fds_read); shortwait.tv_sec = 1; shortwait.tv_usec = 0; usleep(icmperrlimittime); res = select(udpsock + 1, NULL, &fds_write, NULL, &shortwait); if (FD_ISSET(udpsock, &fds_write)) bytes = sendto(udpsock, senddata, sizeof(senddata), 0, (struct sockaddr *) &her, sizeof(struct sockaddr_in)); else { printf("udpsock not set for writing port %d!", ntohs(her.sin_port)); return target->ports; } if (bytes <= 0) { if (errno == ECONNREFUSED) { retries = 10; do { printf("sendto said connection refused on port %d but trying again anyway.\n", ntohs(her.sin_port)); usleep(icmperrlimittime); bytes = sendto(udpsock, senddata, sizeof(senddata), 0, (struct sockaddr *) &her, sizeof(struct sockaddr_in)) ; printf("This time it returned %d\n", bytes); } while(bytes <= 0 && retries-- > 0); } if (bytes <= 0) { printf("sendto returned %d.", bytes); fflush(stdout); perror("sendto"); } } if (bytes > 0 && i > tmp) { num_out++; outports[i] = portarray[j-1]; } } } usleep(sleeptime); tmp = listen_icmp(icmpsock, outports, numtries, &num_out, target->host, &targ et->ports); if (o.debugging) printf("listen_icmp caught %d bad ports.\n", tmp); done = !portarray[j]; for (i=0,k=0; i < o.max_sockets; i++) if (outports[i]) { if (++numtries[i] > max_tries - 1) { if (o.debugging || o.verbose) printf("Adding port %d for 0 unreachable port generations\n", outports[i]); addport(&target->ports, outports[i], IPPROTO_UDP, NULL); num_out--; outports[i] = numtries[i] = 0; } else { done = 0; outports[k] = outports[i]; numtries[k] = numtries[i]; if (k != i) outports[i] = numtries[i] = 0; k++; } } if (num_out == o.max_sockets) { printf("Numout is max sockets, that is a problem!\n"); sleep(1); } } /* si analizas este fragmento de codigo (copia un poco mas grande de lo normal, pero es que es mas entendible asi. Puedes observar que se repite el proceso varias veces; dado el problema de que algunas veces los paquetes ICMP de error no nos lleguen a nosotros, asi es una forma de asegurarse. Ademas, puedes ver que en todo, si falla algo, te devuelve justamente el error. Lo mejor es que analices tu mismo el codigo. */ ... if (o.debugging || o.verbose) printf("The UDP raw ICMP scanned %d ports in %ld seconds with %d parallel so ckets.\n", o.numports, time(NULL) - starttime, o.max_sockets); close(icmpsock); close(udpsock); return target->ports; } /* final de la funcion, devuelve puertos, saca en pantalla datos del escaneo, etc. */ Escaneo UDP basado en recvfrom() y write(): este metodo arregla el problema de que los errores ICMP de port unreachable solo puedan ser leidos por el root. Para saber si ha sido recibido un error de ICMP basta con usar recvfrom() y se recibira ECONNREFUSED ("Connection refused", errno 111) si se ha recibido y EAGAIN("Try Again", errno 13) si no se ha recibido. Un ejemplo de este metodo implementado en C lo encontramos nuevamente en nmap. portlist lamer_udp_scan(struct hoststruct *target, unsigned short *portarray) { /* inicio de funcion que implementa este metodo, no voy a copiar las definiciones de variables e inicializacion del primer socket. Si quieres verlo completo revisa las fuentes de nmap por ejemplo /nmap-1.49/nmap.c */ ... if (o.wait) sleeptime = o.wait; else sleeptime = calculate_sleep(target->host) + 60000; if (o.verbose || o.debugging) printf("Initiating UDP scan against %s (%s), sleeptime: %li\n", target->name, inet_ntoa(target->host), sleeptime); starttime = time(NULL); /* temporizador/saca en pantalla que se inicia el escaneo */ ... for(i = 0 ; i < o.max_sockets; i++) trynum[i] = portno[i] = 0; while(portarray[j]) { for(i=0; i < o.max_sockets && portarray[j]; i++, j++) { if (i >= last_open) { if ((sockets[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {perror("datagram socket troubles"); exit(1);} block_socket(sockets[i]); portno[i] = portarray[j]; } her.sin_port = htons(portarray[j]); bytes = sendto(sockets[i], data, sizeof(data), 0, (struct sockaddr *) &her, sizeof(struct sockaddr_in)); usleep(5000); if (o.debugging > 1) printf("Sent %d bytes on socket %d to port %hi, try number %d.\n", bytes, sockets[i], portno[i], trynum[i]); if (bytes < 0 ) { printf("Sendto returned %d the FIRST TIME!@#$!, errno %d\n", bytes, errno); perror(""); trynum[i] = portno[i] = 0; close(sockets[i]); } } /* envio de datos al puerto a escanear. Comprueba por ti mismo el codigo */ last_open = i; /* Might need to change this to 1e6 if you are having problems*/ usleep(sleeptime + 5e5); for(i=0; i < last_open ; i++) { if (portno[i]) { unblock_socket(sockets[i]); if ((bytes = recvfrom(sockets[i], response, 1024, 0, (struct sockaddr *) &stranger, &sockaddr_in_size)) == -1) { if (o.debugging > 1) printf("2nd recvfrom on port %d returned %d with errno %d.\n", portno[i], bytes, errno); if (errno == EAGAIN /*11*/) { if (trynum[i] < 2) trynum[i]++; else { if (RISKY_UDP_SCAN) { printf("Adding port %d after 3 EAGAIN errors.\n", portno[i]); addport(&target->ports, portno[i], IPPROTO_UDP, NULL); } else if (o.debugging) printf("Skipping possible false positive, port %d\n", portno[i]); trynum[i] = portno[i] = 0; close(sockets[i]); } } else if (errno == ECONNREFUSED /*111*/) { if (o.debugging > 1) printf("Closing socket for port %d, ECONNREFUSED received.\n", portno[i]); trynum[i] = portno[i] = 0; close(sockets[i]); } else { printf("Curious recvfrom error (%d) on port %hi: ", errno, portno[i]); perror(""); trynum[i] = portno[i] = 0; close(sockets[i]); } } else /*bytes is positive*/ { if (o.debugging || o.verbose) printf("Adding UDP port %d due to positive read!\n", portno[i]); addport(&target->ports,portno[i], IPPROTO_UDP, NULL); trynum[i] = portno[i] = 0; close(sockets[i]); } } } ... /* Update last_open, we need to create new sockets.*/ for(i=0, k=0; i < last_open; i++) if (portno[i]) { close(sockets[i]); sockets[k] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); /* unblock_socket(sockets[k]);*/ portno[k] = portno[i]; trynum[k] = trynum[i]; k++; } last_open = k; for(i=k; i < o.max_sockets; i++) trynum[i] = sockets[i] = portno[i] = 0; } /* observa en este fragmento de codigo como se cumple a la perfeccion la teoria de explicacion de este metodo. Me parece un codigo bastante simple de analizar asi que mirate todos los if's y lo entenderas perfectamente (siempre que tengas conocimientos de TCP/IP/C */ ... if (o.debugging) printf("UDP scanned %d ports in %ld seconds with %d parallel sockets\n", o.numports, (long) time(NULL) - starttime, o.max_sockets); return target->ports; } /* fin de la funcion, datos sobre el escaneo, devuelve puertos */ Escaneo de servicios RPC: es relativamente facil hacer un scan de los puertos que ofrecen servicios rpc, bastante rapido y en la mayoria de los casos no se dejan logs en el host remoto. Pero, debido a que han sido descubiertas bastantes vulnerabilidades en estos servicios ciertos sysadmins han optado por bloquear el acceso a este tipo de servicios. Al referirme a RPC lo hago a ONC RPC y no DCE RPC RPC es un sistema basado en query <-> reply. Despues de enviar el numero del programa en el que estas interesado, el numero de procedimiento, algun argumento, autentificacion y otros parametros del host remoto obtienes lo que el procedimiento devuelve o algunas indicaciones de porque fallo. Pero, antes tenemos que saber que puertos UDP estan abiertos, asi que se usara un connect() para saberlo (como se explico anteriormente) y obtendremos un ICMP de PORT_UNREACH en caso de que no este a la escucha. Para que RPC fuese portable todos los argumentos son traducidos a XDR, que es un lenguaje de data encoding que se parece un poco a Pascal (rfc1832). Los programas RPC tienen varios procedimientos pero aun uno que siempre existe, es el procedimiento 0. Este, no admite argumentos y no devuelve ningun valor.. (void rpcping()void). Y de esta forma es como determinaremos el puerto en el que se encuentra un programa, llamaremos al procedimiento ping. A continuacion, te presento un codigo de halflife (gracias, halflife halflife@infonexus.com) en el que se consigue hacer esto: <++> RPCscan/Makefile CC=gcc PROGNAME=rpcscan CFLAGS=-c build: checkrpc.o main.o rpcserv.o udpcheck.o $(CC) -o $(PROGNAME) checkrpc.o main.o rpcserv.o udpcheck.o checkrpc.o: $(CC) $(CFLAGS) checkrpc.c main.o: $(CC) $(CFLAGS) main.c rpcserv.o: $(CC) $(CFLAGS) rpcserv.c udpcheck.o: $(CC) $(CFLAGS) udpcheck.c clean: rm -f *.o $(PROGNAME) <--> <++> RPCscan/checkrpc.c #include #include #include #include #include #include #include extern struct sockaddr_in *saddr; int check_rpc_service(long program) { int sock = RPC_ANYSOCK; CLIENT *client; struct timeval timeout; enum clnt_stat cstat; timeout.tv_sec = 10; timeout.tv_usec = 0; client = clntudp_create(saddr, program, 1, timeout, &sock); if(!client) return -1; timeout.tv_sec = 10; timeout.tv_usec = 0; cstat = RPC_TIMEDOUT; cstat = clnt_call(client, 0, xdr_void, NULL, xdr_void, NULL, timeout); if(cstat == RPC_TIMEDOUT) { timeout.tv_sec = 10; timeout.tv_usec = 0; cstat = clnt_call(client, 0, xdr_void, NULL, xdr_void, NULL, ti meout); } clnt_destroy(client); close(sock); if(cstat == RPC_SUCCESS) return 1; else if(cstat == RPC_PROGVERSMISMATCH) return 1; else return 0; } <--> <++> RPCscan/main.c #include #include #include int check_udp_port(char *, u_short); int check_rpc_service(long); long get_rpc_prog_number(char *); #define HIGH_PORT 5000 #define LOW_PORT 512 main(int argc, char **argv) { int i,j; long prog; if(argc != 3) { fprintf(stderr, "%s host program\n", argv[0]); exit(0); } prog = get_rpc_prog_number(argv[2]); if(prog == -1) { fprintf(stderr, "invalid rpc program number\n"); exit(0); } printf("Scanning %s for program %d\n", argv[1], prog); for(i=LOW_PORT;i <= HIGH_PORT;i++) { if(check_udp_port(argv[1], i) > 0) { if(check_rpc_service(prog) == 1) { printf("%s is on port %u\n", argv[2], i); exit(0); } } } } <--> <++> RPCscan/rpcserv.c #include #include #include #include #include #include long get_rpc_prog_number(char *progname) { struct rpcent *r; int i=0; while(progname[i] != '\0') { if(!isdigit(progname[i])) { setrpcent(1); r = getrpcbyname(progname); endrpcent(); if(!r) return -1; else return r->r_number; } i++; } return atoi(progname); } <--> <++> RPCscan/udpcheck.c #include #include #include #include #include #include #include #include #include #include #include #include extern int h_errno; struct sockaddr_in *saddr = NULL; int check_udp_port(char *hostname, u_short port) { int s, i, sr; struct hostent *he; fd_set rset; struct timeval tv; if(!saddr) { saddr = malloc(sizeof(struct sockaddr_in)); if(!saddr) return -1; saddr->sin_family = AF_INET; saddr->sin_addr.s_addr = inet_addr(hostname); if(saddr->sin_addr.s_addr == INADDR_NONE) { sethostent(1); he = gethostbyname(hostname); if(!he) { herror("gethostbyname"); exit(1); } if(he->h_length <= sizeof(saddr->sin_addr.s_addr)) bcopy(he->h_addr, &saddr->sin_addr.s_addr, he-> h_length); else bcopy(he->h_addr, &saddr->sin_addr.s_addr, size of(saddr->sin_addr.s_addr)); endhostent(); } } saddr->sin_port = htons(port); s = socket(AF_INET, SOCK_DGRAM, 0); if(s < 0) { perror("socket"); return -1; } i = connect(s, (struct sockaddr *)saddr, sizeof(struct sockaddr_in)); if(i < 0) { perror("connect"); return -1; } for(i=0;i < 3;i++) { write(s, "", 1); FD_ZERO(&rset); FD_SET(s, &rset); tv.tv_sec = 5; tv.tv_usec = 0; sr = select(s+1, &rset, NULL, NULL, &tv); if(sr != 1) continue; if(read(s, &sr, sizeof(sr)) < 1) { close(s); return 0; } else { close(s); return 1; } } close(s); return 1; } <--> Escaneo basandose en traceroute: este metodo lo usare para saber algo mas de los sistemas firewalleados. A continuacion detallare los casos especificos que nos podemos encontrar en el uso de traceroute para saber el camino de nuestro ordenador al host remoto y del cual deduciremos el tipo de firewall que protege al host remoto. 1) Cuando el firewall bloquea todo el trafico excepto pings, ICMP de tipo 8, y respuestas de pings, ICMP de tipo 0. En este caso si hacemos un traceroute normal (es decir usando UDP; suponiendo que en la red de ejemplo estamos en 200.0.0.1 y para llegar a 200.0.0.10 hay que pasar por 200.0.0.2, 200.0.0.3... 200.0.0.10): $ traceroute 200.0.0.10 traceroute to 200.0.0.10 (200.0.0.10), 30 hops max, 40 byte packets ... 9 * * * 10 * * * En cambio si forzamos a traceroute a usar ICMPs observamos: $ traceroute 200.0.0.10 traceroute to 200.0.0.10 (200.0.0.10), 30 hops max, 40 byte packets ... 9 200.0.0.9 ... 10 200.0.0.10 ... Vemos, pues, que ya tenemos mas informacion de la que teniamos inicialmente. 2) Cuando un firewall bloquea todo el trafico excepto el puerto UDP 53 (DNS), observamos al hacer un traceroute normal: $ traceroute 200.0.0.10 traceroute to 200.0.0.10 (200.0.0.10), 30 hops max, 40 byte packets ... 9 * * * 10 * * * Por tanto, se puede observar como el firewall nos impide realizar esto, ya que solo admite queries de DNS. A continuacion me basare en lo siguiente; que se puede determinar el numero de hops entre nosotros y el host firewalleado, que se puede controlar el puerto sobre el que lanzamos el traceroute, y que podemos cambiar el numero de pruebas enviadas de cada vez. Basandose en esto, podemos controlar el puerto que se alcanzara en el firewall y esto sera de gran ayuda sobre todo si pensamos en que el firewall no analiza el contenido de los paquetes y podemos hacerle pensar que son queries de DNS. Pero, la cosa no es tan facil y para saber con que puerto empezaremos nuestro escaneo, hacemos: (puerto_host_remoto - (numero_de_hops * numero_de_pruebas)) - 1 y por tanto: (53 - (8 * 3)) - 1 = 28 Y por tanto: $ traceroute -p28 200.0.0.10 traceroute to 200.0.0.10 (200.0.0.10), 30 hops max, 40 byte packets ... 9 200.0.0.9 (200.0.0.9) x.x ms * * 10 * * * Puedes observar por tanto, que el escaneo termina justo despues de pasar el puerto del firewall; esto es debido a que se sigue incrementando el puerto y por tanto se produce una denegacion del firewall para ese tipo de envio. Pero, se puede hacer un patch para traceroute para conseguir que no pase esto (gracias a firewalking, ver bibliografia): /* pertenece a firewalking */ ---------------------8<-------- traceroute.diff ------------------------------ --- traceroute.c.orig Fri Aug 21 15:15:23 1998 +++ traceroute.c Sun Aug 23 18:58:08 1998 @@ -289,6 +289,7 @@ int nprobes = 3; int max_ttl = 30; int first_ttl = 1; +int static_port = 0; u_short ident; u_short port = 32768 + 666; /* start udp dest port # for probe packets */ @@ -352,7 +353,7 @@ prog = argv[0]; opterr = 0; - while ((op = getopt(argc, argv, "dFInrvxf:g:i:m:p:q:s:t:w:")) != EOF) + while ((op = getopt(argc, argv, "dFInrvxf:g:i:m:p:q:Ss:t:w:")) != EOF) switch (op) { case 'd': @@ -406,6 +407,13 @@ options |= SO_DONTROUTE; break; + case 'S': + /* + * Tell traceroute to not increment the destination + * port, useful for bypassing some packet filters. + * Useless without the -p option. + static_port = 1; + break; case 's': /* * set the ip source address of the outbound @@ -744,7 +752,7 @@ register struct ip *ip; (void)gettimeofday(&t1, &tz); - send_probe(++seq, ttl, &t1); + send_probe(static_port ? seq : ++seq, ttl, &t1); while ((cc = wait_for_reply(s, from, &t1)) != 0) { (void)gettimeofday(&t2, &tz); i = packet_ok(packet, cc, from, seq); @@ -1300,9 +1308,9 @@ extern char version[]; Fprintf(stderr, "Version %s\n", version); - Fprintf(stderr, "Usage: %s [-dFInrvx] [-g gateway] [-i iface] \ -[-f first_ttl] [-m max_ttl]\n\t[ -p port] [-q nqueries] [-s src_addr] [-t tos] \ -[-w waittime]\n\thost [packetlen]\n", + Fprintf(stderr, "Usage: %s [-dFInrSvx] [-g gateway] [-i iface] \ +[-f first_ttl]\n\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] \ +[-t tos]\n\t[-w waittime] host [packetlen]\n", prog); exit(1); } ---------------------8<-------- traceroute.diff ------------------------------ Despues de aplicar este parche podremos hacer: $ traceroute -S -p53 200.0.0.10 traceroute to 200.0.0.10 (200.0.0.10), 30 hops max, 40 byte packets ... 9 200.0.0.9 (200.0.0.9) x.x ms * x.x ms 10 200.0.0.10 (200.0.0.10) x.x ms x.x ms x.x ms Aplicando esta tecnica; se observa como podemos obtener mas informacion del firewall que oculta nuestro host a estudiar, como por ejemplo, el tipo de trafico que permite pasar el firewall o la configuracion de la red que se encuentra detras de el. Mediante la herramienta firewalk, se puede obtener esta información de forma automatizada e incluso implementa el escaneo de puertos, basandose en el envio de paquetes TCP y UDP especificando un timeout; si se recibe respuesta antes de hacerse efectivo el timeoute el puerto se considera abierto y si no, se considera cerrado. Pero si quieres saber el funcionamiento a bajo nivel de este programa no dudes en mirar sus fuentes en http://www.es2.net/research/firewalk. Un ejemplo de su funcionamiento seria: $ firewalk -n -P1-80 -pTCP 200.0.0.5 200.0.0.10 ... port 80: open ... Conocimiento de la mascarada de red de la red interna conectada a internet mediante gateway: este metodo funciona correctamente cuando el host remoto es win* pero no funciona en todos los linux. Se basa en el envio de un ICMP de tipo 17 (ICMP Address Mask Request) obteniendo en el caso de win* y algunos linux un ICMP de tipo 18 (Address Mask Reply) con la direccion de la red interna. Para implementar esta tecnica mostrare como hacer en sing: $ sing -vv -mask 127.0.0.1 ... Un ejemplo de la implementacion del envio de este tipo de ICMPs se encuentra en un programa hecho por David G. Andersen de nombre icmpquery.c (la version que analice era 1.0.3); y observamos: ... int initpacket(char *buf, int querytype, struct in_addr fromaddr) { struct ip *ip = (struct ip *)buf; struct icmp *icmp = (struct icmp *)(ip + 1); int icmplen = 0; ip->ip_src = fromaddr; /* si es 0, lo llena el kernel */ ip->ip_v = 4; /* Implementado para ipv4 */ ip->ip_hl = sizeof *ip >> 2; ip->ip_tos = 0; ip->ip_id = htons(4321); ip->ip_ttl = 255; ip->ip_p = 1; ip->ip_sum = 0; /* lo llena el kernel */ /* se observa el inicio de la funcion y definicion de las caracteristicas ip necesarias */ ... icmp->icmp_seq = 1; icmp->icmp_cksum = 0; icmp->icmp_type = querytype; icmp->icmp_code = 0; switch(querytype) { case ICMP_TSTAMP: gettimeofday( (struct timeval *)(icmp+8), NULL); bzero( icmp+12, 8); icmplen = 20; break; case ICMP_MASKREQ: *((char *)(icmp+8)) = 255; icmplen = 12; break; default: fprintf(stderr, "eek: unknown query type\n"); exit(0); } ip->ip_len = sizeof(struct ip) + icmplen; return icmplen; } /* Es interesante la definicion del tipo de query que es ciertamente en la que se basa el switch y tambien debes fijarte en el caso ICMP_MASKREQ ya que la otra opcion es para hallar la hora remota, que no es nuestro proposito. */ ... void sendpings(int s, int querytype, struct hostdesc *head, int delay, struct in_addr fromaddr) { char buf[1500]; struct ip *ip = (struct ip *)buf; struct icmp *icmp = (struct icmp *)(ip + 1); struct sockaddr_in dst; int icmplen; /* inicio de funcion y definicion de variables */ ... bzero(buf, 1500); icmplen = initpacket(buf, querytype, fromaddr); dst.sin_family = AF_INET; /* se observa aqui el uso de la primera funcion copiada iniitpacket() */ ... while (head != NULL) { #ifdef DEBUG printf("pinging %s\n", head->hostname); #endif ip->ip_dst.s_addr = head->hostaddr.s_addr; dst.sin_addr = head->hostaddr; icmp->icmp_cksum = 0; icmp->icmp_cksum = in_cksum((u_short *)icmp, icmplen); if (sendto(s, buf, ip->ip_len, 0, (struct sockaddr *)&dst, sizeof(dst)) < 0) { perror("sendto"); } if (delay) usleep(delay); /* Don't flood the pipeline..kind of arbitrary */ head = head->next; } } /* se observa la especificacion de DEBUG o no, que simplente indica que se saque por pantalla que se esta pingeando y despues se observa el envio en si con sendto() */ ... void recvpings(int s, int querytype, struct hostdesc *head, int hostcount, int broadcast) { char buf[1500]; struct ip *ip = (struct ip *)buf; struct icmp *icmp; int err = 0; long int fromlen = 0; int hlen; struct timeval tv; struct tm *tmtime; int recvd = 0; char *hostto; char hostbuf[128], timebuf[128]; struct hostdesc *foundhost; unsigned long int icmptime, icmpmask; /* inicio de la funcion y definicion de variables */ ... switch(icmp->icmp_type) { case ICMP_TSTAMPREPLY: icmptime = ntohl(icmp->icmp_ttime); tv.tv_sec -= tv.tv_sec%(24*60*60); tv.tv_sec += (icmptime/1000); tv.tv_usec = (icmptime%1000); tmtime = localtime(&tv.tv_sec); strftime(timebuf, 128, "%H:%M:%S", tmtime); printf("%-40.40s: %s\n", hostto, timebuf); break; case ICMP_MASKREPLY: icmpmask = ntohl(icmp->icmp_dun.id_mask); printf("%-40.40s: 0x%lX\n", hostto, icmpmask); break; default: printf("Unknown ICMP message received (type %d)\n", icmp->icmp_type); break; } if (!broadcast) recvd++; } } /* el caso que nos interesa es el ICMP_MASKREPLY y se observa como saca la informacion por pantalla */ Escaneo a traves de proxy: una opcion bastante interesante para que aunque el escaneo de puertos en realidad sea detectable por el host remoto pero detecte la direccion del proxy a traves del cual hacemos el escaneo y no la nuestra. Un ejemplo de codigo en perl de aplicacion de esta tecnica lo tienes a continuacion: /* extraido de Socks Scan V. 2.0 by ICEHOUSE */ ... #!/usr/bin/perl use strict; use Net::SOCKS; /* necesario, claro esta, para el correcto funcionamiento del programa */ ... print "Socks Scan by ICEHOUSE\n" ; print "OK we have ..\n"; print "Host To Scan --> @ARGV[0]\n"; print "End Port --> @ARGV[1]\n"; print "Socks Server --> @ARGV[2]\n"; print "Using Protocol --> @ARGV[3]\n"; /* saca en pantalla los argumentos pasados por el usuario */ ... my ($s); for ($s; $s <= @ARGV[1]; $s++) { my $sock = new Net::SOCKS(socks_addr => @ARGV[2], socks_port => 1080, protocol_version => @ARGV[3]); my $f= $sock->connect(peer_addr => @ARGV[0], peer_port => $s); if ($sock->param('status_num') == SOCKS_OKAY) { print "Port $s is OPEN\n"; $sock->close(); } } /* Como puedes comprobar esta tecnica es de facil implementacion en lenguaje perl, que en cierto modo, en gran numero de veces es mas sencillo para la programacion simple para redes */ Escaneo distribuido: esta opcion es muy interesante, ya que se basa en el escaneo de un mismo host distribuido entre varias maquinas. De esta forma, es casi imposible la deteccion del escaneo por parte de la maquina remota, pero siendo necesario un cierto numero de hosts, cuantos mas mejor. III Relacion de principales servicios con puertos. Daemons. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dado el gran numero de servicios que puede ofrecer una maquina (/etc/services). No me parece del todo logico agregar a este manual hojas y hojas de documentacion sobre todos los servicios, ya que tampoco es el proposito de este manual el enseñar el tipo de servicios que hay, que se supone que un usuario de linux medio/avanzado ya lo sabe, sino más bien el analisis remoto del sistema en si. Te recomiendo que leas TCP/IP Illustrated vol. 1 y 2, Internetworking with TCP/IP vol. 1 y 2 y TCP/IP Network Administration; todos ellos los podras encontrar en www.amazon.com. Solo quiero puntualizar que en base a los resultados obtenidos con un buen escaneador de puertos y un programa que averigüe el sistema operativo es facil analizar los citados servicios obtenidos por cada puerto y los daemons instalados. Por otra parte, es más que recomendable estar al día en packetstorm.securify.com o securityfocus.com en lo que se refiere a las vulnerabilidades de los citados daemons, para así poder parchearlas. Aun asi, recomiendo la lectura de papers o libros (como ya he dicho anteriormente) para tener una vision clara de esto. En realidad este capitulo del manual no era uno de mis objetivos a cumplir al escribirlo ya que creo que ya esta muy bien documentado. Para lo que si que voy a dedicar un capitulo dada su importancia y su generalizado uso en el contexto actual es para los CGIs. CGIs ~~~~ Inicialmente, voy a explicar un poco el problema en lo que se refiere a vulnerabilidades de los scripts CGIs para despues presentar ejemplo de software que puede ser utilizado para encontrar este tipo de vulnerabilidades tan comunes y al mismo tiempo peligrosas. CGI significa Common Gateway Interface. Actualmente su uso en todo tipo de sistemas es normal y el lenguaje de programacion que voy a adoptar para los mismos es PERL, por tanto asumo cierto conocimiento del lenguaje PERL por el lector (si no lo tienes ya sabes :), Programming Perl de Larry Wall, Tom Christiansen y Jon Orwat de Ed. O'Reilly es un buen comienzo). Aunque se pueden usar en sistemas win* yo trataré el caso de sistemas unix, por ser en los que tengo más experiencia. CGI permite la comunicacion entre programas cliente y servidores que operan con http, siendo el protocolo en el que se lleva a cabo esta comunicacion TCP/IP y el puerto el 80 (privilegiado) pero se especifican otros puertos no privilegiados. Hay dos modos basicos en los que operan los scripts CGIs: # Permitir el proceso basico de datos que han sido pasados mediante un input. Lease scripts que por ejemplo chequean la correcta sintaxis de documentos HTML # Actuar como conducto de los datos que son pasados del programa cliente al servidor y devueltos del servidor al cliente. Lease script que por ejemplo actuan como frontend de una base de datos del servidor. Los scripts CGI, en realidad, ademas de PERL (lenguaje interprete de programacion) se puede usar TCL, shell script (de unix) y AppleScript en lo que se refiere a este tipo de lenguajes, pero tambien se pueden usar lenguajes de programacion compilables y lenguajes de scripting. Pero usare PERL ya que los lenguajes interpretados son mas faciles de analizar y cambiar que los programas compilados por razones obvias. Los tres metodos aplicable a programas CGI que voy a presentar son Post, Get y Put. para saber lo que hace cada uno, puedes leer las espeficaciones HTTP 1.0. Las vulnerabilidades de los scripts CGI no estan propiamente en ellos mismos sino en las especificaciones http y los programas de sistema; lo unico que permite CGI es acceder a citadas vulnerabilidades. Mediante ellos un servidor puede sufrir lectura remota de archivos, adquisicion de shell de forma ilegal y modificacion de ficheros del sistema asi que es cierto que hay que analizar bien este tipo de programas, ya que como ves se pone en peligro la integridad del sistema. Por lo tanto en un analisis remoto de un sistema es muy a tener en cuenta este tipo de vulnerabilidades. El primer problema de dichos scripts en la falta de validacion suficiente en el input del usuario, que conlleva ciertos problemas. Los datos pasados mediante un script CGI que use Get estan puestos al final de una url y estos son tratados por el script como una variable de entorno llamada QUERY_STRING, con muestras de la forma variable=valor. Los llamados 'ampersands' separan dichas muestras (&), y junto con los caracteres no alfanumericos deben de ser codificados como valores hexadecimales de dos digitos. Todos ellos, viene precedidos por el signo % en la url codificada. Es el script CGI el tiene que borrar los caracteres que han sido pasados por el usuario mediante input, por ejemplo, si se quieren borrar los caracteres < o > y cosas asi de un documento html: /* este ejemplo pertenece a Gregory Gillis */ {$NAME, $VALUE) = split(/=/, $_); $VALUE =~ s/\+/ /g; # reemplaza '+' con ' ' $VALUE =~ s/%([0-9|A-F]{2})/pack(C,hex,{$1}}/eg; # reemplaza %xx con ASCII $VALUE =~ s/([;<>\*\|'&\$!#\(\)\[\]\{\}:"])/\\$1/g; #borra caracs especiales $MYDATA[$NAME} = $VALUE; Pero, hay una cosa que no hace este pequeño ejemplo, no se es consciente de la posibilidad de crear una nueva linea mediante %0a que se puede usar para ejecutar comandos diferentes de los del script. Por ejemplo, se podria hacer lo siguiente, de no caer en la cuenta de esta vulnerabilidad: http://www.ejemplo.com/cgi-bin/pregunta?%0a/bin/cat%20/etc/passwd %20 es un espacio en blanco y %0a como se ha especificado anteriormente es una especie de return. Digamos que el frontend que hay en una pagina web para llamar a un script CGI es un formulario. En todo formulario tiene que haber un input, este input tiene un nombre asociado que digamos es lo ya expuesto anteriormente variable=valor. Para una cierta seguridad, los contenidos del input deben de ser filtrados y por tanto los caracteres especiales deben de ser filtrados a diferencia del ejemplo comentado anteriormente. Los scripts CGI interpretados que fallan en la validacion de los datos pasan los dichos datos del input al interprete. Otra etiqueta frecuente en los formularios es la select. Esta, permite al usuario elegir una serie de opciones, y dicha seleccion va justo despues de variable=valor. Pasa como con el input, de fallar la validacion se asume que dicha etiqueta solo contiene datos predefinidos y los datos son pasados al interprete. Los programas compilados que no hacen una validacion semejante son igualmente vulnerables. Otro de las vulnerabilidades muy frecuentes es el hecho de que si el script llama al programa de correo de unix, y no filtra la secuencia '~!' esta puede ser usada para ejecutar un comando de forma remota ya que el programa de correo permite ejecutar un comando de la forma '~!command', de nuevo, el problema de filtrado esta presente. Por otra parte, si encuentras una llamada a exec() con un solo argumento esta puede ser usada para obtener una puerta de acceso. En el caso de abrir un fichero por ejemplo, se puede usar un pipe para abrir una shell de la forma: open(FICHERO, "| nombre_del_programa $ARGS"); Continuando con funciones vulnerables, si encuentras una llamada de la forma system() con un solo argumento, esta puede ser usada como puerta de acceso al sistema, ya que el sistema crea una shell para esto. Por ejemplo: system("/usr/bin/sendmail -t %s < %s, $mail < $fichero"); /* supongo que te imaginaras: */ Scripts CGIs que pasan inputs del usuario al comando eval tambien se pueden aprovechar, puesto que: $_ = $VALOR s/"/\\"/g $RESULTADO = eval qq/"$_"/; Asi, si por ejemplo $VALOR contiene algun comando malicioso, el resultado para el servidor remoto puede ser bastante malo. Es muy recomendable revisar que los permisos de fichero son correctos y por ejemplo de usar la libreria cgi-lib, cosa muy normal esta debe de tener los correspondientes permisos ya que sino estariamos ante otra vulnerabilidad. Para chequear estos permisos, se haria de la forma generica: "%0a/bin/ls%20-la%20/usr/src/include". Si se llegase a copiar, modificar y reemplazar dicha libreria se podrian ejecutar comandos o rutinas de forma remota, con lo que conlleva eso. Ademas, si el interprete de PERL utilizado por el cgi es SETUID, sera posible modificar permisos de los ficheros que quieras pasando un comando directamente al sistema a traves del interprete, y asi por ejemplo: $_ = "chmod 666 \/etc\/host.deny" $RESULT = eval qq/"$_"/; Esto es gracias a SSI y la mayoria de los sysadmins competentes tendrian que desactivarlo. Para saber si un server utiliza esto se haria de la siguiente forma: Te recomiendo la lectura de Perl CGI problems by rfp (phrack 55) para tener una vision mas completa del problema, ya que analiza mas fallos de seguridad de CGIs. Actualmente, hay escaneadores de CGIs en los que se han descubierto vulnerabilidades, que en muchos casos son de este tipo, o de otros mas complejos que tampoco me parece factible explicarlos en un paper de este tipo. A continuacion te presento algunos de los escaneadores de vulnerabilidades CGIs que me parecen mas completos (en este apartado simplemente nombrare los especificos de CGIs y no aquellos escaners de tipo vetescan que entre sus funcionalidades añadidas esta este tipo de escaneo): - whisker by rain forest puppy http://www.wiretrip.net/rfp/ - voideye by duke http://packetstorm.securify.com/UNIX/cgi-scanners/voideye.zip - ucgi by su1d sh3ll: http://infected.ilm.net/unlg/ - Tss-cgi.sh http://www.team-tss.org - Cgichk http://sourceforge.net/projects/cgichk/ - cgiscanner.pl (de raza mexicana) http://packetstorm.securify.com/UNIX/scanners/cgiscanner.pl Destacar, que para mi, el mejor de los citados es el whisker de rfp :) Y bueno, se acabo. 3. Bibliografia y agradecimientos ============================== Bibliografia: ~~~~~~~~~~~~~ [1] TCP/IP Illustrated vol. 1 by Richard Stevens [2] Remote OS detection via TCP/IP Stack FingerPrinting by Fyodor [3] BIND 8.2 - 8.2.2 *Remote root Exploit How-To* by E-Mind [4] The Art of Port Scanning by Fyodor [5] Port Scanning without the SYN flag by Uriel Maimon [6] Port Scanning; A Basic Understanding by John Holstein [7] Intrusion Detection Level Analysis of Nmap and Queso by Toby Miller [8] Scanning for RPC Services by halflife [9] Firewalking, A Traceroute-Like Analysis of IP Packet Responses to Determine Gateway Access Control Lists by Cambridge Technology Partners' [10] Techniques To Validate Host-Connectivity by dethy@synnergy.net [11] CGI Security Holes by Gregory Gillis [12] Passive Mapping, The Importance of Stimuli by Coretez Giovanni [13] Examining port scan methods - Analysing Audible Techniques by dethy@synnergy.net [14] A network intrusion detection system test suite by Anzen Computing [15] Passive Mapping: An Offensive Use of IDS by Coretez Giovanni [16] SWITCH - Swiss Academic & Research Network. Default TTL Values in TCP/IP Programas recomendables para hacer mejor uso de este paper: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ queso tcpdump snort ipsend siphon conda iplog sos sing icmpquery iputils isic hping2 ss arping nemesis ethereal checkos dps sendip nmap nessus dscan spak nsat satan icmpenum fragrouter etc... Gracias a los canales: ~~~~~~~~~~~~~~~~~~~~~~ #networking, #hack, #hackers y #linux @ irc-hispano.org. #low-level, #phrack, #spain y #localhost @ efnet. Gracias por su ayuda a: ~~~~~~~~~~~~~~~~~~~~~~~ David Cerezo Sanchez aka bit-quake, impresionante :) icehouse nunotreez Pedro Andujar aka crg Pablo Balzola aka pib lyw0d Fernando Luis aka merphe Y gracias a ti por haber leido este documento. Sun Dec 31 17:11:59 CET 2000 Ultima revision: Sat Apr 21 02:08:42 CEST 2001 -honoriak "callar es asentir, no te dejes llevar"