Fluid Nexus
mobile messaging without the mobile phone network

On Examining Local Crypto Solutions

tl;dr: Well-known Android applications providing encrypted services sometimes store passphrases in the clear in memory; this is nothing surprising; you’re currently screwed if someone gets your Android phone; crypto is not necessarily the solution.

As part of research into current best practices in Android security I decided to examine two widely known pieces of software: Android Privacy Guard and textsecure. This was to help understand approaches that might be applicable to certain aspects of the Fluid Nexus project.

I am not a security researcher, nor a security consultant, and I cannot recite the socialist millionaire protocol by heart. What I do know is how to use the tools that are available to me. And I have experience in knowing what types of data need protection, experience that came from being a lab manager/technical assistant (writing analysis software, sysadmining our compute cluster, RAID arrays, and backup system, etc.) in a neuroimaging lab that was subject to HIPAA regulations.

In human subjects research and as a result of HIPAA you are not allowed to use personally identifiable information to refer to a research subject. Therefore you must choose some sort of unique identifier to store with the collected data that cannot be traced back to a particular subject. However, this trace has to exist somewhere in order to conduct meta-analyses regarding certain demographic information, and to potentially conduct follow-up analyses. So you only make the link on paper and store it in a secure location. Yet if that link is discovered then it’s all over in terms of anonymity.

It’s a similar case with crypto. We know this and have internalized all of the exhortations to not reuse our passphrases, not write them down, ensure that your private keys are secure, etc., etc., etc. But if someone apprehends your device while it is still on (and perhaps after it’s off, in the case of cold boot attacks), you’re screwed no matter what if a) they have the ability to get root and/or dump memory, and b) if your passphrase is cached. If you are detained and your phone is on and you have your passphrases cached, no amount of crypto is going to protect you. Crypto will only help you with an offline attack and when your adversary has not been able to dump the memory of your device where your passphrase might be cached.

Knowing these things let’s just go through a few exercises, wrapping up with what this means for Fluid Nexus and the crypto question.

Note: In this post I am primarily considering client-side crypto. A later post will examine crypto over the wire or over the air via techniques such as OTR and will examine what publicly, vetted libraries may or may not exist for doing broadcast encryption that are relevant for Fluid Nexus.

APG

On my to-do list for a long time was integration with sqlcipher, thanks to the hard work of The Guardian Project and Zetetic. I have avoided spending too much time on this aspect since, according to The Guardian Project, “This R1 release should not be integrated into critical or production software“ (italics and bold theirs). But I decided to examine the procedure for doing so on a separate branch of Fluid Nexus. Downloading notepadbot, it’s incredibly simple to add in the correct libraries and make the necessary changes to your library calls; thanks to The Guardian Project for this easy-to-follow example.

But then you’re faced with a rather sticky problem: how do you a) securely pass a passphrase from your main activity to (in the case of Fluid Nexus) your ContentProvider; and b) how do you securely cache your passphrase in memory so that it can’t be found in a memory dump? The latter question, as we mentioned earlier and will show below, is one with no satisfactory solutions for Android at the moment. Curious about how this works I decided to look at APG and examine how it caches passphrases. Let’s take a look:


package org.thialfihar.android.apg;

public class CachedPassPhrase { public final long timestamp; public final String passPhrase;

public CachedPassPhrase(long timestamp, String passPhrase) { super(); this.timestamp = timestamp; this.passPhrase = passPhrase; } public int hashCode() { int hc1 = (int)(this.timestamp & 0xffffffff); int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); return (hc1 + hc2) * hc2 + hc1; } public boolean equals(Object other) { if (!(other instanceof CachedPassPhrase)) { return false; } CachedPassPhrase o = (CachedPassPhrase) other; if (timestamp != o.timestamp) { return false; } if (passPhrase != o.passPhrase) { if (passPhrase null || o.passPhrase null) { return false; } if (!passPhrase.equals(o.passPhrase)) { return false; } } return true; } public String toString() { return “(” + timestamp + “, *******)”; } }

Is that true? Does APG store the passphrase in the clear in the constructor? Something like this raises all sorts of red flags initially, but on further reflection is likely unavoidable.

But lets go through the motions. If the passphrase is stored in the clear, it ought to be trivially easy to dump it from memory. Let’s find out. First, generate a dummy PGP keypair.

gpg --gen-key

Let’s make sure it’s protected by a really “strong” passphrase: “Super secret passphrase!”. Time for export:

gpg -ao ~/fakeprivkey.asc --export-secret-key 8A958014
gpg -ao ~/fakepubkey.asc --export 8A958014

Let’s push these to our sdcard. We assume you know how to import them into APG.

./adb push ~/fakepubkey.asc /sdcard/
./adb push ~/fakeprivkey.asc /sdcard/

