logotipo

img_google

Introducción

Después de tanto "este se vota a sí mismo" y "estos votan con 50 cuentas cada uno", decidí comprobar lo fácil que sería reventar el concurso. No se trata de demostrar que soy el mejor juáquer que haya existido; en realidad si alguien sabe un poquillo y ve lo que he hecho, probablemente le entre la risa. Pero sí, me gusta que los que saben aún menos (todos somos estúpidos en algo) me digan de vez en cuando lo bueno que soy.

En el proceso de registro y votación, la única identificación que se da es una cuenta de correo, dato que se protege con una contraseña. Obviamente, cualquiera puede registrar varias cuentas y votar una vez con cada una. La única "dificultad" de esto es que cuando se vota se acepta una cookie y en los votos siguientes ya se entra con las credenciales anteriores. Esto tiene fácil solución con borrar esa cookie. Como, además, es una cookie "de sesión", basta con cerrar el navegador para que desaparezca.

Segundo problema: la creación de varias cuentas de correo. No todo el mundo administra su propio servidor de correo, y abrir una cuenta en algunos de los que hay por ahí gratuitos parece más complicado que conseguir un préstamo bancario estando desempleado: nombre de usuario (que no haya cogido otro antes), contraseña, pregunta por si se te olvida la contraseña, dirección de correo alternativa para enviarte la contraseña si se te olvida, adivina cuáles son las letras reviradas de esta imagen, fecha de nacimiento, estudios, salario, gustos personales, color de la ropa interior, suscripciones a esto y a lo otro... Que levanten la mano los que conocieron internet antes del boom comercial (cuando todavía podía uno ver en un día todos los sitios web nuevos que habían aparecido la semana anterior) y lo echen de menos. Lo suponía...

A lo que vamos. Hay sitios en los que te ponen las cosas más fáciles. Mejor aún, hay sitios que dan cuentas antispam de usar y tirar, incluso cuentas públicas, sin contraseña ni registro, que se crean automáticamente con la llegada de un mensaje y desaparecen al cabo de un tiempo. Dada la utilidad pública de esos sitios, no se darán sus nombres. Tampoco es que sean difíciles de encontrar, pero no me gustaría que empezaran a filtrarse.

Ya podemos registrar cuentas sin preocupaciones, pero el proceso de votación sigue sin ser "escalable": requiere nuestra atención, hay que introducir datos que cambian con cada voto, borrar la cookie para votar otra vez... Para unas pocas cuentas vale, pero pasando de la decena resultaría pesado, aburrido y tendente a fallos.

¿No votamos por ordenador? Automaticemos. Todo el proceso se limita a rellenar formularios por web. Veamos qué hace falta:

No he incluido javascript en la lista porque sólo se usa para aplicar estilos, comprobar que se han rellenado los formularios adecuadamente y enviar un montón de información a un "proveedor de estadísticas".

Las tres primeras cosas pueden hacerse con wget. La imagen, supuestamente, es para evitar envíos automatizados, pero es demasiado sencilla: una pequeña modificación y gocr la lee con una fiabilidad cercana al 100%.

Señores de 20minutos (y otros que utilizan estos chismes), apunten estas cosillas si quieren volver a organizar un lío de estos:

En los siguientes scripts hay muchas cosas que se pueden cambiar. Para empezar, lo marcado en rojo (y puede que algo más) hay que cambiarlo según el servidor de correo usado, y hay pausas, generación de claves... El proceso intenta copiar lo que haría una persona al votar; es probable que haya formas mucho más sencillas de hacerlo, sin ni siquiera leer la imagen. Aún así, hay detalles que permitirían saber que se está votando con estos scripts, pero como funcionan, así quedan. Se ha usado cygwin (bash, wget, grep, sed, tr...) y gocr, todo libre. Con pocas modificaciones (o ninguna) se pueden usar en sistemas *NIX o con otro OCR.

Otra cosa: yo soy de los que no cortan las líneas, por largas que sean. Así que los cortes que se ven aquí no están en mis scripts y sólo los he puesto para que no se desparrame el asunto. Puede que hayan caído en mal sitio.

Reconocimiento del código

############################################################################
##  getcode
############################################################################

doneok=0
for gamma in 4 "0.1 -negate" "0.2 -negate" 9; do
  convert -trim -gamma ${gamma} -resize 200% "$1" captcha.pnm
  code=`gocr -C 0123456789lIS captcha.pnm | tr lIS 115`
  if ! echo "$code" | grep -q '[^0-9]'; then
    doneok=1
    break;
  fi
