Problema: Queremos obtener la api de una app

Solución: Instalar la app en Anbox y ejecutar un ataque man-in-the-middle para inspeccionar las llamadas http que hace la app

Para ello vamos a necesitar:

  • Anbox para ejecutar la aplicación
  • adb para interactuar con Anbox
  • mitmdump de mitmproxy para el ataque
  • un script que capture y guarde las peticiones http que nos interesen

1- Instalación

$ sudo -H pip3 install mitmproxy
$ snap install --devmode --beta anbox
$ sudo apt install android-tools-adb

2- Renombrar certificado para usar en android

$ ls ~/.mitmproxy
mitmproxy-ca-cert.cer  mitmproxy-ca-cert.pem  mitmproxy-dhparam.pem
mitmproxy-ca-cert.p12  mitmproxy-ca.pem
$ openssl x509 -inform PEM -subject_hash_old -in ~/.mitmproxy/mitmproxy-ca-cert.cer | head -1
c8750f0d
$ cp ~/.mitmproxy/mitmproxy-ca-cert.cer "$HOME/.mitmproxy/$(openssl x509 -inform PEM -subject_hash_old -in ~/.mitmproxy/mitmproxy-ca-cert.cer | head -1).0"
$ ls ~/.mitmproxy
c8750f0d.0             mitmproxy-ca-cert.p12  mitmproxy-ca.pem
mitmproxy-ca-cert.cer  mitmproxy-ca-cert.pem  mitmproxy-dhparam.pem

3- Instalar certificado en Anbox

$ sudo snap set anbox rootfs-overlay.enable=true
$ sudo snap restart anbox.container-manager
$ sudo snap run --shell anbox.container-manager
$ mkdir -p /var/snap/anbox/common/rootfs-overlay/system/etc/security/cacerts
$ cp /home/TU_USUARIO/.mitmproxy/c8750f0d.0 /var/snap/anbox/common/rootfs-overlay/system/etc/security/cacerts/
$ sudo chown -R 100000:100000 /var/snap/anbox/common/rootfs-overlay
$ exit
$ sudo snap restart anbox.container-manager

4- Configurar proxy en Anbox

$ adb devices
List of devices attached
emulator-5558   device
$ adb shell settings put global http_proxy 192.168.1.126:8080

Nota: Cambia 192.168.1.126 por la ip de la máquina donde se ejecutar mitmproxy

5- Instalar app en Anbox

$ adb install app.apk

6- Crear script para capturar tráfico

Por lo general nos van a interesar las peticiones que devuelvan json. El siguiente script (mitmjson.py) captura esas peticiones y las guarda en una estructura de directorios basada en el hostname y path de la url.

Nota: Para evitar capturar peticiones indiscriminadamente, este script solo guarda ficheros json en carpetas que ya existan, es decir, debemos crear manualmente una carpeta por hostname que queramos capturar.

import json
import os
import sys
from datetime import datetime
from urllib.parse import urlparse
import yaml

from mitmproxy.net.http import cookies

def get_json(text):
    if text:
        try:
            return json.loads(text)
        except Exception as e:
            pass
    return None

def format_request_cookies(fields):
    return format_cookies(cookies.group_cookies(fields))


def format_response_cookies(fields):
    return format_cookies((c[0], c[1][0], c[1][1]) for c in fields)

def format_cookies(cookie_list):
    rv = []

    for name, value, attrs in cookie_list:
        cookie_har = {
            "name": name,
            "value": value,
        }

        for key in ("path", "domain", "comment"):
            if key in attrs:
                cookie_har[key] = attrs[key]

        rv.append(cookie_har)

    return rv

def name_value(obj):
    r = {}
    for k, v in obj.items():
        r[k] = v
    return r

def response(flow):
    uparse = urlparse(flow.request.url)

    if not uparse.hostname:
        print(flow.request.url)
        return

    js = get_json(flow.response.get_text(strict=False))
    if not js:
        return

    if uparse.hostname not in os.listdir("."):
        print(flow.request.url)
        return

    req_cookies = format_request_cookies(flow.request.cookies.fields)
    res_cookies = format_response_cookies(flow.response.cookies.fields)
    req_content_type = flow.request.headers.get("Content-Type", None)
    res_content_type = flow.response.headers.get("Content-Type", None)
    res_redirect = flow.response.headers.get('Location', None)

    info = {
        "request": {
            "method": flow.request.method,
            "url": flow.request.url,
            "headers": name_value(flow.request.headers),
        },
        "response": {
            "status": flow.response.status_code,
            "statusText": flow.response.reason,
            "headers": name_value(flow.response.headers),
            "json": js
        }
    }

    if len(req_cookies) > 0:
        info["request"]["cookies"] = req_cookies
    if len(res_cookies) > 0:
        info["response"]["cookies"] = res_cookies
    if res_content_type:
        info["response"]["mimeType"] = res_content_type
    if res_redirect:
        info["response"]["redirectURL"] = res_redirect

    if flow.request.method in ("GET", "POST", "PUT", "PATCH"):
        req_params = {}
        for a, b in flow.request.urlencoded_form.items(multi=True):
            req_params[a] = b
        req_text = flow.request.get_text(strict=False)
        req_json = get_json(req_text)
        postData = {}
        if req_content_type:
            postData["mimeType"] = req_content_type
        if len(req_params) > 0:
            postData["params"] = req_params
        if req_json:
            postData["json"] = req_json
        elif req_text:
            postData["text"] = req_text
        info["request"]["postData"] = postData

    name = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
    if uparse.path:
        name = name + uparse.path.replace("/", ".")
    name = uparse.hostname + "/" + name + ".json"
    print(flow.request.url + " ---> " + name)
    with open(name, "w") as f:
        f.write(json.dumps(info, indent=4, sort_keys=True))

7- Arrancar mitmdump

$ mkdir hostname.com
$ mitmdump --flow-detail 0 -s "mitmjson.py" | sed '/:.* \(clientdisconnect\|clientconnect\)/d'

Nota: Uso sed para eliminar de la salida los abundantes reportes por conexiones y desconexiones.

Fuentes: jeroenhd.nl, github - anbox/issues/398, github - anbox/issues/1097, docs.anbox.io