Más de uno habrá oído hablar del desbordamiento de pila (buffer overflow en inglés), un error a la hora de desarrollar software que puede ser aprovechado para que un programa realice funciones u operaciones para las cuales no fue programado. Este tipo de vulnerabilidad es esencial para llevar a cabo las pruebas de penetración.
Antes de hacer el salto cuántico y empezar a desbordar pilas a diestra y siniestra, es necesario entender conceptos que quizá no se relacionen a primera vista con el tema de seguridad, sino con cuestiones de programación y de cómo la computadora gestiona la memoria. Ya que se haya entendido esto, se podrá comprender cómo es que se produce un desbordamiento de pila, qué lo ocasiona y cómo es que se toma ventaja de él. Claro, siempre es factible saltar conceptos y obviarlos, tomarlos como si fueran dogmas de fe para simplemente realizar pruebas de seguridad corriendo herramientas automatizadas y esperar que funcionen. Sin embargo, resulta importante, en nuestro esfuerzo de difusión, expandir el panorama.
¿Qué pasa cuando las herramientas no funcionan?, ¿solo levantamos los hombros y decimos “no funcionó”? ¡No! Lo que debemos hacer es verlo de esta manera: sabemos que en la computadora hay una aplicación que es vulnerable, pero que por alguna razón la herramienta que tiene el script que explota la vulnerabilidad no funcionó. Tenemos en nuestro poder la información necesaria que puede ayudar (tipo de sistema operativo, la aplicación, el puerto a la escucha, etcétera); tomemos todo esto, un poco de procesamiento de nuestro cerebro y desarrollemos el script que permita tomar ventaja de la vulnerabilidad que, al final de cuentas, sabemos existe y que simplemente debemos pensar y hallar la manera de explotar.
Para llegar a ese punto primero se requiere entender los conceptos implicados y no solo saber cómo correr una herramienta. Es eso, y no otra cosa, lo que separa a un hacker de un simple mortal que sabe como teclear comandos.
.
Variables, el poder de variar
Cuando se elabora un programa puede surgir la necesidad de esperar por datos que sean proporcionados desde una fuente externa, para lograrlo se hace uso de algo que en el argot de programación se conoce como “variable”. Una variable de programación, al igual que una variable en una función matemática, puede tomar cualquier valor. Tomemos justamente el ejemplo matemático para entenderlo y pensemos en la función: y = x + 5.
Supongamos ahora que deseamos hacer un programa que nos diga cuánto vale “y” una vez que determinemos el valor de “x”. Dicho programa diría algo como: “Hola, tengo un severo problema y necesito de tu ayuda. Debo saber el valor de “y” que resulta de la función “x + 5” pero yo no sé cuánto vale “x” ¿Podrías indicarlo?”
Así pues, sabemos que un programa puede recibir información de una fuente ajena al programa (el usuario, en el caso de nuestro ejemplo) y que esa información será almacenada en variables, pero, ¿cómo se hace esto? Sencillo, el programa, al ser ejecutado toma espacio de memoria, de todo ese espacio que tomó deja una parte pequeña de memoria para los valores que desconoce (las variables) y que está a la espera de que alguna fuente externa se los proporcione.
.
Arreglos
Dentro de la programación existen varios tipos de variables. Están las que llegan a almacenar valores llamados enteros, las que llegan a almacenar valores flotantes o las que almacenan solo un carácter alfanumérico, entre otras. Pero hay un tipo de variable a la que le debemos prestar especial atención: los arreglos.
¿Qué es un arreglo? Imaginemos un largo estante, tan largo como lo deseemos, que tiene cajones y dentro de cada uno podemos guardar lo que sea, lo que queramos, y a ese estante le asignamos un nombre con el cual lo identificamos fácil y rápidamente. Un arreglo es algo similar, es un gran estante que llevará un nombre y que usaremos para guardar valores ya sea de tipo entero, flotante, caracteres, etcétera. Cuando necesitemos un valor en específico solo bastará ir al “cajón” indicado del estante y tomarlo.
La manera en que un programa maneja un arreglo que no ha sido inicializado (es decir, que no tiene valores) es similar a la manera que maneja las variables, les deja un espacio de memoria, pero esta vez la deja de manera continua: si el arreglo es de cinco elementos dejará el espacio para cinco variables, si es de diez, dejará diez espacios, de acuerdo a lo que haya sido definido por el programador.
.
El error que se comete al momento de programar
Imaginemos que es necesario hacer una aplicación que guarde cinco números proporcionados de manera externa. Existen dos opciones, una es declarar cinco variables o hacer uso de un arreglo que conste de cinco elementos. Después de una no tan difícil deliberación decidimos usar el arreglo y corremos la aplicación para probarla. Llegamos al punto en donde el programa solicita introducir los cinco datos y empezamos a proporcionar uno por uno hasta llegar al quinto dato, llegado este punto lo normal sería que ya no se pudieran introducir más datos, sin embargo el programa permite un sexto dato, es decir, meter más datos de los que debería guardar. Es justo ahí donde hemos cometido un error al momento de desarrollar el programa, que más que un error es un descuido, ya que no se tomaron las debidas precauciones para evitar introducir más datos de los que el programa puede manejar de acuerdo con su diseño y es esto justamente la esencia de la vulnerabilidad llamada desbordamiento de buffer.
Cuando un programa nos permite introducir más datos de los que espera y puede manejar, sucede que existe la posibilidad de escribir en zonas de memoria donde se ubica información que ayuda al control y flujo del programa. Ahora entonces, la pregunta lógica es ¿Qué es y cómo funciona esa zona de memoria que controla la ejecución del programa?
.
El stack
La cosa es muy simple, un programa que es inicializado necesita tomar un espacio de memoria que usará para, llegado el momento, poner en ese espacio el valor de las variables que no fueron declaradas desde el código del programa y que será conocido una vez que el programa esté corriendo. Este espacio de memoria se conoce con el nombre de stack, o “pila” si se prefiere la traducción al español.
¿Y cómo funciona el stack?, para ilustrar este concepto hay un sinfín de objetos con los cuales se pude ejemplificar, pero tomemos uno de mis favoritos, los libros. Imaginemos un libro sobre una mesa, y sobre ese libro ponemos otro, y sobre ese libro otro más, y así hasta que tenemos una columna de cincuenta libros. Si eventualmente necesitamos tomar el primero, aquel que está justo entre la mesa y los cuarenta y nueve restantes, no podremos hacerlo sin antes quitar los que están encima de este. De hecho, no podemos tomar el libro cuarenta y nueve sin antes haberle quitado de encima el libro cincuenta. Entonces, para llegar al de hasta abajo tengo que retirar el libro cincuenta, luego el cuarenta y nueve, luego el cuarenta y ocho, y así hasta llegar al primero.
El stack es algo muy similar, es una gran columna (virtual) de memoria donde se almacenará información, ya sea de control o de variables, y, a diferencia del ejemplo de los libros, no es que exista la posición uno, la dos o la tres; lo que existe es una dirección de memoria para cada elemento.
En el ejemplo se señaló que si quería llegar al primer libro, deberían quitarse uno por uno los cuarenta y nueve libros que estaban arriba. Este concepto de ir quitando uno por uno empezando desde el último tiene un nombre y se llama LIFO (last in, first out), que quiere decir que el último que entra será el primero en salir.
El stack es una sección de memoria que suele utilizar el concepto LIFO (aunque no todos los stack operan así) para introducir datos que pueden ser de control y flujo del programa o el valor de variables que no fueron declaradas en el código del programa. Ahora, ¿cómo sabe la computadora en qué sección de memoria debe introducir información? La respuesta tiene un acrónimo y es SP.
SP es el acrónimo para Stack Pointer y su función, como lo dice su nombre, es apuntar a una dirección de memoria de la pila, de hecho la del último elemento. Es importante entender que el SP siempre se encontrará apuntando al último elemento, porque justamente así es como se determinan dos operaciones que se utilizan para manipular datos en la pila: push para introducirle datos y pop para sacar.
Por ahora dejemos el asunto, en la siguiente entrega veremos cómo operan push/pop, cómo se construye una función en el stack una vez que un programa ha sido ejecutado, y qué sucede cuando se hace un desbordamiento de pila.
Continuará…
Considero importante tocar elementos y fundamentos principales para tomar un tema mas profunda y paulatinamente (Back to basics).
Muy buen artículo, claro y de fácil entendimiento. Considero que es bueno decir que el nombre traducido es decir desbordamiento puede traer confusión, por que lo que se pretende aprovechar con esta vulnerabilidad no es desbordar si no descontrolar.
Fernando:
Justamente la intención que me motivó a escribir este articulo fue la de dejar todo más claro para que cualquier persona llegue a entender cómo y porqué sucede un desbordamiento o volcado de buffer. Estoy intentando explicar hasta el aspecto más insignificante de manera sencilla y amena pero sobre todo desde cero, partiendo de la idea de que es posible que llegue a se leído por un lector que tenga el mínimo conocimiento informático y que ese lector no llegue a quedarse con alguna duda. Sé que para que eso suceda debo de volver a las bases, como tú dices, y creo que eso estoy haciendo o eso hice, pero tu comentario me hace pensar que tal vez no lo estoy logrando, por lo que cualquier sugerencia que me ayude a enriquecer este trabajo, ya sea de parte tuya o de cualquier otra persona, siempre será bien agradecida en esta noble labor de tratar de compartir el conocimiento a todas las personas que deseen tenerlo.
Jairo:
Me complace mucho que el articulo haya sido de tu agrado, pero sobre todo que haya sido claro para ti. Me inspira a seguirme esforzando a hacer el conocimiento mas fácil para todos.
Desde siempre la traducción de términos escritos en la lengua de Shakespeare ha sido un problema para los que hablamos la lengua de Cervantes. Se procura no hacer una traducción literal porque puede no llegar a transmitir la idea (incluso la traducción de alguno de ellos termina siendo hilarante), por lo que se utilizan palabras más acorde con la idea.
En este caso la traducción literal no dista mucho de la idea, existe un concepto llamado buffer que es sobrecargado con más datos de los que puede manejar, a ese hecho se le llamó desbordamiento o volcando. Sin embargo no quiero decir que estás equivocado, es cierto que de todo esto termina en un descontrol, pero ese descontrol es consecuencia justamente de haber saturado de más el buffer. La vulnerabilidad es que el buffer pueda llegar a ser desbordado y el descontrol es la consecuencia de poder haberlo logrado, ambas cosas existen sin embargo no son lo mismo, por lo que no puede ser utilizado como traducción al termino anglosajón «Buffer overflow».
Me ha gustado mucho este post. Siempre me había costado entender esto, pero esta primera parte está muy bien explicada, así que iré siguiendo la serie y cuando finalice, si me quedan dudas te las comentaré.
Un saludo!!
Felicidades Gastón,
Gran artículo, y a mi entender muy bien explicado. Disiento con Fernando en que no se haya tocado con claridad lo mas básico, ya que con los ejemplos que has puesto creo que los mas neófitos pueden hacerse a la idea de cómo es el «trabajo» de un programa en memoria, y como se provoca el desbordamiento de la pila.
Seguiré con interés el resto de los artículos.
Un cordial saludo
Me queda claro que existen muchas personas que desconocen del tema por completo y
lo justifico pues es complicado, ya que para realizar este tipo de publicacion
se requiere de un gran esfuerzo tiempo y sobre todo llevarlo a la práctica… ¿lo haz echo amigo?
Con todo respeto amigo tienes fallas en tu publicación,sin embargo para las personas
que realmente tenemos conocimientos en este tema te puedo afirmar que tu publicacion es erronea, confundes muchos conceptos. Te recomiendo que te adentres e investigues más pues desprestigias a tu empresa ya que no se puede realizar publicaciones sin estar totalmente seguro, confundes a las personas en lugar de ayudarlas, para la gente se quedo con esas ideas un Saludos.
Amigo, ¿Estas completamente seguro de tu artÍculo?, ¿realmente haz desarrollado un exploit?
me da la impresion que No, pues el Stack no controla la ejecucion del programa, disculpame
estas mal tienes muchos conceptos que aun no estan consolidados. Lo grave de esta publicación es que comentas que tienes como propósito que cualquier usuario lo entienda, solo los estas confundiendo.
Guiovanny:
Agradezco profundamente tu comentario, tienes razón en que hubo una confusión de mi parte a la hora de utilizar términos, y, a modo de explicación mas no de justificación, se debe a que estoy muy acostumbrado a leer sobre estos temas en ingles, donde para mi ya no necesito hacer una interpretación al español puesto que el entendimiento de los conceptos se da instantáneamente en mi cabeza. Sin embargo, como dije, no justifica que haya sido descuidado a la hora de haber utilizado términos equívocos en este escrito. La aclaración se hará debidamente en la siguiente parte y obviamente prestare más atención a lo que escribo. Si hay algo más que quisieras agregar te agradecería me lo enviaras a mi correo el cual puedes encontrar en esta pagina. Devuelvo tus saludos.
Alberto:
No tengo nada que disculpar, estás en tu derecho de pensar que lo que escribí (y por ende yo) está mal, no hay nada ofensivo o agresivo (hasta el momento) con lo que comentas o como lo comentas. Tengo una idea de como presentar el tema y lo vengo desarrollando poco a poco, tampoco es fácil pensar en una manera de explicación que sea entendible para todo el mundo. Sin embargo puedo decir categóricamente que, al saber que lo que escribo puede ser leído por doctos en el tema, no digo mentiras. Si algo consideras que esta equivocado te pido por favor me lo comuniques de manera detallada a mi correo (si gustas), me ayuda a pulir la manera en como presentar conceptos que no son muy fáciles de entender y por lo tanto transmitir.
Yo al igual que Alberto estamos deacuerdo en ese punto, para ti Alberto una disculpa por plagiarme tu comentario pero estoy totalmente deacuerdo contigo, es un error grave el decir que el STACK controla la ejecución de un programa para ti amigo que publicaste este artículo creo que tienes que estudiar mucho más, también coincido con el comentario de Giovanny DESPRESTIGIAS A TU EMPRESA para ustedes lectores lo que quiso decir el responsable de esta publicación con lo del STACK es esto: El registro EIP contiene la dirección de la siguiente instrucción que ejecutara nuestro procesador ya que si somos capases de controlar registro EIP seremos capaces de controlar el flujo del programa MAS NO LA EJECUCION DEL PROGRAMA, ESP contiene la dirección de la cima de la pila y EBP la dirección del marco actual claro que para poder entender estos terminos sobre esto primero debió de haber explicado los registros del procesador tales como EIP,EBP etc.