> ## Content Index
> Fetch the complete content index at: https://yokai.hakaisecurity.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Beerus Framework – Um Novo Framework Mobile Surge
- URL: https://yokai.hakaisecurity.io/beerus-framework-um-novo-framework-mobile-surge/
- Published: 2025-09-19T16:16:34.000Z
- Updated: 2025-09-19T16:16:34.000Z
- Author: Daniel Franca Lima
- Tags: Insights Blog, #wp, pt-br, #pair-beerus-arises

**Abstract**

O **Beerus Framework** é uma ferramenta ofensiva *mobile* desenvolvida para facilitar todo o processo de **pentest** em dispositivos Android. Com uma **interface unificada** diretamente no dispositivo, o Beerus permite realizar desde a instrumentação de aplicações de forma *built-in* no dispositivo com **Frida Core**, exfiltração de dados do *sandbox*, *memory dumping*, *proxying*, controle de módulos **Magisk**, manipulação de propriedades e muito mais.

Construído sobre **Frida** e **Magisk**, o Beerus é **modular**, **extensível** e projetado para testes em dispositivos com *root*, otimizando tarefas comuns de **pentest** e habilitando automações a partir de um único *app*.

Neste *paper*, exploramos as principais **funcionalidades** do framework, com foco especial em algumas delas. A proposta não é detalhar exaustivamente seu funcionamento, mas oferecer uma visão ampla do que ele abrange e do que é capaz. Para complementar, disponibilizamos abaixo um **vídeo demonstrativo** que mostra como utilizar o **Beerus Framework** no **dia a dia**.

