Zamów książkę sekuraka o bezpieczeństwo aplikacji www!

Testy aplikacji na Androida: zmiana sposobu działania aplikacji przez rekompilację pliku APK

27 marca 2015, 17:15 | Teksty | komentarzy 5

Wstęp

W ostatnich latach praca pentestera coraz częściej składa się też z testów aplikacji mobilnych. Czasem są one tylko cienką nakładką na strony internetowe, kiedy to testowanie ich niewiele różni się od webaplikacji, ale w wielu przypadkach są pełnoprawnymi, dużymi aplikacjami. W przeciwieństwie do typowych aplikacji webowych, gdzie logika zaimplementowana jest głównie po stronie serwera (choć to też przestaje być prawdę od pewnego czasu, gdyż coraz więcej dzieje się po stronie Javascriptu), w aplikacjach mobilnych mamy bezpośredni dostęp do plików wykonywalnych.

Czasem logika działania tychże aplikacji utrudnia bądź uniemożliwia testowanie. Przykładowo:

  • Przy uruchomieniu aplikacja sprawdza, czy telefon jest zrootowany,
  • Aplikacja definiuje własne metody kontroli ważności certyfikatów HTTPS, odmawiając współdziałania z proxy.

Oczywiście nie chcemy „odrootowywać” telefonu na potrzeby jednej aplikacji czy rezygnować z używania proxy, dlatego musimy znaleźć inny sposób, by poradzić sobie z problemem. Zasadniczo możemy zastosować jedno z trzech podejść:

  • Przeanalizować sposób działania uciążliwej funkcji i odnaleźć w niej błąd (np. sprawdzanie czy telefon jest zrootowany może być niewystarczająco dobre),
  • Zmienić w sposób dynamiczny sposób działania aplikacji (np. przez framework Xposed czy Cydia Substrate/Cycript w przypadku iOS),
  • Zdekompilować aplikację, podmienić działanie złośliwej metody i zbudować ponownie plik .apk,

W tym artykule zajmiemy się wyłącznie trzecim sposobem. Do pozostałych (w szczególności do drugiego) prawdopodobnie jeszcze na łamach Sekuraka wrócimy.

Na potrzeby niniejszego artykułu przygotowałem prostą aplikację androidową, której jedynym celem jest próba pobrania pewnego pliku przez HTTPS. W domyślnej konfiguracji jednak się to nie uda (Rys 1.). W kolejnych rozdziałach prześledzimy działanie aplikacji (o której będę dalej pisał jako „testowa aplikacja”) i zmienimy jej kod w taki sposób, by wykonał się poprawnie.

Rys 1. Testowa aplikacja twierdzi, że certyfikat SSL jest niepoprawny

Rys 1. Testowa aplikacja twierdzi, że certyfikat SSL jest niepoprawny

Zanim zaczniemy…

W dalszej części tekstu zakładam, że do przeprowadzenia kolejnych operacji używamy dystrybucji linuksowej Kali, która zawiera w repozytorium (prawie) wszystkie potrzebne pakiety. Oczywiście opisane niżej kroki można wykonywać również na innych systemach, może jednak być wymagana ręczna instalacja narzędzi.

Krok 1. Pobranie pliku APK

Zacznijmy od tego, że musimy mieć plik APK, by mieć na czym przeprowadzać zmiany. W przypadku przeprowadzania testów penetracyjnych, zwykle podsyła je sam klient. Czasem jednak jesteśmy proszeni o instalację aplikacji bezpośrednio z Google Play. W takim wypadku, plik APK możemy pobrać na jeden z dwóch sposobów:

  • Ustawiamy proxy w urządzeniu z Androidem, po czym pobieramy aplikację przez sklep Play. W jednej z odpowiedzi serwera na pewno znajdziemy pełną treść pliku APK,
  • Jeśli mamy roota, plik APK można pobrać bezpośrednio z katalogu /data/app na urządzeniu.

My mamy konkretnego linka do aplikacji testowej, którą musimy najpierw pobrać:

test@mbkali:~/android$ wget http://training.securitum.com/apk/pl.sekurak.ssltest.apk

