Analyse et désassemblage d'applications AndroLua

Cet article détaille le processus d'analyse d'applications développées avec AndroLua, une plateforme permettant d'utiliser le langage de script Lua sur Android. Nous examinerons les méthodes courantes pour extraire et décompiler les scripts Lua, ainsi que les techniques de rétro-ingénierie lorsque ces scripts sont chiffrés.

Présentation d'AndroLua

AndroLua est un framework léger pour le développement mobile qui intègre le langage Lua dans l'écosystème Android. Il facilite le développement rapide et l'extension d'applications existantes en exploitant la syntaxe concise et la performance de Lua. AndroLua prend en charge Lua 5.2 et offre un accès aux API Android, permettant aux développeurs d'interagir avec les fonctionnalités du périphérique telles que la communication réseau, le multimédia et les capteurs.

Processus d'Analyse

Étant donné que Lua est le langage principal, le package Android sert principalement à charger et exécuter des fichiers Lua. Ces fichiers, souvent nommés init.lua ou main.lua, sont généralement stockés dans le répertoire des ressources. L'identification de ces fichiers est la première étape.

Dans de nombreux cas, les scripts Lua ne sont pas chiffrés. Ils peuvent être directement extraits de l'APK et décompilés à l'aide d'outils tels que Lua Decompiler.

Cependant, si les scripts sont chiffrés, le mécanisme de déchiffrement se trouve souvent dans la bibliothèque native libluajava.so, généralement au niveau de la fonction luaL_loadbufferx.

Exemple Pratique

Considérons un extrait de code Java provenant d'une application AndroLua typique:


private void initializeLuaScript() {
    File luaFile = new File(this.storagePath + "/main.lua");
    if (luaFile.exists()) {
        try {
            // Tentative de chargement du script Lua
            int loadStatus = this.luaInterpreter.loadScript(luaFile.getAbsolutePath());
            if (loadStatus == 0) {
                // Création d'une table pour les configurations
                this.luaInterpreter.createNewTable();
                LuaObject configTable = this.luaInterpreter.getTable(-1);
                this.luaInterpreter.setUpValue(-2, 1); // Association de la table à une variable globale Lua

                // Exécution du script
                int executionStatus = this.luaInterpreter.callFunction(0, 0, 0);

                if (executionStatus == 0) {
                    // Lecture des configurations de l'application
                    if (globalConfigInstance == null) {
                        LuaObject appKeyObject = configTable.getField("app_key");
                        if (appKeyObject.isString()) {
                            globalConfigInstance = appKeyObject.getString();
                            AnalyticsService.setApplicationKey(globalConfigInstance);
                        }
                        LuaObject appChannelObject = configTable.getField("app_channel");
                        if (appChannelObject.isString()) {
                            AnalyticsService.setApplicationChannel(this, appChannelObject.getString(), true);
                        }
                        AnalyticsService.enableService(this, 1);
                    }

                    // Définition du titre de l'application
                    LuaObject titleObject = configTable.getField("appname");
                    if (titleObject.isString()) {
                        this.setTitle(titleObject.getString());
                    }
                    LuaObject altTitleObject = configTable.getField("app_name");
                    if (altTitleObject.isString()) {
                        this.setTitle(altTitleObject.getString());
                    }

                    // Configuration du mode debug
                    LuaObject debugModeObject = configTable.getField("debugmode");
                    if (debugModeObject.isBoolean()) {
                        this.isDebugEnabled = debugModeObject.getBoolean();
                    }
                    LuaObject altDebugModeObject = configTable.getField("debug_mode");
                    if (altDebugModeObject.isBoolean()) {
                        this.isDebugEnabled = altDebugModeObject.getBoolean();
                    }

                    // Application d'un thème
                    LuaObject themeObject = configTable.getField("theme");
                    if (themeObject.isNumber()) {
                        this.setApplicationTheme((int) themeObject.getInteger());
                    } else if (themeObject.isString()) {
                        // Recherche dynamique du style par son nom
                        Class<?> styleClass = R.style.class;
                        Field styleField = styleClass.getField(themeObject.getString());
                        this.setApplicationTheme(styleField.getInt(null));
                    }
                } else {
                    // Gestion des erreurs d'exécution
                    throw new LuaScriptException("Erreur d'exécution Lua: " + this.luaInterpreter.getErrorMessage(executionStatus));
                }
            } else {
                // Gestion des erreurs de chargement
                throw new LuaScriptException("Erreur de chargement Lua: " + this.luaInterpreter.getErrorMessage(loadStatus));
            }
        } catch (Exception e) {
            displayErrorMessage(e.getMessage());
        }
    }
}

Ce code illustre le processus de chargement et d'exécution d'un script Lua, ainsi que la lecture de diverses configurations depuis ce script. L'interaction avec libluajava.so est cruciale ici.

Analyse de la Bibliothèque Native

Si le script est chiffré, il est nécessaire d'examiner la bibliothèque native pour comprendre le mécanisme de déchiffrement. Le point d'entrée potentiel est la fonction luaL_loadbufferx.

Dans certains cas, trouver la bibliothèque native appropriée (par exemple, pour l'acrhitecture ARM) peut nécessiter une recherche approfondie. L'utilisation d'outils comme Unidbg peut aider à intercepter les appels à cette fonction et à récupérer les données déchiffrées. La logique de déchiffrement elle-même peut être relativement simple et réalisable via un script dédié.

Voici une représentation conceptuelle de la fonction luaL_loadbufferx qui pourrait être impliquée dans le chiffrement/déchiffrement:


// Représentation simplifiée du code natif
int custom_luaL_loadbufferx(int luaState, const unsigned char* buffer, size_t bufferSize, const char* name, int mode) {
    unsigned char* processedBuffer = malloc(bufferSize);
    if (processedBuffer) {
        // Logique de déchiffrement ou de transformation du buffer ici
        // Par exemple, une simple transformation XOR ou une substitution
        if (bufferSize > 0 && buffer[0] == 0x1B) { // Détection d'un format Lua standard
            processedBuffer[0] = 0x1B; // Garder le marqueur de bytecode Lua
            for (size_t i = 1; i < bufferSize; ++i) {
                // Opération de transformation personnalisée
                processedBuffer[i] = buffer[i] ^ 0xAA; // Exemple: XOR avec une clé
            }
        } else {
            // Copie directe si le buffer n'est pas dans le format attendu
            memcpy(processedBuffer, buffer, bufferSize);
        }

        // Appel à la fonction de chargement Lua originale avec le buffer traité
        // La fonction 'custom_lua_load' serait une version modifiée ou un wrapper
        // pour gérer notre buffer potentiellement transformé.
        return custom_lua_load(luaState, processedBuffer, bufferSize, name, mode);
    }
    return -1; // Erreur d'allocation mémoire
}

L'analyse de cette fonction et de sa logique associée est essentielle pour déchiffrer les scripts Lua utilisés par AndroLua.

Étiquettes: androlua Lua reverse engineering Android JNI

Publié le 21 juillet à 23h56