Índice:
1.-Introducción
2.-Preparando el documento
3.-Analizando el documento usando PHP
4.-Caso real: recopilando, analizando y arreglando

Actualmente no funciona, debido a que al parecer, Google tiene deshabilitada la respuesta JSON de cuando enviamos una petición para geolocalizar

1.-Introducción

Últimamente no paro de hablar de la geolocalización a través de las MAC gracias a Google, y no es para menos. El hecho de que sabiendo una MAC de un router obtengamos la localización geográfica muy exacta (igual falla en unos metros) puede ser benificioso y a la vez peligroso.

Programas como Prey, hacen de esta característica que parezca buena y beneficiosa por si nos roban un dispositivo capacitado para acceder a la red, como un móvil, tablet o notebook.

Sin embargo, todo tiene su lado negativo, y realmente esto de cierta manera atenta contra la privacidad de las personas, en mayor o en menor medida: imaginemos que disponemos de un programa para movil que va captando las MACs y a raiz de eso, se geolocaliza y obtiene la dirección. Tendriamos el móvil totalmente controlado. ¿Y si instalásemos este programa en un dispositivo de una persona ajena? Podriamos saber en todo momento por dónde ha pasado.

De hecho, existe algo muy parecido, y se llama Creepy. Este programa, a partir de una cuenta de twitter, puede situar en el mapa los puntos en los que hay tweets de un usuario en donde se haya geolocalizado a la hora de escribirlos.
Sin embargo, esto no es intrusivo, ya que el usuario es quien decide si poner o no su localización.

Volviendo al tema de antes, se me ocurrió diseñar un script que, a partir de un array con direcciones MACs, te geolocalice y trace una ruta en GMaps. De esta manera, si tuvieramos “algo” capaz de obtener las MACs que nos rodean durante un viaje, podriamos saber la ruta que ha seguido (siempre aproximadamente).

Como no sé programar para Android, al menos todavia, no sé muy bien si esto sería fácil o difícil de programar para un móvil/tablet, pero me he valido de la suite de aircrack (concretamente de airodump y aircrack) para poner a prueba el script que pondré a continuación para probar esto mismo.

Lo que hago es lo siguiente:
1.-Con el airodump capturo paquetes durante todo el viaje o trayecto.
2.-Una vez capturados, uso aircrack y redirecciono la salida para tener todas las MACs en un txt.
3.-Posteriormente lo filtro bien para tener un txt del tipo:
aa:aa:aa:aa:aa:aa
bb:bb:bb:bb:bb:bb
cc:cc:cc:cc:cc:cc
...

4.-Finalmente, leo mediante un script en PHP el txt generado y usando las APIs de GMaps y la de la geolocalización, establezco los puntos y trazo la ruta.

2.-Preparando el documento

Antes de todo, hacemos una captura con el airodump y nos vamos a dar un paseo:
$ airodump-ng mon0 -w captura --output-format pcap

Una vez tengamos todos los datos, nos generará un fichero llamado captura-01.cap. Vamos a abrirlo con el aircrack redireccionando su salida en un txt.
$ aircrack-ng captura-01.cap > documento.txt

Una vez hagamos esto, tenemos todos los datos guardados en el fichero documento.txt. Ahora, simplemente tenemos que filtrar el documento para obtener uno que quede como dije anteriormente:
$ cat documento.txt | grep -e '[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]' -o > macs.txt

Vamos a usar un poco el sentido común: si estamos en una ciudad y damos un paseo de cinco minutos, podemos acabar con varios centenares de direcciones MAC. Esto a la hora de pasarlo como argumento y usarlo para localizar puede que tarde mucho, e incluso es bastante inutil. ¿Para qué queremos 10 direcciones MACs que nos deberian de situar en el mismo sitio? Con una nos deberia bastar, ¿no?

Lo único que se me ocurre para filtrar un poco más, y obtener menos direcciones MAC, son dos cosas: obtener solo las MACs de routers que usan WPA (que son mucho menos que los de WEP, tristemente) o usar las direcciones MAC de redes en las que hemos capturado algún paquete (ya que si vamos andando, de muchas no capturaremos nada).

