At PSI we are always looking for unique ways to improve our techniques across the entire cyber killchain spectrum. Persistence is in our name, and this post is exactly about that.
Knowingly or unknowingly, everyone is familiar with the concept of push notifications, mainly from their interaction with mobile apps. The magic about push notifications is that the receiving app does not need to be running in order to receive the notification data. And that is exactly what we will abuse, by launching our beacon, when needed, via push notifications.
When it comes to Windows apps, push notifications are supported by both packaged/published (i.e. MS store) and unpackaged (i.e. standalone) applications. However both work a little differently. In published apps, a package SID is generated by the store which can be tied to a Notification Hub on Azure where all "device" registrations can be managed from.
On the other hand, unpackaged apps have no SID, causing slightly limited flexibility, but still enough to achieve our goals. As this blogpost tackles standalone and unpublished binaries, the focus lies on unpackaged apps.
There are four types of notifications on Windows:
We're only interested in the raw notifications because they don't include a UI component -we want to be discrete-, and they allow to use unstructured strings up to 5KB.
Push notifications rely on the presence of the Windows App SDK v1.1 or higher. While one of the supported versions might already be installed on a target system, for push notifications to work properly, two additional SDK extensions are also needed:
These two additional packages are rarely pre-installed, but they can be easily deployed by a non-admin user without any specific privilege.
Before diving into the relevant code, it is important to set up the Azure infrastructure that will handle all the different notification channels. Make sure you follow Steps 1, 2 and 3 to create an AAD (Azure Active Directory) app registration. Pay particular attention to the note in Step 2.6.
At the end of the process you should have an Object ID that is tied to your application. This Object ID is the only thing needed to create a notification channel in your app.
When utilizing push notifications it is important to first initialise the process to use the Windows App SDK. The code in .NET 6.0 with WinRT support (net6.0-windows10.0.22621.0) to request a notification channel is as simple as the following snippet:
Essentially, we register the app to handle incoming notifications, even when it's not running, and we request a channel Uri for the AAD app created previously.
Now that the app has registered itself to handle incoming push notifications, how do we launch it remotely?
We simply call Microsoft's notification API with the details of our tenant, app id and channel Uri. A simple Python script which sends a raw notification is shown below:
What happens next is the following: the local push notification service, PushNotificationsLongRunningTask.exe, will spawn our executable as its child process, including our notification payload (eg. a URL for our second stage shellcode) in its arguments.
In other words, we can drop our executable anywhere on the disk and have a Microsoft service launch it for us remotely on demand.
This is a powerful persistence technique, and becomes even more powerful when abused by published apps. Stay tuned for an upcoming blogpost about abusing published apps.
You can find a working VS project on our team's Github: https://github.com/persistent-security/hermes-the-messenger
Establishing and maintaining a push notification channel over a proxy is not supported by Microsoft. The compromised machine will need to establish a direct connection to the WNS servers. VPN interfaces, however, are supported.
Executables must not run from a shared drive or requesting a notification channel will fail.
Unpackaged apps, unlike packaged/published ones, must send the channel Uri to an external server controlled by the operator. In published apps, device registrations and channel Uris can be viewed in the notification hub.
Single-file binaries with (most of the) dependencies embedded can be significant in size, over 100MB. With trimming enabled it can go down to ~20MB. This can be limiting in certain scenarios, or useful in others..