Category: Software


PREVENTING USER AND HARDWARE TRACKING IN MOBILE DEVICES
by 
David Robert Stites

B.S., Purdue University, 2007

Thesis directed by Professor Rory Lewis

A thesis submitted to the Graduate Faculty of the University of Colorado at Colorado Springs in partial fulfillment of the requirements for the degree of Master of Science
Department of Computer Science

© Copyright By David Robert Stites 2012 
All Rights Reserved

Mobile devices, such as smartphones or PDAs, have become increasingly popular with consumers and often provide essential functionality in their everyday life. Usually these mobile devices contain a great deal of sensitive information such as addresses, contacts, ingoing/outgoing call logs, SMS messages and, on the latest models, a calendar, emails and potentially the user’s current location. A smartphone or mobile device today can be as powerful as a desktop or laptop in some respects and, while the latest models feature a complete OS, for many users these devices are “just phones” so there is an underestimation of the risk connected to mobile device privacy. There is a currently existing privacy problem associated with user and hardware tracking in mobile devices. Users can be tracked without their knowledge and consent and have rich profiles built about them using their hardware interface address regarding their location and preferences. This information can be potentially cross correlated to other existing datasets to build advertising profiles for these users. The mitigation to this problem using a framework to support randomly generated, disposable hardware addresses.

Full text of the document can be found here: PDF

Some new classes were introduced in Foundation.framework as part of Mac OS X 10.8 (Mountain Lion) to help ease the pain associated with performing IPC (inter-process communication) in Mac OS applications. Among them were NSXPCConnection, NSXPCListener, NSXPCListenerDelegate and NSXPCInterface. You can find the documentation inside the development portal or as part of the Xcode bundle but this post is meant to show you how easy it truly is to package up messages and send them off to other processes.

But first, a bit of background. What is IPC? Courtesy of WikiPedia, “IPC is a set of methods for the exchange of data among multiple threads in one or more processes. Processes may be running on one or more computers connected by a network. IPC methods are divided into methods for message passing, synchronization, shared memory, and remote procedure calls (RPC). The method of IPC used may vary based on the bandwidth and latency of communication between the threads, and the type of data being communicated.”

Originally, you could have done IPC in OS X using mach messages, which is how drivers traditionally communicated.

While information sharing and modularity are definitely some of the benefits of IPC, one of the biggest wins in my mind is the fact that we can perform privilege separation with IPC. Consider the following: you have wrote some code that will take some user input and crunch on it and then return a result. Note that this doesn’t have to be intensive computation, it could be as easy as interpolating an NSString. User input is a taint source, meaning that the input data is untrusted and could potentially (and perhaps unintentionally) be malicious. If your program were running in a privileged mode or had some increased set of ACLs, then if the input were able to exploit a vulnerability, then it would be able to inherit the same privilege level as the application.

Another benefit of this separation is that suppose the input causes the program to crash. If the processing were done in the main application, it would crash the entire application. If the processing is done in the daemon, then the daemon can crash and the application would still be alive and well.

I have attached a project that demonstrates NSURLConnection’s ability to say “Hello.” hello.tar.gz

Everyone knows — or at least everyone SHOULD know — that email is not a secure form of communication. It’s a lot like yelling across a parking lot. Your message is sent “in the clear” along most of the connections that lie between you and the recipient. For the times when you want to send a message that does NOT stand out in the open for others to read, StegaGram is your answer.

Double Armor
StegaGram protects your communication in two ways. First, it locks the message so that it can only be read by the person to whom you’re sending the message. Then, it hides the locked message inside a picture, so that it doesn’t even look like a locked message is being sent. As an analogy, consider keeping your valuables in a strong safe, located in your front yard. It’s a great safe, but why invite attention to the fact that you have it? Using StegaGram is like keeping that strong safe hidden in a secret panel behind a picture in your house.

No Password, No Problem
Short passwords are not very secure because they can be quickly guessed by computer programs. Long passwords are better, but can be hard to remember. We chose to avoid these problems altogether by using long strings of random numbers, known as keys. If you want more details you can read more below. Otherwise, suffice it to say that it’s stronger than a password but you don’t need to remember anything. You just need to pass a key to your friend using a QR Code — those barcode-looking squares you see all over the place.

Under the Hood
You know those cars that look cool from the outside but lack actual power and performance when driving? Yeah, that’s not us. StegaGram is clean and easy to use, but also employs the latest methods of cryptography and steganography. In fact, our initial version was denied for public distribution because it was too strong. We had to tone it down a bit. As for our hiding methods, they don’t just avoid detection by the human eye. We use a technique that passes well under the radar of digital analysis programs which search for anomalies in histograms.

