sábado, 23 de junio de 2018

Las mentiras en el comunicado de La Liga. Gol por la escuadra a nuestra inteligencia

/* DISCLAIMER

Este post no es un ataque a La Liga de Fútbol Profesional, es simplemente que muchas de las partes que comentaron en su comunicado no concuerdan con la realidad de cómo funciona su aplicación, de ahí que veo que sea necesario elaborar un post explicando en detalle cómo funciona la app y por qué hacen lo que hacen.

*/

Hace unos días escribí sobre la noticia de la Liga de Fútbol Profesional, pero no comenté mucho más, comenté que me parecía extraño lo que decían porque lo veía complicado, pero ahí me quedé, no fui más allá hasta que ayer por la noche antes de acostarme, casi de casualidad, llegué al comunicado que emitió la LFP cuando salió la noticia pero que yo no sabía que existía. Admito que me fui cabreado a la cama porque entendí que la LFP se estaba descojonando de todos los que hemos pasado tiempo a entender cómo funciona la tecnología y que nos apasiona este mundo, pero lo dejé ahí y me fui a la cama.



La versión que se ha analizado es la versión 6.4.7 de la aplicación de la Liga de Fútbol Profesional. Pero antes vamos con el comunicado de la LFP, que me provocó tal cabreo que esa noche no pude dormir porque tenía en la mente analizar la aplicación de la Liga para ver si habían mentido en alguna parte, así que me desperté de madrugada, dejé a mi novia dormir y me puse en el ordenador. Aunque como he dicho, lo primero es el comunicado.



En el origen del comunicado comentan que el deber de la LFP es proteger a los clubes de la emisión fraudulenta de partidos de fútbol. Pobre Florentino Pérez que por culpa de la piratería no tiene para fichar a Neymar. Por esto mismo dicen que incorporaron la funcionalidad de habilitar el micrófono el día 8 de junio de 2018.


Este es el Android Manifest de la app de La Liva v6.4.7 del 8 de junio de 2018. Este archivo lo he sacado haciendo ingeniería inversa con la herramienta online Java Decompilers.

Para comprobar si esto es verdad me fui a la web APK4FUN para ver otras versiones de la app de la Liga.


Vi que la versión 6.4.0 del 21 de febrero de 2018 y SHA1 EFD50120F73C0D674492126CE9E9198DA57C8287 me la podía descargar y extraer de igual forma el Android Manifest.


Así pude comprobar que la versión 6.4.0 del 21 de febrero de 2018, pedía igualmente los permisos para grabar audio del micrófono tal y como hace la v6.4.7. La LFP decía que esta funcionalidad la incorporaron el 8 de junio de 2018, pero vemos que no es cierto. Esto motivaría que siguiera investigando para ver si encontraba algo más.


Aquí ya saltan más contradicciones. Lo primero es que hablan algo de el código binario, pero bueno, vamos a dejar pasar eso y vayamos a la parte en la que dicen que sólo graban fragmentos para asegurar que no se están viendo partidos de equipos de la Liga por emisiones fraudulentas. Esto puede tener cierto sentido, lo que no lo tiene es que digan que hacen esto sin acceder a la grabación.

No dicen tampoco cómo comprueban que se está viendo un partido desde una emisora fraudulenta, pero eso lo veremos nosotros después. Yo lo que creo es que han dicho antes lo del código binario para decir que ellos no aceden a las grabaciones, sino al binario, algo que es una contería enorme, más que nada porque nunca serán la misma cadena de unos y ceros, ya que en el bar o en tu casa hay ciertos ruidos que ya alteran el resultado final. No podemos comparar, por ejemplo, que yo esté copiando una canción como Let It Be si yo estoy poniendo Let It Be pero con gritos de fondo. El binario no será el mismo, por lo que hay que ir, a la fuerza, a la grabación.

Tampoco sé si se refieren a que conforme graban el audio lo borran, algo que no tiene mucho sentido tampoco, pero bueno, sigamos que el comunicado no tiene desperdicio.


Lo de las franjas horarias ya es cachondeo, ya que imaginemos que un equipo Español se va a jugar a Estados Unidos y aquí es de madrugada o es de noche pero no tienes por qué estar viendo el partido y estas, por ejemplo, con tu pareja, pues no es que activen tu micrófono, no , activan los más de 10 millones de micrófonos que pueden ser controlados con la aplicación. Eso es un buen número de personas.


Aquí agarran tu cerebro y lo pisotean. Vuelven a hablar que no acceden a los fragmentos de audios debido a que esos fragmentos se convierten a un código binario en el dispositivo y que es irreversible. Aquí me paré esa noche y dije "No puede ser verdad, se han descojonado de toda la gente".

