> ## 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 – A New Mobile Framework Arises
- URL: https://yokai.hakaisecurity.io/beerus-framework-a-new-mobile-framework-arises/
- Published: 2025-09-19T16:16:27.000Z
- Updated: 2026-09-08T16:29:10.000Z
- Author: Daniel Franca Lima
- Tags: Insights Blog, #wp, en, #pair-beerus-arises

**Abstract**

The **Beerus Framework** is an offensive *mobile* tool developed to simplify the entire **pentesting** process on Android devices. With a **unified interface** directly on the device, Beerus allows performing tasks ranging from application **instrumentation** in a *built-in* way on the device with **Frida Core**, **sandbox data exfiltration**, *memory dumping*, *proxying*, **Magisk** module control, property manipulation, and much more.

Built on **Frida** and **Magisk**, Beerus is **modular**, **extensible**, and designed for testing on *rooted* devices, optimizing common **pentesting** tasks and enabling **automations** from a single *app*.

In this *paper*, we explore the main **functionalities** of the framework, with a special focus on some of them. The aim is not to detail its operation exhaustively, but rather to provide a broad overview of what it covers and what it is capable of. To complement this, we provide below a **demonstrative video** showing how to use the **Beerus Framework** **in practice**.

Remember that the **Beerus Framework** is already available for *download* directly from the official [**GitHub repository**](https://github.com/hakaioffsec/beerus-android?ref=yokai.hakaisecurity.io).

**The Development of Beerus Framework**

During **mobile security assessments**, it is common to rely on several external tools and *complex setups*. The **Beerus Framework** was created with the goal of unifying the essential **functionalities** of a **mobile pentester** into a single Android application. The proposal is to offer a **practical** and **efficient** solution that makes the testing process more **fluid** and **agile**.

With this in mind, we decided to expand the **Beerus project**, initially conceived by Lucas *"luriel"* Carmo and Daniel *"d3v"* Chactoura, giving rise to the **Beerus Framework**. The objective is to provide **functionalities** that simplify and accelerate the daily work of the **security analyst**.

**Introdução Técnica**

The **Beerus Framework** works as a **hub of functionalities** for **security analysts** who work with Android devices. The application provides an **intuitive graphical interface** with access to several tools, leveraging the device’s own resources and *root* privileges (designed to work in better sync with **Magisk**).

Among the **functionalities** offered are:

## Frida Server Setup

The **Frida Server Setup** functionality allows downloading and initializing the **Frida Server** directly on your *mobile* device. Simply choose a version and click *"Run"* for the server to be installed and executed. After installation, it is not necessary to reinstall the same version unless you choose to switch. By default, the **Beerus Framework** will display the 10 most recent versions, but it is also possible to manually enter the desired version. This makes the process of configuring the **instrumentation environment** much simpler.

## Frida Auto Inject

The **Frida Auto Inject** functionality enables the injection of **Frida scripts** directly from the *mobile* device through **Frida Core**, the interaction engine via *RPC* used in both *Python* and *JavaScript* versions of **Frida**. It communicates with the **Frida Server**, which can be started by the **Beerus Framework** itself, facilitating **portability** and the **sharing of exploits** for application **instrumentation**.

We can access the **Frida Setup**, start the **Frida Server**, and then create or import a *script* into the device’s storage. After adding the *script*, it can be edited, the target **APK** selected, and by clicking *"Run"*, the *app* will start already with the *script* injected via **Frida Core**.

Within the official **Frida** repository, under *releases*, we can find a package called *frida-core-devkit*, available for different architectures. The **Frida Core** is used to interact with the **Frida Server** in the following way:

- The **client** (your program in **Python/Node/C** using **frida-core**) opens a *socket* to the *frida-server*.
- It sends messages in the **JSON-RPC** format encapsulated in a binary *framing* (the **Frida** protocol).
- The *frida-server* receives the message and forwards it to its local instance of **Frida Core**.
- The **client** sends ``` `` ```**attach(pid)** → **server** → **Core** injects the **Frida Gadget** into the target.
- The **Gadget**, which also runs **Frida Core**, opens an internal *RPC* channel with the **server**.
- Now the **server** acts as a **proxy** between:  
  - **Client Core** (your *tool*)
  - **Gadget Core** (inside the target)

So, **Beerus** has a modified version of this *devkit* to allow passing *JS scripts* as a parameter to a compiled binary of **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 (Line: 9)**

- Provides the main **APIs** of **Frida** through *"frida-core.h"*.

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

- `on_detached(...)`: *callback* to handle when the session is disconnected.
- `on_message(...)`: *callback* for messages coming from the *JS script*.
- `on_signal(...)`: captures signals such as **SIGINT** and **SIGTERM**.
- `stop(...)`: used to stop the **main loop**.

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

- Stores the **GLib main loop** that processes asynchronous events.

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

- Declares local variables (target **PID**, *JS script*, *manager*, *devices*, session, etc.).
- Calls `frida_init()`.
- Validates arguments: expects `<pid> <script.js>`. If invalid, prints *Usage* and exits.
- Reads the *JS script* (`argv[2]`) into the variable `script_source` with `g_file_get_contents`.
- Creates the **main loop** with `g_main_loop_new`.
- Registers *signal handlers* for **SIGINT** and **SIGTERM**.

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

- Creates `FridaDeviceManager` with `frida_device_manager_new()`.
- Enumerates devices with `frida_device_manager_enumerate_devices_sync`.
- Iterates over the devices found: prints name (`frida_device_get_name`).
- If it is `FRIDA_DEVICE_TYPE_LOCAL`, stores the reference as `local_device`.
- Releases the device list with `frida_unref(devices)`.

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

- Calls `frida_device_attach_sync` to attach to the target process.
- If successful, connects the *callback* `"detached"`.
- Checks if the session was prematurely disconnected.
- Prints `[*] Attached` upon success.

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

- Creates script options (`FridaScriptOptions`): defines the name `"example"` and *runtime QJS*.
- Creates the *script* with `frida_session_create_script_sync`.
- Connects the *callback* `"message"` to `on_message`.
- Loads the *script* with `frida_script_load_sync`.
- Prints `[*] Script loaded`.
- If the **loop** is active, enters `g_main_loop_run(loop)`.

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

- After leaving the **loop**, unloads the *script* (`frida_script_unload_sync`, `frida_unref(script)`).
- Prints `[*] Unloaded`.
- Detaches the session (`frida_session_detach_sync`, `frida_unref(session)`).
- Prints `[*] Detached`.
- If there was an error in *attach*, prints the error message and frees `GError`

The **APK** uses this binary in the file `AutoInject.kt` as follows:

```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 (LINES: 17...29)**

- Retrieves the launch *intent* of the target **app** through the `packageName`.
- Defines **Activity flags**:  
  - **NEW\_TASK**: starts in a new *task*.
  - **NO\_HISTORY**: the **Activity** does not remain in the history.
- Starts the target **application** using the `launchIntent`.
- Waits 5 seconds (time for the **app** to start).
- Executes the **Frida Core** binary, attaching it to the target **app** process and loading the selected *script*.

**Note:** To use this functionality, it is necessary to install the **Magisk module** of the **Beerus Framework**, available when opening the **app** or when accessing a functionality that depends on it.

## ADB Over Network

The **ADB Over Network** functionality allows starting the **Android Debug Bridge** (*ADB/adbd*) via the **TCP** protocol, enabling remote interaction with the device without the need for a cable. This feature is essential for **dynamic analysis**, allowing the installation of applications, access to system information and directories such as `/data/data`, as well as the execution of tools like **Frida Server** and other binaries.

## Boot Options

The **Boot Options** functionality allows configuring the automatic startup of tools such as **Frida Server** and **ADB Over Network** along with the device *boot*, avoiding repetitive manual procedures. It is also possible to promote user certificates to system certificates, which helps bypass protections such as **SSL Pinning**. By using the *scripts* `post-fs-data.sh` and `service.sh` from the **Magisk module**, it is possible to execute binaries and change configurations at the very beginning of the *boot*.

We can see the following code in `post-fs-data.sh`:

```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 (LINES: 7...8)**

- Extraction of the *feature flags* set by the **Beerus Framework APK**.

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

- Checks if the *feature flag* of **System Trusted Certs** is enabled.
- Gets the user certificate directory and moves its certificates to a replica directory of the *filesystem* (`/system`).
- **Magisk** will identify the replica and place the certificates as if they were part of the Android system itself.
- This makes some insecure **SSL Pinning** approaches *bypassed*.

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

- Checks if the *feature flag* of **ADB Over Network** is enabled or not.
- If yes, changes the value of the property `service.adb.tcp.port` to the desired port number and restarts the **adbd** service.
- If not, changes the value of the property `service.adb.tcp.port` to empty and restarts the **adbd** service, in order to stop execution on the next device *restart*.

We can also see the following code in the *script* `service.sh`:

```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 (LINES: 7...8)**

- Extraction of the *feature flag* set by the **Beerus Framework APK**.
- Value of the *path* of the **Frida** binary created by the **Beerus APK**.

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

- Checks if the *feature flag* of **Frida Server Setup** is enabled and if the binary exists.
- Executes the binary in the *background*.

**Note:** The use of this functionality requires the installation of the **Magisk module** of the **Beerus Framework**, available when opening the *app* or when accessing a functionality that depends on it.

**SandBox Exfiltration**

The **Sandbox Exfiltration** functionality allows extracting from the device not only the information saved by the application but also the *.apk* itself. This not only facilitates the **analysis** process but also demonstrates that, in cases of **RATs** (*Remote Access Trojan*), it can be used to extract information from the device. Likewise, in cases of **theft** or **loss**, the attacker can perform *root* on the device and obtain this data.

When accessing this functionality, we are presented with some applications installed on the device. By selecting one of them, we can send it to the **Beerus server** or, by selecting the **USB option**, it generates the file with the command to extract directly using: `adb pull /data/local/tmp/tmp/{package_name}.tar.gz`

It is worth noting that we have a detailed *blog post* explaining this functionality in the very first version of **Beerus**, written by Lucas *"luriel"* Carmo and Daniel *"d3v"* Chactoura, which can be accessed at the following link: [https://hakaisecurity.io/beerus-apk-spotlighting-sandbox-exfiltration/insights-blog/](https://yokai.hakaisecurity.io/beerus-apk-spotlighting-sandbox-exfiltration/)

**Properties Changes**

The **Properties Changes** functionality was created to allow the addition and modification of **system properties** directly through the **graphical interface**, in a **permanent** way, that is, even after rebooting the device. This functionality is especially useful for changing properties used by applications to identify devices with *root* or running in **emulators**, such as: `"ro.hardware=unknown"` and `"ro.kernel.qemu=0"`.

When accessing the functionality, we are presented with a button to add a new property. This button can be used either to create a **non-existent property** or to modify an **already existing property**. When clicking to add, a field is displayed to enter the **property name** (new or existing) and another field to set the **desired value**.

After filling and confirming, the property will be successfully added through the use of the `system.prop` file from the **Magisk module**. Finally, simply reboot the device so that the changes are applied **permanently**.

**Note:** To use this functionality, it is necessary to install the **Magisk module** of the **Beerus Framework**, available when opening the *app* or when accessing a functionality that depends on it.

**Manifest Decoding**

The **Manifest Decoding** functionality performs the *parsing* of the `AndroidManifest.xml` to extract information relevant to the **analysis** of an **APK**, such as exported **Activities** and **Receivers**, declared permissions, identified **Main Activity**, and other sensitive or useful settings for **security analysis**.

To implement this module, we used the [**apk-parser**](https://github.com/hsiafan/apk-parser?ref=yokai.hakaisecurity.io) library, which allows extracting and decoding the **Android Manifest** directly from the **APK**. From there, we perform the *parsing* of the necessary information.

```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 (LINES: 11...20)**

- Defines the *data class* **ComponentInfo**, used for the *activities*, *providers*, *services*, and *receivers* components.
- Defines the *data class* **Manifest**, where the **components**, **user permissions**, and **general information** will be stored.

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

- Creates **mutable variables** for the components, general information, and application permissions.
- Uses *regex* to obtain information from the **app**, such as: **package name**, **expected SDK version**, **Main Activity**, among others.
- Executes a *regex* to capture the **permissions required** by the application.
- Retrieves the **application components**.

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

- Uses **ApkFile** to obtain the application’s **manifest**.
- Uses **parseManifest** to extract the **manifest** information.
- Returns the result of the *parse*.

**Proxy Profiles**

The **Proxy Profiles** functionality allows creating different **proxy profiles** and enabling or disabling them at any time, without the need to manually configure or remove the **proxy** when changing networks or using applications normally.

To create a profile, simply provide a **name** and the **IP address** with a **port**. After that, it is possible to enable/disable, edit, or delete the profile whenever necessary.

When a profile is enabled, all device traffic automatically passes through the **configured proxy**. This is done by the `selectProfile` function, defined in `ProxyProfiles.kt`, which updates the `proxyLists.json` file by marking the selected profile and sets the **global proxy** of the device via *shell* context incorporated with *root* privileges:

```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**

The **Magisk Manager** functionality acts as an extension of **Magisk**, offering **full control** over its **modules**. With it, it is possible to enable or disable already installed modules, as well as install or remove new ones as needed.

When accessing the **interface**, all existing **modules** are listed, along with the option to add new ones from **local files**, making **management** practical and centralized.

In the `MagiskModule.kt` file, we see the following code:

```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 (LINES: 17...39)**

- Takes a *.zip* file and defines its content’s destination as a folder with the same name inside `/data/adb/modules/<Module Name>`.
- Checks if the **module** already exists in the **directory** / if the **module** is already installed.
- Performs the *unzip* of the **module** files into the folder using *root* privileges.
- Displays a *pop-up* to restart the device and apply the **module changes**, since modifications are only applied after the device is rebooted.

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

- Lists the **modules** installed in `/data/adb/modules` and displays their names through the metadata contained in each `module.prop` file.

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

- Checks for the existence of **files** in the module directory that may identify its current **status** as **inactive** or whether it will be removed at the next reboot.

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

- Creates a **file** inside the desired module directory.
- This function exists because, according to the nature of **Magisk**, files such as `disable` or `remove` cause the **modules** to be disabled or removed on the device’s next reboot, respectively. This can be confirmed in the [**official documentation**](https://topjohnwu.github.io/Magisk/guides.html?ref=yokai.hakaisecurity.io).

**Memory Dump**

The **Memory Dump** functionality locally performs a *memory dump* of a **running process** and saves everything into a *.tar.gz* file. This package can be downloaded via **USB** or sent to a **remote server** for later **analysis**.

During this process, several **relevant pieces of information** are collected, including:

- **Loaded .so**: list of **dynamic libraries** present in the process (*names* and *paths* of the *.so* files).
- **Maps**: process **memory mappings** (*addresses*, *permissions*, and *paths* of mapped files).
- **Stacks**: **execution stacks** in **user space** (*per thread*), useful for **reconstructing context** and *call stacks*.
- **Environment variables**: content of the process *environ* (*key-value pairs*).

**Note:** When starting the *memory dump*, the process may take some time. Therefore, do not leave the screen and wait until the operation is complete.

```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 (LINES: 21...28)**

- Checks if the `"dumps"` directory exists; if not, creates it.
- Executes the **quickDump** function.

**quickDump Function (LINES: 31...82)**

- Collects **maps**, **stacks**, **environment variables**, and loaded *.so* files.
- Creates the **process dump file**.
- Finalizes the *dump* by compressing it into a *.tar.gz* file.
- If the transfer is not via **USB**, the file is sent to the **server** and then deleted from the device.
- If the transfer is via **USB**, the file is moved to `/data/local/tmp`.

**sendFile Function (LINES: 86...109)**

- Validates whether the **memory dump** will be exported via **USB** or **server**.
- If the export is via **USB**, the **server** check is skipped.
- Otherwise, a *GET* request is sent to the **server**, at the `/check` *endpoint*, to validate if it is compatible with **Beerus**.

**verify Function (LINES: 111...141)**

- Sends the **file** to the **Beerus server**.
- Checks if the **file** exists before sending.

**Conclusion**

The **Beerus Framework** is now available for *download* and *build* directly from the official **Hakai Offensive Security** repository.

Our goal is to provide a **tool** that facilitates the work of **security analysts** and **mobile pentesters**, offering a **unified interface** and **practical resources**.

The project is open for **community contributions**: if you wish to contribute, feel free to explore the repository, open *issues*, propose *PRs*, or even share **ideas** for new **functionalities** that can further strengthen the tool.

We are constantly working to bring new *features*, **innovations**, and **automations** that can further expand the possibilities of the **Beerus Framework** in the **mobile security ecosystem**.

We’ll be back soon with more updates! ฅ^•ﻌ•^ฅ

**References / Bibliography**

- [Frida](https://github.com/frida/frida?ref=yokai.hakaisecurity.io) – *Dynamic instrumentation toolkit* for **developers**, **reverse engineers**, and **security researchers**.
- [Frida Core](https://github.com/frida/frida-core?ref=yokai.hakaisecurity.io) – **Core library** for building **Frida-based tools** and **instrumentation**.
- [Fridump](https://github.com/Nightbringer21/fridump?ref=yokai.hakaisecurity.io) – Performs *memory dump* and extracts loaded **modules** from Android applications using **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) – Connects to **Android devices** via *Wi-Fi* for **debugging** and **testing**.
- [Magisk](https://github.com/topjohnwu/Magisk?ref=yokai.hakaisecurity.io) – A set of *open source* **tools** for Android **customization**, enabling *root* access and more.
- [Always Trust User Certs](https://github.com/NVISOsecurity/AlwaysTrustUserCerts?ref=yokai.hakaisecurity.io) – Promotes **user-installed certificates** to the **system certificate store**.
- [Magisk Frida](https://github.com/ViRb3/magisk-frida?ref=yokai.hakaisecurity.io) – Automatically starts the **Frida Server** on *boot* using **Magisk**.
- [JADX](https://github.com/skylot/jadx?ref=yokai.hakaisecurity.io) –*Decompiler* from **Dex** to **Java** for Android applications.
- [Metasploit](https://github.com/rapid7/metasploit-framework?ref=yokai.hakaisecurity.io) – **Penetration testing* *framework** for developing and executing *exploit* code.