如何在 Google Drive 中查看隐藏的应用程序数据?

我有一个安卓应用程序,存储我的笔记在 隐藏的应用程序数据。我想导出我的笔记,所以问题很简单:

我如何访问隐藏的应用程序数据在谷歌驱动器为一个特定的应用程序?

78610 次浏览

用户不能直接访问隐藏应用程序文件夹中的数据,只有应用程序可以访问它们。这是为配置或其他用户不应直接操作的隐藏数据而设计的。(用户可以选择删除数据,以释放其使用的空间。)

用户获得访问权限的唯一途径是通过特定应用程序公开的一些功能。

事实上,谷歌并不允许你直接访问这个隐藏的应用程序数据文件夹。

但是,如果你能得到应用程序的客户端 ID/客户端秘密/数字签名,用于对谷歌的服务器进行认证,那么是的,你基本上可以模拟应用程序和访问隐藏的数据在您的谷歌驱动器使用驱动器 API。

它在 Android 中是如何工作的

通常,当一个安卓应用程序想要访问 Google API (比如 开车,Games 或者 Google Sign-In ——并不是所有的都支持)时,它会与 Google Play 服务客户端库通信,而 Google Play 服务客户端库会代表这个应用程序从 Google 那里获得一个访问令牌。然后,这个访问令牌随每个请求一起发送到 API,这样 Google 就知道谁在使用它,以及允许他使用您的帐户(OAuth 2.0)做什么。为了首次获得这个访问令牌,Google Play 服务向 android.clients.google.com/auth发送一个 HTTPS POST 请求,其中包含以下字段(以及其他详细信息) :

  • Token-一个“主令牌”,标识谷歌帐户,基本上允许完全访问它
  • app-应用程序包名称,如 com.whatsapp
  • client_sig-应用程序的 数字签名(以 SHA1发送)
  • 装置是 Android ID
  • service-应用程序希望拥有的 显微镜(权限)

因此,在我们以特定应用程序的名义开始使用驱动器 API 之前,我们需要知道它的签名和我们帐户的主令牌。幸运的是,签名可以很容易地从 .apk文件中提取出来:

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

接下来我们需要的是主令牌。这个特殊的令牌通常被接收和存储在设备上时,一个新的谷歌帐户添加(例如,当第一次设置电话) ,通过发出类似的请求相同的网址。不同之处在于,现在请求权限的应用程序是 Play 服务应用程序本身(com.google.android.gms) ,谷歌还获得了额外的 EmailPasswd参数来登录。如果请求成功,我们将获得主令牌,然后可以将其添加到用户的应用程序请求中。

有关身份验证过程的更详细信息,可以阅读 这篇博文

把它们放在一起

现在,我们可以直接使用这两个 HTTP 请求编写一个认证代码——这个代码可以用任何 Google 帐户浏览任何应用程序的文件。只要选择你喜欢的编程语言和 客户端库。我发现使用 PHP更容易:

require __DIR__ . '/vendor/autoload.php'; // Google Drive API


// HTTPS Authentication
$masterToken = getMasterTokenForAccount("your_username@gmail.com", "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-urlencoded\r\nConnection: 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 . "\r\n";
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-urlencoded\r\nConnection: 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 . "\r\n";
return false;
}


$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "\n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);


return $token;
}

最后,结果

Files:
gdrive_file_map (1d9QxgC3p4PTXRm_fkAY0OOuTGAckykmDfFls5bAyE1rp)
Databases/msgstore.db.crypt9    (1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0)
16467702039-invisible (1yHFaxfmuB5xRQHLyRfKlUCVZDkgT1zkcbNWoOuyv1WAR)
Done.

注意: 这是一个非官方的、粗糙的解决方案,因此可能会有一些问题。例如,访问令牌仅存在一个小时,此后不会自动刷新。

要获取应用程序数据中的所有文件,请尝试使用代码

private void listFiles() {
Query query =
new Query.Builder()
.addFilter(Filters.or(Filters.eq(SearchableField.MIME_TYPE, "text/html"),
Filters.eq(SearchableField.MIME_TYPE, "text/plain")))
.build();
getDriveResourceClient()
.query(query)


.addOnSuccessListener(this,


new OnSuccessListener<MetadataBuffer>() {
@Override
public void onSuccess(MetadataBuffer metadataBuffer) {
//mResultsAdapter.append(metadataBuffer);


for (int i = 0; i <metadataBuffer.getCount() ; i++) {
retrieveContents(metadataBuffer.get(i).getDriveId().asDriveFile());
}
}
}


)
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "Error retrieving files", e);
MainActivity.this.finish();
}
});


}

也可以通过下面的代码下载文件的内容

public void retrieveContents(DriveFile file) {


Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);






openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
@Override
public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();


try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}


Log.e("result ", builder.toString());
}


Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {


}
});




}

2020年9月的一个实例

注意: 这实际上是对 托莫的回答的增加

自从 托莫的原始答案发布以来,情况发生了变化。 目前,要获取主令牌并避免 Error=BadAuthentication,您需要两件事:

  • EncryptedPasswd替换 Passwd字段,并用谷歌公钥(确切的技术是 被某个家伙逆转了)加密它的 RSA 值-这可以用 Phpseclib来完成。
  • 使用与某个受支持的 Android 系统中相同的 SSL/TLS 选项将 HTTPS 连接到 Google 服务器。这包括 TLS 版本和正确顺序支持的密码的确切列表。如果您更改顺序或添加/删除密码,您将得到 Error=BadAuthentication。我花了一整天才想明白。 幸运的是,PHP > = 7.2附带了 openssl-1.1.1,它具备模拟 Android 10客户端所需的所有密码。

因此,这里重写的 getMasterTokenForAccount()函数设置密码并使用 EncryptedPasswd而不是普通的 Passwd。下面是执行加密的 encryptPasswordWithGoogleKey()实现。

Phpseclib 是必需的,可以用作曲器 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-urlencoded\r\nConnection: 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 . "\r\n";
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 phpseclib\Math\BigInteger($modulus_bin, 256);
$exponent = new phpseclib\Math\BigInteger($exponent_bin, 256);


$rsa = new phpseclib\Crypt\RSA();
$rsa->loadKey(['n' => $modulus, 'e' => $exponent], phpseclib\Crypt\RSA::PUBLIC_FORMAT_RAW);
$rsa->setEncryptionMode(phpseclib\Crypt\RSA::ENCRYPTION_OAEP);
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$encrypted = $rsa->encrypt("{$email}\x00{$password}");


$hash = substr(sha1($google_key_bin, true), 0, 4);
return strtr(base64_encode("\x00{$hash}{$encrypted}"), '+/', '-_');
}
public void retrieveContents(DriveFile file) {


Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);






openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
@Override
public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();


try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}


Log.e("result ", builder.toString());
}


Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {


}
});




}
public void retrieveContents(DriveFile file) {


Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);






openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
@Override
public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();


try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}


Log.e("result ", builder.toString());
}


Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {


}
});




}