Android-app reverse-engineeren: de zwakke plekken van mydlink SharePort

De app naast de router

Bij veel IoT-apparaten hoort een mobiele app, en die app is een volwaardig aanvalsvlak dat bij beveiligingsonderzoek vaak wordt vergeten. In ons onderzoek naar de D-Link DIR-890L hoorden twee apps bij de router: mydlink SharePort (versie 1.6.0.23), een app voor het delen van bestanden en printers op je netwerk. De router zelf bleek kwetsbaar, maar de app deed daar nauwelijks voor onder.

In dit artikel reverse-engineeren we de app en laten we je zien welke zwakke plekken we vonden. De techniek die je hier leert, werkt op elke Android-app: van je eigen apps tot die van je bank.

Stap 1: de APK uitpakken met apktool

Een Android-app is een zip-archief met een specifieke structuur, en apktool is de tool om dat archief fatsoenlijk te ontleden:

apktool d -s com.dlink.srd1.app.shareport-v1.6.0.23.apk

De vlag -s laat de dalvik-bytecode ongemoeid (die pakken we later uit) en decodeert alleen de resources en het manifest. Wat je daarna als eerste leest: AndroidManifest.xml, met alle permissies die de app vraagt. Bij SharePort viel het op hoeveel de app wil:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

Een app voor het delen van bestanden die je telefoonstatus mag uitlezen, uitgaande gesprekken wil zien en je locatie wil weten: dat roept vragen op. Soms is er een legitieme reden, vaak is het gewoon slordig meegevraagd. Dit is de eerste les van app-analyse: permissies zijn een natuurlijke indicatie van bedoelingen.

Stap 2: van dex naar leesbare Java

De eigenlijke programmacode zit in classes.dex, gecompileerde dalvik-bytecode. Die lees je niet zomaar. De standaardroute:

d2j-dex2jar classes.dex
jd-gui classes-dex2jar.jar

d2j-dex2jar zet de dex om naar een Java-archief, en JD-GUI toont de broncode in leesbare (zij het geobfusceerde) vorm. Namen als a.class, e.class en FM.class verraden dat de maker de code heeft geobfusceerd, maar de structuur van het programma blijft zichtbaar. En voor beveiligingsonderzoek is structuur genoeg.

Bevinding 1: MD5 als “encryptie”

In a.class duikt een klassieke op:

System.arraycopy(MessageDigest.getInstance("MD5").digest(this.k.getBytes()), 0, arrayOfByte, 2, 16);

MD5 wordt hier gebruikt om een wachtwoord of sleutel tot een vaste hash te verwerken, vermoedelijk voor een authenticatiesleutel richting de router. Het probleem: MD5 is al sinds 2004 gebroken voor collision-weerstand en is sinds jaar en dag ongeschikt voor ook maar één beveiligingsdoel. Dezelfde constructie bleek terug te komen in c.class, j.class en l.class: het patroon zit dus in de kern van de app, niet in één los functietje.

Het lieve aan deze bevinding is de eenvoud: je zoekt in de broncode gewoon naar getInstance en leest welke algoritme er staat. MD5 of SHA-1? Vaak niet de bedoeling. De moderne keuze is SHA-256 of beter.

Bevinding 2: de certificatencheck die alles goedkeurt

Dit was de zwaarste vondst. In e.class, de netwerkbibliotheek van de app, staat een eigen TrustManager:

private class c implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] param1ArrayOfX509Certificate, String param1String) throws CertificateException {}

    public void checkServerTrusted(X509Certificate[] param1ArrayOfX509Certificate, String param1String) throws CertificateException {}

    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

Lees de methodes goed: ze zijn leeg. checkServerTrusted ontvangt het certificaat van de server en gooit het keurig weg zonder één controle. Elke server met elk certificaat, self-signed of gestolen, wordt vertrouwd.

De praktische betekenis: TLS heeft nog wel versleuteling, maar de identiteitscontrole is weg. Wie op hetzelfde netwerk zit (denk: openbaar wifi, of een aanvaller die via de router zelf binnenkomt, zie de HNAP-injectie op de bijbehorende router) kan een man-in-the-middle opzetten en verkeer van en naar de app meelezen en aanpassen. Dit patroon komt overigens in meer apps voor dan je hoopt: het is de “make it work”-oplossing van een developer die met een ontwikkeling-certificaat zat te worstelen. Het patroon is in de broncode met een vergrootglas te herkennen.

Bevinding 3: plain HTTP naar de cloud

De derde vondst was een kwestie van zoeken in de broncode op http://. De app praat op meerdere plekken over onversleutelde verbindingen:

public static List<c> getSupportDeviceFromServer() {
    String str = b.i("http://dj76r4wyeddaj.cloudfront.net/devices.cfg").replace("\r\n", "");
    ...
    e1.c(String.format("http://%s/index.php?ss=%s", ...));

En daarnaast: checkip.dyndns.org voor het bepalen van het externe IP-adres, en wrpd.dlink.com voor firmware-updates en IP-query’s, allemaal via http. Wat er precies over die lijnen gaat is in elk geval deelbaar met iedereen op het pad. Voor een app die gaat over bestanden delen is dat een wrange combinatie.

Wat leert dit je over app-analyse?

De drie bevindingen zijn gevonden met drie eenvoudige technieken, en dat is precies de les: app-analyse is laagdrempelig en levert meestal binnen een uur iets op.

  1. Lees het manifest. Permissies vertellen je waar de app belang bij heeft.
  2. Zoek op algoritmenamen. MD5, SHA-1, DES in getInstance-aanroepen zijn vlaggen.
  3. Zoek op TrustManagers. Lege checkServerTrusted-implementaties zijn bijna altijd fout.
  4. Zoek op http:// (niet https). Elke hit is op z’n minst een vraag.

De mitigaties, voor als je zelf apps bouwt

  • Gebruik SHA-256 of SHA-3 voor hashing; MD5 en SHA-1 horen alleen nog in legacy-dumpsters.
  • Laat de TrustManager intact. Als een certificaat niet vertrouwd is, is dat een feature, geen bug.
  • HTTPS overal, ook voor “even een config-bestand ophalen”. Er is geen excuus meer.
  • Vraag permissies die je niet nodig hebt niet aan. READ_PHONE_STATE en PROCESS_OUTGOING_CALLS horen niet in een bestands-app.

Zelf aan de slag

De volledige gereedschapskist voor dit onderzoek (apktool, dex2jar, JD-GUI, en de router-kant met Binwalk en FirmAE) draait op Kali Linux, en die zet je op in een middagje: zie onze handleiding zet je eerste eigen hacklab op. Het volledige onderzoek, van firmware-download tot root-shell, lees je in de complete walkthrough, en de statische analyse van de router zelf staat in het Binwalk-artikel.