Autor Tema: Rompiendo el captcha de Claro (en un montón de líneas de código)  (Leído 11496 veces)

0 Usuarios y 3 Visitantes están viendo este tema.

Desconectado mxgxw

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 5665
  • Starlet - 999cc
    • mxgxw
Luego de ver el comentario de hkadejo se me ocurrió que sería bueno, por fines didácticos intentar romper el captcha de Claro.

Realmente no hay mucha diferencia, solo que la gente de claro se le ocurrió ponerle "ruido" a su captcha para hacer difícil la lectura por medio del software de OCR.

Así que en el fondo, este post no se trata tanto de como romper el captcha, sino de una serie de manipulaciones gráficas que se le hace a la imagen para poder quitar el "ruido" de fondo.

En esta ocasión vamos a utilizar un algoritmo recursivo que nos permitirá aislar nuestras letras de ese fondo ruidoso que ha colocado la gente de claro. Este algoritmo entre otras cosas puede servir también para identificar el "tamaño" en pixels de objetos dento de nuestra imagen.

El algoritmo básico va de esta manera:

Código: [Seleccionar]
1-Conectarse al formulario claro, obtener la cookie de sesión y la URL del captcha.
2-Descargar el captcha usando el cookie de sesión.
3-Convertir la imagen a una versión monocromática.
4-Utilizar un algoritmo recursivo de relleno para identificar los "bloques grandes" (aka nuestras letras)
5-"Suavizar" los bordes
6-Enviar nuestra imagen a Tesseract y esperar el resultado.

Nota*: Disculpenme que el codigo se vea tan "estructurado" pero para procesos secuenciales muchas veces es más facil descibir el proceso de forma estructurada y luego comenzar a hacer un modelado mas detallado. Igual para una prueba de concepto de un proceso simple no valía la pena hacer un modelado OO, así que disculpen mi abuso de metodos estáticos y repeticiones de código en Java.

Una vez dicho esto, agarrense que aquí viene el código:
Código: [Seleccionar]
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import java.util.*;
import java.net.*;

/*
 * @Author mxgxw
 * Codigo protegido bajo licencia GNU/GPL 2.0 o posterior
 */
public class Denoise {

    public static class Pixel {
public int x;
public int y;
public Pixel(int x, int y) {
    this.x=x;
    this.y=y;
}
    }

    public static double brightness(Color c) {
return (0.2126*c.getRed()+0.7152*c.getGreen()+0.0722*c.getBlue())/255;
    }

    public static int fillPixel(BufferedImage img, int x,int y,int increment,java.util.List<Pixel> pixels) {
if(img.getRGB(x,y)==Color.BLACK.getRGB()) {
            img.setRGB(x,y,(new Color(increment,0,0)).getRGB());
}
if((y-1)>=0 && img.getRGB(x,y-1)==Color.BLACK.getRGB()) {
    pixels.add(new Pixel(x,y-1));
    increment=fillPixel(img,x,y-1,increment+1,pixels);
}
if((x+1)<img.getWidth() && img.getRGB(x+1,y)==Color.BLACK.getRGB()) {
    pixels.add(new Pixel(x+1,y));
    increment=fillPixel(img,x+1,y,increment+1,pixels);
}
if((y+1)<img.getHeight() && img.getRGB(x,y+1)==Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x,y+1));
    increment=fillPixel(img,x,y+1,increment+1,pixels);
}
if((x-1)>=0 && img.getRGB(x-1,y)==Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x-1,y));
    increment=fillPixel(img,x-1,y,increment+1,pixels);
}
if(increment>1) {
    img.setRGB(x,y,(new Color(increment,0,0)).getRGB());
}
return increment;
    }
   
    public static void main(String args[]) throws Exception {

// Descargar el formulario de envio de mensajes
HttpURLConnection client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/telecomv2.aspx").openConnection());
client.addRequestProperty("User-Agent","Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
client.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");

client.connect();

BufferedReader response = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
String session_cookie = client.getHeaderField("Set-Cookie"); // Extraer la cookie de sesion
System.out.println("Session Cookie: " + session_cookie);
String captcha_url ="";
while(response.ready()) {
    line = response.readLine();
    // Buscar la linea con la imagen del captcha
    if(line.contains("_pages_telecomv2_enviosms21_noticaptcha_CaptchaImage")) {
int base = line.indexOf("src='")+5;
int fin = line.indexOf("'",base);
captcha_url = line.substring(base,fin); // extraer la URL
    }
}
System.out.println("Captcha URL:"+captcha_url);

// Descarga de la imagen
client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/"+captcha_url).openConnection());
        client.addRequestProperty("User-Agent","Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
        client.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");
client.addRequestProperty("Cookie",session_cookie);
        client.connect();

byte[] buffer = new byte[1024];
int bytesRead =0;
FileOutputStream fileOutput = new FileOutputStream(new File(".\\captcha_claro.jpg"));
while((bytesRead=client.getInputStream().read(buffer))>0) {
    fileOutput.write(buffer,0,bytesRead);
}
fileOutput.close();
BufferedImage img = ImageIO.read(new File(".\\captcha_claro.jpg"));
System.out.println("Width: "+img.getWidth()+" Height: "+img.getHeight());

int x,y;
// Generacion de imagen en monocromo
for(x=0;x<img.getWidth();x++) {
    for(y=0;y<img.getHeight();y++) {
img.setRGB(x,y,(brightness(new Color(img.getRGB(x,y)))>0.5) ? Color.WHITE.getRGB() : Color.BLACK.getRGB());
    }
}
int i,increment;
java.util.List<Pixel> pixelList;
for(x=0;x<img.getWidth();x++) {
            for(y=0;y<img.getHeight();y++) {
if(img.getRGB(x,y)==Color.BLACK.getRGB()) {
    pixelList = new Vector<Pixel>();
    increment=fillPixel(img,x,y,1,pixelList);
    pixelList.add(new Pixel(x,y));
    for(i=0;i<pixelList.size();i++) {
img.setRGB(pixelList.get(i).x,pixelList.get(i).y,(new Color(increment,0,0)).getRGB());
    }
}
            }
        }
Color currentColor;
for(x=0;x<img.getWidth();x++) {
            for(y=0;y<img.getHeight();y++) {
currentColor = new Color(img.getRGB(x,y));
if(currentColor.getRGB()!=Color.WHITE.getRGB()) {
    if(currentColor.getRed()>10) {
img.setRGB(x,y,Color.BLACK.getRGB());
    } else {
img.setRGB(x,y,Color.WHITE.getRGB());
    }
}
    }
}

  // Borra pixels solos en la horizontal PASO 1
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x-1,y)==Color.WHITE.getRGB() &&
                img.getRGB(x+1,y)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
  // Borra pixels solos en la horizontal PASO 2
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x,y-1)==Color.WHITE.getRGB() &&
                img.getRGB(x,y+1)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
  // Borra pixels solos en la horizontal PASO 1
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x-1,y)==Color.WHITE.getRGB() &&
                img.getRGB(x+1,y)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
  // Borra pixels solos en la horizontal PASO 2
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x,y-1)==Color.WHITE.getRGB() &&
                img.getRGB(x,y+1)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}