For the Nerds
StegaGram uses a combination of asymmetric cryptography and an optimized version of the Graph-Theoretic approach to steganography. The asymmetry of the cryptographic keys allows for a distributed authentication model, similar to that used in the PGP community. Our initial version uses 2048-bit RSA encryption. As for the key exchange, the QR-Code method prevents the classic ‘Man-in-the-Middle Attack’ used against the Diffe-Helman pattern, because there is no communication over a network during the exchange. In addition, our steganographic algorithm preserves first-order statistics, unlike most other freely-available steganographic software. For more details, take a look through our research paper.

Fine Print
This application was created for academic and recreational purposes, and comes with no guarantees or warrantees for its information protection. It’s pretty burly, as mentioned above, but is used at your own risk. Thank you to Alex Renger for the idea of StegaGram and for the steganographic algorithm. Thank you to Dr. Yue from the University of Colorado at Colorado Springs for his teaching and support.

I recently had a need to build a licensing module in Java for a project I was working on.  All of the modules out there cost money, so I figured that I would release a free one.  What is neat about this is that it is pretty simple easy and fast to implement and it comes with a license key generator.  The best part is that it relies on PKI, so unless someone were able to patch the binary to skip the authentication check, they would have to be able to break a 2048bit RSA key (which is pretty safe considering no one has broken a 1024bit RSA key).

This particular licensing module has support for multiple license types: Trial, Single Version and Lifetime.  Also, the license has support for information such as name, email, license number, license type, expiration date and version number.  There is support for blacklisted, invalid, phony, and expired keys.  The one thing I want to mention before we get into the code is that you’ll need a public and private keypair (you can use OpenSSL to do this) in .der format (an X509 certificate using OpenSSL again).  So without further ado…

Licensing code zip

License.java: This is your license object.  It will be written to disk and checked with the program starts.  It contains all the information the managers will need to check the license.

package License;

import java.io.*;
import java.util.*;

public class License implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String email;
    private String licenseNumber;
    private LicenseType licenseType;
    private Date expiration;
    private String version;

    public License() {
        name = "";
        email = "";
        licenseNumber = "";
        expiration = new Date();
        version = "";
        licenseType = LicenseType.TRIAL;
    }

    public License(String name, String email, String licenseNumber, Date expiration, LicenseType licenseType, String version) {
        this.name = name;
        this.email = email;
        this.licenseNumber = licenseNumber;
        this.expiration = expiration;
        this.licenseType = licenseType;
        this.version = version;
    }

    // getters and setters here
}

KeyStatus.java: This is an enumeration that returns the status of a key validation operation.

package License;

public enum KeyStatus {
    KEY_GOOD,
    KEY_INVALID,
    KEY_BLACKLISTED,
    KEY_PHONY,
    KEY_EXPIRED
}

LicenseType.java: This is an enumeration that represents the type of license being generated.

package License;

public enum LicenseType {
    TRIAL, SINGLE_VERSION, LIFETIME
}

LicenseFileFilter.java: This is purely for usability on the UI end.  We can provide the JFileChooser this filter and it will find only our license files.

package License;

import Utility.FileExtension;
import java.io.File;
import javax.swing.filechooser.FileFilter;

public class LicenseFileFilter extends FileFilter {
    public boolean accept(File f) {
        return f.isDirectory() || f.getName().toLowerCase().endsWith(FileExtension.ZIP);
    }

    public String getDescription() {
        return "License files";
    }
}

LicenseManager.java: This is the main workhorse for the licensing code.  It generates the keys and checks them.

package License;

import java.io.*;
import java.net.*;
import java.security.*;
import java.util.*;
import javax.swing.JOptionPane;

public class LicenseManager {
    private static LicenseManager instance;
    public static boolean IS_TRIAL = true;
    public static boolean IS_LICENSED =  false;
    public static License LICENSE = null;
    private static final int ENTROPY = 456456456;
    private static final String HEXES = "0123456789ABCDEF";

    public static final String LICENSE_FILENAME = "license";
    public static final String HASH_FILENAME = "license.sha1";
    public static final String SIGNATURE_FILENAME = "license.sig";

    private static final int KEY_LEN = 62;
    private static final byte[] def = new byte[]{24, 4, 124, 10, 91};
    private static final byte[][] params = new byte[][]{{24, 4, 127}, {10, 0, 56}, {1, 2, 91}, {7, 1, 100}};
    private static final Set blacklist = new TreeSet();

    private Timer t;
    private static final int DELAY = 900000;

    static {
        blacklist.add("11111111");
    }