Creo que hasta la persona con menos idea de informática sabe que en un ordenador todo son datos binarios y que hasta este texto, se convierte a un texto binario, pero decir que es irreversible es un despropósito.


Por su puesto que es reversible, es más, por eso mismo somos capaces de escuchar música en Spotify o Youtube por ejemplo. Ahí también se pasan las canciones a binario y damos en reproducir y lo escuchamos. No sé quién ha escrito esto, pero es de las pocas veces que en cada párrafo tengo que decir que se están diciendo mentiras.

Aún así y como no quería acusar tan fácilmente, le dediqué unas cuantas horas a pensar a qué se referían. A la única conclusión que llegué es que trabajen de una forma similar  a la que trabaja Shazam, los cuales sí generan un hash de cada audio. Si queréis saber cómo funciona Shazam en profundidad, os recomiendo este artículo llamado How Shazam works. No digo que no pueda ser eso, pero no sé, Shazam sí que tiene mucha pasta para invertir en tecnología potente para hacer esto y duda que la LFP, con la crisis que vive España, tenga dinero para esta tecnología.

Sin olvidar que Shazam comprueba en una base de datos la canción grabada por el usuario y ver si coincide con alguna canción de su base de datos, que es finita, pero para un partido ¿Cómo lo haces? Es decir, necesitas saber lo que va a decir cada comentarista de cada canal que retransmite fútbol cada segundo de partido, ya que en teoría el fútbol es en directo ¿Cómo creas esa Base de datos que sólo puedes crear a la vez que ves el partido? La única solución que se me ocurre es que viajen al futuro ¿Veis el sin sentido de lo que dicen?

Esto lo tiene que hacer La Liga, lo cual parece que es muy complejo computacionalmente para ellos, pero claro, ni en su pliego de condiciones dicen nada de pasar datos a terceros ni en su aviso legal sobre privacidad y cookies dicen de pasar datos a otras empresas, es más dicen justo lo contrario.



No dicen nada de pasar datos a terceros, y cuando hablan de esto, se refieren a proovedores de servicios, pero esto es ambiguo, ya que si yo quiero mañana, puedo proporcionar servicio de analíticas, hosting o dar publicidad, por lo que permite traficar con los datos del usuario sin problema.


Esto es a lo que me refería antes con lo de viajar al futuro ¿Cómo saben que estoy viendo un partido si, en teoría, ni ellos mismos saben qué va a decir en cada segundo el comentarista de turno? Sólo nos quedaría asumir que la LFP controla cada partido y ya saben lo que va a ocurrir y por tanto dan un guión a cada comentarista si asumimos como cierto lo que dicen. Esto no creo que sea favorable para la LFP, por esas cosillas de que algún presidente de la Federación ha tenido que irse por corrupción, noticias de amaño de partidos y demás.


Este punto es otra cuestión que es mentira. Yo para probar, me he descargado la app en mi móvil y me ha dejado entrar sin registro, por lo que supongo-sin mirar aún el código- es que me asignarán un ID nada más entrar. Lo de la IP...bueno, si ahora quiero utilizar una VPN sería gracioso ver sus caras al ver que estoy en China y viendo un partido en el bar de mi barrio ¿Alguien dijo omnipresencia?


Esto ya no sabía cómo tomármelo. Creo que pensé algo como "Sí, faltaría más. Además, esto no es algo que hagas tú, sino las nuevas versiones de Android".

Como he dicho, al poco rato me fui a dormir pero con la mosca detrás de la oreja, por lo que me desperté de madrugada nada más que para ver el código. Me tiré toda esa noche y parte de la mañana siguiente, pero lo analicé todo. Es por eso que esta parte que viene ahora será larga, pues es análisis de código en profundidad.

Código Android de la app de la Liga

Serían las 2 ó 2:30 de la mañana, encendí el ordenador y utilicé el sitio online de Java Decompilers para obtener el código de la APK de la app de la Liga en su versión 6.4.7, que es la más actual.

Aviso que no desarrollo Android y que sus 9-10 horas me tiré delante del ordenador analizando y pensando bastante qué hacía cada línea y cada función. Lo que no sabía lo iba buscando en docs y algo que me llevaba aprendido. Esto lo digo por si veis algún fallo muy cantoso y sabéis más del tema para que lo digáis sin problema en los comentarios o por correo.


Lo primero que te encuentras en es.lfp.gi.main es que hardcodea la API del sitio match.fluzo.com y claro, aquí tenemos ya la primera en la frente. En el agreement no dice nada de que se pasen datos a terceros, pero en las primeras líneas de código del programa principal de la app ya te lo encuentras. No tienen mucha pinta de ser grandes mentes, pero sigamos.

