Solución:
De hecho, Google no le permite acceder directamente a esta carpeta de datos de aplicaciones oculta.
Pero, si puede tener en sus manos el ID de cliente / secreto de cliente / firma digital de la aplicación que se usa para la autenticación contra los servidores de Google, entonces sí, básicamente puede emular la aplicación y acceder a los datos ocultos en su Google Drive usando la API de Drive. .
Cómo funciona en Android
Por lo general, cuando una aplicación de Android desea acceder a una API de Google (como Drive, Juegos o Inicio de sesión de Google, no todos son compatibles), se comunica con la biblioteca cliente de los servicios de Google Play, que a su vez obtiene un token de acceso de Google en nombre. de la aplicación. Este token de acceso luego se envía con cada solicitud a la API, para que Google sepa quién lo está usando y qué puede hacer con su cuenta (OAuth 2.0). Para obtener este token de acceso por primera vez, el servicio Google Play envía una solicitud HTTPS POST a android.clients.google.com/auth
con estos campos (junto con otros detalles):
Token
– un “token maestro” que identifica la cuenta de Google y básicamente permite el acceso completo a ellaapp
– el nombre del paquete de la aplicación, comocom.whatsapp
client_sig
– las aplicaciones firma digital (enviado como SHA1)device
– ID de Android del dispositivoservice
– los alcances (permisos) que la aplicación quiere tener
Entonces, antes de que podamos comenzar a usar la API de Drive en el nombre de una aplicación específica, necesitamos saber su firma y el token maestro de nuestra cuenta. Afortunadamente, la firma se puede extraer fácilmente del .apk
expediente:
shell> unzip whatsapp.apk META-INF/*
Archive: whatsapp.apk
inflating: META-INF/MANIFEST.MF
inflating: META-INF/WHATSAPP.SF
inflating: META-INF/WHATSAPP.DSA
shell> cd META-INF
shell> keytool -printcert -file WHATSAPP.DSA # can be CERT.RSA or similar
.....
Certificate fingerprints:
SHA1: 38:A0:F7:D5:05:FE:18:FE:C6:4F:BF:34:3E:CA:AA:F3:10:DB:D7:99
Signature algorithm name: SHA1withDSA
Version: 3
Lo siguiente que necesitamos es el token maestro. Este token especial normalmente se recibe y se almacena en el dispositivo cuando se agrega una nueva cuenta de Google (por ejemplo, cuando se configura el teléfono por primera vez), al realizar una solicitud similar a la misma URL. La diferencia es que ahora la aplicación que solicita permisos es la propia aplicación de servicios de Play (com.google.android.gms
), y Google también recibe información adicional Email
y Passwd
parámetros para iniciar sesión. Si la solicitud es exitosa, recuperaremos nuestro token maestro, que luego podría agregarse a la solicitud de la aplicación del usuario.
Puede leer esta publicación de blog para obtener información más detallada sobre el proceso de autenticación.
Poniendolo todo junto
Ahora, podemos escribir un código para la autenticación usando estas dos solicitudes HTTP directamente: un código que puede explorar los archivos de cualquier aplicación con cualquier cuenta de Google. Simplemente elija su lenguaje de programación y biblioteca cliente favoritos. Lo encontré más fácil con PHP:
require __DIR__ . '/vendor/autoload.php'; // Google Drive API
// HTTPS Authentication
$masterToken = getMasterTokenForAccount("[email protected]", "your_password");
$appSignature = '38a0f7d505fe18fec64fbf343ecaaaf310dbd799';
$appID = 'com.whatsapp';
$accessToken = getGoogleDriveAccessToken($masterToken, $appID, $appSignature);
if ($accessToken === false) return;
// Initializing the Google Drive Client
$client = new Google_Client();
$client->setAccessToken($accessToken);
$client->addScope(Google_Service_Drive::DRIVE_APPDATA);
$client->addScope(Google_Service_Drive::DRIVE_FILE);
$client->setClientId(""); // client id and client secret can be left blank
$client->setClientSecret(""); // because we're faking an android client
$service = new Google_Service_Drive($client);
// Print the names and IDs for up to 10 files.
$optParams = array(
'spaces' => 'appDataFolder',
'fields' => 'nextPageToken, files(id, name)',
'pageSize' => 10
);
$results = $service->files->listFiles($optParams);
if (count($results->getFiles()) == 0)
print "No files found.n";
else
print "Files:n";
foreach ($results->getFiles() as $file)
print $file->getName() . " (" . $file->getId() . ")n";
/*
$fileId = '1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0';
$content = $service->files->get($fileId, array('alt' => 'media' ));
echo var_dump($content);
*/
function getGoogleDriveAccessToken($masterToken, $appIdentifier, $appSignature)
if ($masterToken === false) return false;
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$requestedService = 'oauth2:https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file';
$data = array('Token' => $masterToken, 'app' => $appIdentifier, 'client_sig' => $appSignature, 'device' => $deviceID, 'google_play_services_version' => '8703000', 'service' => $requestedService, 'has_permission' => '1');
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencodedrnConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
/* Handle error */
print 'An error occured while requesting an access token: ' . $result . "rn";
return false;
$startsAt = strpos($result, "Auth=") + strlen("Auth=");
$endsAt = strpos($result, "n", $startsAt);
$accessToken = substr($result, $startsAt, $endsAt - $startsAt);
return ""access_token":"" . $accessToken . "", "refresh_token":"TOKEN", "token_type":"Bearer", "expires_in":360000, "id_token":"TOKEN", "created":" . time() . "";
function getMasterTokenForAccount($email, $password)
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$data = array('Email' => $email, 'Passwd' => $password, 'app' => 'com.google.android.gms', 'client_sig' => '38918a453d07199354f8b19af05ec6562ced5788', 'parentAndroidId' => $deviceID);
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencodedrnConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
/* Handle error */
print 'An error occured while trying to log in: ' . $result . "rn";
return false;
$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);
return $token;
Y finalmente, los resultados:
Files:
gdrive_file_map (1d9QxgC3p4PTXRm_fkAY0OOuTGAckykmDfFls5bAyE1rp)
Databases/msgstore.db.crypt9 (1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0)
16467702039-invisible (1yHFaxfmuB5xRQHLyRfKlUCVZDkgT1zkcbNWoOuyv1WAR)
Done.
NOTA: Esta es una solución pirata no oficial, por lo que podría tener algunos problemas. Por ejemplo, el token de acceso está activo solo durante una hora, después de lo cual no se actualizará automáticamente.
El usuario no puede acceder directamente a los datos de las carpetas ocultas de la aplicación, solo la aplicación puede acceder a ellos. Está diseñado para la configuración u otros datos ocultos que el usuario no debe manipular directamente. (El usuario puede optar por eliminar los datos para liberar el espacio utilizado).
La única forma en que el usuario puede acceder a él es a través de alguna funcionalidad expuesta por la aplicación específica.
Un ejemplo práctico a septiembre de 2020
Nota: esto es en realidad una adición a la respuesta de Tomer.
Las cosas cambiaron desde que se publicó la respuesta original de Tomer. Actualmente, para obtener el token maestro y evitar el Error=BadAuthentication
, necesitas dos cosas:
- Reemplazar
Passwd
campo conEncryptedPasswd
y cifrar su valor mediante RSA con el público de Google key (la técnica exacta fue invertida por un tipo) – esto se puede hacer usando phpseclib. - Realice una conexión HTTPS al servidor de Google con las mismas opciones de SSL / TLS que en uno de los sistemas Android compatibles. Esto incluye las versiones de TLS y la lista exacta de cifrados admitidos en el orden correcto. Si cambia el orden o agrega / elimina cifrados, obtendrá
Error=BadAuthentication
. Me tomó un día entero resolver esto … Afortunadamente, PHP> = 7.2 viene con openssl-1.1.1 que tiene todos los cifrados necesarios para emular el cliente Android 10.
Así que aquí está reescrito getMasterTokenForAccount()
función que establece los cifrados y utiliza EncryptedPasswd
en lugar de simple Passwd
. Y abajo esta encryptPasswordWithGoogleKey()
implementación que hace el cifrado.
phpseclib es necesario y se puede instalar con composer: composer require phpseclib/phpseclib:~2.0
function getMasterTokenForAccount($email, $password)
$url = 'https://android.clients.google.com/auth';
$deviceID = '0000000000000000';
$data = array('Email' => $email, 'EncryptedPasswd' => encryptPasswordWithGoogleKey($email, $password), 'app' => 'com.google.android.gms', 'client_sig' => '38918a453d07199354f8b19af05ec6562ced5788', 'parentAndroidId' => $deviceID);
$options = array(
'ssl' => array(
'ciphers' => 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+AES:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5:!DSS'),
'http' => array(
'header' => "Content-type: application/x-www-form-urlencodedrnConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
/* Handle error */
print 'An error occured while trying to log in: ' . $result . "rn";
return false;
$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);
return $token;
function encryptPasswordWithGoogleKey($email, $password)
define('GOOGLE_KEY_B64', 'AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ==');
$google_key_bin = base64_decode(GOOGLE_KEY_B64);
$modulus_len = unpack('Nl', $google_key_bin)['l'];
$modulus_bin = substr($google_key_bin, 4, $modulus_len);
$exponent_len = unpack('Nl', substr($google_key_bin, 4 + $modulus_len, 4))['l'];
$exponent_bin = substr($google_key_bin, 4 + $modulus_len + 4, $exponent_len);
$modulus = new phpseclibMathBigInteger($modulus_bin, 256);
$exponent = new phpseclibMathBigInteger($exponent_bin, 256);
$rsa = new phpseclibCryptRSA();
$rsa->loadKey(['n' => $modulus, 'e' => $exponent], phpseclibCryptRSA::PUBLIC_FORMAT_RAW);
$rsa->setEncryptionMode(phpseclibCryptRSA::ENCRYPTION_OAEP);
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$encrypted = $rsa->encrypt("$emailx00$password");
$hash = substr(sha1($google_key_bin, true), 0, 4);
return strtr(base64_encode("x00$hash$encrypted"), '+/', '-_');
Si te apasiona la informática, tienes la opción de dejar un escrito acerca de qué te ha parecido esta división.