done
if [ $doneok -eq 0 ]; then
  touch badimage
  cp "$1" badimage.${1##*.}
  cygstart .
  read code
fi
echo $code

############################################################################

Si no lo encuentra, pregunta, de forma que la salida de este script puede usarse en los otros. Fuera de cygwin probablemente display "$1" sea mejor en el caso de error (con cygwin también, si se tiene el servidor X funcionando).

Es bastante eficaz, aunque parece que tiene problemas en reconocer dos cuatros seguidos en algunas circunstancias (se tocan y no los separa).

Diciembre

En Diciembre añadieron una línea a la imagen del mismo color del texto, con lo que el script anterior ya no reconocía ni una. Así que hubo que hacer cambios.

############################################################################
##  getcode - diciembre
############################################################################

declare -i b w i n
declare -a gamma

while [ -n "$1" ]; do

  convert -monochrome "$1" info.pnm
  histogram=`identify -verbose info.pnm | grep '[0-9][0-9]: '`
  b=`echo "$histogram" | sed -n '/ 0,/s/[^0-9]*\([0-9]*\):.*/\1/p'`
  w=`echo "$histogram" | sed -n '/255,/s/[^0-9]*\([0-9]*\):.*/\1/p'`
  if [ $b -gt $w ]; then
    gamma=(2.5 2.7)
    convert -negate "$1" captchatidy.png
  else
    gamma=(2 2.5 3)
    convert "$1" captchatidy.png
  fi

  n=${#gamma[*]}
  found="0"

  convert -draw 'image over 0,1 0,0 captchatidy.png' captchatidy.png \
          captchamoved.png
  convert -fx 'max(u,v)' captchatidy.png captchamoved.png captchaclean.png
  for (( i=0; i<n; i++ )); do
    convert -trim -gamma ${gamma[i]} -resize 200% captchaclean.png \
            captcha.pnm
    code=`gocr -C 0123456789IlS captcha.pnm | tr IlS 115`
    if ! echo "$code" | grep -q '[^0-9]'; then
      found="1"
      break
    fi
  done

  if [ "$found" == "0" ]; then
    if [ $b -gt $w ]; then
      gamma=(8 5)
    else
      gamma=(8)
    fi
    n=${#gamma[*]}

    for (( i=0; i<n; i++ )); do
      convert -trim -sharpen 0 -gamma ${gamma[i]} -resize 200% \
              captchaclean.png captcha.pnm
      code=`gocr -C 0123456789IlS captcha.pnm | tr IlS 115`
      if ! echo "$code" | grep -q '[^0-9]'; then
        found="1"
        break
      fi
    done
  fi

  if [ "$found" == "0" ]; then
    touch badimage
    cp "$1" badimage.${1##*.}
    cygstart .
    read code
  fi

  echo $code

  shift
done

############################################################################

Falla más que el anterior y, según por dónde pase la raya, puede confundir la lectura. Aún así, creo que el porcentaje de acierto sigue siendo bueno.

Registro de cuentas

############################################################################
##  makeaccount
############################################################################

AGENT='Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050922 Firefox/1.0.7'
SERVERS=(example.com mailer.example.com fakemailer.example.com)

function request {
  OUTPUT="$1"
  URL="$2"
  COOKIES="$3"
  if [ -n "$4" ]; then
    REFERER="--referer $4"
  else
    REFERER=""
  fi
  shift 4
  wget -q -O ${OUTPUT} --load-cookies ${COOKIES} --save-cookies ${COOKIES} \
       --keep-session-cookies --header='Accept: text/xml,text/html,text/plain;\
       q=0.8,image/png,*/*;q=0.5' --header='Accept-Language: es' ${REFERER} \
       --user-agent="${AGENT}" "$@" ${URL}
}

function absoluteurl {
  if [ "${1:0:5}" != "http:" ]; then
    if [ "${1:0:1}" == "/" ]; then
      echo "http://www.20minutos.es""$1"
    else
      echo "http://www.20minutos.es/""$1"
    fi
  else
    echo "$1"
  fi
}

rm form cookiesreg cookiesmail
url=http://www.20minutos.es/premios_20_blogs/votar/462/2/
request form ${url} cookiesreg
if grep -q "$var recordandoPass" form; then
  sleep 1
  referer=${url}
  url=http://www.20minutos.es/registro_popup/premios_20_blogs_votar/462/2/
  request form ${url} cookiesreg ${referer}
  if grep -q "$var enviandoRegistro" form; then
    referer=${url}
    url=`sed -n 's/<div class="code"><img src="\([^"]*\)".*/\1/p' form`
    request captcha.png `absoluteurl ${url}` cookiesreg ${referer}
    line='registro_operacion=inscripcion_usuario&code_ok='
    code=`sed -n '/code_ok/s/.*value="\([^"]*\)".*/\1/p' form`
    user=$(( 1+${RANDOM}%`wc -l words | cut -f1 -d" "` ))
    user=`sed -n ${user}p words`
    pass=`echo ${user} | tr [a-m][n-z] [n-z][a-m]`${RANDOM}
    user=${user}_${RANDOM}
    server=$(( ${RANDOM}%${#SERVERS[*]} ))
    line=${line}`./urlencode ${code}`'&user_nickname='${user}'&user_email\
         ='${user}%40${SERVERS[server]}'&user_password_1='${pass}'&user_\
         password_2='${pass}'&codigo='`./getcode captcha.png`
    url=${referer}
    sleep $(( 4+${RANDOM}%6 ))
    request form ${url} cookiesreg ${referer} --header='Content-Type: \
            application/x-www-form-urlencoded' --post-data="${line}"
    if grep -q "Gracias por registrarte" form; then
      sleep 100
      url='http://www.example.com/fakemailer/maildir.jsp?email='${user}
      request form ${url} cookiesmail
      referer=${url}
      url='http://www.example.com/fakemailer/showmail.jsp?email='${user}'\
          &msgnum='`sed -n '/mailer@20minutos/s/.*msgnum=\([0-9]*\).*/\1/p' form`
      if [ -n "${url}" ]; then
        request form ${url} cookiesmail "${referer}"
        url=`sed -n '/confirmar_registro/s/.*href="\([^"]*\).*/\1/p' form`
        if [ -n "${url}" ]; then
          request form ${url} cookiesreg
          if grep -q "Gracias por darte de alta" form; then
            echo -n "OK "
          fi
        fi
      fi
    fi
  fi
fi
echo ${user} ${SERVERS[server]} ${pass}

############################################################################

En words se espera una lista de palabras (una palabra por línea) para usar como base del nombre de cuenta. Tampoco es que sea necesario, podría generarse el nombre de forma totalmente aleatoria. Para evitar problemas, que las palabras sólo tengan símbolos que no necesiten cambios para usarlos en una url. Si sólo hay letras [a-z] se está seguro (sí, la ñ y los acentos están fuera de la lista). Si hiciera falta, amplíese urlencode y úsese para los otros campos en la generación de line.

############################################################################
##  urlencode
############################################################################

BAD=(\; : @ \& = $ , \< \> \" { } \| ^ [ ] \` \( \))
GOOD=(3B 3A 40 26 3D 24 2C 3C 3E 22 7B 7D 7C 5E 5B 5D 60 28 29)

c=${1//\%/%25}
c=${c//\#/%23}
c=${c//\?/%3F}
c=${c//\//%2F}
c=${c//\\/%5C}
c=${c//+/%2B}
c=${c// /+}

declare -i i n

n=${#BAD[*]}

for (( i=0; i<n; i++ )); do
  c=${c//${BAD[i]}/%${GOOD[i]}}
done
echo "${c}"

############################################################################

Envío de votos

En el archivo accounts guardaremos los datos de las cuentas registradas (el script que ejecuta n veces makeaccount, comprueba su salida y añade las cuentas al archivo, queda como ejercicio para el lector). En cualquier caso, pueden tenerse varios archivos con distintos datos y usar uno u otro según las circunstancias.

Una cuenta en cada línea con los siguientes campos separados por un tabulador:

Ejemplo:

login_email=nombre%40example.com&login_password=trae353	12	1562/2	frivoblog	462/2	humor
login_email=usu34%40example.com&login_password=3865nv	1	462/2	\(humor\)\|\(personal\)

En este caso nombre@example.com (clave trae353, navegador 12) vota al blog 1562 en frivoblog y al 462 en humor. usu34@example.com (clave 3865nv, navegador 1) vota al blog 462 en humor y personal.

############################################################################
##  sendvotes
############################################################################

function request {
  OUTPUT="$1"
  URL="$2"
  shift 2
  wget -q -O ${OUTPUT} --load-cookies cookies --save-cookies cookies \
       --keep-session-cookies --header='Accept: text/xml,text/html,text/plain;\
       q=0.8,image/png,*/*;q=0.5' --header='Accept-Language: es' \
       --referer=${REFERER} --user-agent="${AGENT}" "$@" ${URL}
}

function panic {
  echo
  echo "    *** PANIC ***   "
  echo "$1"
  echo
  echo URL "$2"
  echo LOGIN "$3"
  echo CATS "$4"
  echo
  cygstart .
  read -p "Press ENTER to continue: " line
  echo RUNNING
}

function absoluteurl {
  if [ "${1:0:5}" != "http:" ]; then
    if [ "${1:0:1}" == "/" ]; then
      echo "http://www.20minutos.es""$1"
    else
      echo "http://www.20minutos.es/""$1"
    fi
  else
    echo "$1"
  fi
}

. ./readdb "${1:-accounts}"

declare -i usersleft=users

while [ $usersleft -gt 0 ]; do
  rm cookies
  echo $usersleft
  let i=${RANDOM}%users
  while [ -z "${userlogin[i]}" ]; do
    let i++
    if [ $i -eq $users ]; then
      let i=0
    fi
  done
  AGENT="${agents[${agent[i]}]}"
  REFERER="http://www.mozilla.org"
  declare -i ivote j
  let ivote=voteindex[i]
  for (( j=0; j<votemany[i]; j++ )); do
    iurl="http://www.20minutos.es/premios_20_blogs/votar/"${vote[ivote]}
    let ivote++
    request form.html ${iurl}
    REFERER=${iurl}
    if grep -q "$var recordandoPass" form.html; then
      sleep $((4+$RANDOM%3))
      request form.html ${iurl} --post-data ${userlogin[i]}
    fi
    declare -i sentform
    let sentform=0
    while grep -q "$function EnviarVotos" form.html; do
      imurl=`sed -n 's/<div class="code"><img src="\([^"]*\)".*/\1/p' \
            form.html`
      imurl=`absoluteurl ${imurl}`
      request captcha.png ${imurl}
      BOUNDARY="-----------------------------"${RANDOM}${RANDOM}${RANDOM}
      ./genform form.html "${vote[ivote]}" captcha.png "${BOUNDARY}" | \
            unix2dos > form.dat
      iurl=`sed -n '/<form .*id="form_votacion"/s/.*action="\([^"]*\).*/\1/p' \
           form.html`
      iurl=`absoluteurl ${iurl}`
      sleep $((6+$RANDOM%4))
      request form.html ${iurl} --header="Content-Type: multipart/form-data; \
              boundary=${BOUNDARY}" --post-file form.dat
      let sentform++
    done
    let ivote++
    if [ $sentform -eq 0 ]; then
      panic "Not a form" ${iurl} ${userlogin[i]} ${vote[ivote-1]}
      continue
    fi
    if ! grep -q "Gracias por tu voto" form.html; then
      panic "No acepta el voto" ${iurl} ${userlogin[i]} ${vote[ivote-1]}
      continue
    fi
    sleep $((10+$RANDOM%12))
  done
  userlogin[i]=""
  let usersleft--
  sleep $((1+$RANDOM%120))
done

############################################################################
############################################################################
##  readdb
############################################################################

declare -a userlogin
declare -ai agent voteindex votemany;

declare -a vote;

declare -i users=0;

declare -a agents

declare -i i

MYIFS="$IFS"
IFS="	"
i=0
{
while read -r agents[i]; do
  let i++
done
} < agents

{
while read -r -a voteline; do
  userlogin[users]=${voteline[0]}
  agent[users]=${voteline[1]}
  declare -i ivote nvoteline
  ivote=${#vote[*]}
  nvoteline=${#voteline[*]} 
  let voteindex[users]=ivote
  let votemany[users]=nvoteline/2-1
  for (( i=2; i<nvoteline; i++ )); do
    vote[ivote]=${voteline[i]}
    let ivote++
  done
  let users++
done
} < "$1"

IFS="$MYIFS"

############################################################################

Sí, el primer IFS es un tabulador.

############################################################################
##  genform
############################################################################

{
sed -n '/type="hidden"/s/.*name="\([^"]*\)" value="\([^"]*\)".*/\1 \2/p' "$1" | \
    sed "s/operacion/operacion votacion/"
sed -n '/type="checkbox"/s/.*name="\([^"]*\)" value="\([^"]*\)"[^>]*>\(.*\)<\/
    label.*/\1 \2 \3/p' "$1" | sed -n /"$2"'/s/\([^ ]*\) \([^ ]*\) .*/\1 \2/p'
echo "codigo "`./getcode "$3"`
} | {
while read name value; do
  echo "--""$4"
  echo "Content-Disposition: form-data; name=\""${name}\"
  echo
  echo ${value}
done
echo "--""$4"--
}

############################################################################

ICQ#325544822 antiswen@yahoo.es