28
nov.
'11

Désactiver à froid AdMob grâce à Apktool et un poil de python

Publié le 28 novembre 2011

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.

29
sept.
'10

Reversing d'applications Android

Publié le 29 septembre 2010

Je suis tombé il y a quelques mois sur un supayre tool de RE dédié à Android. Ayant moi-même développé une ou deux applis pour ce système d'exploitation, je me suis intéressé aux possibilités offertes par cet outil grandiose qu'est Smali. Et je dois avouer que c'est bluffant, à la limite du désassemblage/réassemblage que permet IDA.

La plate-forme Android est de plus en plus en vogue, certainement grâce à son ouverture et aux nombreux systèmes l'utilisant, mais il faut avouer que l'Android Market offre quand même plein d'applications, dont les plus intéressantes sont en version payante. Ceci dit, les personnes ayant migré sur Android l'ont choisi en partie pour le côté ouvert, qui en l'occurence est souvent synonyme de gratuit, et la conversion des utilisateurs gratuit en clients du market est donc difficile à réaliser. C'est pourquoi des versions "Lite" ou "Trial" sont disponibles sur l'Android Market, avec bien sûr des fonctionnalités limitées mais complètement implémentées. Et là se trouve un excellent terrain de jeu pour les apprentis reversers: nous avons à disposition un bon nombre de programmes téléchargeables directement du Market, et nous sommes en mesure de les customiser, voire même de modifier le code du programme, et ce beaucoup plus facilement que sur des plateformes x86.

Android et Dalvik

Le système d'exploitation Android est basé sur Dalvik, qui est a priori une version custom de VM basée sur Java, et développée spécialement pour Android.

“Every Android application runs in its own process, with its own instance of the Dalvik virtual machine. Dalvik has been written so that a device can run multiple VMs efficiently. The Dalvik VM executes files in the Dalvik Executable (.dex) format which is optimized for minimal memory footprint. The VM is register-based, and runs classes compiled by a Java language compiler that have been transformed into the .dex format by the included "dx" tool.

The Dalvik VM relies on the Linux kernel for underlying functionality such as threading and low-level memory management.”

Chaque application tourne dans une VM Dalvik, et a accès aux composants systèmes. Dalvik n'est pas Java, et on ne reverse pas du bytecode Dalvik de la même façon.

Smali & Baksmali

Jesus Freke a pour ce faire développé deux outils (en Java), nommés baksmali et smali [JFSMALI]. Le premier est un désassembleur dédié à Dalvik, tandis que le second n'est rien de moins ... qu'un assembleur ! Grosso modo, nous avons les clefs en main pour désassembler des APK (format de paquetage pour Android, en réalité de simples JARs signés renommés), les triturer de toute part, et les réassembler, afin de reconstruire des APKs patchés, customisés, adaptés à nos besoins.

Le problème principal de ces outils, c'est la syntaxe du code produit/utilisé: des instructions spécifiques, orientées objet (un peu du style de celles utilisées pour du .Net), bien différentes de l'assembleur que nous connaissons, notre bon vieux Intel x86. Alors oui, il faut faire quelques efforts pour lire ce type de code, mais sur le principe (analyse, identification de protections) ça ne change pas grand chose, les structures de contrôle étant quasi les mêmes. Un exemple de code produit par Smali:

.method public DialogID()I
    .registers 2

    .prologue
    .line 126
    const/4 v0, 0x0

    return v0
.end method

Bon, on a quand même [DALOPCODE] sur le net pour comprendre les opcodes, mais c'est loin de valoir les manuels Intel...

Utilisation de Smali & Baksmali

Un bon document existe en français [SMARTFR], explicitant l'utilisation de cet outil, je ne vais pas m'étendre sur le sujet. J'ai pris comme sujet d'étude le programme BeyondPod, en version Trial, histoire de jauger les possibilités de cet outil.

Purchase license key =)

L'application peut être récupérée directement via le market, et récupérée grâce au SDK d'Android [ANDSDK]. Les commandes suivantes, exécutées dans le dossier tools du SDK, permettent de récupérer l'APK installé sur le mobile, et de le décompresser dans un dossier dédié.

