Problem: there is a website somewhere that hates robots and stuff. But you are a good robot or script that still wants to access, say, your own data.
Solution: Pretend to be an Android App! These also are kind of human controlled robots/scripts, that will call whatever API and get resuts.
So you want to figure out how the app works on a phone, what kind of credentials it’s using, and understand how the remote API is being called.
adb push mitmproxy_certificate.der /sdcard/Download (make sure the file has .der extension)
In Android Settings Go to Security & Privacy → Encryption & credentials → Trusted Credentials
Add mitmproxy_certificate.der
Reboot
Redirect your phone’s traffic to your instance of mitmproxy
nftables example :
ip saddr $android_phone_ip tcp dport 80 dnat to $machine_running_mitmproxy:8080
ip saddr $android_phone_ip tcp dport 443 dnat to $machine_running_mitmproxy:8080
start mitmproxy
Install the app you want to install. Some helpful commands:
adb install app.apk
If you have an .apkx:
unzip app.apkx
adb install-multiple *.apk, or, if in Windows PowerShell adb.exe install-multiple @(Resolve-Path *.apk)
Start your app, requests should show up on mitmproxy
Save your current mitmproxy session as HAR with : save.har @all output.har
Start hacking
Why is this so complicated?
Problem 1: apps don’t work well on x86_64 emulators
Back in the days people would write Java. But that was annoying because companies want to write one line of code and have it work everywhere… Which was the whole point of Java…. But the Java of today is HTML+JS or something like that.
So people who want Apps write them on top of Flutter. Which takes Dart as input, and machine code as output. And since every machine out there is a phone running ARM64, if you download an APK, it’ll most certainly be only containing code for arm64 platforms.
Android tells us that it’s fine because the OS provides magical translation of arm into x86, so you can run your arm64 native code on x86_64.
But also everytime I try my x86_64 android emulator crashes so here’s that.
Also, since the offician Android emulator is based on Qemu, you should be able to run a arm64 qemu virtual machine on x86_64, at a performance cost, but this is not supported anymore.
So use a physical phone.
Problem 2: Can’t mess up with Android 14+ system certificate store easily anymore.
One obviously can’t just sniff the network connection, because SSL, so you want to use mitmproxy. But for this to work, you’ll need to make the App you want to analyze accept the certificate used by the proxy.
But since Android 14, the certificates stored in the /system partition are ignored, and favored by these stored in the Conscrypt module APEX, and you can’t just drop files in there because security.
This will do magic (lol) stuff during boot, and copy the user certificates in the proper place.
Problem 3: What if the App does pinning and doesn’t trust Android’s store? Or refuses to run if the device is rooted? Or has some other kind of protection against all this?
That sucks.
Automated patching with Medusa
You can try to use Medusa that builds on top of Frida. It maintains a list of modules that are scripts that will tell Frida to do the appropriate hooks and in-memory code modification to try and defeat code in the App that does any of this stuff we don’t want. For example:
But the couple times I’ve tried these, the app would crash.
More manual approach with Frida
Decompile the apk
I recommend using Bytecode Viewer. Just drop a .apk file and you can start exploring and maybe figure out what method to patch.
Here for example, I figured out that in the app “AppBaseActivity”, they were loading a class called x8 where a static value a was could be set to True, which enables ‘debug mode’, and then none of the Root checks would run.
I easily found it by using Bytecode Viewer to search for the strings displayed in the warning when the app detects your rooted phone and complains about it.
setTimeout(function() { // avoid java.lang.ClassNotFoundException
Java.perform(function() {
// Root detection bypass example
var BaseActivity = Java.use("com.app.android.views.core.AppBaseActivity");
console.log("info: hooking activity class");
const onCreate = BaseActivity.onCreate;
onCreate.implementation = function(v) {
var params = Java.use("x8.a");
console.log("Is_debug_build value is ",params.a.value.booleanValue());
params.a.value = Java.use("java.lang.Boolean").$new(true);
console.log("Is_debug_build value is ",params.a.value.booleanValue());
onCreate.call(this, v);
}
});
}, 0);
My goal is to run multiple silly ruby web applications one one machine, with the following caveats:
I’ll use fancy libs, not available on my host packaging system (there can be only one of those)
Theses libs, my code using it, and Ruby itself are probably riddled with vulnerabilities
I want to maintain & update as little OS & environment as possible
Docker, which means “Cancer” in ancient greek, is a no-go because
It’s cancer
I’ve decided to go with a simple LXC for the “special environement” with the shiny new packages I can play with.
In this LXC, I’ll run each of my snowflakes web app in their own sandbox, using Firejail.
All the following is running a LXC, running debian Sid. The LXC host is debian Stable running the Debian grsec kernel.
Step 1: the Ruby code
I usually write my silly webapps with Ruby + slim + sinatra. They are all nicely packaged in Debian Sid.
They look like this
$ tree /var/www/kurzy
.
├── config.ru
├── db
│ └── kurzy.sqlite
├── kurzy.rb
├── public
│ ├── jquery-3.2.1.min.js
│ └── kurzy.js
└── views
└── main.slim
Running ruby kurzy.rb will spawn a Rack webserver listening on port 4567. The problem with that is it’s single
threaded and heavy and weird.
Step 2: unleash the puma
Even though it has a .io domain name, I’ve decided to use puma for the webserving part. It
advertises some multithreading capabilities, and is also nicely packaged in Debian Sid.
This is where it gets a bit hairy, or furry (lol). I’m going to deploy more than one silly webapp, and need some
kind of centralized management of them so I can start/stop part or all of the apps.
Puma is easier dealt with when you use config files. I’ll store them in /etc/puma.conf.d
the .pid & state files are not going to be really used there, they probably can be ignored.
Don’t forget to create the puma folder in your app dir.
Your puma will listen on 9290, we’ll need this to drill holes in the host firewall and link it to nginx.
Step 3: Firejail, aka ‘bruteforcing config files & bash scripts until it works’
From here, this is mainly just madness from my part, as I don’t really know what I’m doing. You’ve been warned.
Each Puma will start in its own sandbox. Despite the fact that the website is hosted on Wordpress (srsly), Firejail is nicely packaged again, and definitely works out of the box, with caveats.
Grsecurity: Firejail does weird mount dances in chrooted environment, make sure you allow this.
The defaults for a Firejail sandbox are nice, but I wanted to disallow access to stuff below /var/www/ from /var/www/sillywebapp
This is a profile example:
read-write /var/www/kurzy
noblacklist /var/www/kurzy
blacklist /var/www/*
include /etc/firejail/default.profile
Store this file in your webapp dir to make it easier later.
I’m running these from a puma system user, and will start them from a init script. As seen in the Firejail profile, I allow /var/www/kurzy to be writeable. For this, /var/www/kurzy need to be owned by the puma user.
The server should start as user ‘puma’ and listen en port 9292. If it doesn’t, good luck. Try to append –debug everywhere and strace all the things. It’s a huge mess.
Step 4: Automate the madness
Everything is nice and tight. Repeat for all your webapps. Update the firejail profile to your needs.
Here is a simple example for a shitty /etc/init.d/puma to automate all of the start & stopping:
```bash
#! /bin/sh### BEGIN INIT INFO# Provides: puma# Required-Start: $remote_fs $syslog# Required-Stop: $remote_fs $syslog# Default-Start: 2 3 4 5# Default-Stop: 0 1 6# Short-Description: Puma web servers# Description: Start all the pumas### END INIT INFO# "set -e" probably breaks everything# Classic stuffPATH=/usr/local/bin:/usr/local/sbin/:/sbin:/usr/sbin:/bin:/usr/bin
DESC='Pumas motherboard script'NAME=puma
SCRIPTNAME=/etc/init.d/$NAMEPID_DIR=/var/run/puma
# Specifiv coptionsFIREJAIL_BIN='/usr/bin/firejail'FIREJAIL_OPTS='--quiet'PUMA_BIN='/usr/bin/puma'PUMA_OPTS='--quiet'PUMA_USER='puma'PUMA_CONFIG_DIR='/etc/puma.conf.d'. /lib/init/vars.sh
. /lib/lsb/init-functions
if[!-d"${PUMA_CONFIG_DIR}"];then
log_failure_msg "missing or empty config file $RSYNC_CONFIG_FILE"exit 0
fi
if[!-d"${PID_DIR}"];then
log_warning_msg "creating ${PID_DIR}"mkdir"${PID_DIR}"fi
read_puma_conf_val(){local readonly conf_file=$1local readonly key=$2echo$(grep"${key}""${conf_file}" | cut-d' '-f 2 | sed"s/\"//g" | sed s"/'//g")}
is_running_puma(){local readonly app_name=$1local firejail_cmd="${FIREJAIL_BIN} --quiet --list"
su -s /bin/sh -c"${firejail_cmd}""${PUMA_USER}" | grep-q"name=app_${app_name}"}
do_start_one_puma(){local readonly app_config=$1local readonly app_filename=$(basename"${app_config}")local readonly app_name="${app_filename%.*}"local readonly app_workdir=$(read_puma_conf_val "${app_config}""directory")if is_running_puma "${app_name}";then
log_success_msg "${app_name} already running"return 0
fi
if[!"$(stat-c %U ${app_workdir})"="${PUMA_USER}"];then
log_failure_msg "$app_workdir should be owned by ${PUMA_USER}"return 1
fi
log_daemon_msg "Starting ${app_name}"local puma_cmd="${PUMA_BIN}${PUMA_OPTS} -C ${app_config} >/dev/null &"if[!-f"${app_workdir}/firejail.profile"];then
generate_firejail_profile "${app_workdir}/firejail.profile""${app_workdir}"fi
jail_opts="${FIREJAIL_OPTS} --profile=${app_workdir}/firejail.profile"local firejail_cmd="${FIREJAIL_BIN}${jail_opts} --name=app_${app_name} -- ${puma_cmd}"
su -s /bin/sh -c"${firejail_cmd}""${PUMA_USER}"
log_end_msg $?}
generate_firejail_profile(){local readonly profile_file=$1local readonly app_workdir=$2local app_workdir_parent=$(dirname"${app_workdir}")cat<<EOF > "${profile_file}"
# This has been autogenerated by ${SCRIPTNAME}
# Feel free to modify to your needs
read-write ${app_workdir}
noblacklist ${app_workdir}
whitelist ${app_workdir}
blacklist ${app_workdir_parent}/*
include /etc/firejail/default.profile
EOF
}
do_stop_one_puma(){local readonly app_config=$1local readonly app_filename=$(basename"${app_config}")local readonly app_name="${app_filename%.*}"if! is_running_puma "${app_name}";then
log_success_msg "${app_name} not running"return 0
fi
log_daemon_msg "Stopping ${app_name}"local firejail_cmd="${FIREJAIL_BIN}${FIREJAIL_OPTS} --shutdown=app_${app_name} > /dev/null"
su -s /bin/sh -c"${firejail_cmd}""${PUMA_USER}"
log_end_msg $?}
do_start(){for puma_config in"${PUMA_CONFIG_DIR}"/*.conf;do
test-f"${puma_config}"||continue
do_start_one_puma "${puma_config}"done}
do_stop(){for puma_config in"${PUMA_CONFIG_DIR}"/*.conf;do
test-f"${puma_config}"||continue
do_stop_one_puma "${puma_config}"done}
do_status(){local firejail_cmd="${FIREJAIL_BIN} --list"
su -s /bin/sh -c"${firejail_cmd}""${PUMA_USER}"}case"$1"in
status)
do_status
;;
start)echo"Starting the pumas"
do_start
;;
stop)echo"Stopping the pumas"
do_stop
;;
restart)echo"Restarting $DESC"
do_stop && do_start
;;*)echo"Usage:">&2
echo" $SCRIPTNAME {start|stop|restart|status}">&2
;;esac```
I can try bleeding edge stuff in my Debian Stable host
some kind of containment, but as we’ll see this implementation is FAR FAR AWAY from secure things
Do the things
Setup the Host
So I’m running this on a almost-all-you-need-in-a-motherboard ASrock J3160DC. You need a new-ish kernel so all the good stuff in the Intel chipset is loaded. Also don’t disable stuff in your BIOS.
I’ll output video & sound through the HDMI cable.
Check that the following devices exist. If you don’t, try a more recent kernel (in this example I have linux-image-4.7.0-0.bpo.1-amd64 from jessie-backports).
# ls /dev/dri/card0
/dev/dri/card0
# ls /dev/snd/
by-path controlC0 hwC0D0 hwC0D2 pcmC0D0c pcmC0D0p pcmC0D1p pcmC0D2c pcmC0D3p pcmC0D7p pcmC0D8p seq timer
You’ll of course need some packages for your container.
Update your LXC config. This is very dirty, as it gives your guest access to your host’s hardware which basically defeats the purpose of container. Oh well.
# Common configuration
lxc.include = /usr/share/lxc/config/debian.common.conf
# HERE DO YOUR NETWORK CONFIG
#lxc.network.type = veth
lxc.network.flags = up
# that's the interface defined above in host's interfaces file
lxc.network.link = ....
lxc.network.hwaddr = ....
lxc.network.ipv4 = ....
lxc.network.ipv4.gateway = ....
lxc.rootfs = /dev/VG00/kodi-lxc
lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir
lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir
lxc.mount.entry = /dev/input dev/input none bind,optional,create=dir
lxc.mount.entry = /dev/tty7 dev/tty7 none bind,optional,create=file
lxc.mount = /var/lib/lxc/kodi-lxc/fstab
lxc.utsname = kodi-lxc
lxc.arch = amd64
lxc.autodev = 1
lxc.kmsg = 0
lxc.cgroup.devices.allow = c 226:0 rwm # /dev/dri/card0
lxc.cgroup.devices.allow = c 136:6 rwm # /dev/console
lxc.cgroup.devices.allow = c 116:* rwm # /dev/snd/*
lxc.cgroup.devices.allow = c 13:* rwm # /dev/input/* input devices
lxc.cgroup.devices.allow = c 4:7 rwm # /dev/tty7
Do what you need to connect to your guest, then it’s time for some the usual hygiene procedure.
Then for the user kodi, I made a silly script /home/kodi/kodi.sh
logger "Trying to start kodi"
while true ; do
if [[ `pidof kodi.bin` == "" ]]; then
if [ -f /tmp/.X0-lock ] ; then
logger "X is here! starting kodi"
DISPLAY=:0 kodi-standalone
logger "Kodi over and out"
fi
else
logger "kodi is around already"
exit
fi
sleep 2
done