Now force close APG so we start from a (relatively) clean slate. Open it up and use it to sign a trivial message so that the passphrase is cached. Now we’re going to dump the memory of the process and analyze it in a very simple way. The following commands in the adb shell require some way of gaining root; busybox is also used to make navigation and other tasks easier.

Follow along:

someuser@somehost platform-tools> ./adb shell
$ su
# chmod 777 /data/misc
# ps
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
[...snip...]
app_110   17973 2381  217088 24612 ffffffff afd0ee48 S org.thialfihar.android.apg
shell     18061 2390  648    336   c031b39c afd0eafc S /system/bin/sh
root      18062 18061 648    336   c031b39c afd0eafc S sh
app_107   18064 2381  209388 15956 ffffffff afd0ee48 S com.noshufou.android.su
root      18071 18062 796    336   00000000 afd0dbbc R ps
# kill -10 17973
# ls /data/misc
bluetoothd
bluetooth
keystore
vpn
systemkeys
radio
wifi
dhcp
heap-dump-tm1313820900-pid16096.hprof
heap-dump-tm1313854763-pid17973.hprof
# cp /data/misc/heap-dump-tm1313854763-pid17973.hprof /sdcard/
# $ someuser@somehost platform-tools> ./adb pull /sdcard/heap-dump-tm1313854763-pid17973.hprof .
2666 KB/s (4361160 bytes in 1.597s)
someuser@somehost platform-tools> ../tools/hprof-conv heap-dump-tm1313854763-pid17973.hprof apg.hprof
someuser@somehost platform-tools> jhat apg.hprof

Okay, now we’re going to use jhat to make it easy to examine what’s in memory. Go to http://localhost:7000 and you’ll be presented with an easy to read dump of the memory at the time we sent the signal.

Let’s take a look at what’s in the CachedPassPhrase class we suspected earlier was storing our passphrase in the clear. Navigate to the specific instance. And take a look:

There it is under the passPhrase instance variable.

I do hope you created a keypair for your phone that is different from the one that you use elsewhere? And that it uses a different passphrase?

The shortest time that APG allows you to cache your passphrase is 15 seconds; from what I can tell there is no way to tell it to not cache your passphrase at all.

You can try this at home as well with gpg-agent: sudo gcore `pidof gpg-agent`. grep the dump for your passphrase.

Writing an application that runs as a service with root privileges and pulls passphrases from memory is left as an exercise to the reader1.

But again, if someone has root, you’re screwed.

textsecure

I thought I’d use similar techniques to examine textsecure. Not having the source I must move, somewhat blindly, through the memory dump. But there’s still some interesting things.

Unsurprisingly, if you force a memory dump at the precise time of passphrase entry it’s trivial to find the passphrase. But what about for some time afterwards? Is a garbage collector pass forced after the dialog is dimissed? Or is the instance variable manually set to something else after the passphrase has been used to generate your keys? Let’s take a look. First, we change our textsecure passphrase to “Super secret passphrase!”. We force close to start with a clean slate. Then we enter in our passphrase; note the time is 12:00PM.

We wait a minute to dump the memory; see the following logcat output and note that the dump started at 12:01:08:

08-20 12:01:08.363: INFO/dalvikvm(18323): SIGUSR1 forcing GC and HPROF dump
08-20 12:01:08.375: INFO/dalvikvm(18323): hprof: dumping VM heap to "/data/misc/heap-dump-tm1313856068-pid18323.hprof-hptemp".
08-20 12:01:09.519: INFO/dalvikvm(18323): hprof: dumping heap strings to "/data/misc/heap-dump-tm1313856068-pid18323.hprof".
08-20 12:01:13.355: INFO/dalvikvm(18323): hprof: heap dump completed, temp file removed

Copy, pull, and convert the hprof file as before and fire up jhat. Since we don’t have the source we’ve got to poke around a bit. Perhaps PassphrasePromptActivity is what we want. In there is an EditText called passphraseText. And what’s it’s value?

Yup, there’s our passphrase.

What this suggests to us is that textsecure does not a) force a gc pass after passphrase entry to remove the passphrase from memory; or b) reset the value of the EditText so that it cannot be found in memory. Of course such techniques are not entirely predictable and the passphrase might still be lurking around somewhere.

Seeing how long after passphrase entry you can still retreive it is left as an exercise to the reader; our record is one minute, as shown here, and a dump done about an hour after passphrase entry resulted in nothing. Of course this will likely be highly dependent on the somewhat unpredictable nature of dalvik’s GC processes.

From what I can tell the shortest you can tell textsecure to cache your passphrase is 1 minute.

textsecure storage

But I wanted to look further. What does the actual database look like? Pretty easy to find out.