test@mbkali:~/android$ md5sum pl.sekurak.ssltest.apk 
cf8cfdb6689237d3e7a988b943a5a5c6  pl.sekurak.ssltest.apk

Krok 2. Dekompilacja i poszukiwanie „winnej” metody

Problem z testową aplikacją jest taki, że próbuje pobrać plik przez HTTPS, jednak certyfikat SSL nie przechodzi walidacji. Możemy domniemywać, że gdzieś w kodzie znajduje się metoda odpowiedzialna za weryfikację poprawności certyfikatów. W tym kroku spróbujemy ją odnaleźć.

Potrzebować będziemy dwóch narzędzi: dex2jar oraz JD-GUI. W dystrybucji Kali ten pierwszy pakiet możemy ściągnąć bezpośrednio z repozytorium:

apt-get install dex2jar

JD-GUI też znajduje się w repozytorium, ale polecam jednak ściągnąć najnowszą wersję z oficjalnej strony aplikacji:

wget https://github.com/java-decompiler/jd-gui/raw/master/dist/jd-gui-1.0.0-RC2.jar
APK (Android application package) – to format plików używanych do dystrybucji aplikacji na Androida. Zawierają one wszystkie elementy niezbędne do prawidłowego działania aplikacji, takie jak: certyfikaty, podpisy cyfrowe, zasoby (resources/assets), biblioteki i – przede wszystkim – skompilowany kod aplikacji w pliku classes.dex. W rzeczywistości pliki APK są zwykłym archiwum zip, które można rozpakować dowolnym archiwizatorem.

Plik classes.dex zawiera skompilowany kod aplikacji w postaci zrozumiałej dla wirtualnej maszyny Androida (zwanej Dalvik). Narzędzie dex2jar służy do zamiany pliku dex do pliku jar, który z kolei będzie można czytać przez dekompilator.

JD-GUI – to dekompilator plików .jar. Użyjemy go do wygodnego przeglądania kodu aplikacji androidowej.

Wyciągnijmy teraz plik classes.dex z pliku APK, a następnie zamieńmy go do postaci JAR-a.

test@mbkali:~/android$ unzip pl.sekurak.ssltest.apk classes.dex
Archive:  pl.sekurak.ssltest.apk
  inflating: classes.dex             
test@mbkali:~/android$ d2j-dex2jar classes.dex 
dex2jar classes.dex -> classes-dex2jar.jar
test@mbkali:~/android$ ls
classes.dex  classes-dex2jar.jar  jd-gui-1.0.0-RC2.jar  pl.sekurak.ssltest.apk
Narzędzie d2j-dex2jar można uruchomić bezpośrednio na plik apk; nie ma potrzeby wyciągać pliku classes.dex. Plik .dex będzie nam jednak potrzebny później do rekompilacji źródeł, dlatego wypakowałem go na samym początku.

Jak widzimy, utworzony został plik classes-dex2jar.jar. Odpalmy więc dekompilator Javy i zobaczmy, co jest w środku (Rys 2.).

java -jar jd-gui-1.0.0-RC2.jar
Rys 2. Domyślne okno JD-GUI

Rys 2. Domyślne okno JD-GUI

Z lewej strony widzimy listę klas, zaś po kliknięciu na jednej z nich, jej zdekompilowane źródło pojawia się w głównej części aplikacji. Jak widać, aplikacja testowa jest mało rozbudowana, bowiem składa się wyłącznie z dwóch klas. Oczywiście w rzeczywistych przypadkach najczęściej jest ich o wiele więcej i samo przeglądanie kodu w poszukiwaniu interesującej nas funkcji może zająć sporo czasu.

Szybko możemy znaleźć, że klasa pl.sekurak.ssltest.a zawiera metodę odpowiedzialną za sprawdzanie poprawności certyfikatów SSL.

package pl.sekurak.ssltest;

import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;

public class a
  implements X509TrustManager
{
  public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) {}
  
  public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString)
  {
    if (paramArrayOfX509Certificate[0] == null) {
      throw new CertificateException();
    }
    if (paramArrayOfX509Certificate[0].getIssuerDN().getName() != "CN=sekurakowy.pl,O=sekurak.pl,C=PL") {
      throw new CertificateException();
    }
  }
  
  public X509Certificate[] getAcceptedIssuers()
  {
    return new X509Certificate[0];
  }
}

