web-dev-qa-db-ja.com

Googleドライブで非表示のアプリデータを表示するにはどうすればよいですか?

Android hidden app data にノートを保存するアプリがあります。質問が簡単になるようにノートをエクスポートしたい:

特定のアプリについて、Googleドライブの非表示のアプリデータにアクセスするにはどうすればよいですか?

50
bizzz

実際、Googleでは、この非表示のapp-dataフォルダーに直接アクセスすることはできません。

ただし、Googleのサーバーに対する認証に使用されるアプリのクライアントID /クライアントシークレット/デジタル署名を取得できる場合-はい、基本的にアプリをエミュレートし、Drive APIを使用してGoogleドライブの非表示データにアクセスできます。

Androidでの仕組み

通常、AndroidアプリケーションがGoogle API( ドライブ 、ゲーム、またはGoogleサインイン-すべてがサポートされているわけではありません)にアクセスしたい場合)-と通信します- Google Playサービスクライアントライブラリ 、アプリに代わってGoogleからアクセストークンを取得します。このアクセストークンはAPIへの各リクエストとともに送信されるため、Googleはそれを使用しているユーザーと彼はあなたのアカウントで行うことができます( OAuth 2. )。このアクセストークンを初めて取得するために、Google Play開発者サービスはHTTPS POST=リクエストを送信しますこれらのフィールドを持つAndroid.clients.google.com/authに(他の詳細とともに):

  • Token-Googleアカウントを識別し、基本的にそのアカウントへのフルアクセスを許可する「マスタートークン」
  • app-com.whatsappなどのアプリケーションパッケージ名
  • client_sig-アプリケーションのデジタル署名(SHA1として送信)
  • device-デバイスの Android ID
  • service- スコープ (許可)アプリが持っていることを望んでいる

そのため、特定のアプリの名前でDrive 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

次に必要なのはマスタートークンです。通常、この特別なトークンは、同じURLに対して同様のリクエストを行うことにより、新しいgoogleアカウントが追加されたとき(たとえば、電話を最初にセットアップしたとき)にデバイスに受信され、保存されます。違いは、許可を求めているアプリはPlayサービスアプリ自体(com.google.Android.gms)であり、Googleにはログインするための追加のEmailおよびPasswdパラメーターも与えられることです。リクエストが成功した場合、マスタートークンを取得し、ユーザーのアプリリクエストに追加できます。

認証プロセスの詳細については、 this blogpost をご覧ください。

すべてを一緒に入れて

これで、これら2つのHTTPリクエストを直接使用して認証用のコードを作成できます。これは、Googleアカウントでアプリのファイルを閲覧できるコードです。好みのプログラミング言語と クライアントライブラリ を選択するだけです。 [〜#〜] 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-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.

注:これは非公式でハッキングされたソリューションであるため、いくつかの問題がある可能性があります。たとえば、アクセストークンは1時間のみ有効であり、その後は自動的に更新されません。

50
Tomer

ユーザーは非表示のアプリフォルダー内のデータに直接アクセスできません。アプリのみがデータにアクセスできます。これは、ユーザーが直接操作してはならない構成またはその他の非表示データ用に設計されています。 (ユーザーはデータを削除して、使用するスペースを解放することができます。)

ユーザーがアクセスできる唯一の方法は、特定のアプリで公開されている機能を使用することです。

8
Cheryl Simon

アプリデータ内のすべてのファイルを取得するには、コードを試してください

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

                    }
                });


    }
0
Chayon Ahmed