ImageIO.write(img,"JPG",new File(".\\captcha_claro_processed.jpg"));

        Process tess_cmd = Runtime.getRuntime().exec("Tesseract.exe captcha_claro_processed.jpg output -l eng");
        tess_cmd.waitFor();


        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(".\\output.txt"))));
        System.out.println("Decoded Capcha:"+reader.readLine());
        reader.close();
       
    }
}


1-Conectarse al formulario claro, obtener la cookie de sesión y la URL del captcha.

Primero lo primero, vamos a descargar nuestro formulario de la página de Claro. Java no tiene una clase sencilla de utilizar como la WebClient de .NET, así que vamos a complicarnos la vida un poquito haciendo uso de una clase muy util llamada URLConnection.

URLConnection sirve para conectarnos a recursos que sean identificados por URI.

La gente de (SUN ahora Oracle), pensó en las personas como nosotros que queríamos conectarnos por medio de HTTP a servidores web, así que definieron una clase abstracta llamada HttpURLConnection, que nos permite trabajar más comodamente.

Como ambas son clases abstractas, no pueden ser inicializadas directamente. Por suerte cuando creamos instancias de la clase URL existe un método que nos crea URLConnections.

Pero dejando de hablar tanto, todo lo que les puse arriba se realiza con la siguiente línea de código:

Código: [Seleccionar]
HttpURLConnection client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/telecomv2.aspx").openConnection());

¡¡Momentito!! Si notan estoy llamando al método "openConnection()". Esto es porque las URLConnections funcionan en "dos pasos".

Cuando llamamos OpenConnection, se inicializa nuesto URLConnection y en teoría podemos definir nuestros parámetos de conección.

Una vez hemos inicializado nuestra conección llamamos al método "connect", para que se realice el request y podamos leer los datos de la respuesta.

Para nuestro caso, lo que nos interesa es "disfrazar" a nuestro programita como Firefox, que es lo que haremos a continuación:

Código: [Seleccionar]
client.addRequestProperty("User-Agent","Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
client.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");

Ahora lo único que nos queda es abrir la conexión y leer los datos:

Código: [Seleccionar]
client.connect();

Lo siguiente es utiliza un BufferedReader para poder leer el contenido de la erspuesta línea por línea. Vamos a aprovechar también el extraer la cookie de la sesión.

Código: [Seleccionar]
BufferedReader response = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
String session_cookie = client.getHeaderField("Set-Cookie"); // Extraer la cookie de sesion
System.out.println("Session Cookie: " + session_cookie);
String captcha_url ="";

A diferencia del código de telefónica en esta ocasión estamos interesados en obtener una línea específica delc código, en este caso el atributo src de la página de envío de mensajes.

Idealmente, todo sería tan sencillo como cargar la página como un XML y hacer uso de las funciones DOM para poder encontra el atributo, el problema es que la página de CLARO no valida el XML así que el parser de Java, como todo buen parser. TRUENA.

Lo que nos obliga a buscarla como lo hacíamos en la vieja escuela:

1-Vamos a buscar la línea que contenga "_pages_telecomv2_enviosms21_noticaptcha_CaptchaImage", que es el id del SRC que contiene el captcha.
2-Luego vamos  a buscar dentro de esa línea la posición del texto "src='" (noten la comilla simple)
3-Vamos a buscar, desde esa posición, la primera ocurrencia de una comilla "'".
4-El texto que se encuentre entre la posición base y la posición fin, es la URL de nuestro captcha.

Código: [Seleccionar]
while(response.ready())  {
  line = response.readLine();
  // Buscar la linea con la imagen del captcha
  if(line.contains("_pages_telecomv2_enviosms21_noticaptcha_CaptchaImage")) {
int base = line.indexOf("src='")+5;
int fin = line.indexOf("'",base);
captcha_url = line.substring(base,fin); // extraer la URL
    }
}
System.out.println("Captcha URL:"+captcha_url);

2-Descargar el captcha usando el cookie de sesión.

Ahora que tenemos nuestro ID de sesión y nuestra cookie, todo se resume a crear una nueva HttpURLConnection y hacer un request con la información deseada:

Código: [Seleccionar]
client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/"+captcha_url).openConnection());
client.addRequestProperty("User-Agent","Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
client.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");
client.addRequestProperty("Cookie",session_cookie);
client.connect();

A diferencia del request anterior en este caso vamos a recibir datos binarios, vamos a utilizar entonces un FileOutputStream para crear un archivo nuevo y vamos a utilizar un pequeño buffer de 1Kb para ir escribiendo los bloques en nuestro archivo:

Código: [Seleccionar]
byte[] buffer = new byte[1024];
int bytesRead =0;
FileOutputStream fileOutput = new FileOutputStream(new File(".\\captcha_claro.jpg"));
while((bytesRead=client.getInputStream().read(buffer))>0) {
  fileOutput.write(buffer,0,bytesRead);
}
fileOutput.close();

Es muy importate cerrar el archivo, si no lo hacemos puede darnos problemas cuando querramos utilizarlo posteriormente.

3-Convertir la imagen a una versión monocromática.

Ahora que hemos capturado nuestro captcha, vamos a comenzar con la parte gráfica.

Lo primero es convertir el captcha en una versión monocromática, esto tiene dos objetivos: El primero reducir los artifacts agregados por la compresión JPEG y segundo eliminar el anti-alias que podría dar problemas en el OCR.

Para convertir a monocromático vamos a utilizar una funcion llamada brightness. En Java no hay una funcion brightness como en .NET, así que vamos a crear una:

Código: [Seleccionar]
    public static double brightness(Color c) {
return (0.2126*c.getRed()+0.7152*c.getGreen()+0.0722*c.getBlue())/255;
    }

Luego de esto el algoritmo para convertir a monocromatico es bien sencillo, basta con calcular el brillo para cada pixel y colocar los mas oscuros en negro y los más claros en blanco:

Código: [Seleccionar]
BufferedImage img = ImageIO.read(new File(".\\captcha_claro.jpg"));
System.out.println("Width: "+img.getWidth()+" Height: "+img.getHeight());

int x,y;
// Generacion de imagen en monocromo
for(x=0;x<img.getWidth();x++) {
    for(y=0;y<img.getHeight();y++) {
img.setRGB(x,y,(brightness(new Color(img.getRGB(x,y)))>0.5) ? Color.WHITE.getRGB() : Color.BLACK.getRGB());
    }
}

4-Utilizar un algoritmo recursivo de relleno para identificar los "bloques grandes" (aka nuestras letras)

¿Se han fijado en paint cuando apretan el baldecito de relleno y les llena los espacios vacíos?

Ayer que estaba pensando en como eliminar el ruido se me ocurrió que una buena forma de distinguir entre el ruido y las letras sería si tuviera una forma de rellenar cada letra y que me dijera cuantos pixels han cambiado de color.

Hace algunos años recuerdo que en una tarea de java nos dejaron un Buscaminas, esa vez utilicé este mismo algoritmo para buscar las minas al rededor de un punto al que hicieras click.

Describo un poco el algoritmo:

1-Tome una pixel X.
2-Si el pixel es blanco o está marcado, continue al siguiente pixel.
3-Si el pixel es negro, incremente el contador en 1 y marquelo.
4-Realice  el paso 3 para los pixels, arriba, abajo, izquierda, derecha.
5-Cuando ya no hayan pixels por revisar termine.



Uploaded with ImageShack.us

La idea es simple: Cuando encuento un pixel negro, lo agrego a la lista y lo marco. Cuando ya no encuente pixels negros significa que no hay más pixels adyacentes por lo tanto no hay mas que rellenar, el contador final corresponde a la cantidad de pixels negros.

Pero vamos a agregar un extra. El numero de pixels es el valor que vamos a utilizar para colorear nuestro maravilloso captcha :) luego para quitar el ruido, simplemente vamos a quitar todas las "manchas" cuyo valor (almacenado en su propio color) sea <10.

Este algorimo se ejecuta en dos pasos, el código principal es el que "desencadena" la función recursiva, y obviamente la función recursiva es la que se llama a si misma en la busqueda de los cuadros negros.

Código: [Seleccionar]
int i,increment;
java.util.List<Pixel> pixelList;
for(x=0;x<img.getWidth();x++) {
  for(y=0;y<img.getHeight();y++) {
    if(img.getRGB(x,y)==Color.BLACK.getRGB()) {
      pixelList = new Vector<Pixel>();
      increment=fillPixel(img,x,y,1,pixelList);
      pixelList.add(new Pixel(x,y));
      for(i=0;i<pixelList.size();i++) {
        img.setRGB(pixelList.get(i).x,pixelList.get(i).y,(new Color(increment,0,0)).getRGB());
      }
    }
  }
}

Función recursiva:
Código: [Seleccionar]
    public static class Pixel {
public int x;
public int y;
public Pixel(int x, int y) {
    this.x=x;
    this.y=y;
}
    }

public static int fillPixel(BufferedImage img, int x,int y,int increment,java.util.List<Pixel> pixels) {
if(img.getRGB(x,y)==Color.BLACK.getRGB()) {
            img.setRGB(x,y,(new Color(increment,0,0)).getRGB());
}
if((y-1)>=0 && img.getRGB(x,y-1)==Color.BLACK.getRGB()) {
    pixels.add(new Pixel(x,y-1));
    increment=fillPixel(img,x,y-1,increment+1,pixels);
}
if((x+1)<img.getWidth() && img.getRGB(x+1,y)==Color.BLACK.getRGB()) {
    pixels.add(new Pixel(x+1,y));
    increment=fillPixel(img,x+1,y,increment+1,pixels);
}
if((y+1)<img.getHeight() && img.getRGB(x,y+1)==Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x,y+1));
    increment=fillPixel(img,x,y+1,increment+1,pixels);
}
if((x-1)>=0 && img.getRGB(x-1,y)==Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x-1,y));
    increment=fillPixel(img,x-1,y,increment+1,pixels);
}
if(increment>1) {
    img.setRGB(x,y,(new Color(increment,0,0)).getRGB());
}
return increment;
    }