Okazuje się, że aplikacja sprawdza, czy nazwa wystawcy certyfikatu to „CN=sekurakowy.pl,O=sekurak.pl,C=PL”. Oczywiście moglibyśmy na potrzeby testów wystawić certyfikat z taką nazwą. Spróbujemy jednak zmodyfikować aplikację w taki sposób, aby przepuszczała każdy certyfikat, niezależnie od jego parametrów.

Z tego kroku musimy zapamiętać, że metoda, której działanie będziemy chcieli zmienić, znajduje się w klasie pl.sekurak.ssltest.a i nazywa się checkServerTrusted. Logika działania metody jest taka, że jeśli certyfikat jest poprawny, metoda nie zwraca nic; zaś w przeciwnym wypadku rzucany jest wyjątek.

Krok 3. Rekompilacja i podpisanie pliku APK

Aby zacząć wprowadzać zmiany w kodzie pliku classes.dex, musimy najpierw skonwertować do formatu smali.

Smali – to asembler/disasembler plików .dex, którego składnia opiera się na Jasmin. Zestaw narzędzi składa się z dwóch aplikacji: smali – do asemblacji (smali->dex) oraz baksmali (dex->smali).

Zaczynamy od zainstalowania narzędzi smali:

apt-get install smali

Następnie konwertujemy plik classes.dex do postaci plików smali:

Narzędzia smali/baksmali są w dystrybucji Kali trochę nieszczęśliwie skonfigurowane. Dlatego konieczne jest dodawanie $PWD/ (aktualny katalog roboczy), by brane pod uwagę były pliki z obecnego katalogu.
baksmali $PWD/classes.dex -o $PWD/smali

W wyniku wykonania powyższego polecenia, utworzony zostanie katalog smali składający się z wielu folderów oraz plików o rozszerzeniu .smali, odpowiadających klasom w pliku classes.dex (tym samym, które wcześniej oglądaliśmy z poziomu JD-GUI).

Pamiętamy, że chcieliśmy edytować klasę pl.sekurak.ssltest.a. Dlatego przyjrzymy się zawartości pliku smali/pl/sekurak/ssltest/a.smali:

.class public Lpl/sekurak/ssltest/a;
.super Ljava/lang/Object;

# interfaces
.implements Ljavax/net/ssl/X509TrustManager;


# direct methods
.method public constructor <init>()V
    .registers 1

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method


# virtual methods
.method public checkClientTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    .registers 3

    return-void
.end method

.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    .registers 5

    const/4 v1, 0x0

    aget-object v0, p1, v1

    if-nez v0, :cond_b

    new-instance v0, Ljava/security/cert/CertificateException;

    invoke-direct {v0}, Ljava/security/cert/CertificateException;-><init>()V

    throw v0

    :cond_b
    aget-object v0, p1, v1

    invoke-virtual {v0}, Ljava/security/cert/X509Certificate;->getIssuerDN()Ljava/security/Principal;

    move-result-object v0

    invoke-interface {v0}, Ljava/security/Principal;->getName()Ljava/lang/String;

    move-result-object v0

    const-string v1, "CN=sekurakowy.pl,O=sekurak.pl,C=PL"

    if-eq v0, v1, :cond_1f

    new-instance v0, Ljava/security/cert/CertificateException;

    invoke-direct {v0}, Ljava/security/cert/CertificateException;-><init>()V

    throw v0

    :cond_1f
    return-void
.end method

