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.