Si queréis hacer vosotros la prueba, yo os recomiendo que descarguéis el zip con todos los archivos y lo reviséis tranquilamente en vuestro equipo y no por la web, ya que yo primero revisé todo el código y ahora me estoy volviendo loco buscando todo lo que tenía que comentar.


Ese isFirstTime lo que nos dice es que cada vez que un usuario accede a la app, se comprueba la variable hasRecordPermission es 0, entonces inicia la función startFluzo y si la versión de la SDK es mayor a 23 inicia el micrófono. Es decir, cada vez que accedes a la app, si tiene acceso al micrófono, lo enciende y sino, activa otra función que ahora veremos en qué consiste. Por cierto, en ese If donde comprueban si tienen permisos para activar el micrófono, comprueba si tiene los permisos y si el ContentManager, es decir, si el contenido-el idioma- está en Español, nada de direcciones IP ni pollas.




Si nos vamos a startFluzo, vemos que lo que hace es iniciar un servicio con startService. Posteriormente inicia el método onRequestPermissionResult que recibe un requestCode además de un array sin definir espacio con los permisos que ha concedido el usuario. Ese requestCode se utiliza para utilizarlo en un switch-case donde, como el hasRecordPermission es igual a 0-no tiene permisos para habilitar el micrófono- lo activa.

Una vez que entra en el switch-case, crea un bucle for con la variable tipo int y que ha llamado "i" ¿Recordáis que he dicho que el string del array de la variable permissions no tenía definido un tamaño? Pues en el bucle for va desde que la variable i es igual a 0 hasta la longitud del array del string de los permisos que el usuario haya aceptado. Lo siguiente será buscar en todo ese array el permiso de grabar audio. Esto lo hace dentro de ese primer if. A continuación anida un if con la variable grantResult, que es otro array de extensión sin definir y busca si en el array que ocupa la posición "i" en ese momento es igual a [-1], en caso de ser así, anida otro if para permitir el micrófono.

Esto está muy bonito, pero ¿veis alguna comprobación más? No, simplemente activa el micrófono si el usuario da permisos y empieza a grabar, nada de comprobar si hay partido ni nada, pero bueno, presunción de inocencia, sigamos mirando el código.



Esta parte de código me parece interesante compartirla, pero hay poco que sea interesante, simplemente crea un método llamado writeDataForLocationService que recibe un string que es el ID de fluzo y básicamente en estos 2 métodos, lo que hace es solicitar permisos para acceder a la ubicación. Es algo sencillo y que tampoco tiene mucho que comentar. Solamente dejarlo plasmado para que lo veáis.



Ya que estaba con el tema de la localización, me pasé al LocationService, que no hace falta saber mucho para deducir que es el que se encargará de la posición GPS del dispositivo del usuario. Aquí me parece interesante que os quedéis con ese cycleMinutes=30, va a ser importante y en breve veréis el por qué.


Aquí ya entramos en materia interesante. De momento vemos que el micrófono lo activa cada vez que el usuario entra y sale de la aplicación. En caso de no tener permisos, los pide, pero no comprueba si hay partido o no, por lo que podría grabar de forma totalmente independiente de que un equipo de la Liga esté o no jugando ¿Pasará lo mismo con la ubicación?

Por el momento nos encontramos con la clase SendLocationAsyncTask, que recibe el id de fluzo, la longitud y la latitud. Ahora pasamos a la clase doInBackground que contiene una comprobación con un if que valora si DataManager.INSTANCE.getMatchAlive es true. En caso de serlo, utilizan la función sendUserLocation {con su id de fluzo, su latitud, longitud y tiempo en milisegundos} dentro de DataManager.INSTANCE.

Siendo un poco lógicos, deducimos que getMatchAlive es una función que comprueba si hay un partido o no. Parece que la localización sí que la comprueba...parece.


Si os he reconmendado que os descarguéis el zip es para que no os pase como a mí ahora. Como he dicho, revisé todo el código por la noche de madrugada, y ahora conforme estaba escribiendo el post se me ha olvidado de dónde saqué la función getMatchAlive para analizarla en profundidad. Sólo me acordaba que había un enlace del que ahora hablaremos. Si os pasa esto con grep -r ["búsqueda"] [directorio de la carpeta extraída del zip] *.java lo encontraréis. Era de simple lógica, estaba en DataManager.java, pero es lo que pasa cuando estás sin dormir revisando el código de una app, mil perdones.