.method public getAcceptedIssuers()[Ljava/security/cert/X509Certificate;
    .registers 2

    const/4 v0, 0x0

    new-array v0, v0, [Ljava/security/cert/X509Certificate;

    return-object v0
.end method

Powyższy kod odpowiada dokładnie temu,co wkleiłem wcześniej w postaci źródła Javy; różnica polega na tym, że teraz mamy kod w postaci asemblera smali. Zmodyfikujmy kod metody checkServerTrusted w taki sposób, aby natychmiast kończyła swoje działanie. Powinna więc zawierać wyłącznie dyrektywę return-void.

.class public Lpl/sekurak/ssltest/a;
.super Ljava/lang/Object;

# interfaces
.implements Ljavax/net/ssl/X509TrustManager;


# direct methods
.method public constructor <init>()V
    .registers 1

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method


# virtual methods
.method public checkClientTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    .registers 3

    return-void
.end method

.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    .registers 5

   return-void
.end method

.method public getAcceptedIssuers()[Ljava/security/cert/X509Certificate;
    .registers 2

    const/4 v0, 0x0

    new-array v0, v0, [Ljava/security/cert/X509Certificate;

    return-object v0
.end method

Teraz musimy z powrotem zamienić pliki smali do classes.dex. Robimy to poleceniem smali:

smali $PWD/smali -o $PWD/classes.dex

A następnie władowujemy nowy classes.dex do pliku APK:

test@mbkali:~/android$ zip pl.sekurak.ssltest.apk classes.dex 
updating: classes.dex (deflated 58%)

Pliki APK są podpisane cyfrowo. Skoro zmieniliśmy zawartość pliku, podpis cyfrowy również się zmieni. Na szczęście w ramach dex2jar istnieje narzędzie, które pozwala szybko podpisać plik APK: d2j-apk-sign:

test@mbkali:~/android$ d2j-apk-sign pl.sekurak.ssltest.apk 
sign pl.sekurak.ssltest.apk -> pl.sekurak.ssltest-signed.apk

W wyniku wykonywania tego polecenia został utworzony nowy plik: pl.sekurak.ssltest-signed.apk. Pozostaje go tylko zainstalować na urządzeniu.

Krok 4. Instalacja nowego APK

Instalujemy teraz nowy plik apk na urządzeniu z Androidem. Musimy jednak wpierw odinstalować poprzednią wersję aplikacji testowej, ponieważ Android wykryje, że zmienił się podmiot podpisujący aplikację (poprzednio był to deweloper aplikacji, teraz d2j-apk-sign; Rys 3.).

Rys 3. Android odmawia aktualizacji aplikacji z innym podmiotem podpisującym

Rys 3. Android odmawia aktualizacji aplikacji z innym podmiotem podpisującym

Po odinstalowaniu i ponownej instalacji aplikacji, możemy wreszcie uruchomić ją ponownie. Powinniśmy na ekranie zobaczyć to samo co na Rys 4.

Rys 4. Działanie aplikacji testowej po rekompilacji

Rys 4. Działanie aplikacji testowej po rekompilacji

(wyświetlana jest zawartość pliku spod adresu https://raw.githubusercontent.com/securityMB/random-stuff/master/apk-file.txt)

Podsumowanie

W tekście przedstawiłem jak w prosty sposób można zmienić działanie aplikacji na Androida. Ogólnie proces rekompilacji aplikacji dla własnych potrzeb składa się z następujących elementów:

  1. Pobranie pliku .apk,
  2. Dekompilacja pliku .apk w celu odnalezienia metody, której działanie chcemy zmienić,
  3. Deasemblacja skompilowanego pliku classes.dex do plików smali,
  4. Modyfikacja wybranego pliku/wybranych plików smali,
  5. Ponowne skompilowanie pliku classes.dex i utworzenie nowego pliku .apk,
  6. Ponowny podpis pliku .apk.

Wkrótce na Sekuraku przedstawimy inne podejście do tego samego problemu – tj. jak zmienić działanie aplikacji bez modyfikacji oryginalnego pliku APK.

–Michał Bentkowski, prowadzi bloga, pentester w Securitum.

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. mam nizsze wyksztalcenie

    proste, gorzej jak apka jest obfuskowana i posiada z 500 klas ;)

    Odpowiedz
    • ader

      Nee – jak wiesz czego szukać to znajdziesz to w 2 minuty (nazwy Javowych klas i metod nie mogą zostać zmienione)

      Odpowiedz
  2. oi

    ader, może podpowiesz na jakie klasy warto zwrócić uwagę? ;)

    Odpowiedz
    • Michał Bentkowski

      Warto zacząć właśnie od szukania metody checkServerTrusted lub po nazwie klasy SSLSocketFactory.

      Odpowiedz
  3. Radosław

    A jak exploita dodać do jakiejś aplikacj na przykład

    Odpowiedz

Odpowiedz na oi