AndroidのCIFS、または壊れた電話からファイルを取得した方法

たまたま、愛するネクサス4の画面を壊してしまいました。最初に考えたのは、「くそー! 今、私はこれらのならず者の一人のようになり、画面が壊れます!」 しかし、どうやらネクサス4の作成者は、画面の破損とともにタッチスクリーンが完全に機能しなくなったため、物beいの熱烈な反対者でした。 一般に、電話で修理しても構いません。それだけです。 しかし、数週間ではなく、今必要なファイルが電話にありました。 しかし、画面のロックが解除され、電話が「極秘」ジェスチャーを必要とし、カテゴリとして外部ドライブとして動作することを望まなかった場合にのみ、それらを取得できるように思われました。







adbを少し掘り下げて、コンソールから画面のロックを解除しようとしました。 ロック画面を解除するためのすべてのヒントにはルートが必要でしたが、私の電話はこれらの1つではありません。 内から行動することになりました。 JCIFSライブラリを選択する前に、JCIFSライブラリで作業する必要があり、その使用に問題はなかったためです。



電話からWi-Fi経由で共有されるフォルダーにファイルを個別にコピーするアプリケーションを作成する必要がありました。 このようなトリックの前提条件:USB経由でデバッグを有効にし、電話が既にデバッグ許可を発行しているコンピューターと、電話が表示されるとすぐに接続するWi-Fiネットワークが存在すること(このホームWi-Fiがあります)。



準備作業



1つのアクティビティでプロジェクトを作成します。 ロック画面のために彼女は白色光を見ることができませんが、主な仕事をするサービスを開始する必要があります。
MainActivity.java
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); startService(new Intent(this, SynchronizeService.class)); } }
      
      







ファイルのコピーは別のサービスになります。 アクティビティは表示されないため、その実行可能性に頼るべきではありませんが、フォアグラウンドで起動されたサービスはこのタスクを完全に実行します。

SynchronizeService.java
 public class SynchronizeService extends Service { private static final int FOREGROUND_NOTIFY_ID = 1; @Override public void onCreate() { super.onCreate(); final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.synchronize_service_message)) .setContentIntent(getDummyContentIntent()) .setColor(Color.BLUE) .setProgress(1000, 0, true); startForeground(FOREGROUND_NOTIFY_ID, builder.build()); //    CPU    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SynchronizeWakelockTag"); wakeLock.acquire(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } }
      
      





先に進む前に、プロジェクトにJCIFSライブラリーを追加するbuild.gradleファイルの依存関係を追加します。

 dependencies { ... compile 'jcifs:jcifs:1.3.17' }
      
      





また、マニフェストにいくつかのアクセス許可を追加する必要があり、そこに私たちのサービスについて書くことを忘れないでください。 最終的に、AndroidManifest.xmlはこのように見えました。

AndroidManifest.xml
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ru.kamisempai.wifisynchronizer"> //      . <uses-permission android:name="android.permission.WAKE_LOCK"/> //     . <uses-permission android:name="android.permission.INTERNET"/> //    SD . <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:label="@string/app_name" android:icon="@mipmap/ic_launcher"> <activity android:name=".activities.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".services.SynchronizeService"/> </application> </manifest>
      
      





ファイルをコピーする



これで準備はすべて完了です。 これで、アプリケーションを起動すると、通知のリストにサービスメッセージが表示されます(Android 5以降、ロック画面にメッセージを表示するように設定できます。Androidバージョンが小さい場合、このメッセージは表示されません)。最もおいしいのはファイルをダウンロードすることです。