Lembrando que o **Beerus Framework** já está disponível para *download* diretamente no [**repositório oficial no GitHub**](https://github.com/hakaioffsec/beerus-android?ref=yokai.hakaisecurity.io).

**The Development of Beerus Framework**

Durante avaliações de segurança em dispositivos móveis, é comum depender de diversas ferramentas externas e *setups* complexos. O **Beerus Framework** nasceu com o objetivo de unificar as **funcionalidades** essenciais de um **pentester mobile** em um único aplicativo Android. A proposta é oferecer uma solução **prática** e **eficiente**, que torne o processo de testagem mais **fluido** e **ágil**.

Pensando nisso, decidimos expandir o projeto **Beerus**, idealizado inicialmente por Lucas *"luriel"* Carmo e Daniel *"d3v"* Chactoura, dando origem ao **Beerus Framework**. O objetivo é oferecer **funcionalidades** que simplifiquem e agilizem o trabalho diário do **analista de segurança**.

**Introdução Técnica**

O **Beerus Framework** funciona como um **hub de funcionalidades** para **analistas de segurança** que trabalham com dispositivos Android. O aplicativo fornece uma **interface gráfica intuitiva** com acesso a diversas ferramentas, aproveitando recursos do próprio dispositivo e privilégios *root* (desenhado para funcionar em melhor sincronia com o **Magisk**).

Entre as **funcionalidades** oferecidas, estão:

## Frida Server Setup

A funcionalidade **Frida Server Setup** permite baixar e inicializar o **Frida Server** diretamente no seu dispositivo *mobile*. Basta escolher uma versão e clicar em *"Run"* para que o servidor seja instalado e executado. Após a instalação, não é necessário reinstalar a mesma versão, a menos que você opte por trocar. Por padrão, o **Beerus Framework** exibirá as 10 versões mais recentes, mas também é possível inserir manualmente a versão desejada. Isso torna o processo de configuração do ambiente de **instrumentação** muito mais simples.

## Frida Auto Inject

A funcionalidade **Frida Auto Inject** possibilita a injeção de *scripts* **Frida** diretamente do dispositivo *mobile* por meio do **Frida Core**, motor de interação via *RPC* utilizado tanto nas versões *Python* quanto *JavaScript* do **Frida**. Ele se comunica com o **Frida Server**, que pode ser iniciado pela própria funcionalidade do **Beerus Framework**, facilitando a **portabilidade** e o **compartilhamento de exploits** para **instrumentação** de aplicações.

Podemos acessar o **Frida Setup**, iniciar o **Frida Server** e, logo em seguida, criar ou importar um *script* para o armazenamento do dispositivo. Após adicionar o *script*, é possível editá-lo, selecionar o **APK** alvo e clicar em *"Run"* para que o *app* seja iniciado já com o *script* injetado via **Frida Core**.

Dentro do próprio repositório do **Frida**, podemos ver, em *releases*, um pacote chamado [***frida-core-devkit***](https://github.com/frida/frida/releases?ref=yokai.hakaisecurity.io), disponível para diferentes arquiteturas. O **Frida Core** é utilizado para interagir com o **Frida Server** da seguinte forma:

- O **cliente** (seu programa em **Python/Node/C** usando **frida-core**) abre um *socket* para o *frida-server*.
- Envia mensagens no formato **JSON-RPC** encapsuladas em um *framing* binário (protocolo **Frida**).
- O *frida-server* recebe a mensagem e a repassa para sua instância de **Frida Core** local.
- O **cliente** envia **attach(pid)** → **server** → **Core** injeta o **Frida Gadget** no alvo.
- O **Gadget**, que também roda o **Frida Core**, abre um canal *RPC* interno com o **server**.
- Agora o **server** atua como **proxy** entre:  
  - **Cliente Core** (sua *tool*)
  - **Gadget Core** (dentro do alvo)

Então o **Beerus** possui uma versão modificada desse *devkit*, para aceitar que passemos os *scripts JS* como parâmetro para um binário compilado do **Frida Core**:

```c
/*
 * Compile with:
 *
 * clang -DANDROID -ffunction-sections -fdata-sections frida-core-example.c -o frida-core-example -L. -lfrida-core -llog -ldl -lm -latomic -pthread -Wl,--export-dynamic
 *
 * Visit https://frida.re to learn more about Frida.
 */

#include "frida-core.h"

#include <stdlib.h>
#include <string.h>

static void on_detached (FridaSession * session, FridaSessionDetachReason reason, FridaCrash * crash, gpointer user_data);
static void on_message (FridaScript * script, const gchar * message, GBytes * data, gpointer user_data);
static void on_signal (int signo);
static gboolean stop (gpointer user_data);

static GMainLoop * loop = NULL;

int
main (int argc,
      char * argv[])
{
    guint target_pid;
    FridaDeviceManager * manager;
    GError * error = NULL;
    FridaDeviceList * devices;
    gint num_devices, i;
    FridaDevice * local_device;
    FridaSession * session;
    gchar * script_source;
    gsize script_size;

    frida_init ();

    if (argc != 3 || (target_pid = atoi (argv[1])) == 0)
    {
        g_printerr ("Usage: %s <pid> <script.js>\n", argv[0]);
        return 1;
    }

    if (!g_file_get_contents(argv[2], &script_source, &script_size, &error)) {
        g_printerr ("Failed to read script: %s\n", error->message);
        g_error_free (error);
        return 1;
    }

    loop = g_main_loop_new (NULL, TRUE);

    signal (SIGINT, on_signal);
    signal (SIGTERM, on_signal);

    manager = frida_device_manager_new ();

    devices = frida_device_manager_enumerate_devices_sync (manager, NULL, &error);
    g_assert (error == NULL);

    local_device = NULL;
    num_devices = frida_device_list_size (devices);
    for (i = 0; i != num_devices; i++)
    {
        FridaDevice * device = frida_device_list_get (devices, i);
        g_print ("[*] Found device: \"%s\"\n", frida_device_get_name (device));

        if (frida_device_get_dtype (device) == FRIDA_DEVICE_TYPE_LOCAL)
            local_device = g_object_ref (device);

        g_object_unref (device);
    }
    g_assert (local_device != NULL);

    frida_unref (devices);
    devices = NULL;

    session = frida_device_attach_sync (local_device, target_pid, NULL, NULL, &error);
    if (error == NULL)
    {
        FridaScript * script;
        FridaScriptOptions * options;

        g_signal_connect (session, "detached", G_CALLBACK (on_detached), NULL);
        if (frida_session_is_detached (session))
            goto session_detached_prematurely;

        g_print ("[*] Attached\n");

        options = frida_script_options_new ();
        frida_script_options_set_name (options, "example");
        frida_script_options_set_runtime (options, FRIDA_SCRIPT_RUNTIME_QJS);

        script = frida_session_create_script_sync (session, script_source, options, NULL, &error);
        g_assert (error == NULL);

        g_clear_object (&options);
        g_free (script_source);

        g_signal_connect (script, "message", G_CALLBACK (on_message), NULL);

        frida_script_load_sync (script, NULL, &error);
        g_assert (error == NULL);

        g_print ("[*] Script loaded\n");

        if (g_main_loop_is_running (loop))
            g_main_loop_run (loop);

        g_print ("[*] Stopped\n");

        frida_script_unload_sync (script, NULL, NULL);
        frida_unref (script);
        g_print ("[*] Unloaded\n");

        frida_session_detach_sync (session, NULL, NULL);
        session_detached_prematurely:
        frida_unref (session);
        g_print ("[*] Detached\n");
    }
    else
    {
        g_printerr ("Failed to attach: %s\n", error->message);
        g_error_free (error);
    }

    frida_unref (local_device);
    frida_device_manager_close_sync (manager, NULL, NULL);
    frida_unref (manager);
    g_print ("[*] Closed\n");

    g_main_loop_unref (loop);

    return 0;
}

...
```

**Frida Header (LINHA: 9)**

- fornece as **APIs** principais do **Frida** através do *"frida-core.h"*.

**Static callbacks declarations (LINHAS: 14...17)**

- `on_detached(...)`: *callback* para tratar quando a sessão é desconectada.
- `on_message(...)`: *callback* para mensagens vindas do *script JS*.
- `on_signal(...)`: captura sinais como **SIGINT** e **SIGTERM**.
- `stop(...)`: usado para encerrar o **loop principal**.

**Global Variable GMainLoop (LINHA: 19)**

- Armazena o **loop principal GLib** que processa eventos assíncronos.

**Inicialization (LINHAS: 21...52)**

- Declara variáveis locais (**PID** alvo, *script JS*, *manager*, *devices*, sessão, etc.).
- Chama `frida_init()`.
- Valida argumentos: espera `<pid> <script.js>`. Se inválido, imprime *Usage* e encerra.
- Lê o *script JS* (`argv[2]`) para a variável `script_source` com `g_file_get_contents`.
- Cria o **loop principal** `g_main_loop_new`.
- Registra *signal handlers* para **SIGINT** e **SIGTERM**.

**Device Management (LINHAS: 54...74)**

- Cria `FridaDeviceManager` com `frida_device_manager_new()`.
- Enumera dispositivos com `frida_device_manager_enumerate_devices_sync`.
- Itera dispositivos encontrados: imprime nome (`frida_device_get_name`).
- Se for `FRIDA_DEVICE_TYPE_LOCAL`, guarda referência como `local_device`.
- Libera lista de *devices* com `frida_unref(devices)`.

**Attaching to Session (LINHAS: 76...86)**

- Chama `frida_device_attach_sync` para anexar ao processo alvo.
- Se sucesso, conecta *callback* `"detached"`.
- Verifica se a sessão foi desconectada prematuramente.
- Imprime `[*] Attached` em caso de sucesso.

**Script Creation and Loading (LINHAS: 88...106)**

- Cria opções de *script* (`FridaScriptOptions`): define nome `"example"` e *runtime QJS*.
- Cria *script* com `frida_session_create_script_sync`.
- Conecta *callback* `"message"` para `on_message`.
- Carrega *script* com `frida_script_load_sync`.
- Imprime `[*] Script loaded`.
- Se o **loop** está ativo, entra em `g_main_loop_run(loop)`.

**Session End (LINHAS: 108...123)**

- Após sair do **loop**, descarrega *script* (`frida_script_unload_sync`, `frida_unref(script)`).
- Imprime `[*] Unloaded`.
- Desanexa sessão (`frida_session_detach_sync`, `frida_unref(session)`).
- Imprime `[*] Detached`.
- Se houve erro em *attach*, imprime mensagem de erro e libera `GError`.

O **APK** usa esse binário no arquivo `AutoInject.kt` da seguinte forma:

```kotlin
package io.hakaisecurity.beerusframework.core.functions.frida

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.OpenableColumns
import io.hakaisecurity.beerusframework.core.utils.CommandUtils.Companion.runSuCommand
import java.io.File
import java.util.concurrent.ConcurrentHashMap

class AutoInject {
    companion object {
        private val scriptCache = ConcurrentHashMap<String, String>()
        private var lastCacheUpdate = 0L
        private const val CACHE_EXPIRY_MS = 5000L

        fun injectFridaCore(context: Context, packageName: String, script: String) {
            val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
            if (launchIntent != null) {
                launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY
                context.startActivity(launchIntent)

                val scriptsFullPath = File(context.filesDir, "scripts").absolutePath + "/" + script

                try {
                    runSuCommand("sleep 5 && fridaCore \$(pidof $packageName) $scriptsFullPath") {}
                } catch (e: Exception) {}
            }
        }

...
```

**injectFridaCore Function (LINHAS: 17...29)**

- Recupera a *intent* de lançamento do **app** alvo pelo `packageName`.
- Define *flags* da **Activity**:  
  - **NEW\_TASK**: inicia em uma nova *task*.
  - **NO\_HISTORY**: a **Activity** não fica no histórico.
- Inicia o **aplicativo alvo** usando o `launchIntent`.
- Espera 5 segundos (tempo para o **app** iniciar).
- Executa o binário do **Frida Core** anexando no processo do **app** alvo e carregando o *script* selecionado.

**Observação:** Para utilizar esta funcionalidade, é necessário instalar o **módulo Magisk** do **Beerus Framework**, disponível ao abrir o **app** ou ao acessar uma funcionalidade que dependa dele.

## ADB Over Network

A funcionalidade **ADB Over Network** permite iniciar o **Android Debug Bridge** (*ADB/adbd*) via protocolo **TCP**, possibilitando a interação remota com o dispositivo sem necessidade de cabo. Essa função é essencial para **análise dinâmica**, permitindo instalar aplicativos, acessar informações do sistema e diretórios como `/data/data`, além de executar ferramentas como **Frida Server** e outros binários.

## Boot Options

A funcionalidade **Boot Options** permite configurar a inicialização automática de ferramentas como **Frida Server** e **ADB Over Network** junto com o *boot* do dispositivo, evitando procedimentos manuais repetitivos. Também é possível promover certificados de usuário a certificados de sistema, o que ajuda a contornar proteções como **SSL Pinning**. Utilizando os *scripts* `post-fs-data.sh` e `service.sh` do **módulo Magisk**, é possível executar binários e alterar configurações ainda no início do *boot*.

Podemos ver no `post-fs-data.sh` o seguinte código:

```bash
#!/system/bin/sh
MODDIR=${0%/*}

STATUS_FILE="$MODDIR/status"
[ ! -f "$STATUS_FILE" ] && exit 0

systemTrustedCerts=$(grep '^systemTrustedCerts=' "$STATUS_FILE" | cut -d'=' -f2)
adbOverNetwork=$(grep '^adbOverNetwork=' "$STATUS_FILE" | cut -d'=' -f2)

if [ "$systemTrustedCerts" = "true" ]; then
    CERT_SRC="/data/misc/user/0/cacerts-added"
    CERT_DST="$MODDIR/system/etc/security/cacerts"

    mkdir -p "$CERT_DST"
    rm -f "$CERT_DST"/*
    cp -f "$CERT_SRC"/* "$CERT_DST/" 2>/dev/null
fi

if [ "$adbOverNetwork" = "true" ]; then
    resetprop -n service.adb.tcp.port 5555
    stop adbd
    start adbd
elif [ "$adbOverNetwork" = "false" ]; then
    resetprop -n service.adb.tcp.port ""
    stop adbd
    start adbd
fi
```

**Variables (LINHAS: 7...8)**

- Extração das *feature flags* setadas pelo **APK** do **Beerus Framework**.

**System Trusted Certs Condition (LINHAS: 10...17)**

- Faz a checagem se a *feature flag* do **System Trusted Certs** está habilitada.
- Pega o diretório de certificados do usuário e move seus certificados para um diretório réplica do *filesystem* (`/system`).
- O **Magisk** identificará a réplica e colocará os certificados como se fossem do próprio sistema Android.
- Isso faz com que algumas abordagens inseguras de **SSL Pinning** sejam *bypassed*.

**ADB Over Network Condition (LINHAS: 19...27)**

- Faz a checagem se a *feature flag* do **ADB Over Network** está habilitada ou não.
- Caso sim, troca o valor da propriedade `service.adb.tcp.port` para o número da porta desejada e reinicia o serviço do **adbd**.
- Caso não, troca o valor da propriedade `service.adb.tcp.port` para vazio e reinicia o serviço do **adbd**, com a finalidade de parar a execução no próximo *restart* do dispositivo.

Podemos ver também no *script* `service.sh` o seguinte código:

```bash
#!/system/bin/sh
MODDIR=${0%/*}

STATUS_FILE="$MODDIR/status"
[ ! -f "$STATUS_FILE" ] && exit 0

fridaProp=$(grep '^frida=' "$STATUS_FILE" | cut -d'=' -f2)
fridaBin="/data/local/tmp/hiddenBin"

if [ "$fridaProp" = "true" ] && [ -x "$fridaBin" ]; then
    "$fridaBin" &
fi

...
```

**Variables (LINHAS: 7...8)**

- Extração da *feature flag* setada pelo **APK** do **Beerus Framework**.
- Valor do *path* do binário do **Frida** criado pelo **APK** do **Beerus**.

**Frida Server Setup Condition (LINHAS: 10...12)**

- Faz a checagem se a *feature flag* do **Frida Server Setup** está habilitada e se há a existência do binário.
- Realiza a execução do binário em *background*.

**Observação:** O uso dessa funcionalidade requer a instalação do **módulo Magisk** do **Beerus Framework**, disponível ao abrir o **app** ou ao acessar uma funcionalidade que dependa dele.

**SandBox Exfiltration**

A funcionalidade **Sandbox Exfiltration** permite extrair do dispositivo informações salvas pelo aplicativo além do próprio *.apk*, não só facilitando na hora de fazer a **análise**, mas também demonstrando que, em casos de **RATs** (*Remote Access Trojan*), pode ser utilizada para extrair informações do dispositivo. Da mesma forma, em casos de **roubo** ou **furto**, o invasor pode efetuar o *root* do dispositivo e obter esses dados.

Ao acessarmos essa funcionalidade, nos deparamos com algumas aplicações instaladas no dispositivo. Ao selecionarmos uma delas, podemos enviar para o [**servidor**](https://github.com/hakaioffsec/beerus-server/tree/main?ref=yokai.hakaisecurity.io) do beerus ou, ao selecionarmos a opção de **USB**, ele gera o arquivo com o comando para extrair diretamente usando: `adb pull /data/local/tmp/tmp/{package_name}.tar.gz`

Lembrando que temos um *blog post* detalhado explicando a funcionalidade ainda na primeira versão do **Beerus**, escrito por Lucas *"luriel"* Carmo e Daniel *"d3v"* Chactoura, que pode ser acessado neste link: [https://hakaisecurity.io/beerus-apk-spotlighting-sandbox-exfiltration/insights-blog/](https://yokai.hakaisecurity.io/beerus-apk-spotlighting-sandbox-exfiltration/)

**Properties Changes**

A funcionalidade **Properties Changes** foi criada para permitir a adição e modificação de **propriedades do sistema** diretamente pela **interface gráfica**, de forma **permanente**, ou seja, mesmo após reiniciar o dispositivo. Essa funcionalidade é especialmente útil para alterar propriedades utilizadas por aplicativos para identificar dispositivos com *root* ou executando em **emuladores**, como por exemplo: `"ro.hardware=unknown"` e `"ro.kernel.qemu=0"`.

Ao acessarmos a funcionalidade, nos deparamos com um botão para adicionar uma nova propriedade. Esse botão pode ser utilizado tanto para criar uma propriedade inexistente quanto para modificar uma já existente. Ao clicarmos para adicionar, será exibido um campo para inserir o **nome da propriedade** (nova ou existente) e outro campo para definir o **valor desejado**.

Após preencher e confirmar, a propriedade será adicionada com sucesso através do uso do arquivo `system.prop` do **módulo Magisk**. Por fim, basta reiniciar o dispositivo para que as alterações sejam aplicadas **permanentemente**.

**Observação:** Para utilizar esta funcionalidade, é necessário instalar o **módulo Magisk** do **Beerus Framework**, disponível ao abrir o *app* ou ao acessar uma funcionalidade que dependa dele.

**Manifest Decoding**

A funcionalidade **Manifest Decoding** realiza o *parsing* do `AndroidManifest.xml` para extrair informações relevantes à **análise** de um **APK**, como **Activities** e **Receivers** exportados, permissões declaradas, **Main Activity** identificada e outras configurações sensíveis ou úteis para **análise de segurança**.

Para implementar esse módulo, utilizamos a biblioteca [**apk-parser**](https://github.com/hsiafan/apk-parser?ref=yokai.hakaisecurity.io), que permite extrair e decodificar o **Android Manifest** diretamente do **APK**. A partir disso, fazemos o *parsing* das informações necessárias.

```kotlin
package io.hakaisecurity.beerusframework.core.functions.Manifest

import android.util.Log
import io.hakaisecurity.beerusframework.core.functions.Properties.Properties.PropertyData
import io.hakaisecurity.beerusframework.core.utils.CommandUtils.Companion.runSuCommand
import net.dongliu.apk.parser.ApkFile
import java.io.File
import java.util.Dictionary

class Manifest {
    data class ComponentInfo(
        val name: String,
        val exported: Boolean
    )

    data class Manifest(
        val userPermissions: List<String>,
        val components: Map<String, List<ComponentInfo>>,
        val General: Map<String, String>
    )

    fun parseManifest(androidManifest: String, callback: (Manifest) -> Unit) {
        val general = mutableMapOf<String, String>()
        val permissions = mutableListOf<String>()
        val components = mutableMapOf(
            "activities" to mutableListOf<ComponentInfo>(),
            "providers" to mutableListOf<ComponentInfo>(),
            "services" to mutableListOf<ComponentInfo>(),
            "receivers" to mutableListOf<ComponentInfo>()
        )

        val tagToKey = mapOf(
            "activity" to "activities",
            "provider" to "providers",
            "service" to "services",
            "receiver" to "receivers"
        )
        Regex(
            "<activity\b[^>]*android:name=\"([^\"]+)\"[^>]*>(?:(?!</activity>).)*?<intent-filter>(?:(?!</intent-filter>).)*?<action[^>]*android:name=\"android.intent.action.MAIN\"[^>]*/>(?:(?!</intent-filter>).)*?<category[^>]*android:name=\"android.intent.category.LAUNCHER\"[^>]*/>(?:(?!</intent-filter>).)*?</intent-filter>",
            RegexOption.DOT_MATCHES_ALL
        ).find(androidManifest)?.let {
            general["Main Activity"] = it.groupValues[1]
        }

        Regex("<manifest[^>]*package=\"([^\"]+)").find(androidManifest)?.let {
            general["Package Name"] = it.groupValues[1]
        }

        Regex("android:versionName=\"([^\"]+)\"").find(androidManifest)?.let {
            general["Version Name"] = it.groupValues[1]
        }

        Regex("<uses-sdk[^>]*minSdkVersion=\"([^\"]+)\"").find(androidManifest)?.let {
            general["Min SDK"] = it.groupValues[1]
        }
        Regex("<uses-sdk[^>]*targetSdkVersion=\"([^\"]+)\"").find(androidManifest)?.let {
            general["Target SDK"] = it.groupValues[1]
        }
        Regex("<application[^>]*android:name=\"([^\"]+)\"").find(androidManifest)?.let {
            general["Application"] = it.groupValues[1]
        }

        // Permissions
        var regex = Regex("<uses-permission[^>]*android:name=\"([^\"]+)\"")
        var matches = regex.findAll(androidManifest)
        for (i in matches) {
            var perm = i.groupValues[1]
            if (perm.startsWith("android.permission.")) {
                perm = perm.split("android.permission.", limit = 2)[1]
            }

            permissions.add(perm)
        }

        // Components
        for (tag in tagToKey.keys) {
            regex = Regex(
                "<$tag\b([^>]*)>(.*?)</$tag>",
                RegexOption.DOT_MATCHES_ALL
            )
            matches = regex.findAll(androidManifest)
            Log.d("{OUTPUT}", "TAG: $tag")
            for (match in matches) {
                val attributes = match.groupValues[1]
                val innerContent = match.groupValues[2]

                val nameRegex = Regex("android:name\s*=\s*\"([^\"]+)\"")
                val name = nameRegex.find(attributes)?.groupValues?.get(1) ?: continue

                val exportedRegex = Regex("android:exported\s*=\s*\"([^\"]+)\"")
                val exportedRaw = exportedRegex.find(attributes)?.groupValues?.get(1)
                var exported = when (exportedRaw?.lowercase()) {
                    "true" -> true
                    "false" -> false
                    else -> null
                }

                if (exported != true && innerContent.contains("<intent-filter")) {
                    exported = true
                }

                val list = components.getValue(tagToKey[tag]!!)
                list.add(ComponentInfo(name, exported == true))
            }
        }

        callback(Manifest(permissions, components, general))
    }

    fun getManifest(artifactPath: String): Manifest? {
        val latch = java.util.concurrent.CountDownLatch(1)
        val apkFile = File(artifactPath)
        val parser = ApkFile(apkFile)
        val manifestXml = parser.manifestXml
        var ParsedManifest: Manifest? = null
        parseManifest(manifestXml) { result ->
            ParsedManifest = result
            latch.countDown()

        }

        latch.await()
        return ParsedManifest
    }
}
```

**Data Class Dеclaration (LINHAS: 11...20)**

- Define o *data class* **ComponentInfo**, utilizado para os componentes *activities*, *providers*, *services* e *receivers*.
- Define o *data class* **Manifest**, onde serão armazenados os **componentes**, **permissões dos usuários** e **informações gerais**.

**parseManifest Mеthod (LINHAS: 22...109)**

- Cria variáveis **mutáveis** para os componentes, informações gerais e permissões do aplicativo.
- Utiliza *regex* para obter informações do **app**, como: **nome do pacote**, **versão do SDK** esperado, **Main Activity**, entre outros.
- Executa um *regex* para capturar as **permissões requeridas** pelo aplicativo.
- Obtém os **componentes da aplicação**.

**getManifest Mеthod (LINHAS: 112...126)**

- Utiliza o **ApkFile** para obter o **manifest** do aplicativo.
- Usa o **parseManifest** para extrair as informações do **manifest**.
- Retorna o resultado do *parse*.

**Proxy Profiles**

A funcionalidade **Proxy Profiles** permite criar diferentes **perfis de proxy** e ativá-los ou desativá-los a qualquer momento, sem precisar configurar ou remover manualmente o **proxy** ao mudar de rede ou ao usar aplicativos normalmente.

Para criar um perfil, basta informar um **nome** e o **endereço IP** com **porta**. Após isso, é possível ativar/desativar, editar ou excluir o perfil sempre que necessário.

Quando um perfil é ativado, todo o tráfego do dispositivo passa automaticamente pelo **proxy configurado**. Isso é feito pela função `selectProfile`, definida em `ProxyProfiles.kt`, que atualiza o arquivo `proxyLists.json` marcando o perfil selecionado e define o **proxy global** do dispositivo via contexto *shell* incorporado por privilégios *root*:

```kotlin
...

    fun selectProfile(context: Context, conString: String) {
        val fileName = "proxyLists.json"
        val file = File(context.filesDir, fileName)

        try {
            if (file.exists()) {
                val jsonObject = JSONObject(file.readText())
                val connectionsArray = jsonObject.getJSONArray("connections")

                for (i in 0 until connectionsArray.length()) {
                    val obj = connectionsArray.getJSONObject(i)
                    val selected = obj.getString("conString") == conString
                    obj.put("selected", selected)
                }

                file.writeText(jsonObject.toString(4))
                runSuCommand("runcon u:r:shell:s0 sh -c 'settings put global http_proxy $conString'") { }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

...
```

**Magisk Manager**

A funcionalidade **Magisk Manager** atua como uma extensão do **Magisk**, oferecendo **controle total** sobre seus **módulos**. Com ela, é possível ativar ou desativar módulos já instalados, além de instalar ou remover novos conforme necessário.

Ao acessar a **interface**, todos os **módulos existentes** ficam listados, acompanhados da opção de adicionar novos a partir de **arquivos locais**, tornando a **gestão** prática e centralizada.

No arquivo `MagiskModule.kt`, vemos o seguinte código:

```kotlin
package io.hakaisecurity.beerusframework.core.functions.magiskModuleManager

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import io.hakaisecurity.beerusframework.core.models.MagiskManager.Companion.showsMagiskDialog
import io.hakaisecurity.beerusframework.core.utils.CommandUtils.Companion.runSuCommand
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream

class MagiskModule {
    companion object {
        fun startModuleManager(context: Context, zipUri: Uri) {
            val zipFile = getFileNameFromUri(context, zipUri)
            val dirDestination = zipFile?.split(".")?.get(0)

            val cacheDir: File = context.cacheDir
            val cacheFile = zipFile?.let { File(cacheDir, it).absoluteFile }

            if (zipFile != null) {
                copyFileToCache(context, zipUri, zipFile)
            }

            println(dirDestination)

            runSuCommand("ls /data/adb/modules") { result ->
                if (!result.contains("No such file or directory")) {
                    runSuCommand("mkdir /data/adb/modules/${dirDestination}") {
                        runSuCommand("unzip -o $cacheFile -d /data/adb/modules/${dirDestination}") {
                            showsMagiskDialog()
                        }
                    }
                }
            }
        }

        fun getAllModules(modulePropsList: MutableList<String>) {
            runSuCommand("find /data/adb/modules/ -type f -name \"module.prop\" -exec ls -l {} \\; | cut -d \" \" -f 8") { result ->
                result.split("\n").filter { it.isNotBlank() }.let { paths ->
                    modulePropsList.addAll(paths)
                }
            }
        }

        fun getStatusModule(modulePath: String, status: String): Boolean {
            val path = modulePath.replace("module.prop", status, ignoreCase = true)
            var result = false

            val lock = Object()

            runSuCommand("ls $path") { output ->
                result = output.trim() == path
                synchronized(lock) {
                    lock.notify()
                }
            }

            synchronized(lock) {
                lock.wait()
            }

            return result
        }

        fun moduleOps(modulePath: String, status: Boolean, file: String) {
            val path = modulePath.replace("module.prop", file, ignoreCase = true)

            if(status){
                runSuCommand("touch $path") {}
            }else{
                runSuCommand("rm -rf $path") {}
            }
        }

...
```

**StartModuleManager Function (LINHAS: 17...39)**

- Pega um arquivo *.zip* e define o destino de seu conteúdo para uma pasta de mesmo nome dentro de `/data/adb/modules/<Nome do Módulo>`.
- Checa se o **módulo** já existe dentro do **diretório** / se o **módulo** já está instalado.
- Realiza o *unzip* dos arquivos do **módulo** para a pasta usando privilégios *root*.
- Mostra um *pop-up* para reiniciar o dispositivo e aplicar as alterações do **módulo**, devido às mudanças só serem aplicadas após a inicialização do dispositivo.

**getAllModules Function (LINHAS: 41...47)**

- Lista os **módulos instalados** em `/data/adb/modules` e exibe seus nomes através dos **metadados** contidos no arquivo `module.prop` de cada um.

**getStatusModule** **Function** **(LINHAS: 49...67)**

- Checa a existência de **arquivos** no diretório do **módulo** que possam identificar seu **status** atual como **inativo** ou se será removido na próxima inicialização.

**moduleOpsFunction (LINHAS: 70...78)**

- Cria um **arquivo** dentro do diretório do **módulo** desejado.
- Essa função existe porque, de acordo com a natureza do **Magisk**, arquivos como `disable` ou `remove` fazem com que os **módulos** sejam desabilitados ou removidos na próxima inicialização do dispositivo, respectivamente. Isso pode ser confirmado na [**documentação oficial**](https://topjohnwu.github.io/Magisk/guides.html?ref=yokai.hakaisecurity.io).

**Memory Dump**

A funcionalidade **Memory Dump** realiza localmente o *dump* da **memória** de um **processo em execução** e salva tudo em um arquivo *.tar.gz*. Esse pacote pode ser baixado via **USB** ou enviado para um **servidor remoto** para **análise posterior**.

Durante esse processo, são coletadas diversas **informações relevantes**, incluindo:

- **.so carregados**: lista de **bibliotecas dinâmicas** presentes no processo (*nomes* e *caminhos* dos arquivos *.so*).
- **Maps**: **mapeamentos de memória** do processo (*endereços*, *permissões* e *caminhos* de arquivos mapeados).
- **Stacks**: **pilhas de execução** em **espaço de usuário** (*por thread*), úteis para **reconstruir contexto** e *call stacks*.
- **Variáveis de ambiente**: conteúdo de *environ* do processo (*pares chave-valor*).

**Observação:** Ao iniciar o *dump de memória*, o processo pode levar algum tempo. Portanto, não saia da tela e aguarde até a finalização da operação.

```kotlin
package io.hakaisecurity.beerusframework.core.functions.memoryDump

import android.annotation.SuppressLint
import android.content.Context
import io.hakaisecurity.beerusframework.core.utils.CommandUtils.Companion.runSuCommand
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.Response
import org.json.JSONObject
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date

object MemoryDump {
    fun collectionTriagge(context: Context, server:String, isUSB: Boolean, selectionData: String, onComplete: (String) -> Unit) {
        val dir = File(context.filesDir, "dumps")
        if (!dir.exists()) {
            dir.mkdirs()
        }

        quickDump(context, server, isUSB, selectionData, onComplete)
    }

    @SuppressLint("SimpleDateFormat")
    private fun quickDump(context: Context, server: String, isUSB: Boolean, PID: String, onComplete: (String) -> Unit) {
        runSuCommand("""
            echo -e "==== maps ====" && cat /proc/$PID/maps && \
            echo -e "\n==== stack ====" && cat /proc/$PID/stack && \
            echo -e "\n==== .so loaded ====" && cat /proc/$PID/maps | grep -oE '/[^ ]+\.so' | sort -u && \
            echo -e "\n==== envs ====" && cat /proc/$PID/environ
        """.trimIndent()) { output ->
            runSuCommand("cat /proc/$PID/cmdline") { processName ->
                val safeProcessName = processName.trim()
                    .replace(Regex("[^a-zA-Z0-9._-]+"), "_")
                    .removePrefix("_")
                    .removeSuffix("_")
                val date = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(Date())
                val dir = File(context.filesDir, "dumps").apply { mkdirs() }
                val file = File(dir, "$date-$safeProcessName-quick-dump.txt")
                file.writeText(output)

                val pid = PID.trim()
                val stringDumpDirName = File(dir, "$date-$safeProcessName-string-dump")
                val tarFile = File(dir, "$date-$safeProcessName.tar.gz")
                runSuCommand("""
                    mkdir -p ${stringDumpDirName.absolutePath} && \
                    while IFS= read -r line; do \
                        RANGE=$(echo "${'$'}line" | awk '{print ${'$'}1}'); \
                        PERMS=$(echo "${'$'}line" | awk '{print ${'$'}2}'); \
                        if [[ "${'$'}PERMS" == *"r"* ]]; then \
                            START_HEX=0x${'$'}{RANGE%-*}; \
                            END_HEX=0x${'$'}{RANGE#*-}; \
                            START=$(printf "%u" ${'$'}START_HEX); \
                            END=$(printf "%u" ${'$'}END_HEX); \
                            SIZE=${'$'}((END - START)); \
                            FILE="${stringDumpDirName.absolutePath}/${'$'}RANGE"; \
                            dd if=/proc/$pid/mem bs=1 skip=${'$'}START count=${'$'}SIZE status=none 2>/dev/null | strings > "${'$'}FILE"; \
                        fi; \
                    done < /proc/$pid/maps && \
                    cd ${dir.absolutePath} && \
                    tar -czf ${tarFile.absolutePath} $date-$safeProcessName-quick-dump.txt $date-$safeProcessName-string-dump && \
                    rm -rf ${file.absolutePath} ${stringDumpDirName.absolutePath}
                """.trimIndent()) {
                    if (!isUSB) {
                        sendFile(tarFile.absolutePath, server) { R ->
                            runSuCommand("rm -rf ${tarFile.absolutePath}") {
                                onComplete("OK")
                            }
                        }
                    } else {
                        runSuCommand("cp -r ${tarFile.absolutePath} /data/local/tmp") {
                            runSuCommand("rm -rf ${tarFile.absolutePath}") {
                                onComplete("OK")
                            }
                        }
                    }
                }
            }
        }
    }

    private val client = OkHttpClient()

    private fun sendFile(fileName: String, server:String, onComplete: (String) -> Unit) {
        val sourceFile = File(fileName)
        if (!sourceFile.exists()) {
            onComplete("Compressed file not found: $fileName")
        }

        val fileBody = sourceFile.asRequestBody("application/octet-stream".toMediaTypeOrNull())
        var body = MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("file", sourceFile.name, fileBody).build()
        val request = Request.Builder().url(server).post(body).build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                onComplete("ERROR: Failed to send the file")
            }

            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    onComplete("SUCCESS: File sent successfully")
                } else {
                    onComplete("ERROR: Failed to send the file")
                }
            }
        })
    }

    fun verify(server: String, isUSB: Boolean, onComplete: (Boolean) -> Unit) {
        if (isUSB) {
            onComplete(true)
            return
        } else {
            val request = Request.Builder().url("$server/check").get().build()
            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    onComplete(false)
                }

                override fun onResponse(call: Call, response: Response) {
                    if (response.isSuccessful) {
                        val jsonBody = response.body?.string()
                        val json = JSONObject(jsonBody)
                        if (json.has("app")) {
                            if (json.getString("app") == "Beerus Server") {
                                onComplete(true)
                                return
                            }
                        }
                        onComplete(false)
                        return
                    } else {
                        onComplete(false)
                        return
                    }
                }
            })
        }
    }
}
```

**collectionTriagge Function (LINHAS: 21–28)**

- Verifica se o diretório `"dumps"` existe; caso contrário, cria-o.
- Executa a função **quickDump**.

**quickDump Function (LINHAS: 31–82)**

- Coleta os **maps**, **stacks**, **variáveis de ambiente** e arquivos *.so* carregados.
- Cria o **arquivo de dump** do processo.
- Finaliza o *dump* compactando-o em um arquivo *.tar.gz*.
- Caso o envio não seja por **USB**, o arquivo é enviado para o **servidor** e posteriormente apagado do dispositivo.
- Caso o envio seja por **USB**, o arquivo é movido para `/data/local/tmp`.

**sendFile Function (LINHAS: 86–109)**

- Valida se o **memory dump** será exportado via **USB** ou **servidor**.
- Se a exportação for via **USB**, a verificação do **servidor** é ignorada.
- Caso contrário, é enviada uma requisição *GET* ao **servidor**, no *endpoint* `/check`, para validar se ele é compatível com o **Beerus**.

**verify Function (LINHAS: 111–141)**

- Verifica se o **arquivo** existe antes do envio.
- Envia o **arquivo** para o **servidor do Beerus**.

**Conclusion**

O **Beerus Framework** já está disponível para *download* e *build* diretamente no repositório oficial da **Hakai Offensive Security**.

Nosso objetivo é disponibilizar uma **ferramenta** que facilite o trabalho de **analistas de segurança** e **pentesters mobile**, oferecendo uma **interface unificada** e **recursos práticos**.

O projeto está aberto para contribuições da **comunidade**: se você deseja contribuir, fique à vontade para explorar o repositório, abrir *issues*, propor *PRs* ou até mesmo compartilhar **ideias** sobre novas **funcionalidades** que possam fortalecer ainda mais a ferramenta.

Estamos trabalhando constantemente para trazer novas *features*, **inovações** e **automações** que possam expandir ainda mais as possibilidades do **Beerus Framework** no **ecossistema de segurança mobile**.

Retornamos em breve com mais novidades! ฅ^•ﻌ•^ฅ

**References / Bibliography**

- [Frida](https://github.com/frida/frida?ref=yokai.hakaisecurity.io) – *Toolkit* de **instrumentação dinâmica** para **desenvolvedores**, **engenheiros de reversa** e **pesquisadores de segurança**.
- [Frida Core](https://github.com/frida/frida-core?ref=yokai.hakaisecurity.io) – **Biblioteca central** para construção de **ferramentas** e **instrumentações** baseadas em **Frida**.
- [Fridump](https://github.com/Nightbringer21/fridump?ref=yokai.hakaisecurity.io) – Faz *dump* da **memória** e dos **módulos carregados** de **aplicativos Android** usando **Frida**.
- [Wireless ADB: ADB over TCP/IP](https://play.google.com/store/apps/details?id=com.origiq.wirelessadb&hl=pt%5FBR&ref=yokai.hakaisecurity.io) – Conecta a **dispositivos Android** via *Wi-Fi* para **depuração** e **testes**.
- [Magisk](https://github.com/topjohnwu/Magisk?ref=yokai.hakaisecurity.io) – Conjunto de **ferramentas** *open source* para **customização** do Android, habilitando acesso *root* e muito mais.
- [Always Trust User Certs](https://github.com/NVISOsecurity/AlwaysTrustUserCerts?ref=yokai.hakaisecurity.io) – Promove **certificados instalados pelo usuário** para o **repositório de certificados do sistema**.
- [Magisk Frida](https://github.com/ViRb3/magisk-frida?ref=yokai.hakaisecurity.io) – Inicia automaticamente o **Frida Server** no *boot* usando **Magisk**.
- [JADX](https://github.com/skylot/jadx?ref=yokai.hakaisecurity.io) – *Decompiler* de **Dex para Java** em **aplicações Android**.
- [Metasploit](https://github.com/rapid7/metasploit-framework?ref=yokai.hakaisecurity.io) – *Framework* de **testes de penetração** para desenvolver e executar códigos de *exploit*.