Conversation
First-phase Android guest analyzer, ported from analyzer/linux's lib/ scaffolding (procfs semantics are identical -- Android runs a real Linux kernel). No dynamic instrumentation (Frida) yet -- this covers install + launch + process-tree monitoring + clean shutdown only. New: - lib/core/packages.py: Android-specific Package base (the Linux one is tightly coupled to strace, which doesn't apply here) - modules/packages/apk.py: installs the submitted APK, resolves its package name via pm list packages diffing (no aapt dependency for the common case), launches via monkey, resolves the resulting PID via pidof with a /proc fallback - lib/api/process.py: adds a working Process.terminate() -- upstream analyzer/linux's Process is missing this entirely, so its own terminate_processes cleanup path silently no-ops today - lib/common/constants.py: falls back to TMPDIR=/data/local/tmp since stock Android has no writable /tmp Validated end-to-end on a live Android-x86 9.0 guest: installed a real third-party APK, launched it (confirmed on screen), tracked its PID through a full monitored run, and shut down cleanly. Along the way, found and fixed a real bug: /system/bin/monkey ships with no shebang line on this build, so subprocess exec's it directly with ENOEXEC where an interactive shell would silently retry through sh. Known follow-ups, not fixed here: - analyzer.py's status report-back hardcodes 127.0.0.1:8000; agent.py can't bind 0.0.0.0 on this Android/bionic build (socket.gaierror), so it binds the concrete guest IP instead, and loopback then refuses the connection - lib/cuckoo/core/guest.py hardcodes /tmp as the non-Windows temp path for placing the submitted sample; needs an android case (or a guest-side /tmp symlink) to line up with constants.py's override
Two real bugs found and fixed by actually submitting tasks through a live CAPE instance against a real android1 machine (not just manual testing): - analyzer.py: Android's /system/bin/date is toybox, not GNU coreutils -- it has no -s flag at all (date: Unknown option s), so every task crashed immediately in prepare() when a clock value was set. toybox's SET syntax takes a bare @unixtime positional argument instead, which sidesteps needing to match its other SET format (MMDDhhmm[[CC]YY][.ss]). - guest.py: determine_system_drive() returned / for android, same as the generic non-Windows fallback, but stock Android mounts / read-only (tmpfs). upload_analyzer()'s mkdtemp(dir=/) would have failed outright. Added an android case returning /data/local/tmp/, the writable partition analyzer/android's own constants.py already standardizes on. determine_temp_path() gets the matching case for where the submitted sample gets placed. Validated against a live android1 KVM machine (kvm.conf, not committed -- local deployment config) added via git-log-visible submit.py runs: task correctly selected android1, uploaded the analyzer, installed and launched a real third-party APK, ran for the full configured timeout, and produced a complete report.json with platform=android, package=apk. Confirmed the live-snapshot revert model works cleanly end-to-end (agent.py comes back up immediately on revert, no reprovisioning needed per task).
Addresses all four confirmed findings from a full review pass, each re-validated live through the real submission pipeline (task IDs below), not just re-read as code: - analyzer.py never called sys.exit(), so its process always exited 0 regardless of whether the analysis crashed. agent.py's get_subprocess_status() (polled by the host via /execpy) maps exitcode to Status.COMPLETE/FAILED, so this made guest-level success/failure indistinguishable to anything that depends on it (the "Analysis failed" vs "Analysis completed successfully" log line in guest.py, and the accuracy of the report's "timeout" field). Confirmed via task kevoreilly#41 (deliberately corrupt "APK"): guest.py now correctly logs "Analysis failed", and report.json shows timeout=false plus the real pm-install error text in debug.log, where before it would've read identically to a genuine success. Note the top-level tasks.status column still normalizes to "reported" for both outcomes -- that's a pipeline-completion flag CAPE uses for every platform, not a defect; the real signal lives one level down. - Stop-task-early was broken on both sides. Guest-side: analyzer.py's marker path used PATHS["root"] (a random per-run directory) instead of the shared TMPDIR path guest.py actually creates the marker in -- a regression against analyzer/linux, which this was ported from. Host-side: web/apiv2/views.py had no android branch for dest_folder, which would've raised UnboundLocalError. Both fixed together since neither half works without the other. - No host-side routing to Android existed at all. Traced and empirically confirmed three independent gaps: File.get_platform() had no android branch (defaulted to "windows"); "apk" wasn't in sandbox_packages, so _identify_aux_func() never promoted it out of sflock's own generic classification; and even that classification was wrong -- sflock_identify() returns "jar" for a real APK (only sflock.unpack()'s separate .package attribute correctly says "apk"), so a bare .apk with no flags would've been silently treated as a Java jar, a package analyzer/android doesn't implement. Fixed by checking our own libmagic-based File.get_type() first (it already distinguishes them correctly: "Android package (APK)"), adding the android platform branch, and moving "apk" from blacklist_extensions to whitelist_extensions in demux.py (matching "jar", another zip-based format already handled that way) as defense in depth for the fallback path. Package detection alone isn't sufficient for correct machine selection: find_machine_to_service_task() filters on task.platform independently of task.package, so _identify_aux_func() resolving "apk" doesn't by itself keep a bare submission off a Windows machine. demux_sample_and_add_to_db() now also sets platform = "android" when package resolves to "apk" and no platform was given. Confirmed via task kevoreilly#43 (both android1 and cuckoo1 free at submit time): `submit.py --timeout 30 file.apk` with no other flags produces task.package=apk, task.platform=android, machine=android1, and completes normally. - No unit tests existed for the new package. Added analyzer/android/tests/modules/packages/test_apk.py covering _install()'s package-name resolution (including the ambiguous reinstall/aapt-fallback/multiple-new-packages branches), _launch()'s exact argv construction (asserting package_name only ever reaches the shell as a positional parameter, never string-interpolated into the script body), and the empty-PID failure path in start(). Matches analyzer/linux/tests' existing structure and pytest.ini convention.
demux_sample_and_add_to_db only wrote platform=android after auto-identify, so submit.py --package apk left task.platform empty and find_machine_to_service_task() could pick a free Windows VM. Move the assignment out of `if not package` so it also applies before demux_sample (which echoes the incoming platform for a pinned package). Re-apply after the extracted_files unpack, which shadows the outer platform name, so archive-extracted APKs identified in the loop get the same pin. Also send status=failed on the analyzer loopback POST when the run errored, matching the sys.exit(1) path.
### What was changed: 1. **Multi-Process Sibling Monitoring (`analyzer/android/analyzer.py`)**: * Added the `get_package_pids(package_name)` helper function to dynamically scan `/proc` for running sibling processes whose command line matches the target `<package_name>` or its subcomponents (e.g., `<package_name>:remote`). * Updated `monitor_new_processes` to combine both procfs-based direct child process tracking (for native execution/forks) and Zygote-forked sibling tracking. * Passed the target APK’s resolved `package_name` down to the monitoring thread. 2. **Robust Local Routing Fallback (`analyzer/android/analyzer.py`)**: * Added the `get_local_ip_via_routing(host_ip)` helper function which uses a lightweight UDP socket routing check to determine the exact local IP used to reach the CAPE host. * Updated the loopback status reporting logic to attempt a `/status` POST to loopback (`127.0.0.1:8000`) first, falling back gracefully to the concrete local IP address if the local agent bound there due to Android/Bionic `0.0.0.0` socket errors. 3. **Backward Compatibility in `pm install` (`analyzer/android/modules/packages/apk.py`)**: * Modified `_install()` to catch unrecognized option errors for `-g` (e.g. `Unknown option: -g` on Android API < 23) and fall back gracefully to a standard `pm install -r` without crashing the entire analysis package. 4. **Case-Insensitive Host-Side magic File Detection (`lib/cuckoo/core/data/tasking.py`)**: * Updated the host's `libmagic` type classification check to be case-insensitive (`"android package" in file_type.lower()`) to ensure reliable APK identification across different distribution builds of host-side `libmagic`.
|
thank you, how do you create android analysis vm? do you install android studio inside of the linux vm? |
|
thanks for your initial perspective @nyrivera . I have a few thoughts and questions regarding the architecture.
Thanks again for driving this! |
|
@doomedraven Not Android Studio, and not a Linux VM with an emulator nested inside. This PR was validated on a KVM domain whose guest OS is Android-x86 9.0 (x86_64), registered in CAPE as I can write a short guest-image note (ISO → KVM → agent.py → snapshot → machines table) if you want that in-tree. Happy to treat ARM/AVD as a later machine type; this slice is the x86_64 KVM path only. |
|
@dsecuma Thanks — those are the right long-term questions. This PR is deliberately install/launch/process-lifetime + host routing only.
|
Documents the ISO → KVM → agent.py → snapshot → machines table path used to validate analyzer/android. x86_64 KVM only; ARM/AVD is a later machine type.
Answer the PR review questions in-tree: ARM/AVD is a later machine type, eBPF/bypass and CAPE-facing VNC are later slices, Rootbeer is a live sample on this x86_64 KVM guest (rooted userdebug, so detectors should fire).
|
@dsecuma Follow-up on your architecture questions. The in-tree write-up is now in ARM / AVD. Not tested. This PR’s guest is Android-x86 9.0 x86_64 KVM so it sits next to the Windows domain: libvirt snapshot revert, eBPF / detection bypass. Not in this PR. Linux CAPE already has a first-cut capture path (Tracee → VNC / user interaction. This slice launches with RootBeer. The image is userdebug with |
|
@dsecuma RootBeer baseline on the existing x86_64 guest (control sample for later work). Sample. Built from source at scottyab/rootbeer
CAPE task #48.
Why
So the important result is not merely “rooted.” It is: this sandbox is a rooted userdebug x86_64 KVM, RootBeer’s Java + native paths can load, and CAPE still installs/launches/times out cleanly. That is the control for ARM / Frida / hardening later. Caveat. The live analyzer tree on the host already has an in-progress Frida auxiliary (not in this PR). Task 48’s guest log shows No further feature commits planned on this PR. |
Sprint lock / #3210 freezeThis PR is frozen after the x86_64 RootBeer control sample (task #48). Further work stays off this branch. Follow-up order is now locked as: Baseline → Observability → Architecture → Realism → Additional instrumentation → Human interaction Concrete sequence:
|
|
@doomedraven Hey — whenever you have a spare look, this slice is frozen and ready for review. It is just APK install / launch / process lifetime plus host routing, and the short Android-x86 guest note. RootBeer control is task #48 in the thread. No Frida or extra feature commits on this branch unless you want changes. Thanks for the VM-build question earlier — happy to follow up on anything that is still unclear. |
|
Hey @doomedraven — checking back in on this. It's been about 8 days since I froze this slice (APK install / launch / process lifetime, RootBeer control sample included) and pinged for review. No pressure, just want to make sure it didn't fall through the cracks. Happy to split it further or clarify anything that'd make review easier. |
|
I'm on work trip, so I will try to review it next week.
El lun, 14 sept 2026, 21:25, Nelson Rivera ***@***.***>
escribió:
… *nyrivera* left a comment (kevoreilly/CAPEv2#3210)
<#3210 (comment)>
Hey @doomedraven <https://github.com/doomedraven> — checking back in on
this. It's been about 8 days since I froze this slice (APK install / launch
/ process lifetime, RootBeer control sample included) and pinged for
review. No pressure, just want to make sure it didn't fall through the
cracks. Happy to split it further or clarify anything that'd make review
easier.
—
Reply to this email directly, view it on GitHub
<#3210?email_source=notifications&email_token=AAOFH34CIDFMJDEAJQXICB35PCLATA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNRXGMZDKMJVGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5673251545>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAOFH367MKNRBNPKEK5LE3D5PCLATAVCNFSNUABFKJSXA33TNF2G64TZHMZDCNJTGY3DOMJTHNEXG43VMU5TKMZVGU3DGMZSGUY2C5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAOFH32WHRNZFF42X35F7HD5PCLATA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNRXGMZDKMJVGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AAOFH32JAW6VSBLJP5U636D5PCLATA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNRXGMZDKMJVGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
What this is
A first-phase Android guest analyzer for CAPE.
It:
GuestManager/execpypipeline;.apk(or--package apk) toplatform=androidsofind_machine_to_service_task()does not pick a free Windows VM.It does not include Frida or any other dynamic instrumentation. Reports from this phase will not contain behavioral events, signatures, or malscore. That absence is intentional. Instrumentation is a follow-up.
Host routing
File.get_type()containingAndroid package (APK)wins oversflock_identify(), which returnsjarfor real APKs (onlysflock.unpack().packagesaysapk)."apk"is insandbox_packages.File.get_platform()has anandroidbranch.demux.pytreatsapklikejar(whitelist, not blacklist).demux_sample_and_add_to_db()setsplatform=androidwhenpackage == "apk"and no platform was given — including explicit--package apk, and again after theextracted_filesunpack.both store
task.package=apk,task.platform=android.Guest paths
Analyzer and sample land under
/data/local/tmp(determine_system_drive()/determine_temp_path()). Windows and Linux path behavior is unchanged. Clock set uses toyboxdate @UNIXTIME, not GNUdate -s.Known limitations (not treated as defects)
tasks.statusstaysreportedfor both guest success and guest failure. That is CAPE-wide (GuestManagermaps agentcomplete/failedto DBcomplete). Look at the guest log (Analysis failedvscompleted successfully) andreport.json.http://127.0.0.1:8000/statusfails on guests whereagent.pycannot bind0.0.0.0and binds only the guest IP. Host completion uses/execpychild exit via GET/status. Do not retarget the callback to the guest’s routed IP; agent pinning would drop it. The bind must be fixed later. When the POST does succeed, a crashed run now sendsfailed.upload_scripts()still forces\in the guest path (pre-existing). Unused unless pre/during scripts are configured.Zip archive data(noAndroid package (APK)) still goes throughsflock_identify()→jar.Process.terminate()exists on the AndroidProcessclass (Linux’s class is missing it even though the Linux analyzer shutdown loop calls it). Intentional.Validation
#40: F-Droid APK, full timeout,report.jsonwith correct hashes, no behavioral telemetry. Early run had emptyplatform(fixed later).#41: corrupt APK → host logsAnalysis failed,timeout=false,pm installerror in debug.log.sys.exit(1)path.#43/#44: bare submit, both VMs free →package=apk,platform=android,machine=android1.#45:submit.py --package apk --timeout 30with no--platform,cuckoo1unlocked →package=apk,platform=android,machine=android1.🤖 Generated with Claude Code