メインスレッドでネットワーク操作を実行しないように、AsyncTaskですべてを取り出します。

 public class CopyFilesToSharedFolderTask extends AsyncTask<Void, Double, String> { private final File mFolderToCopy; private final String mSharedFolderUrl; private final NtlmPasswordAuthentication mAuth; private FileFilter mFileFilter; public CopyFilesToSharedFolderTask(File folderToCopy, String sharedFolderUrl, String user, String password, FileFilter fileFilter) { super(); mFolderToCopy = folderToCopy; // ,    . mSharedFolderUrl = sharedFolderUrl; // Url   ,       . mAuth = (user != null && password != null) ? new NtlmPasswordAuthentication(user + ":" + password) : NtlmPasswordAuthentication.ANONYMOUS; mFileFilter = fileFilter; } }
      
      



ユーザーとパスワードのパラメーターには特に注意を払う必要があります。 これは、NtlmPasswordAuthenticationの作成に使用されるネットワークフォルダーのユーザー名とパスワードです。 フォルダーにアクセスするためにパスワードが必要ない場合は、認証としてNtlmPasswordAuthentication.ANONYMOUSを使用する必要があります。 一見シンプルに見えますが、認証はネットワークフォルダーを操作するときに発生する最大の問題です。 通常、問題のほとんどは、コンピューターのセキュリティポリシーの不適切な設定に潜んでいます。 設定を確認する最良の方法は、ネットワーク経由での作業をサポートする他のファイルマネージャーを使用して、電話機のネットワークフォルダーを開くことです。



SmbFileは、ネットワークファイルを操作するためのファイルです。 驚くべきことに、JCIFSではファイルを扱うのは非常に簡単です。 SmbFileと通常のFileの違いはほとんど感じられません。 目を引くのは、ほぼすべてのクラスメソッドに制御された例外が存在することだけです。 また、SmbFileオブジェクトを作成するには、前に作成した認証データが必要です。

 private double mMaxProgress; private double mProgress; ... @Override protected String doInBackground(Void... voids) { mMaxProgress = getFilesSize(mFolderToCopy); mProgress = 0; publishProgress(0d); try { SmbFile sharedFolder = new SmbFile(mSharedFolderUrl, mAuth); if (sharedFolder.exists() && sharedFolder.isDirectory()) { copyFiles(mFolderToCopy, sharedFolder); } } catch (MalformedURLException e) { return "Invalid URL."; } catch (IOException e) { e.printStackTrace(); return e.getMessage(); } return null; }
      
      



doInBackgroundメソッドはエラーメッセージを返します。 nullが返された場合、すべてがエラーなくスムーズに実行されました。



多くのファイルがあるかもしれません...いいえ、そうではありません。 非常に多くの可能性があります! したがって、進捗を示すことは重要な機能です。 再帰的なgetFilesSizeメソッドは、合計進行状況を計算するために必要な合計ファイルサイズを計算します。

 private double getFilesSize(File file) { if (!checkFilter(file)) return 0; if (file.isDirectory()) { int size = 0; File[] filesList = file.listFiles(); for (File innerFile : filesList) size += getFilesSize(innerFile); return size; } return (double) file.length(); } private boolean checkFilter(File file) { return mFileFilter == null || mFileFilter.accept(file); }
      
      



コンストラクターに渡されるフィルターは、不要なファイルとフォルダーを排除するのに役立ちます。 たとえば、ピリオドで始まるすべてのフォルダーを除外したり、Androidフォルダーをブラックリストに追加したりできます。



前に言ったように、SmbFileの操作は通常のファイルの操作と変わりません。したがって、電話からコンピューターにデータを転送するプロセスはオリジナルではありません。 このコードをネタバレの下に隠して、さらにわかりやすいコードで記事が乱雑にならないようにします。

CopyFilesおよびcopySingleFileメソッド
 private static final String LOG_TAG = "WiFiSynchronizer"; private void copyFiles(File fileToCopy, SmbFile sharedFolder) throws IOException { if (!checkFilter(fileToCopy)) return; //       ,   . if (fileToCopy.exists()) { if (fileToCopy.isDirectory()) { File[] filesList = fileToCopy.listFiles(); //       "/". SmbFile newSharedFolder = new SmbFile(sharedFolder, fileToCopy.getName() + "/"); if (!newSharedFolder.exists()) { newSharedFolder.mkdir(); Log.d(LOG_TAG, "Folder created:" + newSharedFolder.getPath()); } else Log.d(LOG_TAG, "Folder already exist:" + newSharedFolder.getPath()); for (File file : filesList) copyFiles(file, newSharedFolder); //   } else { SmbFile newSharedFile = new SmbFile(sharedFolder, fileToCopy.getName()); //    ,    . // ,   ,      ,       . if (!newSharedFile.exists()) { copySingleFile(fileToCopy, newSharedFile); Log.d(LOG_TAG, "File copied:" + newSharedFile.getPath()); } else Log.d(LOG_TAG, "File already exist:" + newSharedFile.getPath()); //  . mProgress += (double) fileToCopy.length(); publishProgress(mProgress / mMaxProgress * 100d); } } } //       . private void copySingleFile(File file, SmbFile sharedFile) throws IOException { IOException exception = null; InputStream inputStream = null; OutputStream outputStream = null; try { outputStream = new SmbFileOutputStream(sharedFile); inputStream = new FileInputStream(file); byte[] bytesBuffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(bytesBuffer)) > 0) { outputStream.write(bytesBuffer, 0, bytesRead); } } catch (IOException e) { exception = e; } finally { if (inputStream != null) try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } if (outputStream != null) try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (exception != null) throw exception; }
      
      



コードは明らかですが、1つありますが、まったく明らかではありません。これは、新しいSmbFileを作成するときにフォルダー名の最後に「/」文字を追加することです。 実際、JCIFSは、「/」記号で終わらないすべてのファイルを、ディレクトリとしてではなくファイルとしてのみ扱うということです。 したがって、ネットワークフォルダーのURLが「file:// MY-PC / shared / some_foldel」のような場合、「some_foldel」フォルダーに新しいフォルダーを作成するときにインシデントが発生します。 つまり、「some_foldel」はドロップされ、新しいフォルダーには、予期される「file:// MY-PC / shared / some_foldel / new_folder」ではなく、「file:// MY-PC / shared / new_folder」というURLが付けられます。 同時に、そのようなフォルダーでは、isDirectory、mkdir、またはlistFilesメソッドが正しく機能します。



ほぼ完了。 次に、onCreateサービスでこのタスクの実行を開始します。

 private static final int FOREGROUND_NOTIFY_ID = 1; private static final int MESSAGE_NOTIFY_ID = 2; private static final String SHARED_FOLDER_URL = "file://192.168.0.5/shared/"; ... final File folderToCopy = getFolderToCopy(); CopyFilesToSharedFolderTask task = new CopyFilesToSharedFolderTask(folderToCopy, SHARED_FOLDER_URL, null, null, null) { @Override protected void onProgressUpdate(Double... values) { builder.setProgress(100, values[0].intValue(), false) .setContentText(String.format("%s %.3f", getString(R.string.synchronize_service_progress), values[0]) + "%"); mNotifyManager.notify(FOREGROUND_NOTIFY_ID, builder.build()); } @Override protected void onPostExecute(String errorMessage) { stopForeground(true); if (errorMessage == null) showNotification(getString(R.string.synchronize_service_success), Color.GREEN); else showNotification(errorMessage, Color.RED); stopSelf(); wakeLock.release(); //    wakeLock } @Override protected void onCancelled(String errorMessage) { //     .   ,    - . //        . stopSelf(); wakeLock.release(); } }; task.execute();
      
      



私の場合、ログインとパスワードは不要です。フィルターも指定しませんでした。 onProgressUpdateメソッドは、進行状況を表示するためにオーバーライドされ、onPostExecuteはダウンロードの終了またはエラーに関するメッセージを表示し、サービスは終了します。



アプリケーションを起動します。 実行中のサービスからメッセージが表示されました。 合計ファイルサイズが計算されている間、未確定の進行状況が表示されます。 しかし、ここではインジケーターは0%を示し、その後、ストリップは小さく、わずかに目立つステップで徐々に100%に移動し始めます。





作業が完了すると、成功した結果に関するメッセージが画面に表示され、コンピューターでは以前に壊れた電話に必要なファイルがすべて保存されていました。



予期せぬ発見



私が受け取った必要なもの。 同時に、カモメを醸造し、ソファでバラバラになり、いくつかのシリーズをオンにします。 しかし、待ってください! 電話は私のものであり、その上のファイルへのアクセスはロシアの法律と矛盾しないという事実にもかかわらず、私はパスワードを使用せずにそれらを取り出しました! 同時に、ルートは電話ではありませんでした。 つまり、デバッグモードがオンの場合、パスワードを知らなくても「SDカード」の内容にアクセスすることは難しくありません。 ハッキングから保護するための唯一の太い点は、既にデバッグ権限を持っているコンピューターを使用する必要があることだけです。



少し前に発表されたAndroidの新しいバージョンでは、必要な権限にアクセスするにはユーザーの確認が必要になるため、画面がロックされている場合は不可能であるため、このが閉じられる可能性があります。 それまでの間、Android開発者は、他の人に自分のヌード写真を見せたくない場合は目を光らせてください。 また、USBを介してどのコンピューターでもデバッグできるようにすると、自分の電話を壊すための別の抜け穴ができることを忘れないでください。



ご清聴ありがとうございました。 コメントであなたの考えを見てうれしいです。

ソースコードは次のリンクにあります: github.com/KamiSempai/WiFiFolderSynchronizer



UPD:予想どおり、私の問題に対するより簡単な解決策があります。 つまり、adb pullコマンドを使用します。 残念ながら、adbのまれな使用と問題の狭い視野により、私はそれに到達することができませんでした。 ファイルのダウンロードではなく、画面のロックを解除しようとしました。

EvgenTに感謝します。



All Articles