-[ 0x0A ]-------------------------------------------------------------------- -[ Captcha-Killer Wargame: OCR ]--------------------------------------------- -[ by blackngel ]----------------------------------------------------SET-38-- ^^ *`* @@ *`* HACK THE WORLD * *--* * ## || * * * * (C) Copyleft 2009 everybody _* *_ 1 - Introduccion 2 - Breve Analisis 3 - Dise~o e Implementacion 4 - Wargame: El Reto 5 - Ultimas 6 - Conclusion 7 - Referencias ---[ 1 - Introduccion Chema Alonso, de Informatica 64, junto con sus secuaces, son bastante conocidos a estas alturas, ya sea por sus conferencias o por el desarrollo de todos los wargames que de un tiempo a esta parte llevan realizando. Todos ellos son gente que, haciendo honor al nombre de su blog, estan bastante asentados en lo que se conoce como "el lado del mal", es decir, con amplios conocimientos orientados a las plataformas de Microsoft. Es por ello que la mayoria de los retos que han planteado estan basados en tecnologias web asi como ASP.NET, Microsoft SQL Server, el servicio de protocolo ligero de acceso a directorio (LDAP) de la misma plataforma y demas... Aun siendo esto asi, no evita que todos los retos pudieran ser superados desde un entorno Linux o al menos de base Unix. Al fin y al cabo, casi todos se basaban en tecnicas de inyeccion, ya sea SQL, LDAP y otros juegos intelectuales. Teniendo en cuenta que hasta ahora todos se habian desarrollado por fases, en las que unos descubrimientos llevaban a otros niveles (las pistas estaban ahi para ayudar, o no), la aparicion del ultimo reto, el numero 9, parecia un caso un poco especial. Lo especial esta en que se centra unicamente en un aspecto de la seguridad de los entornos web, especificamente el problema que plantea la superacion del desafio-respuesta de una imagen "CAPTCHA" para un programa automatizado. Problema que supuestamente solo deberia poder ser solucionado por un humano. Cada nivel en el reto presentaba una debilidad y debia ser encontrada para pasar al siguiente. Claro que siempre quedaba implementar alguna clase de reconocimiento OCR, y con ello poder superar todas las fases (menos la ultima que era distinta y se superaba de un modo muy sencillo), con la misma herramienta. Durante este articulo nos centraremos en esta ultima posibilidad. Si quieres ir calentando motores entra en la siguiente direccion perteneciente al Reto 9 [1]. ---[ 2 - Breve Analisis Cuando empece a jugar con este juego, como siempre mucho mas tarde de que ya hubiera terminado y los premios hubieran sido otorgados (el reconocimiento del Hall of Fame), observe que las primeras fases estaban hechas para novatos. ¿Como puede ser que las 4 primeras tengan el mismo nivel de dificultad? Es mas, puedo decir que la tercera es mas dificil incluso que la cuarta, debido a que en las pruebas 1, 2 y 4 el valor del texto mostrado en el Captcha podia ser leido directamente del codigo HTML. Como es sabido, cualquier persona con un minimo conocimiento de Perl o algo por el estilo puede automatizar las peticiones web, leer los codigos, y realizar un POST con la informacion adecuada. En resumen, si alguien quiere ver como se puede estudiar y superar cada reto de forma individual, recomiendo la lectura del solucionario escrito por nuestro amigo Kachakil [2]. Habiendo perdido un poco la gracia, a uno se le vino a la mente, como no, la idea de desarrollar una solucion global para todos los niveles. Si generalizamos, sabemos que en todos los niveles tenemos una imagen, el Captcha, y que podemos descargarla. Si tuviesemos la capacidad de interpretar los caracteres en el contenidos y enviar el codigo de regreso a la pagina, tal vez tendriamos el premio en nuestras manos. OCR viene a nuestras mentes, reconocimiento de caracteres dentro de una imagen, pero la pregunta es siempre la misma: ¿Como? Como bien se ha dicho por ahi, nadie puede pretender abrir una imagen con un editor de texto, ya sea hexadecimal o no, y buscar a lo largo de todos los caracteres una cadena con el texto del captcha en si, esto es impensable desde luego. Pregunta: ¿La solucion? Respuesta: Reconocimiento de Patrones Si pudiesemos aislar los caracteres del Captcha, esto es, crear una imagen individual de cada uno, almacenarlas todas en una base de datos en relacion a su caracter real, y luego establecer un metodo de comparacion, por norma general deberiamos obtener en un amplio porcentaje de ocasiones el codigo correcto. Por suerte, buscando informacion sobre el tema en la red, encontre un pequeño motor con una idea bastante aceptable desarrollada en PHP que cubria mis espectativas para adaptarlo al reto. Esquematicamente el programa realiza lo siguiente: 1 -> Abre la imagen captcha. Explicacion: Para el reto se trata de abrir un GIF y convertirlo en JPEG. 2 -> La limpia. Explicacion: Consigue dejar en negro las letras y en blanco el fondo. 3 -> Divide. Explicacion: Crea una nueva imagen por cada caracter individual. 4 -> Crear un mapa de los pixels. Explicacion: Un documento incluyendo grupos de tres cifras indicando el valor de los colores rojo, verde y azul en cada pixel individual. 5 -> Compara. Explicacion: Compara el mapa de pixeles del caracter actualmente tratado con todos los mapas previamente almacenados en la base de datos. Aquel que presente mas coincidencias es candidato a ser el correto. Debo añadir que una vez acabado el desarrollo, lo probe antes de nada en otra clase de captchas en donde resulto ser mas efectivo que en el propio reto. ---[ 3 - Dise~o e Implementacion A continuacion muestro el codigo que finalmente diseñe para la solucion del reto. Ire introduciendo antes de cada funcion todos los comentarios que sean necesarios para comprender su funcionamiento. Al final del codigo se explica de un modo mas sencillo como funciona el codigo y como realizamos el aprendizaje basico. -[ ocr.php ]- nombre de la imagen captcha // [-l] -> Codigo de la imagen captcha en modo aprendizaje if (($argc >= 1) && (($argv[1] == "-l") || ($argv[3] == "-l"))) { $mode_learn = 1; if ($argv[1] == "-l") { $input_code = strtoupper($argv[2]); } elseif ($argv[3] == "-l") { $input_code = strtoupper($argv[4]); } } if ($argv[1] == "-f") { $file_captcha = $argv[2]; } // Se obtiene una lista de los archivos contiendo los mapas de pixeles // que forman la base de datos. De no haber ninguno, se al usuario // que inicie la sesiones de aprendizaje. $archivos = fileslist(); if ( empty($archivos) && !($mode_learn)) { print "\nBase de datos vacia. Inicie sesiones de aprendizaje.\n"; print "\nUsage: ". $argv[0] ." [-f CAPTCHA] [-l CODE]\n"; Exit; } $archivos = substr($archivos, 0, strlen($archivos)-1); $archivos = explode(",",$archivos); read_captcha(); function read_captcha() { global $file_captcha, $mode_learn; // Las imagenes de los caracteres individuales seran en // principio de 28 x 80 pixeles. $width = 28; $height = 80; // Abrimos la imagen y comprobamos que existe. $img = ImageCreateFromGif($file_captcha); if (!$img) { print "\nEl archivo no existe\n"; Exit; } // Convertimos el GIF a JPG para trabajar mejor. imagejpeg($img, "captcha.jpg"); $newimg = ImageCreateFromJpeg("captcha.jpg"); // Limpiamos la imagen, esto es convertir el color de los // caracteres a negro y el fondo a blanco. $cleanimg = clean($newimg); imagejpeg($cleanimg, "newcapt.jpg"); // Creamos 8 imagenes individuales de 28 x 80. $imgchar1 = imagecreate($width, $height); $imgchar2 = imagecreate($width, $height); $imgchar3 = imagecreate($width, $height); $imgchar4 = imagecreate($width, $height); $imgchar5 = imagecreate($width, $height); $imgchar6 = imagecreate($width, $height); $imgchar7 = imagecreate($width, $height); $imgchar8 = imagecreate($width, $height); // Dividimos el captcha en 8 partes de igual ancho (28 pixeles) // y copiamos cada parte en las imagenes individuales. imagecopy($imgchar1, $cleanimg, 1, 1, 0, 0, 28, 80); imagecopy($imgchar2, $cleanimg, 1, 1, 28, 0, 28, 80); imagecopy($imgchar3, $cleanimg, 1, 1, 56, 0, 28, 80); imagecopy($imgchar4, $cleanimg, 1, 1, 84, 0, 28, 80); imagecopy($imgchar5, $cleanimg, 1, 1, 112, 0, 28, 80); imagecopy($imgchar6, $cleanimg, 1, 1, 140, 0, 28, 80); imagecopy($imgchar7, $cleanimg, 1, 1, 168, 0, 28, 80); imagecopy($imgchar8, $cleanimg, 1, 1, 196, 0, 28, 80); // Guardamos las nuevas imagenes conteniendo los caracteres // individuales en el disco duro. imagejpeg($imgchar1, "chr1.jpg"); imagejpeg($imgchar2, "chr2.jpg"); imagejpeg($imgchar3, "chr3.jpg"); imagejpeg($imgchar4, "chr4.jpg"); imagejpeg($imgchar5, "chr5.jpg"); imagejpeg($imgchar6, "chr6.jpg"); imagejpeg($imgchar7, "chr7.jpg"); imagejpeg($imgchar8, "chr8.jpg"); // Reabrimos dichas imagenes y ejecutamos una funcion que // intenta averiguar la posicion del caracter para aislarlo // y crear una imagen mas recortada del mismo. $ch1 = cut_img(ImageCreateFromJpeg("chr1.jpg")); $ch2 = cut_img(ImageCreateFromJpeg("chr2.jpg")); $ch3 = cut_img(ImageCreateFromJpeg("chr3.jpg")); $ch4 = cut_img(ImageCreateFromJpeg("chr4.jpg")); $ch5 = cut_img(ImageCreateFromJpeg("chr5.jpg")); $ch6 = cut_img(ImageCreateFromJpeg("chr6.jpg")); $ch7 = cut_img(ImageCreateFromJpeg("chr7.jpg")); $ch8 = cut_img(ImageCreateFromJpeg("chr8.jpg")); // Guardamos nuevamente los resultados. #imagejpeg($ch1, "chr1.jpg"); #imagejpeg($ch2, "chr2.jpg"); #imagejpeg($ch3, "chr3.jpg"); #imagejpeg($ch4, "chr4.jpg"); #imagejpeg($ch5, "chr5.jpg"); #imagejpeg($ch6, "chr6.jpg"); #imagejpeg($ch7, "chr7.jpg"); #imagejpeg($ch8, "chr8.jpg"); // Creamos un mapa de pixeles para cada caracter. $datamap1 = createchardatamap($ch1, "noname"); $datamap2 = createchardatamap($ch2, "noname"); $datamap3 = createchardatamap($ch3, "noname"); $datamap4 = createchardatamap($ch4, "noname"); $datamap5 = createchardatamap($ch5, "noname"); $datamap6 = createchardatamap($ch6, "noname"); $datamap7 = createchardatamap($ch7, "noname"); $datamap8 = createchardatamap($ch8, "noname"); $datamap1 = explode("@", $datamap1); $datamap1 = substr($datamap1[1], 1, strlen($datamap1[1])); $datamap1 = explode(":", $datamap1); $nummap1 = count($datamap1); $datamap2 = explode("@", $datamap2); $datamap2 = substr($datamap2[1], 1, strlen($datamap2[1])); $datamap2 = explode(":", $datamap2); $nummap2 = count($datamap2); $datamap3 = explode("@", $datamap3); $datamap3 = substr($datamap3[1], 1, strlen($datamap3[1])); $datamap3 = explode(":", $datamap3); $nummap3 = count($datamap3); $datamap4 = explode("@", $datamap4); $datamap4 = substr($datamap4[1], 1, strlen($datamap4[1])); $datamap4 = explode(":", $datamap4); $nummap4 = count($datamap4); $datamap5 = explode("@", $datamap5); $datamap5 = substr($datamap5[1], 1, strlen($datamap5[1])); $datamap5 = explode(":", $datamap5); $nummap5 = count($datamap5); $datamap6 = explode("@", $datamap6); $datamap6 = substr($datamap6[1], 1, strlen($datamap6[1])); $datamap6 = explode(":", $datamap6); $nummap6 = count($datamap6); $datamap7 = explode("@", $datamap7); $datamap7 = substr($datamap7[1], 1, strlen($datamap7[1])); $datamap7 = explode(":", $datamap7); $nummap7 = count($datamap7); $datamap8 = explode("@", $datamap8); $datamap8 = substr($datamap8[1], 1, strlen($datamap8[1])); $datamap8 = explode(":", $datamap8); $nummap8 = count($datamap8); // Busca en la base de datos que archivos (mapas de pixeles) // son mas parecidos a los mapas que acabamos de crear para // los caracteres del captcha a analizar. $texto .= charletter($datamap1, "chr1.jpg", 0); $texto .= charletter($datamap2, "chr2.jpg", 1); $texto .= charletter($datamap3, "chr3.jpg", 2); $texto .= charletter($datamap4, "chr4.jpg", 3); $texto .= charletter($datamap5, "chr5.jpg", 4); $texto .= charletter($datamap6, "chr6.jpg", 5); $texto .= charletter($datamap7, "chr7.jpg", 6); $texto .= charletter($datamap8, "chr8.jpg", 7); // Borramos las imagenes temporales de los caracteres. #unlink("chr1.jpg"); #unlink("chr2.jpg"); #unlink("chr3.jpg"); #unlink("chr4.jpg"); #unlink("chr5.jpg"); #unlink("chr6.jpg"); #unlink("chr7.jpg"); #unlink("chr8.jpg"); if ($mode_learn == 0) { #print "\nTexto Reconocido: $texto\n"; print $texto; } #print "\n"; return; } // Esta funcion se encarga de convertir el color de las letras a negro y el // de fondo a blanco. Para ello se ha estudiado el captcha con GIMP y se ha // advertido que por norma general el color verde de los pixeles que forman // los caracteres no supera el valor 50, y que para el azul suele estar por // debajo de 95. // La funcion recorre la imagen pixel por pixel comprobando sus colores verde // y azul, de estar por debajo de los valores citados se convierten a negro, // (0,0,0) en cualquier otro caso el pixel se convierte en blanco (255,255,255). function clean ($image) { $w = imagesx($image); $h = imagesy($image); for ($x = 0; $x <= $w; $x++) { for ($i = 0; $i <= $h; $i++) { $rgb = imagecolorat($image, $x, $i); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; if (($g < 50) && ($b < 95)) { imagesetpixel($image, $x, $i, imagecolorallocate($image, 0, 0, 0)); } else { imagesetpixel($image, $x, $i, imagecolorallocate($image, 255, 255, 255)); } } } return $image; } // Esta funcion toma como primer parametro el mapa de pixeles de un caracter // individual a comparar con los mapas de pixeles de todos los caracteres // almacenados previamente en la base de datos. Se va incrementando un contador // de errores que indica el numero de diferencias entre los mapas, y se tomara // como caracter correcto aquel que tuviera menos errores. // // Luego, si se esta en modo aprendizaje, se llama a learnchar() para almacenar // el mapa de pixeles del caracter comparado, siempre y cuando no exista ya uno // exactamente igual en la base de datos (errores == 0). function charletter($datamap1, $file, $pos) { global $archivos, $mode_learn, $input_code; $errores =0; for ($i=0; $i < count($archivos); $i++) { $data = base64_decode(str_rot13(fileread($archivos[$i]))); $data = explode("@",$data); $data = substr($data[1], 1, strlen($data[1])); $data = explode(":",$data); $numdata = count($data); for ($x=0; $x <= $numdata-1; $x++) { if (($data[$x]) != ($datamap1[$x])) { $errores++; } } $erroresa[$i] = $errores; $erroresb[$i] = $archivos[$i]; $errores = 0; } $value = min($erroresa); $key = array_search($value, $erroresa); $letra = base64_decode(str_rot13(fileread($erroresb[$key]))); $letra = explode("@",$letra); if ($value != 0 && $mode_learn) { learnchar($file, $input_code[$pos]); } return $letra[0]; } // Lee el contenido de un fichero individual de la base de datos. function fileread($filename) { $filename= "./bd/".trim($filename); $handle = fopen($filename, "r"); $contents = fread($handle, filesize($filename)); fclose($handle); return $contents; } // Obtiene una lista de todos los archivos presentes en la base de datos. function fileslist() { $archivos=""; $dir = opendir("./bd/"); while (false !== ($file = readdir($dir))) { if(strpos($file,"charmap")) { $archivos .= $file.","; } } return $archivos; } // Aprende un caracter, esto es: Abrir la imagen del caracter individual, // crear el mapa de pixeles y guardar el resultado en un nuevo archivo para // la base de datos codificandolo previamente en Base64 y Rot13. function learnchar($image,$name) { global $width,$height; $imagen = ImageCreateFromJpeg($image); $datamap = str_rot13(base64_encode(createchardatamap($imagen,$name))); save_datamap($datamap,$name); print "Nuevo caracter ['$name'] agregado a la Base de Datos\n"; } // Esta funcion no es necesaria en aquellos tipos de captcha donde todos sus // caracteres se encuentran en la misma linea horizontal. // // Su mision es detectar en que linea comienza el caracter dentro de la imagen // y para ello se base en encontrar una amplia presencia de pixeles negros (o // al reves, donde no existe gran concentracion de pixeles blancos). Una vez // obtenida la linea de comienzo, simplemente se corta la imagen a partir de // ahi con una altura de 28 pixeles. function cut_img($image) { global $width,$height; $white_pix = 0; $has_black = 0; $w = imagesx($image); $h = imagesy($image); for ($x = 0; $x <= $h; $x++) { for ($i = 0; $i <= $w; $i++) { $rgb = imagecolorat($image, $i, $x); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; if (($r == 255) && ($g == 255) && ($b == 255)) { $white_pix += 1; } else if (($r == 0) && ($g == 0) && ($b == 0)) { $has_black = 1; } } if ( ($white_pix < 13) && ($has_black == 1) ) { $init_line = $x - 3; $width = 28; $height = 28; $imgcut = imagecreate($width, $height); imagecopy($imgcut, $image, 1, 1, 0, $init_line, $width, $height); imagejpeg($imgcut, "chrcut.jpg"); $rimg = ImageCreateFromJpeg("chrcut.jpg"); $is_letter = check_letter($rimg); if ($is_letter) break; else $rimg = null; } $white_pix = 0; $has_black = 0; } return $rimg; } // Funcion de apoyo a la anterior. Comprueba que la imagen resultante de la // funcion previa tenga al menos una cantidad igual o superior a 70 pixeles // negros. En caso contrario es que nos hemos equivocado en la linea de comienzo // y no hemos recortado el caracter correctamente. function check_letter($image) { $black_pix = 0; $w = imagesx($image); $h = imagesy($image); for ($x = 0; $x <= $h; $x++) { for ($i = 0; $i <= $w; $i++) { $rgb = imagecolorat($image, $i, $x); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; if (($r == 0) && ($g == 0) && ($b == 0)) { $black_pix += 1; } } } if ($black_pix >= 70) return 1; else return 0; } // Guarda el mapa de pixeles en la base de datos. En nombre del archivo estara // compuesto por: // 1 - Caracter // 2 - Cadena md5 aleatoria // 3 - "-charmap.datamap" // // Esto se hace porque en el proceso de aprendizaje almacenaremos no solo un // mapa de pixeles por cada letra, sino varios, de modo que el motor de // comparacion tenga mas posibilidades de acertar con el caracter adecuado. function save_datamap($datamap,$name) { $fl = fopen("./bd/$name-".md5(time()*rand(1,100))."-charmap.datamap","w+"); fwrite($fl,$datamap); fclose($fl); } // Crea el mapa de pixeles. En resumen el formato es: // 1 - Caracter // 2 - @ // 3 - : // 4 - coordenada x // 5 - coordenada y // 6 - color del pixel (0 = blanco, 1 = negro) // // Por ejemplo: A@:0,0,1:0,1,0:0,2,1:... function createchardatamap($image,$name) { global $width,$height; $datamap=$name."@"; for ($x=0; $x<=$width; $x++){ for ($y=0; $y<=$height; $y++){ $datamap.=":".$x.",".$y.",".pixelcolor($image,$x,$y); } } return $datamap; } // Esta funcion recibe como parametros una imagen y las coordenadas de un // pixel. Devuelve 0 si este es blanco o 1 si es negro. function pixelcolor($im, $x, $y) { $rgb = imagecolorat($im,$x,$y); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; if (($r > 55) || ($g > 55) || ($b > 55)){ return 0; } return 1; } ?> -[ end ocr.php ]- Os aseguro que con un poco de paciencia es extremadamente sencillo de entender. Imaginemos el siguiente captcha: o-----------------o | J F | | F O S | | B H X | o-----------------o Los caracteres tienen todos diferentes tonalidades de rojo, y el fondo es una especie de azul grisaceo. La funcion clean( ) creaba una nueva imagen siendo los caracteres de color negro y el fondo de blanco (aunque algun pixel se puede escapar si no cumple con las condiciones establecidas). Luego se crean 8 nuevas imagenes tal que asi: o---o o---o o---o o---o o---o o---o o---o o---o | | | | | | | | | J | | | | F | | | | | | | | F | | O | | | | S | | | | | | B | | H | | | | | | | | | | | | X | o---o o---o o---o o---o o---o o---o o---o o---o Y finalmente llamamos a cut_img( ) para que detecte la posicion real del caracter y recorte las imagenes adecuadamente: o---o o---o o---o o---o o---o o---o o---o o---o | B | | H | | F | | O | | J | | S | | F | | X | o---o o---o o---o o---o o---o o---o o---o o---o Si es la primera vez que ejecutamos el programa, y tenemos la base de datos vacia, nos pedira que iniciemos algunas sesiones de aprendizaje. Para aprender este captcha especifico tendriamos que llamar al programa de la siguiente manera: blackngel@mac:~/captcha-killer$ php ocr.php -l BHFOJSFX Y el programa nos responderia: Nuevo caracter ['B'] agregado a la Base de Datos Nuevo caracter ['H'] agregado a la Base de Datos Nuevo caracter ['F'] agregado a la Base de Datos Nuevo caracter ['O'] agregado a la Base de Datos Nuevo caracter ['J'] agregado a la Base de Datos Nuevo caracter ['S'] agregado a la Base de Datos Nuevo caracter ['F'] agregado a la Base de Datos Nuevo caracter ['X'] agregado a la Base de Datos Y de forma manual podriamos ir descargando captchas con todos los diversos caracteres e ir aprendiendolos. Fijate que puedes aprender muchas veces el mismo caracter, ya que este estara en posiciones distintas, y esto ayudara a acertar muchas mas veces cuando se hagan las comprobaciones. Para detectar el texto de un captcha tan solo tenemos que indicarle el nombre del mismo con la opcion "-f" o sin ningun parametro si se llama "captcha.gif" ya que este es el nombre por defecto. Lo unico que queda por comentar aqui es esto: if ($mode_learn == 0) { #print "\nTexto Reconocido: $texto\n"; print $texto; } #print "\n"; Para un uso normal descomentariamos las dos sentencias "print" que hemos comentado (y comentariamos la descomentada) de forma que el resultado se imprima de una forma mas amigable. Yo lo he puesto asi de momento, ya que este programa sera llamado dentro de un script perl que es el encargado de conectarse a la web del reto con los valores adecuados y descargar la imagen captcha a analizar. Dentro de este script perl se utiliza la sentencia "$captcha = `php ocr.php`;" que retornara el texto detectado para el captcha recien descargado, y es por eso que solo queremos que imprima el texto del captcha sin ningun agregado que pueda molestarnos. Vamos seguidamente a estudiar este script. ---[ 4 - Wargame: El Reto El siguiente script escrito en lenguaje Perl tiene como mision conectarse a la pagina del Nivel 4 del Reto Hacking 9 de Informatica 64, descargando la imagen captcha que en el mismo se presenta, analizar el texto correspondiente con la ayuda de "ocr.php" (descrito en la seccion anterior), y rellenar el formulario de la citada pagina hasta conseguir 1000 aciertos. La direccion es la siguiente: http://retohacking9.elladodelmal.com/Niveles/Nivel04/Default.aspx Previamente debemos haber iniciado una sesion en la pagina con un navegador normal y corriente (como firefox). Luego, con un plugin como Tamper-Data obtenemos los datos que se envian al rellenar el formulario. Entre ellos lo mas importante es la Cookie, de la cual debemos coger los valores: .ASPXAUTH y ASP.NET_SessionId, de modo que el script pueda hacer peticiones como si se tratase del navegador y estuviese bien autentificado. A la hora de rellenar el formulario debemos cumplimentar los campos: __EVENTARGUMENT -> Valor extraido de la pagina __VIEWSTATE -> Valor extraido de la pagina __EVENTVALIDATION -> Valor extraido de la pagina ctl00\$ContentPlaceHolder1\$txtCaptcha -> El codigo del captcha ctl00\$ContentPlaceHolder1\$btEnviar -> "Enviar" Pero antes de enviar este formulario debemos leer el contenido de la pagina en busca de la cadena "ImgCapcha" sabiendo que 16 caracteres mas alla se encuentra la direccion o nombre de la imagen captcha, que guardaremos en la variable $img y que descargaremos de la direccion: "http://retohacking9.elladodelmal.com/Niveles/Nivel04/$img" Despues de enviar el formulario con toda la informacion cumplimentada, solo queda esperar ir obtentiendo las respuestas que son extraidas de la misma: Aqui el script: -[ reto_cap.pl ]- #!/usr/bin/perl -w use LWP::UserAgent; use HTTP::Cookies; use HTTP::Request::Common qw(POST); my $captcha = ""; my $ua = LWP::UserAgent->new() or die; my $cookie = '.ASPXAUTH=FC5BD1B2E0FC8A3C881E698C58A5CBC87150BC9FB8E9C721D06D6CA E0D7065844E1F69951595059B340363B8E6A1A433B24A00054D0AAE68E380AACEC4164014FFC026 318FA979304EEB7BEF3558BDE7; ASP.NET_SessionId=r4yjfs2dkckyun55e20ujhbu'; my $useragent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008 072820 Firefox/3.0.1'; my @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300'); my $url = 'http://retohacking9.elladodelmal.com/Niveles/Nivel04/Default.aspx'; my $response = $ua->post($url, @header); my $content = $response->content; my $x = index($content, "ImgCapcha") + 16; $content = substr($content, $x); $x = index($content, "style") - 2; my $img = substr($content, 0, $x); $txtcaptcha = "ctl00\$ContentPlaceHolder1\$txtCaptcha"; $btenviar = "ctl00\$ContentPlaceHolder1\$btEnviar"; while (1) { print "\nImage = $img"; $req = HTTP::Request->new(GET => "http://retohacking9.elladodelmal.com /Niveles/Nivel04/$img"); $request = $ua->request($req); open(IMAGEN, ">captcha.gif") || die "No se pudo crear archivo: $!"; print IMAGEN $request->content; close IMAGEN; $captcha = `php ocr.php`; print "\nCaptcha: $captcha\n"; @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300', Content => [ __EVENTTARGET => "", __EVENTARGUMENT => "", __VIEWSTATE => "/wEPDwUKMTEwNjI0MjY0MQ 9kFgJmD2QWAgIDD2QWAgIBD2QWAgIDD2QWAgIBD2QWAgIDDw8WAh4ISW1hZ2VVcmw FFWltYWdlbmVzL0dOQlFHT0dHLmdpZmRkGAIFHl9fQ29udHJvbHNSZXF1aXJlUG9z dEJhY2tLZXlfXxYCBSNjdGwwMCRMb2dpblZpZXcxJExvZ2luU3RhdHVzMSRjdGwwM QUjY3RsMDAkTG9naW5WaWV3MSRMb2dpblN0YXR1czEkY3RsMDMFEGN0bDAwJExvZ2 luVmlldzEPD2QCBWTuf7wPzWAP7wbywWopu9vknBCuhg==", __EVENTVALIDATION => "/wEWBALp/4JbAqyhgvUPArXmppIHArC746EO5duXAU3C nqKFhQ0c4HrRx+AvpUw=", $txtcaptcha => $captcha, $btenviar => "Enviar" ]); $response = $ua->post($url, @header); my $res = $response->content; $x = index($res, "lbSalida") + 66; $respuesta = substr($res, $x); $x = index($respuesta, ""); $respuesta = substr($respuesta, 0, $x); print "\n-> $respuesta"; $x = index($res, "ImgCapcha") + 16; $res = substr($res, $x); $x = index($res, "style") - 2; $img = substr($res, 0, $x); } -[ end reto_cap.pl ]- El unico defecto es que la conjuncion de este script con el programa en php de reconocimiento OCR no trabajan todo lo rapido que uno desearia, y la necesidad de acertar 1000 captchas en menos de 1 hora hace que la unica solucion temporal sea ejecutar ambos programas desde varios ordenadores al mismo tiempo. ---[ 5 - Ultimas El nivel 4 de este reto resulto ser bueno y malo al mismo tiempo para comenzar con esta opcion del reconocimiento OCR. Por una parte es obvio que podria haberse superado de un modo mucho mas sencillo, ya que el nombre de la imagen que se podia leer en el codigo fuente de la pagina solicitada era exactamente el codigo del captcha. Pero cabe decir que esto a su vez fue estupendo para realizar las sesiones de aprendizaje de un modo automatico, ya que bastaba con crear un script que actualizara la pagina, descargara la imagen captcha y le pasara al programa ocr.php el nombre de la imagen leido mediante la opcion de aprendizaje "-l". El script que aqui presento realiza perfectamente esa tarea, y se ejecuta en un bucle infinito hasta que el usuario lo detiene con Ctrl-C. -[ learn.pl ]- #!/usr/bin/perl -w use LWP::UserAgent; use HTTP::Cookies; use HTTP::Request::Common qw(POST); my $captcha = ""; my $ua = LWP::UserAgent->new() or die; my $cookie = '.ASPXAUTH=3C562E5B97165AAC052D9AAB19521ADC91F3ABB160A43427EF120E 5158074C106455D0AFE2421D0058A6CA2C4258523C74ED746C0ABC755E3C4378EDD63D67013A41 A2DEA67D45A97096AF4AF6F1A694; ASP.NET_SessionId=43qmmfrhpgvsffitm3lafmm3'; my $useragent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072820 Firefox/3.0.1'; my @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300'); while(1) { my $url = 'http://retohacking9.elladodelmal.com/Niveles/Nivel04/Default.aspx'; my $response = $ua->post($url, @header); my $content = $response->content; my $x = index($content, "ImgCapcha") + 16; $content = substr($content, $x); $x = index($content, "style") - 2; my $img = substr($content, 0, $x); $req = HTTP::Request->new(GET => "http://retohacking9.elladodelmal.com/ Niveles/Nivel04/$img"); $request = $ua->request($req); open(IMAGEN, ">captcha.gif") || die "No se pudo crear archivo: $!"; binmode IMAGEN; print IMAGEN $request->content; close IMAGEN; my $code = substr($img, 9); my $salida = `php ocr.php -f captcha.gif -l $code`; print "\n$salida\n"; print "\n"; } -[ end learn.pl ]- Recuerda que, al igual que en el script "reto_cap.pl", debes cambiar los valores de la variable $cookie por aquellos que se correspondan con tu sesion. ---[ 6 - Conclusion Como se ha podido ver, puede que no sea la implementacion mas limpia, y ademas no utiliza ninguna clase de algoritmo complejo, pero si es una solucion completamente valida, y tan solo hace falta adaptar el modo de descarga de la imagen en cada nivel respectivo para que funcione correctamente. Lo correcto hubiera sido intentar programar todas las operaciones en una misma aplicacion y con un mismo lenguaje. Perl en si mismo puede ser una estupenda opcion si se sabe como manejar los graficos con el, os remito a una estupenda referencia sobre esto en [3]. Java tampoco seria un lenguaje a despreciar, ya que tiene formas (librerias) increiblemente sencillas para tratar con imagenes o composicion de graficos, solo que la tarea de interactuar con una web a modo de web-scraping requiere un poco mas de paciencia y estudio. Sea como fuere, el hacker siempre debe buscar un equilibrio entre eficiencia y eficacia. Para desarrollos e invetigaciones personales, o con metas de futuro, uno debe utilizar toda su magia para encontrar el metodo mas eficiente, pero un reto es un reto, y para superarlo simplemente debemos hacer que funcione. No es asi? Feliz Hacking! blackngel ---[ 7 - Referencias [1] Captcha Killer (Reto 9 Informatica 64) http://retohackin9.elladodelmal.com [2] Reto Hacking IX de Informatica 64 http://www.kachakil.com/retos/I64_Reto_9.pdf [3] Graphics Programming with Perl http://man.lupaworld.com/content/develop/Perl/Graphics_Programming_With_ Perl.pdf *EOF*