Je suis plutôt occupé sur pas mal de projets ces dernières semaines (dont un qui je l'espère sera publié avant noël ...) et je n'ai pas franchement eu le temps de rédiger quoique ce soit qui a un tant soit peu d'intérêt. Jusqu'à aujourd'hui, où je me suis dit que cela commençait à bien faire, et qu'il fallait que je me sorte les doigts du ... enfin que je me motive pour publier un truc sympa. J'avoue aussi que je suis malade à crever (comme tous les mecs malades), et que cela m'occupe un brin dans cette soirée hivernale.
Le modèle économique d'Android
Google promeut via Android la philosophie du Libre en diffusant le code source de son système d'exploitation mobile, contrairement à Apple et son IOS. Le problème, c'est que le Libre est bien (trop ?) souvent associé à la notion de gratuité, et les premiers geeks ayant opté pour le système Android s'attendaient de fait à avoir un panel d'applications gratuites intéressant et complet. Force est de remarquer que les développeurs d'applications n'avaient pas trop à y gagner, contrairement à l'iPhone où le prix du terminal détermine assez facilement le type d'acheteur/utilisateur/consommateur. Type qui ne se refuse pas de payer une application lorsqu'il en a besoin, contrairement à l'utilisateur Android.
Il est donc apparu comme essentiel, pour les développeurs d'applications mobiles destinées à Android, que la conception et la promotion d'une application se devait de passer par l'ancien modèle des sharewares: une version lite gratuite avec pub et dans certains cas des limitations, et une version complète payante cette fois-ci, qui supprime la pub et offre des fonctionnalités avancées. Regardez par exemple l'application Winamp.
Et bien sûr, nous simples utilisateurs d'Android, on doit subir les bandeaux de pub et autres joyeusetés qui nous prennent pour des vaches à lait. Sans parler des applications ne nécessitant a priori aucune connection de données qui de fait se mettent à pomper de la bande passante, faute à la pub. Et j'oublie encore le tracking des utilisateurs effectué par ces régies de pub ... Bref, il est des applications où l'on se dit que cela suffit, si on pouvait enlever cette satané pub cela nous ferait économiser de la bande passante et de précieuses minutes.
AdMob est le système de publicité (de monétisation d'application que l'on dit) géré par Google et qui s'intègre avec bon nombre d'applications présentes sur l'Android Market.
De la manière de désactiver AdView
Alors oui, AdMob c'est lourd, AdMob c'est pas bien. Et AdMon on va le désactiver, enfin pour les applications reposant sur le paquetage com.google.ads.AdView. Pour cela on se munit d'Apktool, notre super outil de désassemblage/réassemblage d'application Android, et on jette un oeil à une application qui utilise AdMob (tiens,* TaskManager* par exemple). Premier problème rencontré, l'outil proguard qui obfusque un brin les noms des classes et des méthodes. En fait, il s'agit d'un faux problème, car AdMon étant intégré à une application à l'aide d'une bibliothèque externe (un fichier .jar référencé dans le projet), les noms de classes et de méthodes sont restées intactes (ou presque). Pour être précis, certaines méthodes implémentées dans AdView sont exemptes d'obfuscation, car appelée par différents composants. J'ai pu identifier une méthode qui ne subit pas d'obfuscation:
.method public loadAd(Lcom/google/ads/AdRequest;)V .locals 1 .parameter "adRequest" .prologue iget-object v0, p0, Lcom/google/ads/AdView;->a:Ld; invoke-virtual *v0*, Ld;->e()Landroid/app/Activity; move-result-object v0 if-nez v0, :cond_0 const-string v0, "activity was null while checking permissions." invoke-static *v0*, Lcom/google/ads/util/a;->e(Ljava/lang/String;)V :goto_0 return-void :cond_0 invoke-virtual *p0*, Lcom/google/ads/AdView;->isRefreshing()Z move-result v0 if-eqz v0, :cond_1 iget-object v0, p0, Lcom/google/ads/AdView;->a:Ld; invoke-virtual *v0*, Ld;->c()V :cond_1 iget-object v0, p0, Lcom/google/ads/AdView;->a:Ld; invoke-virtual *v0, p1*, Ld;->a(Lcom/google/ads/AdRequest;)V goto :goto_0 .end method
Cette méthode est appelée lors du chargement de la publicité. Nous allons la court-circuiter en la patchant comme ceci:
.method public loadAd(Lcom/google/ads/AdRequest;)V .locals 1 return-void .end method
Et on reconstruit le paquetage Android (APK):
$ apktool b mobi.infolife.taskmanager-1 mobi.infolife.taskmanager-nopub.apk $ jarsigner -v -keystore my.keystore myalias mobi.infolife.taskmanager-nopub.apk $ adb mobi.infolife.taskmanager-nopub.apk
On peut ensuite admirer le résultat, une belle application sans publicité.
Bon allez zou, on automatise
Histoire de m'amuiser un peu, j'ai développé un petit script python qui automatise ce patch, libre à vous de l'utiliser dans un script bash ou je ne sais comment, c'est gratuit, cadeau, offert par la maison.
""" This is a little python script to disable Google AdView from disassembled Android's APKs (disassembled thanks to Apktool). Pre-requesites: 1. Install Apktool (http://code.google.com/p/android-apktool/) 2. Be sure that jarsigner is installed on your system How to use it: 1. Disassemble your APK with Apktool $ apktool d com.example-1.apk 2. Launch winston.py $ ./winston.py com.example-1 3. Rebuild your APK $ apktool b com.example-1 com.example-noad.apk $ keytool -genkey -v -keystore my.keystore -alias myalias -keyalg RSA -keysize 2048 -validity 10000 $ jarsigner -v -keystore my.keystore myalias com.example-noad.apk $ adb install com.example-noad.apk And that's it ! Have fun ;) """ import re import os import optparse from subprocess import Popen class WinstonWolf: def __init__(self, directory): self.base_dir = directory def __checkGoogleAdView(self): return os.path.exists(os.path.join(self.base_dir,'smali/com/google/ads/AdView.smali')) def patchAdView(self): print '[i] Checking AdView presence ...' if self.__checkGoogleAdView(): try: print '[i] Patching AdView file ...' f = open(os.path.join(self.base_dir,'smali/com/google/ads/AdView.smali'),'r') src = f.read() start = src.index('.method public loadAd(Lcom/google/ads/AdRequest;)V') patch = """ .method public loadAd(Lcom/google/ads/AdRequest;)V .locals 1 return-void .end method """ if start: end = src`start:].index('.end method') if end: patched = src[:start] + patch + src[(start+end+11):] f.close() f = open(os.path.join(self.base_dir,'smali/com/google/ads/AdView.smali'),'w') f.write(patched) f.close() print '[i] AdView patched !' return True else: print '[!] Cannot patch: end method not found' return False else: print '[!] Cannot find AdView <loadAd() method' return False except IOError,e: print '[!>`_ Unable to open/read/write file.' return False else: print '[!] AdView.smali not found !' def createParser(): parser = optparse.OptionParser("usage: %prog [options]") parser.add_option('-d','--directory',dest='directory',help='Directory of android application disassembly (created by apktool)',metavar='DIR') return parser def banner(): print '[ Android APK GoogleAds remover - By virtualabs (http://virtualabs.fr) ]' if __name__ == '__main__': banner() (options, args) = createParser().parse_args() if options.directory: ww = WinstonWolf(options.directory) ww.patchAdView()
Conclusion
Je tiens tout de même à préciser qu'il existe pas mal de régies de pub qui supportent Android et qui utilisent chacune leur propre framework. On pourrait envisager d'améliorer ce petit bout de code de manière à gérer un grand nombre de bibliothèques de régies, ou tout simplement migrer sur une ROM custom qui permet d'éviter toute connexion à un serveur de publicité (de par la configuration du /etc/hosts). Bref, ce ne sont pas les solutions qui manquent, celle-ci a tout de même l'avantage de fonctionner sur n'importe quelle version d'Android. Et oui, je vais aller me soigner.