Si se fijan verán que utilizo una Lista para almacenar los pixels. Esto es porque los primeros pixels visitados almacenan el valor de incremento más bajo, así que luego de haber visitado todos los pixels, reviso el valor de incremento y lo asigno a todo el grupo de pixels.

Nota interesante: Si quisieramos, como tenemos la lista de pixels almacenada podemos "extraer" ese objeto de manera independiente. Y si cada Pixel representara una unidad de aera, podríamos calcular el area total de la "mancha".

Es decir, ese sencillo algoritmo nos puede servir para extraer elementos individuales de un grafico monocromático y para calcular su "aera" de una sola vez.

Pero vamos a hacer una pausa para ver como va cambiando nuestro "CAPTCHA" en el camino:

Original


Versión monocromática


Versión "coloreada"

Nota: Color mas fuerte = area mayor

Y aquí es donde viene lo bonito. Para quitar el ruido, lo único que tenemos que hacer es quitar todo lo que no se vea suficientemente "rojo". Para el captcha de claro, decidí utilizar un umbral de 10 en el componente rojo del color:

Código: [Seleccionar]
Color currentColor;
for(x=0;x<img.getWidth();x++) {
            for(y=0;y<img.getHeight();y++) {
currentColor = new Color(img.getRGB(x,y));
if(currentColor.getRGB()!=Color.WHITE.getRGB()) {
    if(currentColor.getRed()>10) {
img.setRGB(x,y,Color.BLACK.getRGB());
    } else {
img.setRGB(x,y,Color.WHITE.getRGB());
    }
}
    }
}

El resultado es una imágen monocromática, pero sin ruido:


5-"Suavizar" los bordes

Hasta aquí solo hay un problema, nuestra imagen ha quedado con "pelitos". Estos son pixels de ruido que quedaron muy cerca de las letras y que gracias al antialias se quedaron "pegados" a nuestras letras cuando la convertimos en monocromático.

Para limpiarlos vamos a buscar todos los items que queden "solos", es decir con dos pixels blancos a los lados, en las lineas verticales y horizontales.

Voy a pasar dos veces el barrido por si quedara algun pixel ocioso resagado:

Código: [Seleccionar]
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x-1,y)==Color.WHITE.getRGB() &&
                img.getRGB(x+1,y)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x,y-1)==Color.WHITE.getRGB() &&
                img.getRGB(x,y+1)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x-1,y)==Color.WHITE.getRGB() &&
                img.getRGB(x+1,y)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