    protected LicenseManager() {
        t = new Timer();
        t.scheduleAtFixedRate(new CheckLicenseTask(), DELAY, DELAY);
    }

    public static LicenseManager getLicenseManager() {
        if (instance == null) {
            instance = new LicenseManager();
        }

        return instance;
    }

    /**
     *
     * @param lic
     */
    private void writeLicenseFile(License lic, String path) {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(path + LICENSE_FILENAME)));
            oos.writeObject(lic);
            oos.close();
        }
        catch(Exception ex) { }
    }

    public static KeyStatus readLicenseFile(String licensePath, String signaturePath, String hashPath) {
        try {
            // read in file and validate the LICENSE based on the signature
            // this will remove changes of faking a LICENSE file
            // the LICENSE file has to be signed with our key
            File licenseFile = new File(licensePath);
            File signatureFile = new File(signaturePath);
            File hashFile = new File(hashPath);

            KeyStatus status = EncryptionManager.getEncryptionManager().verify(licenseFile, signatureFile, hashFile);

            if(!status.equals(KeyStatus.KEY_GOOD)) {
                return KeyStatus.KEY_INVALID;
            }

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(licenseFile));
            LICENSE = (License)ois.readObject();

            String lic = LICENSE.getLicenseNumber();
            if(LICENSE.getLicenseType().equals(LicenseType.TRIAL)) {
                IS_TRIAL = true;

                Calendar c = Calendar.getInstance();
                if(c.getTime().after(LICENSE.getExpiration())) {
                    return KeyStatus.KEY_EXPIRED;
                }

                Date expiration = LicenseManager.getLicenseManager().LICENSE.getExpiration();

                long val = expiration.getTime() - c.getTime().getTime();
                val /= (1000 * 60 * 60 * 24);
                JOptionPane.showMessageDialog(null, "This is a trial version of the software.  You have " + val + " days remaining in your trial.", "Trial Version", JOptionPane.INFORMATION_MESSAGE);
            }
            else if(LICENSE.getLicenseType().equals(LicenseType.SINGLE_VERSION)) {
                IS_TRIAL = false;
                status = checkKey(lic);
                IS_LICENSED = true;
            }
            else if(LICENSE.getLicenseType().equals(LicenseType.LIFETIME)) {
                IS_TRIAL = false;
                status = checkKey(lic);
                IS_LICENSED = true;
            }

            if(!status.equals(KeyStatus.KEY_GOOD)) {
                return status;
            }

            return KeyStatus.KEY_GOOD;
        }
        catch(Exception ex) {
            System.out.println(ex.toString());
            return KeyStatus.KEY_INVALID;
        }
    }

    /**
     *
     * @param name
     * @param email
     * @param authCode
     * @param licenseType
     * @param expiration
     * @param version
     * @param path
     */
    public void createLicense(String name, String email, String authCode, LicenseType licenseType, Date expiration, String version, String path) {
        byte[] entropy = null;

        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            digest.reset();
            entropy = digest.digest(getByteArrayFromHexString(authCode));
        }
        catch(NoSuchAlgorithmException ex) { /* this will never happen */ }

        License lic = new License(name, email, LicenseManager.makeKey(ENTROPY, entropy), expiration, licenseType, version);
        writeLicenseFile(lic, path);
    }

    /**
     *
     * @return
     */
    private static byte[] getHardwareEntropy() {
        byte[] mac;
        try {
            NetworkInterface ni = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
            if (ni != null) {
                mac = ni.getHardwareAddress();
                if (mac == null) {
                    mac = def;
                }
            } else {
                mac = def;
            }
        }
        catch (Exception ex) {
            mac = def;
        }

        byte[] entropyEncoded = null;
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            digest.reset();
            entropyEncoded = digest.digest(mac);
        }
        catch(NoSuchAlgorithmException ex) { /* this will never happen */ }

        return entropyEncoded;
    }

    /**
     *
     * @param seed
     * @param a
     * @param b
     * @param c
     * @return
     */
    private static byte getKeyByte(final int seed, final byte a, final byte b, final byte c) {
        final int a1 = a % 25;
        final int b1 = b % 3;
        if (a1 % 2 == 0) {
            return (byte) (((seed >> a1) & 0x000000FF) ^ ((seed >> b1) | c));
        } else {
            return (byte) (((seed >> a1) & 0x000000FF) ^ ((seed >> b1) & c));
        }
    }

    /**
     *
     * @param s
     * @return
     */
    private static String getChecksum(final String s) {
        int left = 0x0056;
        int right = 0x00AF;
        for (byte b : s.getBytes()) {
            right += b;
            if (right > 0x00FF) {
                right -= 0x00FF;
            }
            left += right;
            if (left > 0x00FF) {
                left -= 0x00FF;
            }
        }
        int sum = (left << 8) + right;
        return intToHex(sum, 4);
    }

    /**
     *
     * @param seed
     * @param entropy
     * @return
     */
    public static String makeKey(final int seed, byte[] entropy) {
        // fill keyBytes with values derived from seed.
        // the parameters used here must be exactly the same
        // as the ones used in the checkKey function.
        final byte[] keyBytes = new byte[25];
        keyBytes[0] = getKeyByte(seed, params[0][0], params[0][1], params[0][2]);
        keyBytes[1] = getKeyByte(seed, params[1][0], params[1][1], params[1][2]);
        keyBytes[2] = getKeyByte(seed, params[2][0], params[2][1], params[2][2]);
        keyBytes[3] = getKeyByte(seed, params[3][0], params[3][1], params[3][2]);
        for(int i = 4, j = 0; (j + 2) < entropy.length; i++, j += 3) {
            keyBytes[i] = getKeyByte(seed, entropy[j], entropy[j + 1], entropy[j + 2]);
        }      

        // the key string begins with a hexadecimal string of the seed
        final StringBuilder result = new StringBuilder(intToHex(seed, 8));

        // then is followed by hexadecimal strings of each byte in the key
        for (byte b : keyBytes) {
            result.append(intToHex(b, 2));
        }

        // add checksum to key string
        String key = result.toString();
        key += getChecksum(key);

        return key;
    }

    /**
     *
     * @param key
     * @return
     */
    private static boolean validateKeyChecksum(final String key) {
        if (key.length() != KEY_LEN) {
            return false;
        }

        // last four characters are the checksum
        final String checksum = key.substring(KEY_LEN - 4);
        return checksum.equals(getChecksum(key.substring(0, KEY_LEN - 4)));
    }

    /**
     *
     * @param key
     * @return
     */
    public static KeyStatus checkKey(final String key) {
        if (!validateKeyChecksum(key)) {
            return KeyStatus.KEY_INVALID; // bad checksum or wrong number of
            // characters
        }

        // test against blacklist
        for (String bl : blacklist) {
            if (key.startsWith(bl)) {
                return KeyStatus.KEY_BLACKLISTED;
            }
        }

        // at this point, the key is either valid or forged,
        // because a forged key can have a valid checksum.
        // we now test the "bytes" of the key to determine if it is
        // actually valid.

        // when building your release application, use conditional defines
        // or comment out most of the byte checks! this is the heart
        // of the partial key verification system. by not compiling in
        // each check, there is no way for someone to build a keygen that
        // will produce valid keys. if an invalid keygen is released, you can
        // simply change which byte checks are compiled in, and any serial
        // number built with the fake keygen no longer works.

        // note that the parameters used for getKeyByte calls MUST
        // MATCH the values that makeKey uses to make the key in the
        // first place!

        // extract the seed from the supplied key string
        final int seed;
        try {
            seed = Integer.valueOf(key.substring(0, 8), 16);
        } catch (NumberFormatException e) {
            return KeyStatus.KEY_PHONY;
        }

        // test key 0
        final String kb0 = key.substring(8, 10);
        final byte b0 = getKeyByte(seed, params[0][0], params[0][1], params[0][2]);
        if (!kb0.equals(intToHex(b0, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key1
        final String kb1 = key.substring(10, 12);
        final byte b1 = getKeyByte(seed, params[1][0], params[1][1], params[1][2]);
        if (!kb1.equals(intToHex(b1, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key2
        final String kb2 = key.substring(12, 14);
        final byte b2 = getKeyByte(seed, params[2][0], params[2][1], params[2][2]);
        if (!kb2.equals(intToHex(b2, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test key3
        final String kb3 = key.substring(14, 16);
        final byte b3 = getKeyByte(seed, params[3][0], params[3][1], params[3][2]);
        if (!kb3.equals(intToHex(b3, 2))) {
            return KeyStatus.KEY_PHONY;
        }

        // test the hardware entropy
        byte[] encodedEntropy = getHardwareEntropy();
        for(int i = 16, j = 0; (j + 2) < encodedEntropy.length; i += 2, j += 3) {
String kb = key.substring(i, i + 2);             byte b = getKeyByte(seed, encodedEntropy[j], encodedEntropy[j + 1], encodedEntropy[j + 2]);             if(!kb.equals(intToHex(b, 2))) {                 return KeyStatus.KEY_INVALID;             }         }         // if we get this far, then it means the key is either good, or was made         // with a keygen derived from "this" release.         return KeyStatus.KEY_GOOD;     }     /**      *       * @param n      * @param chars      * @return       */     protected static String intToHex(final Number n, final int chars) {         return String.format("%0" + chars + "x", n);     }          /**      *       * @param raw      * @return       */     public static String getHexStringFromBytes(byte[] raw) {         if ( raw == null ) {             return null;         }         final StringBuilder hex = new StringBuilder( 2 * raw.length );         for ( final byte b : raw ) {             hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    /**
     *
     * @param s
     * @return
     */
    public static byte[] getByteArrayFromHexString(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    private class CheckLicenseTask extends TimerTask {
        public CheckLicenseTask() { }

        @Override
        public void run() {
            System.out.println("checking license");
        }
    }
}

EncryptionManager.java: This module will manage encryption, decryption, signing and validation operations using your keys.  It is a singleton object.

package License;

import Utility.Logger;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import javax.crypto.*;

public class EncryptionManager {

    private static EncryptionManager instance;
    // this file should be in your jar
    private static final String PUBLIC_KEY_FILE = "/License/public_key.der";
    // this file will be on your hard drive
    private static final String PRIVATE_KEY_FILE = "/path/to/your/private_key.der";
    private static PublicKey publicKey;
    private static PrivateKey privateKey;

    protected EncryptionManager() throws GeneralSecurityException {
    }

    public static EncryptionManager getEncryptionManager() {
        if (instance == null) {
            try {
                instance = new EncryptionManager();

                try {
                    privateKey = loadPrivateKey(PRIVATE_KEY_FILE);
                }
                catch (Exception ex) {
                    //Logger.getLogger().ALog("private key failed to load - couldn't instantiate encryption manager.\n" + ex.toString());
                }
                try {
                    publicKey = loadPublicKey(PUBLIC_KEY_FILE);
                }
                catch (Exception ex) {
                    //Logger.getLogger().ALog("public key failed to load - couldn't instantiate encryption manager.\n" + ex.toString());
                }
            }
            catch(GeneralSecurityException ex) {
                //Logger.getLogger().ALog("couldn't instantiate encryption manager.\n" + ex.toString());
            }
        }

        return instance;
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    private static PublicKey loadPublicKey(String filename) throws Exception {
        DataInputStream dis = new DataInputStream(File.class.getResourceAsStream(filename));
        byte[] keyBytes = new byte[dis.available()];
        dis.readFully(keyBytes);
        dis.close();

        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    private static PrivateKey loadPrivateKey(String filename) throws Exception {
        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    /**
     *
     * @param dataToHashPath
     * @return
     */
    public static byte[] digest(File dataToHashPath) {
        try {
            InputStream fin = new FileInputStream(dataToHashPath);
            MessageDigest md5Digest = MessageDigest.getInstance("MD5");

            byte[] buffer = new byte[1024];
            int read;

            do {
                read = fin.read(buffer);
                if (read > 0) {
                    md5Digest.update(buffer, 0, read);
                }
            } while (read != -1);
            fin.close();

            byte[] digest = md5Digest.digest();
            if (digest == null) {
                return null;
            }

            return digest;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     *
     * @param dataToVerify
     * @param signatureFile
     * @param hashFile
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchPaddingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static KeyStatus verify(File dataToVerify, File signatureFile, File hashFile) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            SignatureException, NoSuchPaddingException, FileNotFoundException, IOException {
        // first validate the hash of the file
        FileInputStream hashfis = new FileInputStream(hashFile);
        byte[] hashToVerify = new byte[hashfis.available()];
        hashfis.read(hashToVerify);
        hashfis.close();

        byte[] licenseBytes = digest(dataToVerify);
        if(!Arrays.equals(licenseBytes, hashToVerify)) {
            Logger.getLogger().ALog("key failed to pass hash check");
            return KeyStatus.KEY_INVALID;
        }

        // now validate that we were the ones who shipped it
        Signature rsaSignature = Signature.getInstance("SHA1withRSA");
        rsaSignature.initVerify(publicKey);

        FileInputStream sigfis = new FileInputStream(signatureFile);
        byte[] sigToVerify = new byte[sigfis.available()];
        sigfis.read(sigToVerify);
        sigfis.close();

        FileInputStream datafis = new FileInputStream(hashFile);
        BufferedInputStream bufin = new BufferedInputStream(datafis);

        byte[] buffer = new byte[1024];
        int len;
        while (bufin.available() != 0) {
            len = bufin.read(buffer);
            rsaSignature.update(buffer, 0, len);
        };

        bufin.close();

        if (rsaSignature.verify(sigToVerify)) {
            return KeyStatus.KEY_GOOD;
        } else {
            Logger.getLogger().ALog("key failed to pass signature check");
            return KeyStatus.KEY_INVALID;
        }
    }

    /**
     *
     * @param dataToSign
     * @param signatureFilePath
     * @param hashFilePath
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void sign(byte[] dataToSign, String signatureFilePath, String hashFilePath) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            SignatureException, FileNotFoundException, IOException {
        // initialize the signing algorithm with our private key
        Signature rsaSignature = Signature.getInstance("SHA1withRSA");
        rsaSignature.initSign(privateKey);
        rsaSignature.update(dataToSign, 0, dataToSign.length);

        // sign it
        byte[] sig = rsaSignature.sign();

        // save the signature to disk to verify later
        FileOutputStream fos = new FileOutputStream(signatureFilePath);
        fos.write(sig);
        fos.close();

        fos = new FileOutputStream(hashFilePath);
        fos.write(dataToSign);
        fos.close();
    }
}

The author surveyed over 230 applications (the full list of applications ,can be found in A Survey Of Mobile Device Security: Threats, Vulnerabilities and Defenses), including applications in the “Top” categories on the iTunes store to determine what type of information could be extracted from auditing packet streams. The results were quite surprising.

To perform this audit, the author launched one application at a time and used WireShark to capture and analyze packets. The experiment was performed on an open network that the author created. The access point was a Cisco Small Business router (WAP4410N) and was configured using a hidden SSID and MAC address authentication to prevent outside users from associating with the access point and introducing outside, extra packets. While the author realizes that hidden SSIDs and MAC address authentication are easily defeated mechanisms, it was used to prevent casual users from using the access point. The mobile devices used were an Apple iPod Touch 4G, an Apple iPad 1G and an iPhone 4, configured with iOS 5.0.1.

For reasons of classification, the authors created several different levels of potential security breaches. The levels are defined as:

  • None: This level is defined as having no potential security breaches and no exposure of confidential information.
  • Low: This level is defined as having a few potential security breaches or exposure of confidential information that could not directly affect the user, such as device IDs that could be used in tracking users (in iOS, these are called UUIDs).
  • Medium: This level is defined as having several potential security breaches or exposure of confidential information that is potentially serious or if information is exposed such that an attacker would be able to identify the user on an individual basis, such as addresses, latitudes or longitudes, etc.
  • High: This level is defined as having multiple potential security breaches or exposure of extremely confidential information, such as account numbers, PINs, and username/password combinations.

For more information on the specific application, including the version number of the application with the vulnerability, see Appendix A for a full listing.

Application

Level

Risks Found

GrubHub

Low

UUID

The Weather Channel

Low

Geocoded location

Path

Low

UUID

Handmade

Low

UUID

iHeartRadio

Low

Reverse Geocoded location

TabbedOut

Low

UUID, Platform

Priceline

Low

UUID, Geocoded location, “Search” API is unencrypted

Free WiFi

Low

Geocoded location

Coupious

Medium

Geocoded location, UUID, coupon redemption codes

Delivery Status

Medium

UPS transmits reverse geocoded locations and tracking numbers.

Color

Medium

Reverse geocoded location and photos taken and shared by users

Cloudette

Medium

Username in plaintext and password, hashed with MD5

Gas Buddy

Medium

Username and password, hashed with MD5

Ness

Medium

Reverse geocoded location

Southwest Airlines

High

Username and password in plaintext

Minus

High

Username and password in plaintext

WordPress

High

Username and password in plaintext

Foodspotting

High

Username and password, Geocoded location

ustream

High

Username and password, UUID, geocoded location

Labelbox

High

Username and password, geocoded location

 

The majority of the applications that were surveyed encrypted the exchanges of confidential or sensitive information, such as usernames, passwords and account numbers via SSL/TLS.

However, many applications performed some sort of tracking or storing of analytic information, such as passing the UUID in a call to a web service.  In some of the instances, this identifying information was not encrypted.  While not potentially dangerous in the sense that an attacker could use this information to “identify” a particular person, none of the applications let users know that their information such as UUID, phone OS and model, was being used or recorded, nor did they let the user “opt-out.”

The largest single potential security breach was with the Southwest Airlines application.  Due to the fact that the username and password were submitted to a web server via a POST operation in plaintext, an attacker could simply sniff for this data.  If an example was captured, one could use those credentials to log into a particular account and book travel, use award miles and possibly change information in the victims profile.  This not only obviously worrisome from the standpoint of a potential attacker fraudulently using a victims account and credit card information, but also due to the possibility of a terrorists threats in air travel.

For example, consider the possibility of a person who is currently (and rightfully) on the Department of Homeland Security’s “No-Fly” list.  If this person were able to capture a victim’s credentials and create a fake ID, he could pass through TSA security without being stopped.

Of the 253 applications surveyed, 91.7% had no risk found, 3.1% had a low risk, 2.3% had a medium risk and 2.3% had a high risk.  While it would be desirable to have no applications in the “Medium” or “High” category, the number of applications the authors found presented a security risk was both surprising and far too numerous.  There are over 500,000 applications on the iOS App Store, so extrapolating the results, there could be at least 15,500 applications in the “Low” risk category and 11,500 applications in the “Medium” and “High” risk category.

Overall, the number of applications with some sort of security risk is low.  This is not very surprising to the authors as many of these applications are in the “Top” applications list and any potential security flaws would have already been found.

Due to the fact that iOS does not have a robust privilege system, there is no way that a user could know their information was being used in a dangerous or insecure way.  While there is support for showing users there is network traffic by  using a spinning “network activity indicator”, it is certainly not mandatory for them to do so.  In fact, a legitimate or malware application could access the network interfaces, sending and receiving information and never alert the user on iOS.

Developers typically do not follow the principle of least privilege.  If an application needs a set of privileges for functionality, they will request them up front, not just when they are needed.  This is particularly dangerous because this could be an entry point for an attacker to compromise the application.

[19] performed research where they surveyed 940 Android applications and found that more than 50% required 1 extra unnecessary permission and 6% required more than 4 unnecessary permissions.  The reasons that developers may request more permissions than are necessary could be because 1) they don’t understand the importance of security and least privilege, 2) they are planning on future releases that will require these privileges and 3) they don’t fully understand how to work with the platform and make the code function correctly.

Since mobile devices and smartphones are unique in that they have a built-in billing system, there must be ongoing education of developers with emphasis on security and privacy or additional built-in measures in the OS to enforce security over code the developers write or the permissions for which they ask.

Here is the full list of applications tested.

Bold applications represent applications bundled with iOS from Apple.

Application

Version

Application

Version

Messages 5.0.1 RedLaser Classic 2.9.8
Calendar 5.0.1 eBay 2.4.0
App Store 5.0.1 Craigslist 3.033
Settings 5.0.1 Key Ring 5.4
Spotify 0.4.21 Coupious 1.4.1
Contacts 5.0.1 Cars 1.6.1
Notes 5.0.1 Amazon PriceCheck 1.2.1
Newstand 5.0.1 Linode 1.0.6
Reminders 5.0.1 Unfuddle 1.1.1
Find My Friends 1.0 MiniBooks 1.0.2
Videos 5.0.1 iTC Mobile 2.4
Vlingo 2.1.1 Blueprint viewer 1.7
Photos 5.0.1 Square 2.2
Camera 5.0.1 WordPress 2.9.2
Instagram 2.0.5 Maps 5.0.1
iMovie 1.2.2 FlightTrack 4.2.2
DashOfColor 3.1 Kayak 19.0.6
ColorSplash 1.7.2 Southwest 1.8.2
UStream Broadcaster 2.1 American 1.3.3
TiltShiftGen 2.02 Fly Delta 1.6
Gorillacam 1.2.2 Flysmart 2.5.25
CameraPlus 2.4 Priceline Negotiator 5.6
PS Express 2.03 Free WiFi 1.1.2
Dropcam 1.4.3 Google Earth 3.2
Chase 2.14.5799 Translator 3.1
Citibank 3.7 Phone 5.0.1
Discover 2.1 Mail 5.0.1
Fidelity 1.6.1 Safari 5.0.1
TD Trader 115.12 Music 5.0.1
PayPal 3.6 Flixster 5.02
Mint.com 2.0 Boxee 1.2.1
Stock 5.0.1 redbox 2.3.1
thinkorswim 115.12 Youtube 5.0.1
Geico 2.0.2 Fandango 4.5
Dropbox 1.4.6 XFINITY TV 1.8
1Password 3.6.1 IMDb 2.3.1
Alarm Clock 1.1 i.TV 3.4.1
Planets 3.1 MobiTV 1.0
Dictation 1.1 Netflix 1.4
Inrix Traffic 3.5.1 VNC 3.2.1
Adobe Ideas 1.2 RDP 2.8
IP-Relay 1.2 TouchTerm 2.1
iLlumination 1.0.1 Scorekeeper 4.1
Fake-a-call 5.05 Statware 1.0.3
HeyTell 2.3.2 NIKE+ GPS 3.2.1
Weather 5.0.1 MiLB Triple A 1.1.0
The Weather Channel 2.1.1 Pandora 3.1.16
Calculator 5.0.1 Shazam 4.8.4
Clock 5.0.1 Soundhound 4.1.1
Compass 5.0.1 iHeartRadio 4.0.1
Voice Memos 5.0.1 Last.fm 3.2.0
AroundMe 5.1.0 Songify 1.0.6
myAT&T 2.1.2 iTunes 5.0.1
WeddingWire 3.1 Virtuoso 1.0
LogTen 3.3.1 I Am T-Pain 2.0.0
French 1.0 Scrabble 1.13.78
Binary Calc 1.4 Harbor Master 2.1
Amazon 1.8.0 Zombie Duck 4.1
Groupon 1.5.7 Zombieville 1.7
LivingSocial 3.2.2 Table Tennis 4.1.0
Yowza 2.5 iFighter 1.9
Coupons Hired Gun 1.8
Airport Utility 1.0 Lock n’ Roll 3.0
Walgreens 3.0.2 Sneezies Lite 1.3
MyHumana 3.0.2 Pad Racer 1.1
Nike + iPod Uno 2.0.0
Gold’s Gym Spotter 1.2 CamWow 2.2
Lose It! 3.7.2 Labelbox 1.3.1
FitnessTrack 1.5.5 Photosynth 1.1.2
LIVESTRONG 1.0.1 Color Effects 3.1
MyFitnessPal Saturation 1.0
Nutrisystem 2.3 Peppermint 1.1
Kindle 2.8.5 FlickStackrXP 1.9.6
Instapaper 4.0.1 Minus 2.1.3
iBooks 5.0.1 Gallery 2.0.1
Zinio 2.2 Handmade 1.1
Twitter 4.0 StubHub 2.4.1
Facebook 4.0.3 Pushpins 2.0.1
Google+ 1.0.7.2940 Black Friday 2.0
foursquare 4.1.2 Sam’s Club 2.1.1
LinkedIn 4.2 Cyber Monday 2.1.0
Meebo 1.95 Words With Friends 4.1
Yelp 5.5.0 Ultimate Free Word Finder 1.01
PingChat Mad Gab 2.0
Bump 2.5.6 Metal Storm 4.0.2
Color 1.1 Need For Speed 1.0.11
Cloudette 1.0.1 Madden NFL 2010
soundtracking 2.0.2 Shizzlr 3.2.1
Free RSS 3.4 Flashlight 5.1
NetNewsWire 2.0.5 Tip Calculator 1.3.1
FOX News 1.2.4 PCalc Lite 2.4.3
OpenTable 3.4.2 Fake Call 1.1
Urbanspoon 1.17 To Do 3.2
Epicurious 3.0.1 Google 1.0.0.8117
WinePhD 1.2 Evernote 4.1.6
TabbedOut 2.3.3 Coin Flip 2.2
Foodspotting 2.7 Grades 2 2.03
GrubHub 2.20 Sundry Notes 3.2
RecipeGrazer 1.3 OneNote 1.2
Starbucks 2.1.1 Enigmo 4.1
Starbucks Mobile Card Angry Birds 1.6.3
Ness 1.1 JellyCar 1.5.4
iDisk 1.2.1 Runway 1.6
Remote 2.2 RockBand Free 1.3.49
Apple Store 2.0 Game Center 5.0.1
Find iPhone 1.3 App For Cats 1.1
Pages 1.5 PadRacer 1.1
Places 1.31 Implode 2.2.4
TripAdvisor 5.9 Astronut 1.0.1
Google Latitude 2.2.1 Monopoly 1.2.9
Gas Buddy 1.10 Deliveries 4.5
Maplets 2.2.2 Skype 3.5.454
iTranslate 5.1 Units 2.1.2
Translate 1.6.2 NCAA Football 2011
KG Free ESPN ScoreCenter 2.2.2
Wikipedia 2.2 Ski Report 2.2.1
White Noise 5.0.3 EpicMix 2.0.1
Sleep Machine Lite 2.0.1 MLB At Bat 4.6.1
Inception 1.6 Purdue 3.0
Sleep 2.0.1 NASA 1.43
Night Stand 1.0.4 80,000 Wallpapers 1.98
Geico BroStache 1.0.1 Wedding 911 1.06
CamCard 2.6.0.4 Path 2.0.2
Offline Pages 1.5.2 Facebook Messenger 1.5.2
GPS Tracker 1.2.2 Quora 1.1
TextPics Free 2.2 Big Button Box 3.0
Peel 2.0
All code owned and written by David Stites and published on this blog is licensed under MIT/BSD.