Para ayudarnos a la hora de crear un filtro para estos dos casos, miraremos la estructura de una captura pasada por aircrack:

Posteriormente, es fácil deducir que podriamos filtrarlos de la siguiente manera. En el primer caso (solo las WPA):
$ cat documento.txt | grep 'WPA (' | grep -e '[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]' -o > macs.tx

En el segundo caso (WEPs con datos)
$ cat documento.txt | grep 'WEP (' | grep -e '[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]' -o > macs.tx

3.-Analizando el documento usando PHP

No tengo mucho que decir aquí, ya que el código viene completamente comentado, así que haré un resumen de su funcionamiento.

Tengo una matriz con las MACs. Lo que hago es, a partir de esa matriz, obtener una matriz de localizaciones (en orden siempre) usando la Google Geolocation API. Posteriormente simplemente es usar la API de Google Maps de la que uso 3 funciones: establecer un mapa, establecer los puntos, y trazar una ruta entre los puntos.

Aquí va el código:

<html>
<head>
<?
 
//Primera parte de Javascript: se encarga de configurar el mapa inicial: donde apuntará, zoom y tipo
echo '
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
 
<script type="text/javascript">
function iniciar() {
  var myLatLng = new google.maps.LatLng(39.300299,-3.955078);
  var myOptions = {
    zoom: 4,
    center: myLatLng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
 
  var map = new google.maps.Map(document.getElementById("mapa"),
      myOptions);
 
 
	  ';
 
//Lista de direcciones MAC
$f=fopen("macs.txt","r");
while(!feof($f))
$dir[] = str_replace(":","-",fgets($f));
//Vamos linea a linea, reemplazando los guiones por dos puntos, a la vez que añadimos a la matriz
fclose($f);
 
 
 
foreach($dir as $valor) { //Por cada una, ejecutamos una función
	obtener_coordenadas($valor,$resultado); 
	//Obtenemos las coordenadas
 
	poner_punto($resultado,$valor);
	///Ponemos el punto
 
	$final[] = $resultado[0] . "," . $resultado[1];
	//Vamos creando una matriz que almacenará todas las coordenadas
 
}
 
//Trazamos la ruta entre todas las coordenadas
trazar_ruta($final);
 
 
//Fin del script
echo '
}
 
</script>
';
 
 
 
function trazar_ruta($matriz){
/*A partir de una matriz, trazaremos la ruta entre las direcciones MAC
  ya que tenemos las coordenadas. El contenido será del tipo
  matriz[0]=a,b;
  matriz[1]=c,d; Siendo a,b,c,d coordenadas */
 
echo '
  var flightPlanCoordinates = [
  ';
  //Ponemos todos los valores de los puntos a unir
  foreach($matriz as $clave => $valor)
     echo "new google.maps.LatLng(" . $valor . "),
";
 
//Configuramos las caracteristicas: color, opacidad y tamaño
	echo '
  ];
 
  var flightPath = new google.maps.Polyline({
    path: flightPlanCoordinates,
    strokeColor: "#FF0000",
    strokeOpacity: 1.0,
    strokeWeight: 2
  });
 
 
  flightPath.setMap(map);
 
	';
 
}
 
function poner_punto($coordenadas,$titulo){
/* Esta función nos permite poner un punto en el mapa
   tenemos que darle las coordenadas, y el título que tendrá ese punto
 
*/
 
//Establecemos las coordenadas
echo "var coordenadas = new google.maps.LatLng(" . $coordenadas[0] . "," . $coordenadas[1] . ");
";
 
//Lo ponemos en el mapa
echo '  var marker = new google.maps.Marker({ //opciones
      position: coordenadas,
      map: map,
      //Nombre del mapa
      title:"' . substr(str_replace("-",":",$titulo), 0, -1) . '"
      //Titulo (visible cuando colocamos el ratón sobre el punto)
  });
';
 
}
 
 
 