for(x=1;x<img.getWidth()-1;x++) {
            for(y=1;y<img.getHeight()-1;y++) {
              if(img.getRGB(x,y-1)==Color.WHITE.getRGB() &&
                img.getRGB(x,y+1)==Color.WHITE.getRGB()) {
                  img.setRGB(x,y,Color.WHITE.getRGB());
                }
    }
}
ImageIO.write(img,"JPG",new File(".\\captcha_claro_processed.jpg"));

Resultado final:


Esta imágen es muchísimo más facil de leerse por el OCR.

6-Enviar nuestra imagen a Tesseract y esperar el resultado.

Ok, para este paso debo de aceptar que he tomado un "atajo". Intenté usar directamente el Wrapper tess4j, sin embargo por alguna razón solo funcionaba con las imágenes de ejemplo.

Así que el plan B, es utilizar directamente Tesseract desde la línea de comandos, para hacer esto ejecutamos el siguiente código:

Código: [Seleccionar]
Process tess_cmd = Runtime.getRuntime().exec("Tesseract.exe captcha_claro_processed.jpg output -l eng");
        tess_cmd.waitFor();


        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(".\\output.txt"))));
        System.out.println("Decoded Capcha:"+reader.readLine());
        reader.close();

6-Compile y Ejecute.
Código: [Seleccionar]
PS E:\Development\claro> javac Denoise.java
PS E:\Development\claro> java Denoise
Session Cookie: ASP.NET_SessionId=uyqcay45hjbyhdqvkalcuc45; path=/
Captcha URL:LanapCaptcha.aspx?get=image&amp;c=_pages_telecomv2_enviosms21_noticaptcha&amp;t=2231ee3dc7584920af6bfa988917
c2e5&amp;s=uyqcay45hjbyhdqvkalcuc45
Width: 80 Height: 40
Decoded Capcha:ya3au




P.D: De nuevo gracias a naruto y al hkadejo por recordarme buenos tiempos :) Como vieron este post no era tanto de romper el captcha sino de las aplicaciones de la manipulación gráfica. Ademas aprendimos a abrir páginas y descargar archivos directamente desde Java :)

P.D.: ¿Por qué este en Java y no en C#? Pues por nostalgia, Java fue el primer lenguaje de programación que aprendí y en mi trabajo tengo mi alma vendida a microsoft. Además le da más trabajo a los SPAMMERS que si ayer no sabían que hacer con C# hoy se van a cagar con Java faskljdfh lfkdjfdas
« Última Modificación: diciembre 25, 2011, 10:26:23 pm por mxgxw »


Desconectado hkadejo

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 3345
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #1 : diciembre 25, 2011, 10:34:38 pm »
Ya vi que no soy tan maleta programando... XD

Porque el algoritmo que has usado para limpiar la imagen es un tanto parecido al mio....solo que yo no le cambio de color pero si leo recursivamente la imagen buscando aquello que no parezca formar parte de una letra (puntitos negros solitarios), esos puntitos los quito y ya dejo una imagen que el tesseract si pueda entender.

La clave para romper un captcha consiste en el procesamiento previo que se le hace a la imagen, la imagen tiene que ir lo mas "limpia" posible para que el OCR no tenga mayores inconvenientes para leerla...

El primero que pida este programa y el de captchas de movistar hecho en PHP ya sabemos que es un spammer lamercito que ni modificar Mensajitos.php ha podido XD  :rofl:  :rofl:

Ya los quiero ver a esos spammers cuando libere el codigo de la nubecita...segun ellos en un hosting gratuito lo van a poder hacer funcionar  :rofl:  :rofl:

Desconectado mxgxw

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 5665
  • Starlet - 999cc
    • mxgxw
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #2 : diciembre 25, 2011, 10:42:54 pm »
Ya vi que no soy tan maleta programando... XD

Porque el algoritmo que has usado para limpiar la imagen es un tanto parecido al mio....solo que yo no le cambio de color pero si leo recursivamente la imagen buscando aquello que no parezca formar parte de una letra (puntitos negros solitarios), esos puntitos los quito y ya dejo una imagen que el tesseract si pueda entender.

La clave para romper un captcha consiste en el procesamiento previo que se le hace a la imagen, la imagen tiene que ir lo mas "limpia" posible para que el OCR no tenga mayores inconvenientes para leerla...

El primero que pida este programa y el de captchas de movistar hecho en PHP ya sabemos que es un spammer lamercito que ni modificar Mensajitos.php ha podido XD  :rofl:  :rofl:

Ya los quiero ver a esos spammers cuando libere el codigo de la nubecita...segun ellos en un hosting gratuito lo van a poder hacer funcionar  :rofl:  :rofl:

Fijate que usé ese algoritmo, porque originalmente tenía pensado extraer letra por letra, alinearlas y pasarlo al tesseract, pero sinceramente luego de un rato me dió hueva hahaha y solo la use para quitar el ruido... Realmente hay formas más fáciles de hacerlo pero pues, era quitarle diversion XD hahahaha