Revisando la función en profundidad, vemos que declara una variable "z" de tipo booleano /*me pone de los nervios cuando declaran variables con nombres sin sentido*/ y que se inicializa con el valor false. Posteriormente declara la variable source, que es un string que recoge del sitio https://laliga.fluzo.com/settings. Y aquí viene lo gracioso.

Lo que se está haciendo getBufferReaderJson es leer el JSON que reside en esa URL. Si la visitamos vemos que es cierto.


Vemos que hay algo escrito, pero hay algo. Pues bien, ahora tira un if donde comprueba si la variable source {la que obtiene y lee el JSON} no está vacía, entonces que active la ubicación. Claro, comprueba con ese enlace para ver si el JSON está vacio o no, pero como vemos siempre tiene contenido, por lo que nunca está vacio y para la aplicación SIEMPRE entra dentro de ese if, devolviendo un true al getMachLive y enviando la ubicación del usuario.


Como no quería acusar sin más, comprobé que no hubiese ningún partido jugándose, y aquí veis como en el momento en el que comprobé esa URL, no se estaba jugando ningún partido.


Un vistazo rápido a la clase Settings y vemos que la función isLocation_enabled devolverá la localización del usuario.



Ya sabes cuándo envían la ubicación y cuándo graban el micrófono, ahora nos iremos a Location Service para ver como en la función sendUserMetadataToFluzo utiliza 2 ifs, uno para comprobar si el id de fluzo es igual a null o no. En caso de no serlo, que será lo habitual, enviará el id y la ubicación al objeto senLocationAsyncTask



Envía la localización y al final mete un postDelayed, que no deja de ser un delay normal y corriente que podemos utilizar en la programación de Arduino. El tiempo que espera en milisegundos es ese cycleMinutes que antes os dije qu os quedáiseis con que era 30*60*1000 que es 1800000 ms que si lo pasamos a minutos salen 30 minutos.  Es decir, la localización la pasa cada 30 minutos siempre, independientemente de haya partido o no.


Relacionar a los usuario es fácil, ya que relacionan linkUserIds el id de fluzo y el que le crea la aplicación a cada usuario.



Al principio del post y en base al comunicado he dicho que eso de "No mirar el audio" podría ser porque no se almacenase, porque nada más grabarlo se eliminase, pero es raro que entonces que digan eso de "You can check this user at [enlace]".

Analicemos eso por partes porque está el código ofuscado y puede complicar el entendimiento del mismo.

C0850c.m819b--> Esto tiene pinta de ser una función que permita escribir como puede ser System.out.println o cualquier otra función que permita escribir algo parecido a un log.

f662a--> tiene que ser casi que por fuerza un objeto de fluzo.

f730m--> Id del usuario.

Y hasta aquí puedo leer practicamente, ya que hay algunos códigos ofuscados que hacen referencia al audio pero que es código ofuscado. Es cierto que todo o casi todo con Base64, pero bueno, como no sé si legalmente lo puedo hacer.

Sólo quedaría saber durante cuánto tiempo graba el micrófono. Esto realmente no lo sé, pues al ver un poco por encima que los códigos que me quedaban estaban ofuscados, no seguí mirando, pero yo veo una única opción posible, en gran parte porque no se me ocurren más.

La opción que se me ocurre es que lo dejen hasta que el buffer se llene. Creo que esto es posible porque no lo pueden dejar escuchando y grabando cada media hora porque un audio pesa bastante y no lo vería eficiente, pero bueno, si alguien lo analiza y me confirma le estaría muy agradecido.

A modo de conclusión poco más a lo ya dicho. Ya dije hace unos días que me resultaba extraño que la LFP hiciese esto para detectar fraudes. Es extraño y difícil, porque yo ahora podría ponerme a escuchar la radio y si comprobasen que yo no tengo contratado Movistar Fútbol o cualquier otro canal para ver el fútbol podrían pensar que estoy pirateando la señal.

Sobre el comunicado y la poca coherencia de la LFP con cómo trabaja su aplicación también tengo poco que añadir. No sé si lo que han hecho es legal, pero mi opinión es clara; si dices X y haces Y sin avisar a tus usuarios, estás haciendo que más que usuarios sean "usados". No me parece ético ni mucho menos lo que ha hecho la LFP, ya que se ha visto que sí que almacenan-al menos Fluzo- la localización y grabaciones del ambiente de los usuarios, pero claro, no sabemos si lo han almacenado de forma segura, que esa es otra.

¿Hackeamos el Mundo?

No hay comentarios:

Publicar un comentario

Related Posts Plugin for WordPress, Blogger...

Entrada destacada

El server me sabe a poco.

Soy un fanático del Rock y de Debian . (Creo que voy a inventar Rockbian, que suena bien y todo xD) Llevaba tiempo queriendo unir estos 2 c...