MOABile — mother of all mobile
A multi-device terminal UI for mobile app testing.
Android over adb, jailbroken iOS over usbmux and ssh, both at the same time,
each attached device in its own panel.
It does not reimplement the toolchain: it drives adb, scrcpy,
libimobiledevice, iproxy, ioscpy, frida, objection, curl and xz,
which have to be on PATH already. The startup screen reports what is missing
and which of the two device families that leaves usable — an Android-only
machine never needs libimobiledevice installed to get past it.
An iPhone is reached with libimobiledevice for everything that needs no
cooperation from the phone (device info, syslog, installing an ipa) and with ssh
down an iproxy tunnel for a shell, the filesystem and frida-server. The ssh
password is asked for once and kept in memory. No key is ever installed on the
phone: that would be a file of ours left behind on someone else's device.
Nothing is assumed to be installed on the phone either. Jailbreaks ship
different halves of a userland, so where a tool the panel asks for is not there
it says so once and falls back — the address row reads usb for a phone on the
end of a cable with nothing on the network, because that is what there is to
say: usbmux carries no address, and lockdownd hands out the wifi MAC and never
the lease. What the phone does have is found, though: every command carries
the PATH a non-interactive ssh leaves out — /usr/sbin, and everything a
rootless jailbreak keeps under /var/jb — inside sudo as well as outside it,
so grep and open are not reported missing on a phone that has them.
Installation & Run
Recommended: pipx (Isolated CLI)
The easiest and cleanest way to run MOABile without dependency conflicts or externally-managed-environment errors:
# Install globally in an isolated environment
pipx install moabile
# Run
moabile
Or run it directly on the fly without permanent installation:
pipx run moabile
Via pip
pip install moabile
moabile
From Source
Clone the repository and install requirements:
pip install -r requirements.txt
python3 moabile.py
On distributions that manage their own Python (error: externally-managed-environment), use a virtualenv:
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python3 moabile.py
Python 3.10 or newer, and Textual 6.2 or newer — the floor requirements.txt
names, and the oldest release the suite passes on. Linux and macOS: it needs a
real pty, so it does not run on Windows outside WSL.
Tested and fully verified on Kali Linux (x86_64).
No arguments and no options — --help says as much as there is to say, and
everything else is a key inside.
Tools it drives
The startup screen checks for these and reports each one's version. Nothing here is installed for you — which package manager your machine has, and what it calls a package, is your business.
| Tool | For | Family | From |
|---|---|---|---|
frida |
instrumentation, and its version | both | frida-tools |
objection |
exploration REPL | both | |
curl |
fetch frida-server and codeshare | both | |
xz |
unpack frida-server | both | XZ Utils |
adb |
device control | android | android platform-tools |
scrcpy |
screen mirroring | android | |
idevice_id |
device discovery, info and syslog | ios | libimobiledevice |
ideviceinstaller |
ipa install and app list | ios | |
iproxy |
ssh tunnel over usb | ios | libusbmuxd |
ssh |
shell, filesystem and frida-server | ios | OpenSSH |
ioscpy |
screen mirroring | ios | lautarovculic/ioscpy |
Either family works on its own. Both missing, or a core tool missing, and the gate does not let you through — there would be nothing past it to do.
Keys
r |
rescan for devices | i |
device info dump |
b |
the sidebar, on and off | u |
ssh user for this device |
f |
frida-server, on and off | p |
purge frida-server off the device |
s |
spawn the app under frida, or attach | k |
clear this panel's log |
o |
explore the app with objection | / |
filter log stream by keyword |
w |
mirror the screen in a window | c |
copy log / text viewer modal |
t |
terminal on the device | v |
save an svg of the interface |
l |
stream the device log | m |
dark/light mode |
d |
files: browse host ↔ device | h |
help: keys and widgets |
a |
add an app: install an apk or ipa | alt+c |
copy terminal session / viewer |
e |
export the app's apk/ipa | f8 |
return focus from tool pane |
q, ctrl+q |
quit |
s spawns the app under frida, which is what a script that has to be in
place before the app starts needs. Where the app is already running it offers
to attach to it instead — the app keeps whatever state it is in, and a script
on the running process sees what the device log does not carry: the unified
log's debug and info entries never reach idevicesyslog. Enter and escape keep
the spawn. f toggles frida-server, offering to match the host client, keep
what is installed, or install a specific custom version.
An app that is off screen is suspended on iOS, and attaching to a suspended
process is a prompt that never arrives — so s and o bring the app to the
front first, with open on the phone. A jailbreak that has no open is asked
to do it by hand rather than left looking stuck, and o says so at once
instead of watching a process table that is not going to change. Android needs
none of this: a process there runs whether it is on screen or not.
The device log, l, is pinned to the app's pid rather than its name: logcat
is asked for --pid, and on iOS idevicesyslog has no pid filter at all — its
-p matches process names, and a process merely named something similar
comes with it — so the pid is applied here, on the bracket every syslog line
carries after the process name. Which is why the whole-device-or-one-app
question comes up only while the app is running: with no pid there is no
filter to be had, so the stream is the whole device and the panel says why.
/ filters the active stream in real time by keyword, and c opens the
accumulated log in a selectable viewer modal with native clipboard copy.
Inside the file browser (d): p push host → device, l pull device → host, a
jumps to the app's own data directory and h back to where the device side
opened (/sdcard or /var/mobile), n rename, d delete, ←/→ switch
side, backspace up, r reload, esc close, and enter opens a directory or
transfers the file under the cursor.
Every panel keeps its own selected app and frida arguments, so multiple devices can be worked in parallel without crossing over. The sidebar highlights the active device at the top, and lists its installed packages at the bottom.
Nothing is left behind
Not a design goal that happened to fall out — the point. Nothing is written to disk between runs: no config, no history, no selected app, no ssh account. What you were looking at is a record of the work, and this is a tool for leaving none of that. The single exception is the frida-server download, cached one version deep where caches go.
While an iOS panel is open, ssh's multiplexing socket lives in the temporary directory — an empty file holding no data of yours, unlinked when the panel closes. A crash leaves it there, and the next run clears the one it finds.
Tests
Headless, driven by fake tools on PATH, no device required:
python3 test_moabile.py
It prints a PASS line per check and all good at the end. Run it as a script,
not under pytest: the module executes the suite on import. One copy at a time —
it opens real local ports for the usb tunnel, so two runs at once collide.
Lint:
ruff check .
Scope
A testing tool for devices you own or are authorised to test. It talks to whatever is plugged in over the phone's own debug interfaces — that is the job, and it is yours to have permission for.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moabile-1.1.1.tar.gz.
File metadata
- Download URL: moabile-1.1.1.tar.gz
- Upload date:
- Size: 85.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.21
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8acdce7f39117aeb805241d73c953cbee928ff6d8238643a65afc50966b3932
|
|
| MD5 |
0fdddb8e72e200b9e1d6f0b32f88dff7
|
|
| BLAKE2b-256 |
b0b3b2f4c1dc6074e41f0f575b56cbd3c7c9bc62ed532b7a350777d9cf578164
|
File details
Details for the file moabile-1.1.1-py3-none-any.whl.
File metadata
- Download URL: moabile-1.1.1-py3-none-any.whl
- Upload date:
- Size: 85.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.21
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c12101d5f8e9b859cd0ca7dd9d08c767e43a5faa7e3e1f490113822f63752f78
|
|
| MD5 |
0bf58c45e0d0410826b34c70a75c023e
|
|
| BLAKE2b-256 |
abfd7f3091c254734d36aa0f8c3686b51450d8aa0a4d16fa677407174fd01460
|