Desconectado Charlie

  • The Communiter-
  • *
  • Mensajes: 5592
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #3 : diciembre 25, 2011, 10:44:30 pm »
Viendo como han logrado resolver los captchas para el envió de mensajes me pregunto si no les interesaría llevar esa idea a otro nivel y desarrollar un código capaz de resolver el recaptcha utilizado en varios servers como Fileserve utilizando Tesseract.

Recuerdo que había uno para jdownloader pero estaba muy verde, seria interesante ver hasta donde unos programadores talentosos como ustedes son capaces de llevar adelante un proyecto así que beneficiaria a todos los que hacemos uso de servers protegidos con dicho sistema.

Ademas que si es exitoso podría servir para darle proyección internacional a SVC porque un plugin que resuelva recaptcha con eficiencia seria útil en todo el mundo.
« Última Modificación: diciembre 25, 2011, 10:46:08 pm por Charlietwo »

Desconectado hkadejo

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 3345
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #4 : diciembre 25, 2011, 10:45:14 pm »
Fijate que usé ese algoritmo, porque originalmente tenía pensado extraer letra por letra, alinearlas y pasarlo al tesseract

Justo eso fue lo que intente hacer cuando empece...pero, de ahi me di cuenta que no era necesario tanto desmadre y termine solo quitando el ruidito de fondo...ya con eso resulto al menos lo suficiente para reconocer la mayoria de veces el captcha.

Desconectado Juancho

  • The Communiter-
  • *
  • Mensajes: 1311
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #5 : diciembre 25, 2011, 10:45:28 pm »
Que buenos tutos de mxgxw, tanto los de Claro como Movistar... Siempre habia tenido curiosidad sobre el rompimiento del Captcha pero nunk habia indagado ni leido sobre el tema... Creo q en resumen como dice hkadejo, "la clave para romper el captcha es como tratar la imagen"...  Muy bueno los 2 temas... Ratos de no ver temas de este tipo x aqui...!

Quizas algun dia m toque algo asi y ya tengo de donde iniciar...! Muchas gracias por compartir sus conocimientos..!
<a href="http://www.gametracker.com/player/%7BAiPI%7DJuancho/94.127.17.72:11480/" target="_blank">
<img src="http://cache.www.gametracker.com/player/%7BAiPI%7DJuancho/94.127.17.72:11480/b_560x95.png" border="0" width="560" height="95" alt="" />
</a>

Desconectado mxgxw

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 5665
  • Starlet - 999cc
    • mxgxw
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #6 : diciembre 25, 2011, 10:48:19 pm »
Viendo como han logrado resolver los captchas para el envió de mensajes me pregunto si no les interesaría llevar esa idea a otro nivel y desarrollar un código capaz de resolver el recaptcha utilizado en varios servers como Fileserve utilizando Tesseract.

Recuerdo que había uno para jdownloader pero estaba muy verde, seria interesante ver hasta donde unos programadores talentosos como ustedes son capaces de llevar adelante un proyecto así que beneficiaria a todos los que hacemos uso de servers protegidos con dicho sistema.

Ademas que si es exitoso podría servir para darle proyección internacional a SVC porque un plugin que resuelva recaptcha con eficiencia seria útil en todo el mundo.

Fijate que estos son fáciles de resolver porque realmente no se han "esmerado mucho" en hacer un buen captcha.

Sistemas como el recaptcha o los captcha de google si son muchísimo más complicados de resolver pq ya no solo es procesamiento gráfico sino que también un poco de AI para resolverlos :)

Tal vez si sacamos algún cursito de AI hahahaha pero pues, eso como te digo, ya es otro nivel :) XD


Desconectado vlad

  • Global Moderator
  • The Communiter-
  • *
  • Mensajes: 6351
    • Qualium.net
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #7 : diciembre 26, 2011, 09:16:24 am »
Yo al de Claro lo que le hago es un -despeckle con ImagicK y listo :). Pero a nivel práctico creo que esto (editado: "esto" = el metodo de mxgxw) es mucho mejor porque incurre en menos overhead al no estar cargando programas adicionales para hacer esta tarea.

Editado:

$ convert captchaclaro.jpg -noise 1 -median 1 -unsharp 5 -normalize captchaclaro_limpio.jpg
Probando por curiosidad:


 :rofl: aunque ahora viendolo bien creo que esta mejor el tuyo, ya me voy a recordar bien como hacia el denoise con ImagicK

Editado 2:


$ convert captchaclaro.jpg -noise 1 -unsharp 1 -despeckle -normalize pnm:- | gocr -i -
ya3au

Exito  :drinks:

time:
real    0m0.092s
user    0m0.092s
sys     0m0.052s
« Última Modificación: diciembre 26, 2011, 03:52:58 pm por vlad »

Desconectado rdoggsv

  • Administrator
  • The Communiter-
  • *
  • Mensajes: 6530
  • "Once you go arch , u never go back"
    • SV CommunitY
Re: Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #8 : diciembre 26, 2011, 09:49:32 am »
Barbaros señores, un saludo a todos los que siguen dandole en la cara a las limitantes que tratan de poner estas empresas. Muy buenas soluciones.