someuser@somehost platform-tools> ./adb shell
$ su
# cp /data/data/org.thoughtcrime.securesms/databases/messages.db /sdcard
# $ someuser@somehost platform-tools> ./adb pull /sdcard/messages.db .
416 KB/s (38912 bytes in 0.091s)
someuser@somehost platform-tools> sqlite3 messages.db
SQLite version 3.7.2
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
android_metadata  mms               part              thread          
identities        mms_addresses     sms             
sqlite>

What’s in the sms table?

I’ve obscured some of the info, but I encourage you to try this at home. The third column contains the cleartext phone number of the person who sent you a SMS. I assume it would be the same for the MMS table. The message itself is encrypted, but the phone numbers of your correspondents are not. For messages you send there is an eleven digit number in the third column; I’m still not sure what it is.

This should have been clear in retrospect by anyone looking at the home screen before passphrase entry:

How would it be possible for it to know the recipients if the messages are ostensibly encrypted? The phone number, or some other unique identifier used to link textsecure with your Contacts, would need to be stored unencrypted. The only way around this would be to use something like sqlcipher to write the encrypted pages to your sdcard, which I’m told is in the works.

Finishing up, here’s the create table statement for the SMS database:

CREATE TABLE sms (_id integer PRIMARY KEY, thread_id INTEGER, address TEXT, person INTEGER, date INTEGER, protocol INTEGER, read INTEGER DEFAULT 0, status INTEGER DEFAULT -1,type INTEGER, reply_path_present INTEGER, subject TEXT, body TEXT, service_center TEXT); (24 bytes) 

Note that for messages you send your service center is also stored in the clear, which is may or may not be an issue; I defer to others on this point. The “person” column is interesting; I’m not sure what it’s used for because it wasn’t populated in my own dump and I haven’t been able to trace where and how it gets set.

Thoughts

These exercises were not done out of any malicious intent. Nothing that I have written here is new; all of these “attacks” are known. Rather this was a useful exercise to know to what extent encryption of local storage used for Fluid Nexus will help us.

In the Android model applications run in their own sandbox and getting access to the datastores and/or processes is only possible through applications signed with the same key. No such protections exist if you have root. Let’s say you had root but the database you’re trying to get at was encrypted. These exercises show that this doesn’t help you at all if your passphrase is cached in memory, as might happen if the phone was taken while you were using one of these applications that cached your passphrase. A smart adversary, upon taking possession of a phone, would immediately ensure it is on AC power and dump all memory at once to search for passphrases. And perhaps this isn’t even necessary if the device is susceptible to a cold-boot attack; I’m not sure how this applies to the varied devices running Android.

The last possibility is if you have root, are not able to retrieve passphrases from memory, and are left only with an encrypted database. The textsecure example shows that some information is still available, given that textsecure encrypts messages only. Phone numbers are left in the clear, and the nature of the encryption makes it easy to determine that it’s ciphertext. While indeed telcos already have this information, and could easily search through their records to find the numbers associated with those encrypted messages, I do wonder if having these numbers at hand might help reduce the search space. I defer to others on this point.

The potential solution at the moment is to use sqlcipher, where each page is encrypted/decrypted as necessary. There is still the issue that any form of passphrase caching will introduce a vulnerability. With that said, I do not want to incorporate it into the Fluid Nexus application until I am sure that it is ready, which I hope will be soon.

Another solution is full-phone encryption as provided by WhisperSystems. I don’t have a Nexus S or a Nexus One to test this out. I look forward to it being available for other devices, and to it hopefully becoming free software.

Finally, it’s important important to point out InTheClear (github repository), an application under development for, among other things, wiping data in a panic situation. This has the potential to mitigate some of the discussed attacks and is something better suited to an external, specialized, vetted package, rather than existing within Fluid Nexus itself.

I do fear that there are some who suggest turning to crypto as the magic lock that secures everything. It is not, and there are important situations where all the crypto in the world is not going to help you. I am grateful for the work of the guardian project, thialfihar, and moxie and want to thank them for contributing to the community, both for Android and in other situations. But it is clear that there is more to do. Hopefully in the near future there will be a vetted keychain solution so that we do not have to each write our own caching implementation. Given that our mobile devices have hundreds, thousands of pieces of identifiable information, we need to continue working on implementing solutions that not only protect individual applications, but are also secure and trusted enough to be used systemwide. When done right, crypto is absolutely the best thing we have at the moment for certain classes of applications and communications, but it needs to be understood as only one component of a larger set of tools2.

1 Yes, I hated these “exercises” too in my math and physics classes as an undergraduate and in grad school. Which is why you should take this in jest.

2 Thanks to n8fr8 and moxie for helping me think through these issues; the opinions expressed here are mine alone, and any consequent criticisms should be directed at me.

Posted by admin at .

Add new comment