function obtener_coordenadas($mac, &$resultado){
/* Esta función nos permite guardar en "resultado" (pasado por referencia)
   las coordenadas de la mac pasada en el primer parámetro
*/ 
 
//Datos que enviaremos
$datos_completos = '{version:"1.1.0",request_address:true,wifi_towers:[{mac_address:"' . $mac . '"}]}';
 
//Iniciamos cURL
$ch = curl_init();
//Configuramos cURL
curl_setopt($ch, CURLOPT_POST, 1); 
curl_setopt($ch, CURLOPT_POSTFIELDS, $datos_completos);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
curl_setopt($ch, CURLOPT_URL,'www.google.com/loc/json'); 
 
$respuesta = curl_exec($ch);
curl_close($ch);
 
//Obtenemos la respuesta y posteriormente la decodificamos
$respuesta = json_decode($respuesta, true);
 
//Para terminar, pasamos los resultados a los valores que posteriormente usaremos
$resultado[0]=$respuesta['location']['latitude'];
$resultado[1]=$respuesta['location']['longitude'];
 
}
 
 
?>
</head>
 
<body onLoad="iniciar()">
  <div id="mapa" style="width:100%; height:100%"></div>
 
 
</body>
</html>

4.-Caso real: recopilando, analizando y arreglando

Esta mañana decidí poner a prueba lo que he escrito anteriormente: puse el portatil a escanear y me fui desde donde vivo actualmente hasta la universidad para posteriormente analizarlo y ver si consigo trazar la ruta correctamente.

Para empezar, antes de salir de casa, puse la tty3 (para poder cerrar el portatil sin que se suspenda o apague) de mi Fedora escaneando con airodump y recogiendo paquetes de datos. Al llegar a la uni, filtreé los datos recogidos como anteriormente puse, generando 3 resultados distintos:
-606 MACs en total
-33 MACs con datos WEP (de las 606)
-35 MACs con WPA (de las 606)

Al script de geolocalizar y trazar rutas se le pueden pasar todos los datos que queramos, como si le pasamos over 9000 MACs, lo que pasa que hay que recordar que tenemos un timeout por defecto puesto en 60, por lo que dependiendo de nuestra velocidad de conexión podremos tratar más o menos MACs. Por ejemplo, en la uni puedo tratar hasta 225 MACs, pero en casa, alrededor de 100.

Podemos cambiar el timeout yéndonos al fichero php.ini y cambiando los valores (que están en segundos) de max_execution_time y max_input_time. Recordad que si tenemos corriendo nuestro servidor, para que se hagan efectivos los cambios habría que reiniciarlo.

Los puntos verdes muestran el inicio y el fin desde donde recogí datos. La primera imagen refleja el resultado de usar las 33 MACs con datos WEP:

Esta segunda imagen, es la de las 35 MACs que usan WPA:

Como podemos ver, en algunos puntos se nos va un poco de la ruta, pero creo que eso es debido a que hay repetidores aquí por la ciudad para tener WiFi gratis (además de los propios de la uni) y eso desestabiliza un poco.

Vamos a tratar ahora de solucionar el problema del límite de MACs que anteriormente dijimos ya que, esta captura la he realizado en 15 minutos.. Si fueran horas, tendriamos decenas de miles de direcciones. Para ello, lo que hacemos es pasar la matriz por una función cuyo algoritmo determine qué direcciones MAC deberiamos de eliminar para no pasar el límite.

Por ejemplo, si tenemos 5 MACs y queremos buscar dos, pues obviamente cogeriamos la primera y la última. Si tuvieramos 20 MACs con un límite de 4, podriamos coger 1 cada 5, y así sucesivamente.

El siguiente código no es el más eficaz ni el más perfecto, ya que le dediqué 5 minutos en hacerlo, pero funciona, y nos permite parsear una matriz y establecer otra a partir de un límite:

function reducir(&$matriz, &$resultado, $limite=100){
	$total=count($matriz);
	if($total>$limite) {
	  $division = $total/$limite;
	  $division = explode(".",$division);
	  $division = $division[0];
 
	  for($a=0;$a<$limite;$a++)
	    $resultado[$a]=$matriz[$a*$division];
	  unset($matriz);
	 //$matriz = $nueva;
	}
	//En caso contrario no hace falta modificar
	echo "<br><hr>";
 
}

Un saludo, lipman