Desconectado Jaru

  • The Communiter-
  • *
  • Mensajes: 13252
  • some text
Re:Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #9 : junio 04, 2012, 02:27:27 pm »
hey y esta captcha se podra romper de forma programática :yono

N/A

Desconectado stoke

  • Sv Member
  • ***
  • Mensajes: 342
Re:Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #10 : junio 04, 2012, 05:02:50 pm »
Excelente para fines didacticos, para fines practicos Imagemagick + GOCR  :thumbsup:

Desconectado lNpl

  • The newbie
  • *
  • Mensajes: 1
Re:Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #11 : octubre 09, 2012, 04:46:47 pm »
Perdón por revivir este Post, realmente me acabo de suscribir a la comunidad solo para agradecer a mxgxw por su código ya que me sirvió mucho, "pero leí que no lo hizo con tess4j yo dejo mi aportación con este wrapper, y realize algunas modificaciones en su código sin el wrapper  que NO me funciona hasta que hice esos cambio:



Código: [Seleccionar]
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package OCR;

import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import java.util.*;
import java.net.*;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;

/*
 * @Author mxgxw and lNpl
 * Codigo protegido bajo licencia GNU/GPL 2.0 o posterior
 */
public class Captcha {

    private static BufferedImage limpiar(BufferedImage img) {
        BufferedImage b = null;
        int x, y;
        // Generacion de imagen en monocromo
        for (x = 0; x < img.getWidth(); x++) {
            for (y = 0; y < img.getHeight(); y++) {
                img.setRGB(x, y, (brightness(new Color(img.getRGB(x, y))) > 0.5) ? Color.WHITE.getRGB() : Color.GRAY.getRGB());
            }
        }
        int i, increment;
        java.util.List<Pixel> pixelList;
        for (x = 0; x < img.getWidth(); x++) {
            for (y = 0; y < img.getHeight(); y++) {
                if (img.getRGB(x, y) == Color.BLACK.getRGB()) {
                    pixelList = new Vector<Pixel>();
                    increment = fillPixel(img, x, y, 1, pixelList);
                    pixelList.add(new Pixel(x, y));
                    for (i = 0; i < pixelList.size(); i++) {
                        img.setRGB(pixelList.get(i).x, pixelList.get(i).y, (new Color(increment, 0, 0)).getRGB());
                    }
                }
            }
        }
        Color currentColor;
        for (x = 0; x < img.getWidth(); x++) {
            for (y = 0; y < img.getHeight(); y++) {
                currentColor = new Color(img.getRGB(x, y));
                if (currentColor.getRGB() != Color.WHITE.getRGB()) {
//aumente el umbral a 40 ya que mi captchar ya baja en color rojo.
                    if (currentColor.getRed() > 10) {
                        img.setRGB(x, y, Color.BLACK.getRGB());
                    } else {
                        img.setRGB(x, y, Color.WHITE.getRGB());
                    }
                }
            }
        }

        // Borra pixels solos en la horizontal PASO 1
        for (x = 1; x < img.getWidth() - 1; x++) {
            for (y = 1; y < img.getHeight() - 1; y++) {
                if (img.getRGB(x - 1, y) == Color.WHITE.getRGB()
                        && img.getRGB(x + 1, y) == Color.WHITE.getRGB()) {
                    img.setRGB(x, y, Color.WHITE.getRGB());
                }
            }
        }
        // Borra pixels solos en la horizontal PASO 2
        for (x = 1; x < img.getWidth() - 1; x++) {
            for (y = 1; y < img.getHeight() - 1; y++) {
                if (img.getRGB(x, y - 1) == Color.WHITE.getRGB()
                        && img.getRGB(x, y + 1) == Color.WHITE.getRGB()) {
                    img.setRGB(x, y, Color.WHITE.getRGB());
                }
            }
        }
        // Borra pixels solos en la horizontal PASO 1
        for (x = 1; x < img.getWidth() - 1; x++) {
            for (y = 1; y < img.getHeight() - 1; y++) {
                if (img.getRGB(x - 1, y) == Color.WHITE.getRGB()
                        && img.getRGB(x + 1, y) == Color.WHITE.getRGB()) {
                    img.setRGB(x, y, Color.WHITE.getRGB());
                }
            }
        }
        // Borra pixels solos en la horizontal PASO 2
        for (x = 1; x < img.getWidth() - 1; x++) {
            for (y = 1; y < img.getHeight() - 1; y++) {
                if (img.getRGB(x, y - 1) == Color.WHITE.getRGB()
                        && img.getRGB(x, y + 1) == Color.WHITE.getRGB()) {
                    img.setRGB(x, y, Color.WHITE.getRGB());
                }
            }
        }
        b = img;
        return b;
    }

    public static class Pixel {

        public int x;
        public int y;