$ ./adb pull /data/app/mobi.beyondpod.apk
$ mkdir ~/beyondpod && mv mobi.beyondpod.apk ~/beyondpod/
$ cd ~
$ mkdir ~/beyondpod/apk
$ unzip mobi.beyondpod.apk -d ~/beyondpod/apk/
$ rm ~/beyondpod/apk/META-INF -rf

La dernière ligne de commande supprime les informations de signature, afin que nous puissions reconstruire l'APK avec une clef que l'on aura générée. Mais laissons cela pour plus tard, voyons voir ce que peut produire Smali comme sortie. Pour cela, on prend soin de copier les JARs de baksmali et smali dans le dossier créé, et on lance la commande suivante:

$ cd ~/beyondpod/
$ java -Xmx512m -jar baksmali-1.2.4.jar mobi.beyondpod.apk -o disass

Smali va désassembler le paquetage, et créer des fichiers de code source ayant l'extension .smali. Ces fichiers sources peuvent être modifiés, et correspondent au classes et code associés stockés dans le fichier classes.dex stocké dans le package APK. Ciblons le code gérant la licence d'utilisation, cela est loin d'être facilement compréhensible, mais néanmoins nous pouvons identifier certaines routines (méthodes) par leur nom, telle IsUnlocked():

.method public static IsUnlocked()Z
    .registers 1

    .prologue
    .line 84
    invoke-static {}, Lmobi/beyondpod/rsscore/rss/LicenseManager;->IsKeyInstalled()Z

    move-result v0

    if-nez v0, :cond_e

    invoke-static {}, Lmobi/beyondpod/rsscore/rss/LicenseManager;->PokeKey()Z

    move-result v0

    if-nez v0, :cond_e

    const/4 v0, 0x0

    :goto_d
    return v0

    :cond_e
    const/4 v0, 0x1

    goto :goto_d
.end method

Ce qui pourrait se traduire par un code C++ comme celui-ci:

int LicenseManager::IsUnlocked()
*
    if (!LicenseManager::IsKeyInstalled())
            if (!LicenseManager::PokeKey())
                            return 0;
    return 1;
*

Il est alors aisé de modifier le code smali pour altérer le comportement de cette routine, en la résumant à sa plus simple expression:

.method public static IsUnlocked()Z
    .registers 1
    .prologue

    const/4 v0, 0x1
    return v0

.end method

De cette manière, la fonction renverra toujours vrai. Ici, plus de problème d'alignement, de NOPs, ou même d'agrandissement de section, car on travaille directement sur un code source, et aussi parce que l'on dispose d'un outil permettant de le recompiler. En analysant le code source smali de cette application, j'ai pu aisément reconstruire les méthodes de la classe LicenseManager:

#define LICENSE_TYPE_IN_TRIAL 0x00
#define LICENSE_TYPE_ANDROID_MARKET 0x1
#define LICENSE_TYPE_BETA 0x2
#define LICENSE_TYPE_PAYPAL 0x3
#define LICENSE_TYPE_OTHER 0x4
#define LICENSE_TYPE_EXPIRED 0x5

? LicenseManager::GetLicenseKey()
{
    v7 = "\\|";
    v4 = LicenseManager->ProVersionLicence;
    v5 = LicenseManager->_CheckKeyUri;
    v2 = LicenseManager$License->GetLicenseKey(v4, v5);
    if (v2 != 0)
    {
            v4 = "\\|";
            v4 = v2.split(v7);
            return v4;
    }
    else*
            try {
                    v4  = BeyondPodApplication::getInstance();
                    v0 = v4->getContentResolver(v4);
                    if (v0!=null)
                    {
                            v3 = v0->getType(v4);
                            if (v3 != null)
                            {
                                    v4 = "\\|";
                                    v3.split(v4);
                                    return v4;
                            }
                    }
            }
            catch(Exception e)
            {
                    move-exception v4; /* Dur à traduire en C++ =) */
                    v1 = v4;
                    v4 = "LicenseManager";
                    v5 = "Failed to get License Key";
                    CoreHelper::WriteTraceEntry(v5);
            }
    }
    v4 = v6;
    return v4;
}