        public Pixel(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    public static double brightness(Color c) {
        return (0.2126 * c.getRed() + 0.7152 * c.getGreen() + 0.0722 * c.getBlue()) / 255;
    }

    public static int fillPixel(BufferedImage img, int x, int y, int increment, java.util.List<Pixel> pixels) {
        if (img.getRGB(x, y) == Color.BLACK.getRGB()) {
            img.setRGB(x, y, (new Color(increment, 0, 0)).getRGB());
        }
        if ((y - 1) >= 0 && img.getRGB(x, y - 1) == Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x, y - 1));
            increment = fillPixel(img, x, y - 1, increment + 1, pixels);
        }
        if ((x + 1) < img.getWidth() && img.getRGB(x + 1, y) == Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x + 1, y));
            increment = fillPixel(img, x + 1, y, increment + 1, pixels);
        }
        if ((y + 1) < img.getHeight() && img.getRGB(x, y + 1) == Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x, y + 1));
            increment = fillPixel(img, x, y + 1, increment + 1, pixels);
        }
        if ((x - 1) >= 0 && img.getRGB(x - 1, y) == Color.BLACK.getRGB()) {
            pixels.add(new Pixel(x - 1, y));
            increment = fillPixel(img, x - 1, y, increment + 1, pixels);
        }
        if (increment > 1) {
            img.setRGB(x, y, (new Color(increment, 0, 0)).getRGB());
        }
        return increment;
    }

    public static void main(String args[]) throws Exception {

        // Descargar el formulario de envio de mensajes
HttpURLConnection client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/telecomv2.aspx").openConnection());
        client.addRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
        client.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");

        client.connect();

        BufferedReader response = new BufferedReader(new InputStreamReader(client.getInputStream()));
        String line;
        String session_cookie = client.getHeaderField("Set-Cookie"); // Extraer la cookie de sesion
System.out.println("Session Cookie: " + session_cookie);
        String captcha_url = "";
        while (response.ready()) {
            line = response.readLine();
            // Buscar la linea con la imagen del captcha
//     if(line.contains("_pages_telecomv2_enviosms21_noticaptcha_CaptchaImage")) {
            if (line.contains("<img src=")) {
//     if(line.contains("src=")) {
                int base = line.indexOf("src=\"") + 5;
                int fin = line.indexOf("\"", base);
//                System.out.println(base);
//                System.out.println(fin);
//                System.out.println(line);
                captcha_url = line.substring(base, fin); // extraer la URL
                break;
            }
        }
        System.out.println("Captcha URL:" + captcha_url);

        // Descarga de la imagen
client = (HttpURLConnection)(new URL("http://sms2sv.claro.com.sv/pages/"+captcha_url).openConnection());
        client.addRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; es-ES; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23");
        client.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,* /*;q=0.8");
        client.addRequestProperty("Cookie", session_cookie);
        client.connect();

        byte[] buffer = new byte[1024];
        int bytesRead = 0;
        FileOutputStream fileOutput = new FileOutputStream(new File(".\\captcha_claro.jpg"));
        while ((bytesRead = client.getInputStream().read(buffer)) > 0) {
            fileOutput.write(buffer, 0, bytesRead);
        }
        fileOutput.close();
        BufferedImage img = ImageIO.read(new File(".\\captcha_claro.jpg"));
// System.out.println("Width: "+img.getWidth()+" Height: "+img.getHeight());

        String procesado = ".\\captcha_claro_processed.jpg";
        ImageIO.write(limpiar(img), "JPG", new File(procesado));

//esta modificaion la hice directa ya que no me estaba funcionando la ejecucuion de tesseract.exe y agrege el path completo para su ejecucion
//        Process tess_cmd = Runtime.getRuntime().exec("C:\\Program files\\Tesseract-OCR\\tesseract.exe \"captcha_claro_processed.jpg\" \"output\" ");
//        tess_cmd.waitFor();


//        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(".\\output.txt"))));
//        System.out.println("Decoded Capcha:"+reader.readLine());
//        reader.close();

//esta es la parte del wrapper no olviden descargarlo y agregar la carpeta tessdata para su funcionamiento
        Tesseract instance = Tesseract.getInstance();  // JNA Interface Mapping
        try {
            String result = instance.doOCR(limpiar(img));
            System.out.println(result);
        } catch (TesseractException e) {
            System.err.println(e.getMessage());
        }
    }
}

Saludos !!
lNpl
 :yahoo:
« Última Modificación: octubre 09, 2012, 04:56:37 pm por lNpl »

Desconectado capzero

  • Sv Member
  • ***
  • Mensajes: 389
Re:Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #12 : octubre 09, 2012, 05:11:22 pm »
Disculpen la ignorancia, como le hago para ocupar las lineas de codigo en la configuracion del modem

Desconectado Chaf

  • The newbie
  • *
  • Mensajes: 1
Re:Rompiendo el captcha de Claro (en un montón de líneas de código)
« Respuesta #13 : noviembre 01, 2012, 01:10:53 pm »
Funciona para enviar mensajes de  Claro de Honduras??
haciendole unas pequeñas modificaciones en la url de los servidores??
l