int LicenseManager::IsKeyInstalled()
{
    try {
            v3 = BeyondPodApplication::GetInstance();
            v1 = BeyondPodApplication::getPackageManager();
            if(PackageManager->checkSignatures(v1, "mobi.beyondpod", "mobi.beyondpod.unlockkey")
                    return 1;
            else
                    return 0;
    }
    catch ()
    {
    }
    return 0;
}

int LicenseManager::PokeKey()
{
    v3 = 0;
    v0 = LicenseManager::GetLicenseKey();
    if (v0 != 0)
    {
            v1 = v0.length();
            v2 = 0x2;
            if (v1==v2)
            {
                    v1 = LicenseManager->_Key;
                    v2 = v0;
                    if (v1 <equals(v2))
                            return 1;
            }
    }
    return 0;
}

int LicenseManager::IsUnlocked()
{
    if (IsKeyInstalled())
            return 1;
    if (LicenseManager::PokeKey())
            return 1;
    return 0;
}

int LicenseManager::CheckAndNotifyIfInRestrictedMode()
{
    if (IfInRestrictedMode())
    {
            /* affiche un message Toast */
            return 1;
    }
    return 0;
}

int LicenseManager::CurrentLicenseKind()
{
    if (IsUnlocked())
    {
            v0 = LicenseManager::GetLicenseTypeString(0);
            if (StringUtils::IsNullOrEmpty(v0))
            {
                    v1 = LICENSE_TYPE_UNKNOWN;
                    return v1;
            }
            else if (v0.equals("Beta Tester"))
            {
                    v1 = LICENSE_TYPE_BETA;
                    return v1;
            }
            else if ( v0.equals("Android Market"))
            {
                    v1 = LICENSE_TYPE_ANDROID_MARKET;
                    return v1;
            }
            else if (v0.equals("PayPal"))
            {
                    v1 = LICENSE_TYPE_PAYPAL;
                    return v1;
            }
            else if (IfInRestrictedMode())
            {
                    v1 = "";
                    Configuration::setLicenseTypeString(v1);
                    v1 = LicenseManager->LICENSE_TYPE_EXPIRED;
                    return v1;
            }
    }
    else
    {
            v1 = new Date();
            v2 = LicenseManager->TrialExpirationDate;
            v1 = v1.compareTo(v2);
            if (v1<=0)
            {
                    v1 = LICENSE_TYPE_TRIAL;
                    return v1;
            }
            else
            {
                    v1 = LICENSE_TYPE_OTHER;
                    return v1;
            }
    }
}

Le contournement de cette protection est dès lors trivial: Il suffit de patcher quelques routines pour retourner les bonnes valeurs, en l'occurence:

.method public static CurrentLicenseKind()I
    .registers 3

    .prologue

    const/4 v1, 0x1
    return v1
.end method

.method public static IfInRestrictedMode()Z
    .registers 3

    .prologue
    const/4 v2, 0x0
    return v2

.end method

.method public static IsUnlocked()Z
    .registers 1

    .prologue

    const/4 v0, 0x1
    return v0

.end method

De cette manière, le programme va croire que la licence est valide, et l'on peut observer que le menu "Purchase Unlock Key" a disparu.

Unlocked ...

Mais il reste toutefois dans le About une mention au sujet d'une date d'expiration "Expires ...". Il s'agit ici d'un problème purement graphique, la boîte de dialogue appelant simplement la méthode GetLicenseKey de la classe LicenseManager.

Date d'expiration

Voici le code smali incriminé:

.line 94
    invoke-static {}, Lmobi/beyondpod/rsscore/rss/LicenseManager;->GetLicenseKey()[Ljava/lang/String;

    move-result-object v4

    .line 95
    .local v4, license:[Ljava/lang/String;
    if-eqz v4, :cond_b2

    array-length v8, v4

    if-le v8, v10, :cond_b2

    .line 96
    new-instance v8, Ljava/lang/StringBuilder;

    const-string v9, "Licensed to: "

    invoke-direct *v8, v9*, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    [...]

    .line 98
    :cond_b2
    new-instance v8, Ljava/lang/StringBuilder;

    const-string v9, "Expires "

    invoke-direct *v8, v9*, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    invoke-static {}, Lmobi/beyondpod/rsscore/rss/LicenseManager;->TrialExpirationDate()Ljava/util/Date;
    move-result-object v9
    const-string v10, "MMM d yyyy"
    invoke-static *v9, v10*, Lorg/apache/http/impl/cookie/DateUtils;->formatDate(Ljava/util/Date;Ljava/lang/String;)Ljava/lang/String;
    move-result-object v9
    invoke-virtual *v8, v9*, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v8
    invoke-virtual *v8*, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v8
    invoke-virtual *v6, v8*, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

Nous pouvons donc le patcher correctement, voire même l'améliorer afin d'afficher le texte "Licensed To" suivi d'un texte customisé:

.line 94
    invoke-static {}, Lmobi/beyondpod/rsscore/rss/LicenseManager;->GetLicenseKey()[Ljava/lang/String;

    move-result-object v4

    .line 95
    .local v4, license:[Ljava/lang/String;

    .line 96
    new-instance v8, Ljava/lang/StringBuilder;

    const-string v9, "Licensed to: "

    invoke-direct *v8, v9*, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    const-string v9, "Virtualabs.fr =)"

    invoke-virtual *v8, v9*, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v8

    invoke-virtual *v8*, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v8

    invoke-virtual *v6, v8*, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

De cette manière, la boite de dialogue affichera "Licensed To: Virtualabs.fr =)", au lien de "Android Market" (texte qui s'affiche normalement quand vous enregistrez l'application via le market).

Boite de dialogue "A propos"

Comme vous pouvez le remarquer, il est vraiment aisé de modifier le fonctionnement du programme, d'ajouter une chaîne de caractères, et de la concaténer à une chaîne existante.

Compilation

Nous avons vu comment analyser le code source smali, et comment le modifier de manière à changer le comportement d'un programme. Il est donc temps de "compiler" notre code source modifié de manière à intégrer nos modifications. Pour cela, nous utilisons smali:

$ java -Xmx512m -jar smali-1.2.4.jar ~/beyondpod/disass/
$ cp ~/beyondpod/out.dex ~/beyondpod/apk/classes.dex
$ cd ~/beyondpod/apk/
$ zip mobi.beyondpod_patched.apk -r ./*
$ jarsigner -keystore virtualabs.keystore -alias virtualabs mobi.beyondpod_patched.apk

Ces quelques commandes patchent le fichier classes.dex avec le DEX recréé par Smali, puis reconstruisent le package APK, et enfin le signent ]avec une clef du keystore Java (cf. [APKSIGN]). De là, on peut aisément déployer l'application sur un téléphone connecté:

$ ./adb push ~/beyondpod/apk/mobi.beyondpod_patched.apk /sdcard/

Il ne reste plus qu'à l'installer (via un FileManager), et utiliser la version patchée, qui n'est plus limitée dans le temps, et cela sans licence régulière.

Mots de la fin

Smali et Baksmali sont des outils très puissants, qui permettent de manipuler en profondeur les APKs utilisés par Google. Non seulement le cracking simple est possible, mais la customisation/correction de bug est tout à fait accessible, et ne nécessite pas de grosse bidouille. Certes, il faut se familiariser avec le code assembleur produit par smali (code assembleur relatif à dalvik), mais les possibilités sont énormes. Les plate-formes embarquées sont assez souvent oubliées des reversers, et ces deux outils pourraient peut-être ouvrir certains horizons aux reversers fous (n'est-ce pas Baboon, tu pourrais tronçonner de l'APK :), pour ma part j'avoue que je me suis pas mal amusé sur ce petit exemple, bien que l'aboutissant soit relativement simple.

Bibliographie



Les contenus disponibles sur ce blog sont publiés sous licence Creative Commons BY-NC-SA.
Vous pouvez réutiliser tout ou partie de ces contenus à condition de citer l'auteur et l'origine, vous ne pouvez en faire une utilisation commerciale, et enfin vous devez partager tout travail ou œuvre dérivée sous les mêmes conditions — c'est-à-dire avec la même licence d'utilisation Creative Commons.