Normal view

There are new articles available, click to refresh the page.
Before yesterdayNVISO Labs

Top things that you might not be doing (yet) in Entra Conditional Access – Advanced Edition

18 March 2024 at 08:00
Top things you might not be doing (yet) in Entra ID Conditional Access - Advanced Edition

Introduction

In the first post of the top things that you might not be doing (yet) in Entra Conditional Access, we focused on basic but essential security controls that I recommend you checking out if you do not have them implemented already. In this second part, we’ll go over more advanced security controls within Conditional Access that, in my experience, are frequently overlooked in environments during security assessments. However, they can help you better safeguarding your identities.

Similar to my previous blog post, the list of controls provided here is not exhaustive. The relevance of each control may vary depending on your specific environment. Moreover, you should not rely on those only, but instead investigate whether they would bring any value in your environment. I also encourage you to check out other Conditional Access controls available to make sure your identities are correctly protected.

This article focusses on features that are available in Entra ID Premium P1 and P2 licenses. Therefore, if none of those licenses are available, check my previous blog post on how to protect identities in Entra ID Free: https://blog.nviso.eu/2023/05/02/enforce-zero-trust-in-microsoft-365-part-1-setting-the-basics/. Note that other licenses could also be required depending on the control.

Additionally, should you need any introduction to what Entra Conditional Access is and which security controls are available, feel free to have a look at this post: https://blog.nviso.eu/2023/05/24/enforce-zero-trust-in-microsoft-365-part-3-introduction-to-conditional-access/.

Finally, if you have missed part 1, feel free to check it out: https://blog.nviso.eu/2024/02/27/top-things-that-you-might-not-be-doing-yet-in-entra-conditional-access/.

Entra Conditional Access security controls

Make sure all Operating Systems are covered in your current Conditional Access design

License requirement: Entra ID Premium P1

When performing Entra Conditional Access assessments, we usually see policies to enforce controls on Windows, and sometimes Android and iOS devices. However, other platforms such as MacOS, Windows Phone, and Linux are sometimes forgotten. This can represent a significant gap in your overall security defense as access from those platforms is not restricted by default. You could use all the Conditional Access policy features, but if you do not include them, all your effort will be in vain.

Indeed, it is well known from attackers that “nonstandard” platforms are sometimes forgotten in Conditional Access. By trying to access your environment using them, they might be able to simply bypass your Conditional Access (CA) policies. It is therefore necessary to make sure that your security controls are applied across all operating systems.

The next points will shed some light on controls that you can implement to support all platforms.

Don’t be afraid of blocking access, but only in a considered and reasonable way 🙂

License requirement: Entra ID Premium P1

Based on our numerous assessments over the years, we have observed that ‘Block’ policies are typically not implemented in Conditional Access. While those policies can definitely have an adverse impact on your organization and end users, specific actions, device platforms, or client applications (see part 1), should be blocked.

For example, if you do not support Linux in your environment, why not simply block it? Moreover, if Linux is only required for some users, Conditional Access allows you to be very granular by targeting users, devices, locations, etc. Therefore, platforms can be blocked for most use cases, and you can still allow specific flows based on your requirements. This principle can be extended to (guest) user access to applications. Should guest users have access to all your applications? No? Then, block it. Such control effectively decreases the overall attack surface of your environment.

Conditional Access policy to block access to all cloud applications from Linux.
Example: Conditional Access policy to block access to all cloud applications from Linux.

I highly recommend you giving a thought to ‘Block’ policies. Moreover, they could be extended to many other scenarios on top of the device platforms and (guest) user access to cloud apps.

Before moving on to the next point, I want to highlight that such policies can be very powerful. So powerful that they could lock you out of your own environment. To avoid that, please, always exclude emergency / break-the-glass accounts. In addition, never rollout Conditional Access policies in production before proper testing. The report-only policy mode can be of great help for that. Moreover, the What If tool is also a very good tool that you should be using to assess the correctness of your policies. Once the potential impact and the policy configuration have been carefully reviewed, gradually roll out policies by waves over a period of a few weeks with different pilot groups.

Use App Protection Policies to reduce the risk of mobile devices

License requirement: Entra ID Premium P1 and Microsoft Intune

If access from mobile devices, i.e., Android and iOS, is required for end user productivity for example, App Protection Policies (APPs) can help you preventing data loss on devices that you may not fully manage. Note that App Protection Policies are also now available for Windows devices but are still in preview at the time of writing (beginning of March 2024).

In short, App Protection Policies are a set of rules that ensures that data access through mobile apps is secure and managed appropriately, even on personal devices. APPs can enforce the use of Microsoft-managed applications, such as Microsoft apps, enforce data encryption, require authentication, restrict actions between managed and unmanaged applications, wipe the data from managed applications, etc.

For that purpose, the following Grant control can be configured:

Enforce App Protection Policies in Conditional Access.
Example: Enforce App Protection Policies in Conditional Access.

Of course, to be effective, App Protection Policies should be created in Intune and assigned to users. Because of that, Microsoft Intune licenses are required for users in scope of that control.

Moreover, together with the Exchange Online and SharePoint Online app enforced restrictions capabilities, you can allow, restrict, or block access from third-party browsers on unmanaged devices.

Require Authentication Strengths instead of standard multi-factor authentication

License requirement: Entra ID Premium P1

Authentication Strength policies in Entra Identity Conditional Access enable administrators to mandate certain authentication methods, such as FIDO2 Security Keys, Windows Hello, Microsoft Authenticator, and passwordless authentication. Please note that the authentication methods available to users will be determined by either the new authentication method policies or the legacy MFA policy.

By configuring Authentication Strengths policies and integrating them in Conditional Access policies, you can further restrict (external) user access to sensitive applications or content in your organization. Built-in policies are also available by default:

Authentication strengths policies in Entra ID.
Built-in authentication strengths policies in Entra ID.

One common use case for Authentication Strength policies is to ensure that user accounts with privileged access are protected by requiring phishing-resistant MFA authentication, thus restricting access to authorized users only. In Conditional Access, this goal can be achieved through multiple methods:

  1. Secure Privileged Identity Management role activation with Conditional Access policies (see next point for more details);
  2. Include privileged Entra ID roles in Conditional Access, by selecting directory roles in the policy assignments;
  3. Integrate protected actions into Conditional Access policies to enforce step-up authentication when users perform specific privileged and high-impact actions (see next point for more details).

Other use cases include enforcing stricter authentication requirements when connecting from outside a trusted location, when a user is flagged with a risk in Identity Protection, or when accessing sensitive documents in SharePoint Online.

Finally, as mentioned above, external users (only those authenticating with Microsoft Entra ID, at the time of writing) can be required to satisfy Authentication Strengths policies. The behavior will depend on the status of the cross-tenant access settings, as explained in my previous blog post.

Use Authentication Context to protect specific actions and applications

License requirement: Entra ID Premium P1

Authentication Contexts in Conditional Access allow to extend the locations or actions covered by Conditional Access policies. Indeed, they can be associated with applications, SharePoint Online sites or documents, or even specific privileged and high impact actions in Entra ID.

Before diving into how they can be used, we will quickly go over how they can be created. Authentication Context is a feature of Microsoft Entra Conditional Access and can therefore be managed from the Conditional Access service. Before being able to use them, they need to be created and published to applications:

Add an authentication context in Microsoft Entra Conditional Access.
Add an authentication context in Microsoft Entra Conditional Access.

Once they have been created and published, we can use them in Conditional Access policies. Let’s take a closer look at the different scenarios described above:

  1. Integrate Authentication Contexts in Sensitivity Labels to require step-up authentication or enforce restrictions when accessing sensitive content:

In this first example, the Super-Secret sensitivity label has been configured to require step-up authentication when accessing documents with that label assigned:

Enforce step-up authentication in Sensitivity Labels.
Enforce step-up authentication in Sensitivity Labels.

If we configure the below CA policy with the target resource set to the ‘Sensitive documents’ Authentication Context, users will have to satisfy the phishing-resistant MFA requirements, unless it has already been satisfied, when accessing documents labeled with the Super-Secret label:

Conditional Access policy to require phishing-resistant MFA when accessing sensitive documents (i.e., documents labeled as Super-Secret in our example).
Example: Conditional Access policy to require phishing-resistant MFA when accessing sensitive documents (i.e., documents labeled as Super-Secret in our example).
  • Integrate Authentication Context with Privileged Identity Management roles to enforce additional restrictions on role activation (PIM requires Entra Premium P2 licenses):

Role settings in Entra Privileged Identity Management can be changed to require Authentication Context on role activation. That way, administrators can ensure that high privileged roles are protected against abuse and only available to authorized users themselves:

PIM role setting to require Microsoft Entra Conditional Access authentication context on activation.
PIM role setting to require Microsoft Entra Conditional Access authentication context on activation.

When such control is configured in PIM, users will not be prompted to perform MFA twice if the specified MFA requirement has been previously met during the sign-in process. On the other hand, they will be prompted with MFA if it hasn’t been met before.

Similar to the previous point, the same principle applies to the creation of the Conditional Access policy. The custom PIM Authentication Context should be set as the target resource, and the conditions and access controls configured to meet your security requirements.

Important note: when changing the configuration of high privileged roles, which allow to modify the configuration of Microsoft Entra PIM, make sure you are not locking yourself out of the environment by having at least one active assignment configured.

  • Integrate Entra ID Protected Actions with Conditional Access policies:

Finally, by integrating Entra Identity Protected Actions with Conditional Access, administrators can introduce an extra layer of security when users attempt specific actions, such as modifying Conditional Access policies. Once again, make sure you are not locking yourself out here.

With Protected Actions, you can require strong authentication, enforce a shorter session timeout or filter for specific devices. To create a Protected Action, administrators first need to create an Authentication Context, which will then be assigned to a set of actions in the ‘Entra ID Roles and administrators’-page:

Link protected actions in Entra ID to authentication context.
Link Protected Actions in Entra ID to Authentication Context.

In this example, the ‘Protected Actions’ Authentication Context has been linked to permissions that allow updating, creating, and deleting Entra Conditional Access policies.

Then, in a Conditional Access policy, set the target resource to the ‘Protection Actions’ Authentication Context and define the conditions as well as the access controls.

Once in effect, administrators will be required to meet the configured authentication requirements and/or conditions each time they attempt to modify Conditional Access policies:

Step-up authentication when performing an Entra ID protected actions.
Step-up authentication required when performing an Entra ID protected actions.

Use Device Filters in Conditional Access policies conditions

License requirement: Entra ID Premium P1

Last but not least, the ‘Filter for devices’-condition in Entra Conditional Access is a powerful tool that can be used for multiple purposes. Indeed, by using this condition, it is possible to target specific devices based on their ID, name, Ownership, compliance or join state, model, operating system, custom attributes, etc.

Besides the common scenarios of using the device filter condition to target compliant, non-compliant, registered, or joined devices, it can be used to restrict or block access based on more advanced conditions. For instance, you might require that only devices with certain operating system versions, specific device IDs, or device names that follow a particular pattern are allowed to access specific applications. Custom attributes can also be useful for more granularity, if needed.

The following filter will target devices meeting the following criteria:

  • The display name of the device should contain ‘ADM’;
  • The device should be seen as compliant, in Microsoft Intune, for instance;
  • The device state should be Microsoft Entra joined;
  • And the ExtensionAttribute4 should contain ‘anyvalue’. Extension Attributes for Entra ID registered devices can be added and customized using the Microsoft Graph REST API.
Device filter condition in Entra Conditional Access policies.
Example: Device filter condition in Entra Conditional Access policies.

More information about the different operators and properties can be found in the ‘Resources’-section below.

Bonus: Restrict authentication flows

License requirement: Entra ID Premium P1

The ability to restrict authentication flows in Microsoft Entra Conditional Access, which is still in preview, has been introduced end of February (when I was writing this blog post). I included it to make sure that you are aware of this new feature. However, I do not recommend implementing it in production before it is released in General Availability (at least without proper investigation and testing!).

This functionality has been introduced as a new condition in Microsoft Entra Conditional Access policies and allows to restrict device code flow and authentication transfer.

Firstly, the device code flow has been introduced to facilitate user sign-in on input-constrained devices, referred to as ‘device A.’ With this flow, users can authenticate on ‘device A’ by using a secondary device, referred to as ‘device B.’ They do this by visiting the URL: https://login.microsoftonline.com/common/oauth2/deviceauth. Once the user successfully signs in on ‘device B,’ ‘device A’ will receive the necessary access and refresh tokens.

The flow can be represented as follows:

Device code flow authentication.
Device code flow authentication.

However, this functionality has been, and still is, abused by attackers attempting to trick users into entering the device code and their credentials.

Therefore, Conditional Access policies could now be used to block device code flow, or restrict it to managed devices only. This measure helps ensure that phishing attempts are unlikely to succeed unless the attackers possess a managed device.

Conditional Access policy to block the use of device code flow.
Example: Conditional Access policy to block the use of device code flow.

Moreover, device code flow authentication attempts are visible in the Entra ID Sign-in Logs:

Device code flow authentication attempt.
Device code flow authentication attempt.
Identify device code flow sign-in activities with KQL.
Identify device code flow sign-in activities with KQL.

Secondly, authentication transfer enables users to transfer their authenticated state from one device to another, for instance, by scanning a QR code with their mobile phone from a desktop application. This functionality allows to reduce the number of times users have to authenticate across the different platforms. However, by doing so, users aren’t required to perform MFA again on their mobile phone if they have already performed MFA on their laptop.

Like device code flow authentication, authentication transfer can be blocked using a Conditional Access policy. To do so, simply select ‘Authentication transfer’ under Transfer methods.

Finally, authentication transfer can also be detected in the Entra ID Sign-in logs. Indeed, ‘QR code’ is set as the authentication method in the authentication details.

Evaluate Conditional Access policies

License requirement: Entra ID Premium P1

As a final note, I wanted to highlight the What If tool in Entra Conditional Access. It allows administrators to understand the result of the CA policies in their environment. For that purpose, administrators can select target users, applications, any available conditions, etc., to make sure that existing CA policies have the expected behavior. It also helps troubleshooting the configuration by gaining visibility into the policies that apply to users under specific conditions. The What If tool can be accessed in Entra ID > Conditional Access > Policies:

What if tool in Entra Conditional Access.
What if tool in Entra Conditional Access.

Moreover, the DCToolbox PowerShell module, which is an amazing toolbox for various Microsoft 365 security tasks, developed by Daniel Chronlund, also allows you to evaluate your current Conditional Access policies for a specific scenario. For that purpose, you can use the Invoke-DCConditionalAccessSimulation function and the tool will fetch all existing CA policies and evaluates them against the scenario that you have provided as arguments. You can find the DCToolbox PowerShell module on GitHub here: https://github.com/DanielChronlund/DCToolbox.

I highly recommend using one of these tools to evaluate your newly created or existing Conditional Access policies. Also note that proper testing and validation with different pilot phases and progressive rollouts is essential to avoid impacting end users when creating new policies.

Finally, as a general best practice, Conditional Access policies, and potential exceptions, should be properly documented. For that purpose, the DCToolbox tool allows you to export the current configuration of your Conditional Access policies in an Excel file, for example.

Conclusion

In this second blog post about Entra Conditional Access settings and configurations, we went over important principles that might help you increase the overall security posture of your environment. As for the first part, the settings and configuration items that I have highlighted could be considered when designing or reviewing your Entra Conditional Access implementation. This list is non-exhaustive and has been made based on my experience reviewing and implementing Conditional Access policies in different environments. Also, it is important to rigorously evaluate any policies before rolling them out in production and to make sure that other controls have also been properly configured in your cloud environment. Conditional Access policies are a great way to safeguard your identities and critical resources, but are not the only layer of defense that you should be relying on.

At NVISO, we have built an expertise reviewing cloud environments and have designed and implemented Entra Conditional Access on numerous occasions. If you want to know more about how we can help you in the journey of building or strengthening your Conditional Access setup, among others, feel free to connect on LinkedIn or visit our website at https://www.nviso.eu.

Resources

You can contact me on LinkedIn should you have any questions or remarks. My contact details can be found below.

Additionally, if you want to get a deeper understanding of some of the topics discussed in the blog post, all the resources that I have used can be found below:

About the author

Guillaume Bossiroy

Guillaume Bossiroy

Guillaume is a Senior Security Consultant in the Cloud Security Team. His main focus is on Microsoft Azure and Microsoft 365 security where he has gained extensive knowledge during many engagements, from designing and implementing Entra ID Conditional Access policies to deploying Microsoft 365 Defender security products.

Additionally, Guillaume is also interested into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

Unpacking Flutter hives

13 March 2024 at 08:00
Unpacking Flutter Hives

Intro

When analyzing the security of mobile applications, it’s important to verify that all data is stored securely (See OWASP MASVS-STORAGE-1). A recent engagement involved a Flutter app that uses the Isar/Hive framework to store data. The engagement was unfortunately blackbox, so we did not have access to any of the source code. This especially makes the assessment more difficult, as Flutter is pretty difficult to decompile, and tools like Doldrums or reFlutter only work for very specific (and old) versions. Frida can be used (see e.g. Intercepting Flutter traffic)

The files we extracted from the app were encrypted and we needed to figure out what kind of data was stored. For example, storing the password of the user (even if it’s encrypted) would be an issue, as the password can for example be extracted using a device backup.

In order to figure out how the data is encrypted, we needed to analyze the Hive framework and find some way to extract that data in cleartext. Hive is a “Lightweight and blazing fast key-value database written in pure Dart.” which means we can’t easily monitor what is stored inside of the databases using Frida. There also isn’t a publicly available Hive viewer that we could find, and there’s a probably good reason for that, as we will see.

The goal of this blogpost is to obtain the content of an encrypted Hive without having access to the source code. This means we will:

  • Create a Flutter test app to get some useful Hives
  • Understand the internals of the Hive framework
  • Create a generic Hive reader that works on encrypted Hives containing custom objects
  • Obtain the password of the encrypted Hive
  • (Bonus) Recover deleted items

Let’s start!

Isar / Hive

Hive is a key-value framework built on top of Isar, which is a no-sql library for Flutter applications. It is possible to store all the simple Dart types, but also more complex types like a List or Map, or even custom objects via custom TypeAdapters. The project is currently in a transition phase to v4 so the focus is on v2.2.3, which is what the target application was most likely using.

While Hive is the name of the framework, what we are actually interested in are boxes. Boxes are the actual files that are stored on the system and each box contains one or more data frames. A data frame simply holds one key-value pair.

Each box is either plaintext or encrypted. The encryption is based on AES-256, which means you need a 256-bit key to open an encrypted box. The storage of this key is not the responsibility of Hive, and the documentation suggests to store your key using the flutter_secure_storage plugin. This is interesting, as the flutter_secure_storage plugin does use the system credential storage of the device to store data, so we can potentially intercept the key when it is being retrieved using Frida.

Keys are not encrypted!
One very important thing to realize is that an encrypted box is not actually fully encrypted. For each key-value pair that is stored, only the value is stored encrypted, while the key is stored in plaintext. This is mentioned in the documentation, but it’s easy to miss it. Now this is generally not a big deal, except of course if sensitive data is being used as the key (e.g. a user ID).

Creating a small test app

Let’s create a small Flutter application that uses Hive and saves some data into a box. The code for this was mostly generated by poking Chat-GPT so that we could spend our time reverse-engineering (and fighting XCode). For simplicity’s sake, I’m deploying to macOS so that the boxes are stored directly on the system and we can easily analyze them.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dir = await getApplicationDocumentsDirectory();
  Hive.init(dir.path);
  await createBox();
  runApp(MaterialApp(home:MyApp()));
}

void createBox() async {
  // Storing examples of each supported datatype
  var box = await Hive.openBox('basicBox');
  box.put('myInt', 123); // int
  box.put('myDouble', 123.456); // double
  box.put(0x22, true); // bool
  box.put('myString', 'Hello Hive'); // String
  box.put('myBytes', Uint8List.fromList([68, 97, 114, 116])); // List<int>
  box.put('myList', [1, 2, 3]); // List<dynamic>
  box.put('myMap', {'name': 'Hive', 'isCool': true}); // Map<dynamic, dynamic>
  box.put('myDateTime', DateTime.now()); // DateTime
}
Dart

And our dependencies:

dependencies:
  flutter:
    sdk: flutter

  hive: ^2.2.3
  hive_flutter: ^1.1.0
  isar_flutter_libs: ^3.1.0+1
  path_provider: ^2.1.2
  file_picker: ^6.1.1
  path: ^1.9.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  hive_generator: ^1.1.0 
  build_runner: ^2.0.1
YAML

When we run the application, it creates a new .hive file, which is in fact a box containing all of our key-value pairs:

(Terminal screenshots created using carbon.now.sh)

Hive internals

A box contains multiple frames, and each frame is responsible for indicating how long it is. There is no global index of the frame offsets, which means that we can’t jump directly to a specific frame and we have to parse all the frames one by one until we have parsed all frames. Each frame consists of a key (either a string or an index) and a value, which can be any default or custom type:

The Integer value (123) is stored in Float64 format so it looks a bit weird.

If the key is a String (frames 1 and 2), it has type 0x01, followed by the length and then the actual ASCII value. If the key is an int (frame 3), the encoding is slightly different. The type is 0x00 and the key is encoded as a uInt32. The key will be an int if you specify an int as the key (e.g. myBox.put(0x22, true)) or if you use the autoIncrement feature (myBox.add("test")).

If we run the application a second time, Hive will open the box from the filesystem (based on the name of the box) and load all the current values. When the put instructions are executed again, Hive doesn’t overwrite the frame belonging to the given key (as that would require the entire file to be shifted based on the new lengths, a very intensive operation), but rather it appends a new frame with the new value. As a result, running the code twice will double the size of the box. When the box is read, all frames are parsed sequentially and all key-value pairs simply overwrite any previously loaded information.

Deleting data
Even if you delete a value using .delete(“key”), this simply appends a new delete frame. A delete frame is a frame with an empty value, indicating that the value has been deleted. The previous data is however not deleted from the box.

It is possible to optimize the box using the compact function, or Hive may do this automatically at some point based on the maximum file size of the box, which can be configured when opening the box. This feature is documented, but only under the advanced section. As a result, there is a very good chance that older values are still available in a box, even if they were deleted.

For example, let’s take the following box:

var emptyBox = await Hive.openBox("emptyBox");
emptyBox.put("mySecret", "Don't tell anyone");
emptyBox.delete("mySecret");
Dart

The created box still contains the secret if you look at the binary content:

The delete frame is simply a frame with a key and no value.

Custom types

In addition to storing normal Dart types in a box, it is possible to store custom types as long as Hive knows how to serialize/deserialize them. Let’s look at a quick example with a custom Bee class:

import 'package:hive/hive.dart';

part 'BeeModel.g.dart';

@HiveType(typeId: 1)
class Bee extends HiveObject{
  @HiveField(0)
  final String name;
  @HiveField(1)
  final int age;
  Bee({
    required this.name, 
    required this.age,
  });
}
Dart

The Bee class extends HiveObject and defines two string properties. It’s not technically necessary to extend the HiveObject class, but it makes things easier. We’ve also added annotations so that we can use the hive_generator package to automatically generate a serializer by running dart run build_runner build:

This will generate a new class called BeeModel.g.dart which takes care of serializing/deserializing:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'BeeModel.dart';

// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************

class BeeAdapter extends TypeAdapter<Bee> {
  @override
  final int typeId = 1;

  @override
  Bee read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    final fields = <int, dynamic>{
      for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return Bee(
      name: fields[0] as String,
      age: fields[1] as int,
    );
  }

  @override
  void write(BinaryWriter writer, Bee obj) {
    writer
      ..writeByte(2)
      ..writeByte(0)
      ..write(obj.name)
      ..writeByte(1)
      ..write(obj.age);
  }

  @override
  int get hashCode => typeId.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BeeAdapter &&
          runtimeType == other.runtimeType &&
          typeId == other.typeId;
}
Dart

We can see that the serialization format is pretty straightforward: it first writes the number of fields (2), followed by an index-value pair which correspond to the HiveField annotations. The write() function is the function that is used during a normal put() operation, so the fields will have the same structure as seen earlier.

Finally, to be able to use this new type, the Adapter needs to be registered:

  Hive.registerAdapter(BeeAdapter());
  var beeBox = await Hive.openBox("beeBox");
  beeBox.put("myBee", Bee(name: "Barry", age: 1));
Dart

After running this code, the beeBox is generated, containing one frame:

Decoding unknown types

Let’s now assume that we have access to a box with unknown types. We can still load it, as long as we can figure out a suitable deserializer. It’s not a far stretch to assume that the developer has used the automatically generated adapter, so let’s focus on that. If they haven’t, you’ll have to dive into Ghidra and start disassembling the Hive deserialization, or make some educated guesses based on the hexdump.

We can take the BeeAdapter as a starting point, but rather than creating Bee objects, let’s create a generic List object in which we can store all the deserialized values. Luckily a List can contain any type of data in Dart, so we don’t have to worry about the actual types of the different fields. Additionally, we want to make the typeId dynamic since we want to register all of the possible custom typeIds.

The following GenericAdapter does exactly that:

import 'package:hive/hive.dart';

class GenericAdapter extends TypeAdapter<List> {
  @override
  final int typeId;

  GenericAdapter(this.typeId);

  @override
  List read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    var list = List<dynamic>.filled(numOfFields, null, growable: true);

    for (var i = 0; i < numOfFields; i++) {
      list[reader.readByte()] = reader.read();
    }
    return list;
  }

  @override
  int get hashCode => typeId.hashCode;
  
  @override
  void write(BinaryWriter writer, List obj) {
    // No write needed
  }
}
Dart

We can then register this GenericAdapter for all the available custom typeIds (0 > 223) and read the beeBox we created earlier without needing the Bee or BeeAdapter class:

for(var i = 0; i<223; i++)
{
   Hive.registerAdapter(GenericAdapter(i));
}
var beeBox = await Hive.openBox("beeBox");
List myBee = beeBox.get("myBee");
print(myBee.toString()); // prints [Barry, 1]
Dart

Encrypted hives

As mentioned earlier, it’s possible to encrypt boxes. Let’s see how this changes the internals of the box:

final encryptionKey = Hive.generateSecureKey();
final encryptedBox = await Hive.openBox('encryptedBox', 
                          encryptionCipher: HiveAesCipher(encryptionKey));
encryptedBox.put("myString", "Hello World");
encryptedBox.close();
Dart

The code above generates the box below:

As explained earlier, the key is not encrypted, but the value is. The encryption covers all the bytes between the KEY and the CRC code. There is no special format for indicating an encrypted value, but Hive knows that it needs to decrypt the data due to the encryptionCipher parameter while opening the box. When the frames are read, the value is decrypted and parsed according to the normal deserialization logic. This means that we can use our GenericAdapter for encrypted boxes too, as long as we have the password.

Obtaining the password

Potentially the most tricky part, as this can be very easy, or very difficult. In general, there are a few different options:

  1. Intercept the password when it is loaded from storage
  2. Intercept the password when the box is opened
  3. Extract the password from storage

The first option is only possible if the password is actually stored somewhere (rather than being hardcoded). In the official Hive documentation, the developer recommends to use the flutter_secure_storage plugin, which will use either the KeyStore (Android) or KeyChain (iOS).

On Android, we can hook the Java code to intercept the password when it is loaded from the encrypted shared preferences. For example, there is the FlutterSecureStorage.read function which returns the value for a given key. By default, flutter optimizes the application in release mode, which means we can’t directly hook into FlutterSecureStorage.read because the class and method name will be stripped. It takes a little bit of effort to find the correct method, but the hook is straightforward:

Java.perform(() => {
    // Replace with correct class and method
    let a = Java.use("c0.a");
    a["l"].implementation = function (str) {
        console.log(`a.l is called: str=${str}`);
        let result = this["l"](str);
        console.log(`a.l result=${result}`);
        return result;
    };
});
JavaScript

Running this with Frida will print the base64 encoded password:

On iOS, the flutter_secure_storage plugin has moved to Swift, so intercepting the call is not straightforward. We do know, however, that the flutter_secure_storage plugin uses the KeyChain, and it does so without any additional encryption. This means we can obtain the password by dumping the keychain with objection‘s ios dump keychain command:

In case these options don’t work, you’ll probably want to dive into Ghidra and start reverse-engineering the app.

Recovering deleted items

We now have the password and a generic parser, so we can extract the items from the Hive. Unfortunately, if we use the normal API, we will only see the latest version of each item, or nothing at all in case there are delete frames. We could modify the Hive source code to notify us whenever a Frame is loaded (and there is actually some unreachable debugging code available that does just that), but it would be nicer to have a solution that doesn’t require a custom version of the library.

The way that Hive makes sure that only the latest version of an item is available is by adding each frame to a dictionary based on the frame’s key. Newer frames automatically overwrite older frames, so only the final value is kept. To make sure values don’t get overwritten, let’s just make sure that each frame key is unique by changing it if the key has already been used. Similarly, if we rename delete frames, they will not overwrite the old value either.

When we rename the key of a frame, we need to update the size of the frame and update the CRC32 checksum at the end so that Hive can still load the modified box. The following code copies a given box to a temporary location and updates all the frames to have unique names. It uses the Crc32 class which was copied from the source of the Hive framework so that we can be sure the logic is consistent:

Future<File> recoverHive(originalFile, HiveAesCipher? cipher) async {
  var filePath = await copyFileToTemp(originalFile);
  var file = File(filePath);
  var bytes = await file.readAsBytes();
  int offset = 0;
  var allFrames = BytesBuilder();
  var keyNames = <String, int>{};
  var keyInts = [];

  while (offset < bytes.length) {
    var frameLength = ByteData.sublistView(bytes, offset, offset + 4)
                              .getUint32(0, Endian.little);
    var keyOffset = offset + 4; // Skip frame length
    var endOffset = offset + frameLength;
    if (bytes.length > keyOffset + 2) {

      Uint8List newKey;
      int frameResize;
      int keyLength;
      if(bytes[keyOffset] == 0x01){
        // Key is String
        keyLength = bytes[keyOffset + 1];
        var keyBytes = bytes.sublist(keyOffset + 2, keyOffset + 2 + keyLength);
        var keyName = String.fromCharCodes(keyBytes);

         if (keyNames.containsKey(keyName)) {
            keyNames[keyName] = keyNames[keyName]! + 1;
            keyName = "${keyName}_${keyNames[keyName]}";
          } else {
            keyNames[keyName] = 1;
          }
          var modifiedKeyBytes = Uint8List.fromList(keyName.codeUnits);
          var modifiedKeyLength = modifiedKeyBytes.length;

          // get bytes for TYPE + LENGTH + VALUE
          var bb = BytesBuilder();
          bb.addByte(0x01);
          bb.addByte(modifiedKeyLength);
          bb.add(modifiedKeyBytes);
          newKey = bb.toBytes();
          frameResize = modifiedKeyLength - keyLength;
          keyLength += 2; // add the length of the type
      }
      else{
        // Key is int
        keyLength = 5; // type + uint32
        var keyIndexOffset = keyOffset + 0x01;
        var keyInt = ByteData.sublistView(bytes, keyIndexOffset, keyIndexOffset + 4)
                              .getUint32(0, Endian.little);

        while(keyInts.contains(keyInt)){
          keyInt += 1;
        }
        keyInts.add(keyInt);

        var index = ByteData(4)..setUint32(0, keyInt, Endian.little);
        
        // get bytes for TYPE + index
        var bb = BytesBuilder();
        bb.addByte(0x00);
        bb.add(index.buffer.asUint8List());
        newKey = bb.toBytes();
        frameResize = 0;
      }

      // If there is no value, it's a delete frame, so we don't add it again
      if(frameLength == keyLength + 8){ // 4 bytes CRC, 4 bytes frame length
        offset = endOffset;
        print("Dropping delete frame for " + newKey.toString());
        continue;
      }
      
      // Calculate new length of frame
      frameLength += frameResize;

      // Create a new frame bytes builder
      var frameBytes = BytesBuilder();
      
      // Prepare the frame length in ByteData and add it to the frame
      var frameLengthData = ByteData(4)..setUint32(0, frameLength, Endian.little);
      frameBytes.add(frameLengthData.buffer.asUint8List());

      // Add the new key
      frameBytes.add(newKey);
      
      // Add the rest of the frame after the original key. Don't include the CRC
      frameBytes.add(bytes.sublist(keyOffset + keyLength, endOffset-4));

      // Compute CRC using Hive's Crc32 class
      var newCrc = Crc32.compute(
        frameBytes.toBytes(),
        offset: 0,
        length: frameLength - 4,
        crc: cipher?.calculateKeyCrc() ?? 0,
      );

      // Write Crc code
      var newCrcBytes = Uint8List(4)..buffer.asByteData()
                                            .setUint32(0, newCrc, Endian.little);
      frameBytes.add(newCrcBytes);

      // Update the overall frames with the modified frame
      allFrames.add(frameBytes.toBytes());
    }

    offset = endOffset; // Move to the next frame
  }

  var reconstructedBytes = allFrames.takeBytes();

  try {
    await file.writeAsBytes(reconstructedBytes);
    print('Bytes successfully written to temporary file: ${file.path}');
  } catch (e) {
    print('Failed to write bytes to temporary file: $e');
  }
  return file;
}
Future<String> copyFileToTemp(String sourcePath) async {
  var sourceFile = File(sourcePath);
  // Generate a random subfolder name
  var rng = Random();
  var tempSubfolderName = "temp_${rng.nextInt(10000)}"; // Random subfolder name
  var tempDir = Directory.systemTemp.createTempSync(tempSubfolderName);
  
  // Create a File instance for the destination file in the new subfolder
  var tempFile = File('${tempDir.path}/${sourceFile.uri.pathSegments.last}');

  try {
    await sourceFile.copy(tempFile.path);
    print('File copied successfully to temporary directory: ${tempFile.path}');
  } catch (e) {
    print('Failed to copy file to temporary directory: $e');
  }
  return tempFile.path;
}
Dart

Putting it all together

Now that we can recover deleted items, read encrypted vaults and view custom objects, let’s put it all together. The target vault is created as follows:

final ultimateBox = await Hive.openBox('ultimateBox', 
                                    encryptionCipher: HiveAesCipher(hiveKey));
ultimateBox.add(123);
ultimateBox.add(456);
ultimateBox.deleteAt(1);
ultimateBox.put("myString", "Hello World");
ultimateBox.put("anotherString", "String2");
ultimateBox.add("Something");
ultimateBox.delete("myString");
ultimateBox.add(Bee(age: 12, name: "Barry"));
ultimateBox.put("test", 99999);
ultimateBox.put("anotherString", 200);
Dart

Reading is straightforward, we only need to specify the key and register the Generic adapter:

// Register the GenericAdapter for all available typeIds
for(var i = 0; i<223; i++)
{
  Hive.registerAdapter(GenericAdapter(i));
}
// Decode password and open box
var passwordBytes = base64.decode(password);
var encryptionCipher = HiveAesCipher(passwordBytes);
box = await Hive.openBox<dynamic>(boxName, path: directory, 
                                  encryptionCipher: encryptionCipher);
Dart

Finally, we can create a small UI around this functionality so that we can easily view all of the frames. In the screenshot below, we can see that the none of the data is deleted, and old values (anotherString > String 2) are still visible. The source code for this app can be found here.

Conclusion

It’s always faster to use an available library than create a solution yourself, but for security-critical applications it’s very important to fully understand the libraries you’re using. As we saw above, the Hive framework:

  • Keeps old values in the box until it is compacted
  • Only encrypts values, not keys

In this case, the documentation is clear on both facts, so it’s not really a security vulnerability. However, developers should be aware of the correct way to use the Hive framework in case any type of sensitive information is stored.

Finally, the fact that we don’t have access to the source code doesn’t stop us from identifying weaknesses, it just takes more time to reverse engineer the application/frameworks and develop custom tooling.

Jeroen Beckers

Jeroen Beckers is a mobile security expert working in the NVISO Software Security Assessment team. He is a SANS instructor and SANS lead author of the SEC575 course. Jeroen is also a co-author of OWASP Mobile Security Testing Guide (MSTG) and the OWASP Mobile Application Security Verification Standard (MASVS). He loves to both program and reverse engineer stuff.

Become Big Brother with Microsoft Purview

6 March 2024 at 08:00

Introduction

With the never-ending amount of data we generate, process, and share within and between companies, and the value this information can hold – such as personal data, top secret documents, or even information related to national security – it is natural that one of the main interests in cybersecurity is data security. Let it be data discovery, classification, access control, encryption or Data Loss Prevention (DLP), it is often unclear in which direction to go or which product to use to start tackling the long journey of data security.

In this article, I will focus on Microsoft Purview and its data security capabilities, starting with an overview of the best practices. For those who do not know it yet, Microsoft Purview is a comprehensive set of solutions that aims to help companies in governing, protecting and managing data. Microsoft Purview was born from the merging of Azure Purview and Microsoft 365 compliance solutions.

Microsoft Purview pillars.
Figure 1: Microsoft Purview pillars

To demonstrate the credibility of Microsoft when it comes to data security, it is important to know that Microsoft was recognized as a leader in The Forrester Wave: Data Security Platforms, Q1 2023 and Microsoft Purview product integrates well with the rest of the Microsoft solutions, such as Entra ID and Microsoft 365. You can find the official statement from Microsoft here: Microsoft a Leader in the 2023 Forrester Wave™ for Data Security Platforms | Microsoft Security Blog

Theory and practice

As we all know now that theory will only take you this far, it is important to map the theoretical concept of information protection and practical solutions. It is commonly accepted that the four pillars that serve as a roadmap in information protection can be summarized as follows:

  • Know your data: You want to know what data is being used and generated, and how it is used and generated.
  • Protect your data: Following the what and how, you must create and maintain the first line of defense for your information by enforcing security and access controls.
  • Prevent data loss: Intentional or accidental data leaks can and will happen sooner or later. Defining and enforcing the use cases to mitigate will represent the last line of defense for your data.
  • Govern your data: On top of the three other pillars, it is important to put in place governance processes to identify the gaps that remain uncovered by your current solutions. Additionally, a good governance helps you with achieving compliance with the regulations applicable to your company. However, I will not extend myself further on this in this blog post as the focus is data security.

The good news is that Microsoft Purview does cover these four pillars, by providing the tools and solutions shown in the following picture:

And example of the Microsoft Purview products mapped to the data security pillars.
Figure 2: Microsoft Purview features mapping

Note that the above is not an exhaustive list of all Microsoft Purview tools, but rather an example of the mapping between the theory and some aspects of Microsoft Purview.

It must be noted that to use Microsoft Purview to the fullest extent possible, we strongly advise using the Entra ID Premium P2 license and providing users with Microsoft 365 Enterprise E5 licenses is mandatory.

Triangle of Paranoia

Now I would like to introduce you to the concept of the triangle of paranoia. In data protection, we want to know and control everything, everywhere, all at once. How to do so? Well, let’s have a look at the diagram below:

The triangle of paranoia helps you understand the three main tool for data security in Microsoft Purview.
Figure 3: Triangle of paranoia

On the top sits the Data Classification, where the goal is to discover and classify information. There are multiple ways to proceed with Data Classification. For example, it can be done manually using sensitivity labels (i.e., tags that the users apply to their documents and emails), or automatically by identifying the content of documents based on information types or regular expressions. It is crucial to understand the importance of knowing what data and how it is used across the company. Knowledge is power, and it is especially true when it comes to data protection.

On the left corner of the triangle, we have the Data Loss Prevention (DLP) policies. Each of these policies can contain multiple rules whose goal is to restrict or audit data transfers that are deemed forbidden. The DLP rules can be triggered upon identification of a sensitivity label, or when risky activities – such as copying files to an untrusted network drive – are identified and remediation actions can be taken in the Microsoft 365 suite or directly on the device (i.e., Endpoint DLP). The reason why one policy can contain multiple rules is to have different actions based on slightly different conditions within the same scope of the policy. For example, a single event consisting of an accidental leak of a document will only trigger a restriction and a medium severity alert. While the same kind of event but repeated hundreds of times might be an indicator of intentional and malicious leak of information, therefore additional actions can be triggered, the severity of the alerts will be higher, and notification would be sent to administrators via emails. And the two rules will be inside the same policy that aims to protect sensitive documents from being sent externally via emails.

On the right side of the triangle, the Insider Risk Management (IRM) aims to identify and reduce the risk caused by malicious insiders, by gathering and correlating indicators and applying policies to raise the risk level of a user. The policies can raise the risk level of a user by either focusing on the extraction attempts pattern (e.g., mass download followed by renames and zip, then sent via email) or when the user triggers a DLP policy. The IRM dashboard then shows insights on activities and their risk rating.

The above three pieces work in an interconnected manner, one complementing the other. If you already started implementing data security measures with Microsoft Purview, you might have started by classifying data and then applying DLP right after. While this is indeed a common way to approach data security, you are only focusing on the accidental data leaks but omitting insider risks.

Most companies do not use IRM.
Figure 4: Triangle of paranoia – What most companies are missing

When Insider Risk Management is not in place, users with malicious intent could eventually leak or steal data without being detected. Although DLP policies might be able to block some leak attempts, the malicious users will continue to try and find holes, and there is a high chance that they succeed. This kind of scenario – intentional data leak – can be identified by gathering indicators and creating policies in IRM.

What it is important to remember with the concept of such triangle, is to control all sides, not just one or two of them. Developing the Data Classification, the Insider Risk Management, and the Data Loss Prevention all together will allow you to reach a state of mutual enrichment which is primordial to get the most out of data security with Purview.

Each side of the triangle of paranoia is interacting with its neighbor.
Figure 5: Triangle of paranoia – Mutual enrichment

Purview permissions

As Purview allows its administrators to access sensitive – and sometimes private – information, the permissions model must follow the least-privilege principle to avoid over-permissive set of permissions. While the Security Administrator role in Entra ID allows you to configure and maintain most of Microsoft Purview aspects, it is over-permissive as it also allows you to read the audit log, for example. Therefore, instead of relying on the built-in Entra ID roles, we recommend that you use the Microsoft Purview permissions as they provide more fine-grained functionality. To access the Microsoft Purview permissions, sign in to the Compliance portal (compliance.microsoft.com) with a Global administrator role, navigate to permissions, and select “Roles” under Microsoft Purview solutions.

The Global administrator role is the most privileged role in Entra ID Role-Based Access Control (RBAC).

Permissions dashboard in Microsoft Purview
Figure 6: Microsoft Purview permissions dashboard

From this dashboard, it is possible to assign built-in groups of permissions that are only applicable to Microsoft Purview. Additionally, you can create your own group of permissions that fits your business and security requirements.

Built-in roles in Microsoft Purview
Figure 7: Microsoft Purview built-in permissions

More information about the Purview permissions can be found in the Microsoft documentation.

Our recommended approach

Now that we know about the required licenses, the triangle of paranoia, and the permission model, our Purview implementation needs a direction. There are many approaches to implementing data security with Microsoft Purview, and the one I will describe to you contains everything you need to start in the right direction.

NVISO recommended approach to data security with Microsoft Purview.
Figure 8: Microsoft Purview approach

Phase 1 – Prerequisites

This preparation phase includes everything we need to prepare before starting to work on the solution. The Microsoft Enterprise E5 licenses is non-negotiable and while we understand that the cost can be a blocker, it is important to know the true value of the highest licensing model. If you or your company remain undecided, keep in mind that identity protection, advanced mail protection, premium audit, cloud access security broker, privileged identity management, advanced endpoint detection and response, compliance manager (such as NIST CSF or NIS2 assessments) … etc., are only a few of the products you allow your organization to rely on when adopting the Enterprise E5 licenses. And if you were already relying on other vendors for the same features that are part of the E5, upgrading your licenses could even help you save money.  

As soon as licenses are adopted, the permission model must be designed. Ask yourself the following questions:

  • Who needs access to Purview?
  • What will they do in Purview?
  • Are there built-in Purview roles correctly scoped to my needs?
  • Do we need to create customized set of permissions?

The goal of this exercise is to start identifying the different actors that will use Purview. In any case, a global administrator is required to get started.

Phase 2 – Data Classification

If you remember your theory well, you already know that you should now focus on the very first pillar of data security: Know your data. And this is exactly what we want you to do by starting the implementation and deployment of sensitivity labels. Those labels are metadata added to your information (files, emails, meetings, sites, …). In addition to classify your data for later purpose (i.e., Data Loss Prevention), Sensitivity Labels can also apply content marking to your documents as depicted below:

Sensitivity label menu.
Figure 9: Sensitivity labels – Menu
The content marking configured in the sensitivity label is automatically added to the document.
Figure 10: Sensitivity label – Content marking

And they can also apply encryption to ensure only explicitly authorized users can access the content as shown below:

A tooltip is shown when the sensitivity label enforce encryption.
Figure 11: Sensitivity label – Encryption in an email

Here are our recommendations for this phase:

  • Depending on the company, the different sensitivity levels of information might have already been defined. In that scenario, it is easy to adapt them to the sensitivity labels of Microsoft Purview. If this isn’t the case, then it is important to start thinking about it in general, not just Microsoft Purview but rather for all assets owned by your company.
  • The user adoption takes time and effort as the users must label their documents and emails correctly. Do not underestimate this step: correctly classifying information is one third of the paranoia triangle. Although it is possible to automate some part of the classification, training users to use the different sensitivity labels correctly is primordial in the first place.
  • Proceeding with baby steps is considerably recommended with all tools in Microsoft Purview, especially with sensitivity labels as they should never be deleted. So, our advice is to think twice before creating a label, don’t be too specific in the beginning as you can always work with additional sensitivity labels or sublabels in the future.

You identified some kind of information that should be treated differently while thinking about the sensitivity labels (e.g., your own company tax number that is always added to your signatures)? Great! You successfully identified a Sensitive Information Type (SIT) exception. Keep note of them as they will come in handy as you progress toward maturity in Microsoft Purview.

Phase 3 – Insider Risk Management

Now that the data classification started as the sensitivity labels are defined and being tested, what do you do? As you want to follow the triangle of paranoia principle, you need to involve Insider Risk Management, and it’s the right moment to start looking into it. First and foremost, inform any relevant team about the upcoming action which is the gathering of anonymized indicators. To do so, ensure that the anonymization of usernames is enabled in the Insider Risk Management settings, and all indicators are selected.

Indicators menu from Microsoft Purview admin center.
 
Figure 12: Insider Risk Management – Indicators settings

Note that when selecting some indicators, the consent to process company information from other Microsoft sources such as Entra ID is granted to Microsoft Purview. So, make sure that work council or union is involved in the discussion, if there is one in your company. You might wonder why we recommend proceeding with this step. The answer is short: there is no impact on the end-users’ productivity, and the earlier you gather indicators the sooner you will be able to work with IRM to identify insider risks.

Phase 4 – Data Loss Prevention and enrichment

Let’s assume that the sensitivity labels are now correctly used and applied to all documents and emails, and IRM is gathering indicators. This is now the moment to enable the final piece of the triangle, this is time to design Data Loss Prevention policies. This is where creativity is welcomed, as it is the only limit to what can be done with DLP. Here’s a basic example of an early design of a DLP policy:

  • Identifying a use-case: The users from department A should not send documents containing tax numbers (but not your own company tax number! Remember the Sensitive Information Type exception from earlier?) or document with the “Secret” sensitivity label.
  • Set the scope: This is applicable to SharePoint Online, OneDrive, Exchange Online.
  • Define the action: Block only people outside your organization from receiving emails or accessing shared documents in OneDrive, SharePoint, and Teams.
  • Include additional action: Inform the user with tips about sharing sensitive information and raise an alert when the volume of matched activities reaches a threshold.

While DLP policies can be highly customized, we advise you to start with the basics by focusing on external sharing of documents via SharePoint Online, OneDrive, Teams, and Exchange Online. Please note that every DLP policy must be set in test mode first to gather information and fine-tune the policy if needed. As always, we want you to protect your information while avoiding impact on productivity.

If you remember the triangle of paranoia, we went through all pieces. From the data classification, to the Data Loss Prevention, and enabling Insider Risk Management on the way. You have now entered the enrichment part of the last phase, which is a never-ending loop of continuously raising the maturity level of your Microsoft Purview solution. From now on, you will improve on all pieces of the triangle to get the most of Microsoft Purview. Here is a concrete example of how each side enriches its neighbors:

Example of mutual enrichment between data classification, IRM, and DLP.
Figure 13: Triangle or paranoia – Mutual enrichment

Conclusion

While data security represents a massive effort to be effective due to the tremendous amount of information your company is generating every second, Microsoft Purview is there to provide you with all the necessary tools to tackle this considerable challenge and we are here to guide you during this journey. If you were to remember the main takeaways of this post, think about this:

  • Triangle of paranoia: Microsoft Purview provides three amazing tools – Data Classification, Data Loss Prevention, and Insider Risk Management – so use them all together and rely on the mutual enrichment provided by the connections between them to leverage the most of this data security capability.
  • Baby steps: The features in Microsoft Purview require a lot of critical thinking and testing. Don’t overwhelm yourself and your teams with a gargantuan initial implementation of the three features. It’s better to accomplish something small with solid foundations than being stuck in a complex design that is not going anywhere.
  • Never-ending story: Data security is a dynamic topic. It must be seen as a never-ending race against information leaks where you can only proactively identify use-cases and detect insider risks to stay ahead. Therefore, you must continuously review and assess the state of implementation of Microsoft Purview and include new content. This can be more data classification and refining exceptions, new data loss prevention policies and rules, insider risk management policies, or ideally, all of them at once.

If you feel like we could help you get started with this powerful tool, don’t hesitate to visit our website www.nviso.eu or connect with us on LinkedIn!

Resources

Should you have any questions or remarks, please feel free to connect me with the details at the end of the article.

While the topic tackled in this article reflects professional experience and expertise, the Microsoft resources I used below will also help you in understanding Microsoft Purview:

About the author

Author

Mathéo Boute

Mathéo is a Senior Cybersecurity Consultant and member of the Cloud Security Team. His area of expertise revolves around Azure and Microsoft 365 with a focus on Microsoft Purview, Entra ID, Intune, and Defender, where he has an extensive experience of assessing, designing and implementing multiple security solutions in various industries.

Mathéo confirmed his skills by passing all the security-related Microsoft certification, including the Microsoft Certified Cybersecurity Architect.

Covert TLS n-day backdoors: SparkCockpit & SparkTar

1 March 2024 at 10:59

In early 2024, Ivanti’s Pulse Secure appliances suffered from wide-spread exploitation through the then reported vulnerabilities CVE-2023-46805 & CVE-2024-21887. Amongst the many victims, a critical-sector organization triggered their NVISO incident-response retainer to support their internal security teams in the investigation of the observed compromise of their Ivanti appliance. This report documents two at-the-time undetected covert TLS-based backdoors which were identified by NVISO during this investigation: SparkCockpit & SparkTar. Both backdoors employ selective interception of TLS communication towards the legitimate Ivanti server applications. Through this technique, the attackers have managed to avoid detection by most (if not all) network-based security solutions.

While SparkCockpit is believed to have been deployed through the 2024 Pulse Secure exploitation, SparkTar has been employed at least since Q3 2023 across multiple appliances. The two backdoors offer multiple degrees of persistence and access possibilities into the victim network, for example through traffic tunneling by establishing SOCKS proxy. SparkTar is the most advanced backdoor with the capability of surviving both factory resets as well as appliance upgrades. Both backdoors additionally also provide capabilities to perform file uploads and command execution.

It is important to note that given the purpose of the Ivanti Pulse Secure appliances in the environment, where they allow external, authenticated users access to various internal resources, the attackers would typically not be restricted in what resources they can reach internally in the network. Depending on the network restrictions in place, attackers could gain full network level access to a compromised environment through the network tunneling capabilities embedded in the SparkTar backdoor.

The report provides a comprehensive examination of the two sophisticated and previously undetected backdoors, SparkCockpit & SparkTar. The findings of our investigation have been independently corroborated by the research performed by Mandiant and have partially been observed by Fortinet. Our findings and detection rules detailed within the report are shared to support the cybersecurity community, and to allow for further detections and mitigations to take place. By sharing these insights it is the goal of NVISO to allow for organizations to get an understanding on the capabilities and inner workings of the backdoors, as well as enhancing their security posture and resilience against these evolving advanced cyber threats.

Top things that you might not be doing (yet) in Entra Conditional Access

27 February 2024 at 08:00
Top things you might not be doing (yet) in Entra Conditional Access

Introduction

In this blog post, I focus on the top things that you might not be doing (yet) in Entra Conditional Access. It is not an exhaustive list, but it is based on my experience assessing many different Entra ID, formerly Azure AD, environments as a consultant at NVISO Security.

The following points are, in my opinion, some of the most important security controls that can be enforced and configured using Conditional Access to improve the overall security posture of your Azure or Microsoft 365 tenant. Note that items do not have a particular order in the list. They are all more or less equally important to correctly protect and secure identities in Entra ID as some might rely on others to be fully functional in terms of security or have the intended effect.

Please do keep in mind that this article focuses on features that require the Entra ID Premium P1 and P2 licenses . Therefore, if none of those licenses are available, check my previous blog post on how to protect identities in Entra ID Free: https://blog.nviso.eu/2023/05/02/enforce-zero-trust-in-microsoft-365-part-1-setting-the-basics/.

Additionally, should you need any introduction to what Entra Conditional Access is and which security controls are available, feel free to have a look at this post: https://blog.nviso.eu/2023/05/24/enforce-zero-trust-in-microsoft-365-part-3-introduction-to-conditional-access/.

Entra Conditional Access security controls

Enforce strong authentication for all users

License requirement: Entra ID Premium P1

Strong authentication, such as Multi-Factor Authentication (MFA), represents a huge improvement to the security posture of your identities. Indeed, according to Alex Weinert, based on studies conducted by Microsoft, the Director of Identity Security at Microsoft, using MFA reduces the likelihood of an account breach by 99.9% (reference: Your Pa$$word doesn’t matter – Microsoft Community Hub).

Before diving into more details, where are the main Multi-Factor Authentication types available?

  • Something you know: password, passphrase, PIN, etc.
  • something you have: a phone, an application, a token, etc.
  • Something you are: biometrics, such as fingerprint, facial recognition, etc.
  • Somewhere you are: IP address, geolocation, etc.
  • And, something you do: set of actions and recognizable patterns of behavior.

Most of them are available or can be enforced in Microsoft Entra, with the exception of “something you do”, which is the least common authentication type nowadays.

Moreover, Multi-Factor Authentication is usually considered as relatively easy to implement compared to other solutions such as passwordless authentication as it does not (always) require the provisioning of additional hardware and software components, for instance. Indeed, MFA requirements can be satisfied using SMS, a phone call, or an application on a mobile phone. I will not go into the details of passwordless versus multi-factor authentication in this blog post, as each method has its own advantages and disadvantages.

So, if you are not doing it yet, make sure that all identities are regularly prompted multi-factor authentication when they are authenticating to your environment. There might be some exclusions for break-the-glass (emergency accounts) or service principals, but these should be documented and monitored to detect suspicious and anomalous activities.

The easiest way to enforce MFA in Entra Conditional Access is to create the following Conditional Access policy:

Conditional Access policy - Require Multi-Factor Authentication for all users
Conditional Access policy – Require Multi-Factor Authentication for all users

Note that in this case, “All users” also includes B2B users.

Once the policy is enabled, MFA will be required for all users when authenticating to a cloud application using their company account. The MFA method, such as SMS, call, Microsoft Authenticator, Security Keys, etc., will depend on the configured policies in your environment. There are diverse ways to manage authentication methods in your environment. Some of them will be deprecated in the near future and combined into one unified configuration panel, which is Microsoft Entra Authentication Methods.

First, we have the legacy MFA settings, which are going to be deprecated end September 2025, as you may have guessed based on the name. They can be configured by going to Entra ID > Security > Multifactor authentication > Additional cloud-based multifactor authentication settings:

Legacy MFA settings
Legacy MFA settings

Then, we have the possibility to configure the Self-Service Password Reset (SSPR) policy. The SSPR policy will be phased out at the same time as the legacy MFA settings in September 2025. However, it can be configured here: Entra ID > Password reset > Authentication methods:

Self-service password reset settings
Self-service password reset settings

As mentioned above, the Microsoft Entra Authentication Methods policies will be replacing the legacy MFA and SSPR policies after their deprecation date. The new policy can be configured in Entra ID > Protection > Authentication methods > Policies:

Microsoft Entra Authentication Methods
Microsoft Entra Authentication Methods

It is recommended to migrate to the new authentication methods policy as it provides better control and granularity over authentication methods. For that purpose, Microsoft provides a way to manage the migration to the new method. The different stages of the migration allow to start configuring the new policies but still respect the legacy MFA and SSPR policies and can be summarized in the following table:

OptionDescription
Pre-migrationThe Authentication methods policy is used only for authentication.
Legacy policy settings are respected.
Migration in ProgressThe Authentication methods policy is used for authentication and SSPR.
Legacy policy settings are respected.
Migration CompleteOnly the Authentication methods policy is used for authentication and SSPR.
Legacy policy settings are ignored.

Note that before the migration, all policies are merged together, and all authentication methods are respected. This means that users will be able to use any authentication methods enabled in any of those policies.

Regarding which authentication methods should be enabled for users, we recommend using stronger methods such as Microsoft Authenticator, security keys, One Time Passcode (OTP), Windows Hello, and certificate-based authentication. SMS and Voice methods are not the preferred ones as they are prone to different attacks. This does not mean that the other methods, listed as better and best below, cannot be targeted. This is why it is important to make sure that additional security controls have been implemented to make them more secure.

Authentication methods classification
Authentication methods classification

Make sure guest users are always authenticating with MFA

License requirement: Entra ID Premium P1

During security assessments, we have seen that, in some cases, MFA was not enforced on guest users. Indeed, in some configurations, they were either not included or excluded from Conditional Access policies enforcing strong authentication. The reason was that MFA was already enforced in their home tenant and guest users were prompted MFA twice, impacting user experience.

However, guest users can bypass the MFA control from their home tenant if they directly access the resource tenant. For instance, if I want to access a Tenant B Azure environment using an account in Tenant A, I could do it in two different ways:

  1. Login to the Azure Portal of Tenant A and use the switch button to access Tenant B, also known as the resource tenant. When doing so, I first need to satisfy all the requirements from the Tenant A, meaning password authentication (i.e., primary authentication) and then strong authentication with MFA (i.e., secondary authentication). Then, if MFA is not enforced for my account in the resource tenant, I will simply click on switch and get access without any further authentication requirements. Note that a user password is always managed in their home tenant and not in the resource tenant;
  2. Instead of accessing the resource tenant from my home tenant, I will directly browser to https://portal.azure.com/@tenantb.onmicrosoft.com. When clicking enter, I will be redirected to my home tenant for the primary authentication. Secondary authentication on my home tenant will not apply as I am not accessing a cloud application of my Tenant A. As MFA in the resource tenant is not enforced on my account, I will gain access to the environment without any further authentication requirements, “bypassing” MFA from my home tenant.

Because of that, MFA should be enforced in the resource tenant (too) to prevent that a user account with a compromised password that has been invited as a guest user in your tenant could access your environment without any restrictions.

To tackle the issue of double MFA prompt, Microsoft introduced the cross-tenant access settings which allows to trust the MFA claims of trusted external Entra ID tenants. This can be configured for different tenants individually or by default to apply to all B2B users from any tenants. We do not recommend trusting MFA claims from all external Entra ID tenants as you do not know how external identities are managed in their home tenant. However, this could be configured for trusted partners with whom you have a long-term collaboration and trust the way they secure and protect their identities. The authentication flow using cross-tenant access policies (XTAP) can be represented as follows:

Cross-tenant access policies (XTAP) flow
Cross-tenant access policies (XTAP) flow

In this case, the user requests access to Tenant B resources from Tenant A. Therefore, they first need to perform primary authentication and satisfy Conditional Access policies, i.e., secondary authentication, in their home tenant. If satisfied, the flow continues. Then, when accessing the Tenant B, where XTAP policies to trust MFA from Tenant A have been configured, the authentication claim issued by Tenant A is validated against Tenant B Conditional Access policies. If satisfied, access to the resource tenant applications is granted.

Block legacy authentication protocols

License requirement: Entra ID Premium P1

Legacy authentication refers to authentication that does not support multi-factor authentication, as well as other controls, such as sending the device compliance or join status. Therefore, when using legacy authentication, users are only prompted for their password to sign-in. However, based on Microsoft, 97 percent of credential stuffing attacks use legacy authentication, and more than 99 percent of password spray attacks also use legacy authentication protocols.

To block such attacks, Microsoft started disabling Basic Authentication for Exchange Online in all Microsoft 365 tenants regardless of usage, except for SMTP Authentication, as of October 1, 2022.

To control the use of legacy authentication protocols across your Microsoft 365 environment, we recommend configuring a Conditional Access policy. There are two approaches for creating this policy: directly or indirectly blocking legacy authentication protocols.

  • The following policy can be used to directly block legacy authentication (recommended):
Conditional Access policy - Block Legacy Authentication protocols
Conditional Access policy – Block Legacy Authentication protocols
  • To indirectly block legacy authentication, the grant control could be set to “Require multi-factor authentication”, “Require device to be marked as compliant”, and/or “Require Microsoft Entra hybrid joined device” instead of “Block”.

Using a dedicated policy to block legacy authentication is recommended as exclusions on users, applications, locations, etc., that may apply for the above grant controls might not apply to the use of legacy authentication protocols in your environment.

Regularly force users to reauthenticate

License requirement: Entra ID Premium P1

Next to strong authentication, authentication session management, or sign-in frequency, is also important. The sign-in frequency will define how often users must re-authenticate to keep access to your environment.

While requiring users to regularly reauthenticate might cause frustration and impact user productivity, it can effectively increase the overall security posture of an environment. Setting a proper sign-in frequency can help preventing unauthorized access to sensitive resources or information. By default, the sign-in frequency in Entra ID is 90 days. 90 days might be just fine for specific use cases, but I highly doubt that it will be considered as safe to use for most organizations for every use case.

It is important to note that when configuring such controls, we should not go too far and not be disruptive to users, as they will probably tend to find alternatives or “hacks” to bypass controls. Because of that, the sign-in frequency, to be effective in my opinion, should be based on conditions. For example, a user authenticating from a managed device in a trusted location represents an overall smaller risk than the same user authenticating from an unmanaged device outside of a trusted location. As a result, the sign-in frequency in the first scenario could be lower than for the second one. Moreover, an administrator assigned to the Entra ID Global Administrator role should also not have the same sign-in frequency as a standard user with no privileges. By implementing a proper sign-in frequency, it helps enforcing the principle of Zero Trust in the environment as access to resources is regularly challenged based on the context and signals.

How can the sign-in frequency be configured in Entra ID? Well, here also, we have different possibilities. First, we have the legacy setting in the per-user MFA portal:

Legacy sign-in frequency configuration
Legacy sign-in frequency configuration

As you can see in the above screenshot, this setting will apply tenant-wide and for all scenarios. Therefore, for more advanced configurations and reduce or increase the number of times users are prompted to reauthenticate based on different signals (i.e., location, device, risk, application, privileges, etc.), Entra Conditional Access policies should be used. Indeed, they allow for more granular configurations as we can target specific applications and users, and configure various conditions.

Note that both settings should not be configured. Indeed, if Conditional Access is used to manage the sign-in frequency, which is greatly recommended, the legacy setting should be turned off to avoid impacting end users with unexpected MFA prompts, for example. If you want to know more information about this, I have already discussed those specificities in previous blog posts (e.g., https://blog.nviso.eu/2023/05/24/enforce-zero-trust-in-microsoft-365-part-3-introduction-to-conditional-access/). Feel free to check them out.

Besides this, it is also possible to let users the possibility to trust the device they are using when authenticating. This is represented by the “Stay signed in?” checkbox when authenticating. This checkbox can be controlled in Conditional Access policies or at the tenant level. In Conditional Access, this behavior can be configured in the Session controls, by setting the ‘Persistent browser session‘-option to Always or Never persistent:

Conditional Access policy - Force sign-in frequency (SIF)
Conditional Access policy – Force sign-in frequency (SIF)

At the tenant level, it can be configured in the User settings by setting the Show keep user signed in toggle to On or Off:

Microsoft Entra ID user settings
Microsoft Entra ID user settings

In brief, defining the sign-in frequency and the browser persistence will vary for every organization. However, it is recommended to define it for every use case, such as user, including guest user, or administrative access, and access to sensitive resources or information, from managed or unmanaged devices, from trusted or untrusted locations, etc.

Restrict sensitive user actions

License requirement: Entra ID Premium P1

User actions in Entra Conditional Access policies represent actions that are performed by users, as its name suggests :). At the time of writing, Entra Conditional Access only supports two user actions, namely, register security information, and register or join devices to Entra ID. To better understand why those actions should be protected, let’s see what they actually mean:

  • Regis­ter security information refers to the enrollment of an authentication method using the combined registration. In other words, it covers the action of a user adding an MFA method for their account. If registering security information is not restricted, attackers that would have compromised a user password might be able to register their own MFA to gain access to other cloud applications;
  • Register or join devices to Entra ID allows users to register or join a device to your Entra ID tenant. If device registration or join has not been restricted, someone might abuse it to register a device, get it enrolled in Microsoft Intune, assuming Automatic Enrollment is being used, get the configuration profiles and compliance policies assigned and finally gain access to protected resources that require a compliant device.

Because of that, it is usually recommended to restrict those actions by requiring strong authentication and / or by requiring users to be in a trusted location. Note that the “Devices to be Microsoft Entra joined or Microsoft Entra registered require multifactor authentication”-setting in Entra ID should be set to ‘No’ first, before configuring a Conditional Access policy to require MFA on device join or registration.

Microsoft Entra ID device settings
Microsoft Entra ID device settings

These restrictions will vary depending on your organization’s security requirements. Additional restrictions can be configured for registering or joining a device in Entra ID device settings.

Configure restrictions on service accounts

License requirement: Entra ID Premium P1 & Entra ID Workload Identities Premium

In Entra ID, there are three types of service accounts: user-based service accounts, service principals, and managed identities. These identities are not meant for human access but represent an instance of an application used to access Entra ID or Azure resources such as services, APIs, etc.

A user-based service account is simply a standard user account used as a service account. One common example is the Directory Synchronization Account which is used in hybrid environments. However, creating such accounts for your applications is not recommended as their passwords must be stored somewhere and managed making them more prone to credentials leak. If such accounts are used, their sign-ins should be restricted to specific locations and/or devices. This can be done using a Conditional Access policy.

Then, service principals are a representation of an application object in Entra ID. These service accounts have two authentication methods available: secret- and certificate-based authentication. Certificate-based authentication is recommended as it cannot be (accidentally) hardcoded in the application code, making it less prone to credential leaks. Sign-in attempts from service principals can be covered by Conditional Access policies but it requires additional Entra ID Workload Identities Premium licenses. If available, it is recommended to restrict the use of the service principals to strict locations to prevent abuse in case of credential leak. Risk-based policies for service principals are also available and should be considered. For instance, access from service principals associated with a medium or high risk could be blocked:

Conditional Access policy - Workload Identity Risk policy
Conditional Access policy – Workload Identity Risk policy

Finally, managed identities are identities that can be associated with Azure resources to access other resources or services. They are considered as the most secure option as you do not have to manage any credentials for them. They cannot be covered by Conditional Access.

Further resources on how to secure those identities can be found below.

Enforce restrictions based on user risk, and sign-in risk

License requirement: Entra ID Premium P2, very limited feature in Entra ID Premium P1

Last but not least, Entra Identity Protection signals can be included to Entra Conditional Access to automatically act upon user and sign-in risk. Identity Protection is a service that allows organizations to identify, investigate, and remediate risks related to identities. Entra Identity Protection calculates user and sign-in risk based on real-time and offline detections, such as anomalous token, atypical travel, credentials leak, possible attempt to access Primary Refresh Token (PRT), etc. The full list of detections for both Premium P2 customers, and Free or Premium P1 customers are available in the ‘Resources’-section.

To integrate Identity Protection signals into Conditional Access policies, Entra ID Premium P2 licenses are required. Note that it is recommended to use Conditional Access policies instead of the Identity Protection policies directly as Conditional Access allows for better granularity and more control over the configuration.

As a standard recommendation, user sign-ins that are associated with a medium or higher risk should require users to perform MFA. On the other hand, users associated with a high risk, should, at least, be requested to change their password.

Once implemented, it allows to reduce the risk of unauthorized access in case of account compromise or suspicious activities. Moreover, users with credentials that have leaked can be required to change their password to make sure their account cannot be abused.

Conclusion

The above security controls can help your organization enhance its cloud and identity security posture. Indeed, as identities have become the new perimeter in cloud environments, it is essential to implement strong controls around identities to safeguard your sensitive and critical resources. The above list highlights basic but important controls that should be considered when designing or reviewing your Entra Conditional Access implementation.

While this list is not exhaustive and there are many more essential security controls to take into account, it can already be used to strengthen the defenses of your cloud environment. If some of these controls are not implemented yet in your environment, I highly encourage you to reevaluate your current controls and investigate the different capabilities discussed above.

At NVISO Security, we have built an expertise reviewing cloud environments and we have designed and implemented Entra Conditional Access policies on numerous occasions. If you want to know more about how we can help you in the journey of building or strengthening your cloud environment, feel free to connect on LinkedIn or visit our website at https://www.nviso.eu.

Resources

You can contact me on LinkedIn should you have any questions or remarks. My contact details can be found below.

Additionally, if you want to get a deeper understanding of some of the topics discussed in the blog post, all the resources that I have used can be found below:

About the author

Guillaume Bossiroy

Guillaume Bossiroy

Guillaume is a Senior Security Consultant in the Cloud Security Team. His main focus is on Microsoft Azure and Microsoft 365 security where he has gained extensive knowledge during many engagements, from designing and implementing Entra ID Conditional Access policies to deploying Microsoft 365 Defender security products.

Additionally, Guillaume is also interested into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

Is the Google search bar enough to hack Belgian companies?

22 January 2024 at 08:00

In this blog post, we will go over a technique called Google Dorking and demonstrate how it can be utilized to uncover severe security vulnerabilities in web applications hosted right here in Belgium, where NVISO was founded. The inspiration for this security research arose from the observation that many large organizations have fallen victim to severe security breaches, with the perpetrators often being individuals under the age of 18. A recent incident involved an 18-year-old hacker who managed to infiltrate Rockstar Games using just a hotel TV, a cellphone, and an Amazon Fire Stick while under police protection. This can be caused by two possibilities: either these young cybercriminals possess exceptional talent and skills, which we are certain of, and/or the importance of cybersecurity is not being sufficiently prioritized yet by many organizations.

In 2020, Belgium was ranked first on the National Cyber Security Index (NCSI), which measures the preparedness of countries to prevent cyber threats and manage cyber incidents. As Belgium was leading this ranking, my curiosity was triggered regarding the security of Belgium’s digital landscape.

For the purpose of the security research, I will adopt the role of a ‘script kiddie’ – individuals using existing scripts or programs to exploit computer systems, often without a deep understanding of the underlying technology. Furthermore, I will restrict myself to solely using the Google search bar to identify the vulnerabilities without utilizing any tool that could potentially automate certain tasks.

Hacking the digital infrastructure of companies prior their consent is illegal. However, Belgium has published a new Coordinated Vulnerability Disclosure Policy, which allows citizens with good intentions to identify possible vulnerabilities and report to the affected company within a 72-hour window. This is not the sole prerequisite to identify vulnerabilities within Belgium companies. Additional details about the policy can be found on the website of the Centre for Cybersecurity Belgium (CCB) .

All the vulnerabilities identified during the security research have been appropriately reported to the affected companies and Belgium’s national CSIRT team.

Introduction

The vulnerabilities identified during the security research were not being exploited, as it is still prohibited by the newly published framework to perform such actions. The objective was to verify the existence of a vulnerability, not to examine the extent to which one can penetrate a system, process, or control. Therefore, the identification of vulnerabilities was based on proof-of-concepts or explanations to the organizations about the potential implications of the vulnerabilities. Below is a short list of vulnerabilities identified during the security research, using only the Google search bar on the web applications of Belgian corporations:

  • Remote code execution (RCE)
  • Local file inclusion (LFI)
  • Reflected cross-site scripting (XSS)
  • SQL injection
  • Broken Access Control
  • Sensitive information disclosure

What is Google Dorking?

The technique Google Dorking is also referred to as Google hacking, which is a method used by security researchers and cybercriminals to exploit the power of the Google search engine to unearth vulnerabilities on the Internet.

In essence, the Google search engine functions like a translator, deciphering search strings and operators to deliver the most relevant results to the end user. A couple of examples of Google operators are as follow:

  • site: This operator restricts the search results to a specific website or domain. For example, “site:nviso.eu” would return results only from the NVISO website.
Figure 1 – Site Operator
  • inurl: This operator searches for a specific text within the URL of a website. For example, “inurl:login” would return results for web pages that have “login” in their URL.
Figure 2 – Operator combinations

Google search bar in action

During the research, I developed a methodology and used my creativity to identify numerous high to critical security issues, none of which relied on online resources, such as the Google Hacking Database. However, we will not share any of the actual Google dork queries or the methodology behind them that were used to identify vulnerabilities. Doing so would pose a significant security risk to many Belgian corporations. Moreover, we are committed to making our digital landscape safer, not to teaching someone with bad intentions how to learn and infiltrate these corporations. Therefore, we will discuss each identified specific vulnerability in more depth, the approaches of cybercriminals, and, most importantly, how to fix or remediate such issues.

Default credentials

Description

Default credentials are pre-configured credentials provided by many manufacturers for initial access to software applications. The objective is to simplify the setup process for users who are configuring the application for the first time. However, since these credentials are often common knowledge, they are publicly available and easily found in user manuals or online documentation. As a result, the usage of default credentials can still lead to severe security incidents as they grant highly privileged access to management interfaces. Further, this issue can arise in occasions where a system administrator or developer does not change the default credentials which are provided during the initial setup of the management interface. Therefore, an attacker can easily guess those credentials if not changed and gain access to the management portal.

Approach

The first step for an attacker is to target a specific CRM, CMS, or any other widely-used management portal. This information can be easily obtained with a basic Google search. Upon identifying the target software, the attacker can use a combination of Google search operators to refine the search results to only display web applications hosting that specific management portal.

A lot of management portal software names are written in the title of the web application which is not a security issue at all. However, if an attacker identifies the software version in use on the web application, they can conduct more targeted reconnaissance activities against the web application. An example Google dork which can be found on the Google Hacking Database is as follow:

intitle: "Login - Jorani"

The results of the Google search lists the web applications which have “Login – Jorani” within the title tag. This can also be verified by navigating to one of the web applications listed in the Google search results. As demonstrated below, the title of the web application contains the string used with the intitle operator:

Figure 3 – HTML title tag

Additionally, to reinforce the earlier statement about the publicly available information of default credentials, a simple Google search discloses the default credentials for the Jorani management software without even navigating to the documentation:

Figure 4 – Jorani default credentials

It can be noticed that by simply searching publicly available resources, an attacker can identify and perform targeted reconnaissance on web applications without performing any illegal or harmful actions. The steps taken so far are considered passive and non-malicious, which makes this approach even more interesting.

Real-world scenario

The usage of a single Google dork will, of course, not provide the most refined results to target a specific management portal application. However, the approach is the same; more advanced operators in combination have to be used in order to target very specific management portals. During my research, advanced operators were utilized to search for a popular management portal that also includes default credentials during its initial setup. This was, of course, based on my experience in the penetration testing field and creativity. Upon analyzing the Google search results, one company was identified as using default credentials for their management portal. Further, upon gaining access to the management portal, it can lead to the full compromise of the server hosting the web application.

As penetration testers conducting daily assessments of web applications, we can attest to the severity of the security issue and its potential consequences. Furthermore, adherence to a responsible disclosure policy entails demonstrating the vulnerability without expanding the scope of the research. The first identified vulnerability has raised significant concerns, particularly as the organization in question is a large Belgian company. In short, this vulnerability can allow an attacker to easily establish an initial foothold within the network of the affected organization.

Remediation

Management portals are not intended for daily users or even employees tasked with specific duties. Instead, they are most often accessed by users with administrative rights who manage certain aspects of the software. Therefore, it is not important for a management portal to rank highly in Google search results or for SEO optimization purposes.

To limit the access of robots and web crawlers to your site, you can add the following robots.txt file to the root directory of the web server hosting the management portal application:

User-agent: *
Disallow: /

The robots.txt file is not a security threat in itself, nor is it a foolproof method to block robots and web crawlers from accessing your site. Many robots or web crawlers may not heed the file and simply bypass it. However, following this best practice can serve as part of a defense-in-depth strategy, helping to mitigate potential issues.

Next, as previously mentioned, many management portal applications include the software name and sometimes even the software version within the title tag or generator tag in the source code. While the presence of this information is not a security issue at all, it can make it more easier for attackers to identify the underlying software version and perform targeted reconnaissance.

Last but not least, the use of default credentials remains a common issue in many applications. The compromise of a web server will not be due to web crawlers indexing the endpoint of a management portal or the presence of a software name within the title tag. The root cause is the usage of default credentials on management portals. Since these portals also allow the performance of various tasks with high-level permissions, it is crucial to establish defined policies and conduct regular audits to prevent such simple yet critical misconfigurations.

Local File Inclusion (LFI)

Description

Local File Inclusion (LFI) is a type of vulnerability which allows attackers to include and potentially even execute local files on the server. An attacker exploiting an LFI can read sensitive files, such as configuration files, source code, or data files. However, the impact of this kind of a vulnerability is influenced by the permissions of the user account under which the web server is running. Moreover, a common misconfiguration arises when web servers are run with high-level privileges, such as root on Unix-based and SYSTEM on Windows-based systems. This could allow an attacker to access almost any file on the server and completely compromise the server.

Approach

The methodology for identifying LFI vulnerabilities using Google dork may not be as straightforward as identifying management portals. However, web applications often use common parameters to retrieve the contents of a local file. It is possible to search specifically for these common parameters, which are used for such purposes. Some of these commonly known parameters include “file,” “page,” and others. There are several Google search queries that can be used to search for the presence of these parameters in web applications. The following is an example of a Google dork:

inurl:file

The above shown Google dork query searches web applications with “file” in their URL. However, as you will notice, these search results will not refine to web applications containing parameters named “file” but will also include directories with the string “file.” During my research, I was able to use a combination of several Google search queries with specific key characters and special characters to refine the search results to web applications only with a “file” parameter in their URL.

Real-world scenario

As explained above, I developed a methodology that refines Google search results to identify web applications hosted in Belgium, which contain the specific parameters I was targeting. Using this methodology, I quickly identified a web server hosted in Belgium and retrieved a common local file present on Unix-based systems.

Remediation

To instruct web crawlers or robots not to index URLs with certain parameters, the “Disallow” directive can be used. A real-world example can be found below:

Figure 5 – Disallow parameter entries

The “Disallow” directives in a robots.txt file instruct search engine crawlers not to index the specific paths provided. This helps to keep private or non-content pages out of search engine results and can also prevent the exposure of sensitive areas of the site to the public. Therefore, the steps to take include identifying those certain parameters within your web application and adding Disallow directives for those parameters in the robots.txt file.

Do note that depending on the structure of the web application, it might not be possible to create such Disallow entries, since those parameters are used in functionality that is by design intended to be public and should be included in search results.

However, once again, the root cause of the identified vulnerability, is not due the lack of a Disallow entry. Since, upon investigating the application, an attacker will be still be able to exploit the vulnerability. The only difference would be the ease of identifying the vulnerability just by a Google search.

Test environment externally accessible

Description

Test environments are a setup that closely mimics the production environment but is used exclusively for testing purposes. In the context of web development, test environments have different stages such as commonly called development, user acceptance testing (UAT), pre-production but at the end are not production environments and should be not publicly accessible. Since, test environments may not be secure as production environments with known vulnerabilities that have not been yet patched. Next, it might contain sensitive or real data for testing purposes. Those are just some examples of security issues that can arise when a test environment is publicly accessible.

Approach

Test environments are often hosted under a subdomain of the production web applications. However, the discovery of those subdomains can sometimes be time consuming and will require an attacker to brute-force the domain for the discovery. Besides I’m acting as a script kiddie and want to avoid the automation of certain tasks, the usage of artificial intelligence (AI) can even play a role. Below the question is shown to be asked to an AI Chatbot internally used by NVISO to fulfill my requirements for this task.

Figure 6 – Subdomain discovery using AI

The AI Chatbot responded with several common subdomains which developers use to manage different environments for software development. With the information obtained above a Google Dork can be defined to search specifically for web servers containing those subdomains within the URL. An example Google dork can be found within the Google Hacking Database with the ID 5506:

site:dev.*.*/signin

The Google dork above is used to search for developers’ login pages across various locations. However, this operator may not be specific enough, potentially yielding results for web applications hosted worldwide. Additionally, my research into databases and other online resources did not reveal a straightforward Google dork for locating test environments of web applications. Consequently, it’s necessary to think outside the box and explore how to refine Google searches to specifically target test environments.

Real-world scenario

Successfully, my developed methodology, combined with the use of multiple advanced operators, enabled me to identify several vulnerabilities in the test environments of web applications hosted in Belgium.

Following, during the research several WordPress installations were found within a development environment that were improperly configured. Those misconfigurations allow an attacker to complete the installation of a WordPress instance and achieve remote code execution by uploading a malicious PHP file which is also a publicly available resource.

Second, a different Google dork was used to identify also a common subdomain for development environments and the results were once again eye-opening. A web server exposing the username of the administrator user and the hash of the password within a file.

Remediation

The remediation steps for test environments are quite straightforward. Only authorized users should have access, so a form of authentication should be configured, such as a VPN or a firewall. With this in place, the test environment will no longer be available to the Internet, and any potential attacks from outside the network will be eliminated with this approach.

Sensitive Information Disclosure

Description

The risk due to disclosure of unintended information in web applications can vary significantly based on the type of information disclosed to the public audience. These kinds of issues can occur unnoticed but can have a severe impact depending on the information disclosed. Some examples include plain-text credentials for login portals, databases, repositories, and much more.

Approach

The Google dork can also be utilized to search for specific strings. This functionality can be leveraged to search for sensitive strings or directories to discover potentially sensitive information on web servers.

Besides searching for sensitive strings or directories, directory listing on web servers can ease the reconnaissance phase for an attacker as it already shows all the available directories/files on the web server. For the sake of the research, searching for web servers with directory listing enabled in combination with key strings which can potentially indicate sensitive information can ease the discovery. An example Google dork can be found within the Google Hacking Database with the ID 8376:

intext:"index of" app

The above search query is used to identify openly accessible directories that include the word ‘app.’ While a directory listing on a web server already presents a security issue, it does not necessarily mean that an attacker will immediately find sensitive information due to this issue.

During my research, this part was the most enjoyable because you’re never sure what you’ll come across, and you need to be as creative as possible since sensitive information could be disclosed anywhere on web servers.

Something I discovered after completing my security research was the potential for automation in identifying sensitive information, as this process can be time-consuming. What if this approach could be automated using artificial intelligence? For example, scraping web servers from the search results and then submitting the output to OpenAI’s ChatGPT to determine if the content contains sensitive information.

This approach can significantly automate certain tasks and give us the ability to discover those security issues quicker. However, as I’m performing the security research as a script kiddie with limited programming language ability, this is a side note how things can easily get abused. Therefore, we’ll make a proof-of-concept and submit a config file into ChatGPT and ask ChatGPT like an average user if the submitted content contains sensitive information.

I generated 100 paragraphs of lorem ipsum and inserted the following within one of the paragraphs: `dbpass=admin, dbpass=admin` and asked if the above content contains sensitive information. As expected, ChatGPT notified this and reported to us.

Figure 7 – Check sensitive content using OpenAI

Real-world scenario

Finally, during the research I found a lot of web servers exposing sensitive information such as, plain-text database credentials, plain-text credentials of login portals, web server configurations and even such issues which cannot be mentioned. However, I noticed that the combination of multiple operators can significantly increase the ability to discover sensitive information on web servers. Let’s say that we’re interested in finding all PDF documents within web servers with a TLD .de. Further, it’s common that organizations tag internal documents with a specified classification such as “Classification: Internal use only” or related. Therefore, the presence of a string which can potentially indicate sensitivity within the document can also be used. To wrap up all the above mentioned operators, we’ll get something like below search query:

site:XX intext:XX AND intext:XX ext:XX

The above search query was invented while writing the blog post to avoid revealing the query I originally used to identify the vulnerability. Nevertheless, I still obtained a document that satisfied those results, which could lead to another potential security issue. Moreover, since Germany does not have such a CDVP (Common Vulnerability Disclosure Policy), I did not access the document or check its content. However, the Google search result already revealed enough information.

Remediation

Once again, since sensitive information can be disclosed anywhere in an application and can vary significantly based on the type of information disclosed, there is unfortunately no straightforward way to address all these issues. Therefore, we should adopt a proactive approach and handle these issues with several actions, such as regular security audits or penetration tests. However, even with these assessments, it is possible that some issues may not be identified due to scope limitations. Consequently, a targeted online footprint analysis assessment can be conducted by your professionals if they possess the necessary skills, or hiring a third-party provider.

Reflected Cross-site Scripting

Description

Cross-Site Scripting is a type of vulnerability that allows attackers to inject malicious JavaScript code into web applications. This code is then executed in the user’s browser when the application reflects or stores the injected scripts. In the case of a reflected XSS, the attack is often delivered to the victim via a link from the trusted domain. Once the victim clicks on the link, the web application processes the request and includes the malicious script in the response executing it in the user’s browser. Moreover, the gravity of such an attack is increased if the compromised web application is hosted under a popular trusted domain which convince the users more likely to click on the malicious link.

Approach

The methodology in order to identify XSS vulnerabilities is akin to that used for identifying LFI vulnerabilities though the use of Google dork. The approach involves searching for URL parameters that are commonly susceptible to XSS attacks. Next, refining Google dork queries to target these specific parameters and uncover the web applications for vulnerabilities.

During the research, I have incorporated the use of an AI chatbot to generate a list of the most frequently used URL parameters susceptible for XSS attacks.

Figure 8 – Searching common parameters using AI

As shown above, I received a list of parameters from the AI Chatbot that may be susceptible to XSS vulnerabilities. Further, I asked the Chatbot the appropriate Google search operator that would facilitate the identification of web servers incorporating any of these parameters in their URLs.

Figure 9 – Asking ChatGPT for a Google Dork

As previously demonstrated, we obtained a Google search query to search for web servers that meet our specific criteria. However, the search query did not fully meet our objectives, prompting us to conduct further research and refine our search parameters. Once again, the refined and enhanced Google search query has been omitted for security risk reasons.

Real-world scenario

During my research, the “adjusted” aforementioned approach yielded XSS vulnerabilities on the web servers of Belgium companies. To verify the presence of an injection vulnerability on a web server, I began by attempting to inject HTML tags. Following a successful HTML injection, the next phase involved the insertion of a malicious JavaScript code. For the purposes of the security research, I employed a commonly used XSS payload to demonstrate to the relevant company or organization the presence of such a vulnerability. Regrettably, to protect the confidentiality and security of the involved entity, I am obliged to withhold detailed information regarding the initial discovery, providing only a limited disclosure as follows:

Figure 10 – Exploiting reflected XSS vulnerability

Imagine a scenario where a well-known organization’s web server is discovered to have a vulnerability of the kind previously discussed. Further, obtaining the contact details for such an organization would be trivial task for even a novice hacker, given the wide availability of tools designed for this purpose. Following, armed with this information, the attacker can easily craft and dispatch phishing emails to the organization’s staff, falsely claiming that an urgent password reset is required due to a recent cyber-attack. Next, if an employee were to click on the provided link, they would encounter a compromised user interface on the web server, masquerading as a legitimate password recovery portal. In reality, this portal would be under the attacker’s control.

As shown, the scenarios are endless, and the cybercriminals are for sure aware of it when exploiting those kind of vulnerabilities. Last but not least, how much the above scenario is applicable for the organization in question is for me to know and for the audience to find out.

Remediation

In order to remediate or prevent XSS attacks on web servers, a robust input validation and sanitization strategy has to be implemented. All user-supplied data should be treated as untrusted and validated against a strict set of rules to ensure it conforms to expected formats.

How to prevent Google Dorking

In order to prevent cybercriminals to uncover sensitive information or vulnerabilities on your web applications using Google dork, a proactive approach is required to manage your online resources. A common way used to handle this is the usage of the robots.txt file which will prevent search engine bots from indexing sensitive parts of your website. Further, the presence of a robots.txt file in itself does not pose a security risk at all and can even serve several non-security related purposes such as SEO optimization. However, in the case that a developer solely relied on the robots.txt file to hide the sensitive parts of their web application and included those directories and files, this information can act as a roadmap to your sensitive information on your website for attackers.

In order to align what I stated in the above paragraph, I used the following Google dork to find all robots.txt files in websites that contains a directory called top-secret:

inurl:robots ext:txt "top-secret"

Below is a real-world example of a robots.txt file used by a web server not allowing to index the top-secret directory. At first sight, you might think that this information does not pose a direct security risk for the web application. However, an attacker can gain valuable information by a publicly available file on the web server. Then, if an attacker can identify a vulnerability such as a LFI I found earlier during this research, without any doubt, it will be the first place to exfiltrate data as a directory called top-secret will most likely contain sensitive information.

Figure 11 – Top-secret entry within a robots.txt file

To conclude, the robots.txt file is not a security measure to completely rely on in order to prevent Google Dorking. However, it can serve as a defense-in-depth approach to take a countermeasure for exposing sensitive information on your web server.

An important side note I would like to tell you is that none of the uncovered vulnerabilities are related to Google’s search engine as it only serves a way to find the information requested from the Internet. Nevertheless, it has an impact on the likelihood of the vulnerability. At NVISO, we score the overall risk of a vulnerability during an assessment based on two key factors. The first factor is what kind of an impact the vulnerability has on the web application in question. Furthermore, the second factor is the likelihood. The likelihood is further divided in two sub-sections which are likelihood of discovery and likelihood of exploitation. A very important sub-section is the likelihood of discovery which basically lets us rate how easy it is for an attacker to discover the existing vulnerability. As I’m sure you’ll understand, I brought this to your attention as discovering vulnerabilities on web applications just using a Google search bar will score a high likelihood of discovery. Simply, it’s not only about the impact but also likelihood.

From my research, I concluded that there is not a solid one way solution to prevent attackers easily identifying vulnerabilities and exposed sensitive information on your web servers. Since, if the vulnerability exists there is still a way to uncover it aggressively or passively. However, the following countermeasures in combination can act as a defense-in-depth approach:

  • Implementation of strong access controls: First of all, it should be determined which users should have access to the which sensitive parts of the web application. Further, a clearly defined strong access control mechanism should be implemented.
  • Secure storage of sensitive data: The web server’s file directory should not store sensitive data such as personal information at all. Sensitive information should be solely stored on a central storage location and retrieved by the web server if necessary.
  • Regular penetration and vulnerability assessments: The Google Dorking method will have no sense if a web server is built securely. Meaning, that it’s not vulnerable to any type of vulnerabilities and/or does not expose any sensitive information. Hence, I was able to exploit the vulnerabilities or discover sensitive information due to the fact that the web servers were not built securely. Further, this is not a straight away solution and therefore regular penetration, and vulnerability assessments can discover these kind of issues and help the companies remediated them.

Conclusion

The security of Belgium’s digital environment cannot be determined by a single security research project alone. As a consultant working closely with numerous organizations, I am convinced that everyone in this sector is striving to enhance our digital safety. Even so, there is considerable scope for improvement. Because it is easier for cybercriminals to know a vulnerability and look which resource is vulnerable, rather than to target a known resource for potential vulnerabilities.

However, based on my research, it appears that, while it’s possible for someone with minimal technical expertise to exploit vulnerabilities in certain companies, the statement that large Belgian corporates can be hacked solely using Google search queries would be a broad generalization. It’s important to clarify that these search queries can be helpful during the reconnaissance phase of an attack, which is just the initial step in a series of actions taken by potential adversaries. This preliminary phase presents an opportunity for companies to implement preventive and detective measures to mitigate and manage the impact.

The objective of the blogpost is to enhance the security awareness within our community by highlighting the potential risks associated with seemingly minor misconfigurations.

From Belgium, we stand strong in our commitment to cybersecurity. Let’s keep working together to keep our online world safe. Stay alert, stay secure, and stay safe.

References

  1. CCB – Vulnerability reporting to the CCB
  2. CBC News – Teen who leaked GTA VI sentenced to indefinite stay in “secure hospital”
  3. Exploit-DB – Google Hacking Database
  4. PortSwigger – Robots.txt file

Alpgiray Saygin

Alpgiray Saygin works as a cybersecurity consultant within the NVISO Software Security Assessment team. His expertise is primarily focused towards conducting a variety of assessments, including internal, external, web, wireless, and vulnerability assessments. An enthusiast for self-improvement, he enjoys the opportunity to challenge himself by pursuing certifications and engaging in security research during his free time.

Deobfuscating Android ARM64 strings with Ghidra: Emulating, Patching, and Automating

15 January 2024 at 08:00

In a recent engagement I had to deal with some custom encrypted strings inside an Android ARM64 app. I had a lot of fun reversing the app and in the process I learned a few cool new techniques which are discussed in this writeup.

This is mostly a beginner guide which explains step-by-step how you can tackle a problem like this. Feel free to try it out yourself, or just jump to the parts that interest you.

In this tutorial-like blogpost, we will:

  • Create a small test app that decrypts some strings in-memory
  • Use the Ghidra Emulator to figure out the decrypted value. This will require some manual intervention
  • Automate the decryption using Python

While I learned these techniques analyzing an Android app, they can of course be used on any ARM64 binary, and the general techniques work for any architecture.

Creating a test app

Let’s start with creating a small test app that decrypts some strings using a basic XOR algorithm. It’s always good to isolate the problem so that you can focus on solving it without other potential issues getting in the way. The code snippet below contains three encrypted strings, and a xorString function that takes a string and a key and performs the XOR operation to obtain the actual string. Additionally, there is a status integer for each string to indicate if the string has already been decrypted. The status integer is atomic, so that if multiple threads are using the same string, they won’t interfere with each other while decrypting the string. Using atomic status flags isn’t actually necessary in this small example, since we only have one thread, but it is what the original app was using, and it is very common to see this kind of approach.

#include <stdio.h>
#include <stdatomic.h>
#include <string.h>
#include <stdlib.h>

void xorString(char *str, char *key, int size, _Atomic int *status) {
    // Check and update status atomically
    int expected = 1;
    if (atomic_compare_exchange_strong(status, &expected, 0)) {
        // Perform XOR operation if the string is encrypted
        for (int i = 0; i < size; i++) {
            str[i] ^= key[i % 4];
        }
    }
}

char string1[] = {0x70,0xea,0xc7,0xd4,0x57,0xaf,0xfc,0xd7,0x4a,0xe3,0xcf,0x00};
_Atomic int status1 = 1; // 1 for encrypted
char string2[] = {0xce,0xc6,0x40,0x93,0xaf,0xf7,0x51,0x9f,0xfd,0xca,0x44,0x88,0xe6,0xdc,0x5a,0x00};
_Atomic int status2 = 1; // 1 for encrypted
char string3[] = {0x45,0xf6,0x8d,0x57,0x32,0xcf,0x80,0x4b,0x7a,0xf0,0x97,0x00};
_Atomic int status3 = 1; // 1 for encrypted

char key1[4] = {0x38, 0x8f, 0xab, 0xb8};
char key2[4] = {0x8f, 0xb3, 0x34, 0xfc};
char key3[4] = {0x12, 0x9f, 0xf9, 0x3f};

int main() {

    xorString(string1, key1, strlen(string1), &status1);
    xorString(string2, key2, strlen(string2), &status2);
    xorString(string3, key3, strlen(string3), &status3);

    printf("String 1: %sn", string1);
    printf("String 2: %sn", string2);
    printf("String 3: %sn", string3);

    return 0;
}

Note: The code above definitely still has race conditions, as one thread could be reading out the string before it is completely decrypted. However, I didn’t want to make the example more complex and this example has all the necessary ingredients to examine some interesting Ghidra functionality.

In order to compile it, let’s use the dockcross project, which allows us to very easily crosscompile via a docker instance:

docker run --rm dockcross/android-arm64 > ./dockcross-android-arm64
chmod +x dockcross-android-arm64
./dockcross-anroid-arm64 bash -c '$CC main.c -o main'
file main
# main: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, not stripped

This will result in a non-stripped ARM aarch64 binary. In a real scenario, the binary would most likely be stripped, but for this exercise, that’s not needed. The binary can be pushed to our Android device and it will print the decrypted strings when running it:

adb push main /data/local/tmp/
adb shell /data/local/tmp/main

String 1: Hello World
String 2: Auto Decryption
String 3: With Python

Great! Let’s open this in Ghidra and get started. Create a new project, import the main binary and perform a quick auto analysis. This will give you the main Ghidra listing, and the decompiled main function is decompiled very cleanly:

Main ghidra listing

Looking at string1, string2 and string3, Ghidra doesn’t identify any interesting strings, which makes sense due to the fact that they are obfuscated:

Obfuscated strings

In the main listing, we can see three invocations to the xorString function:

xorString calls

The arguments are kept in x0, x1, x2 and x3. This is of course pretty standard for aarch64, though it’s not uncommon to see other calling conventions due to optimizations or obfuscation.

In this example, the xorString function is quite straightforward, but let’s just imagine it’s a bit more complex and we can’t immediately figure out how it works based on the listing or decompiled code. One way to figure out the decrypted string is to attach a debugger and put a breakpoint right after the function call. However, any Android app that has custom string encryption most likely has some kind of Runtime Application Self-Protection (RASP), which means a debugger (or Frida) will immediately be detected. So rather than trying to get that up and running, let’s use Ghidra’s emulator.

Tracing in Ghidra

Go to the main Ghidra project window and drag the main binary onto the emulator:

Start emulator

This will open the Ghidra emulator. We want to emulate the xorString function, which means we have to properly initialize all the registers. The first function call starts at 0x101860, so make sure that line is selected, and click the button to start a new trace:

Start trace

Before letting the trace continue, add a breakpoint to line 0x101890 which is right after the first call to xorString. You can add a breakpoint by selecting the line and pressing k or by right-mouse clicking all the way on the left (where the trace arrow is) and choosing ‘Toggle Breakpoint’. Leave the default options and click OK.

Set breakpoint

Finally, click the green Resume button at the top or tap F5 to start the actual trace. After starting the trace, not much will actually happen. The emulator will continue executing until the PC (as indicated in the Registers window) is trying to execute 0x104018 which is not a valid instruction address. So what happened? We can restart the trace by selecting line 0x101860 and clicking the Emulator button. Apparently, the emulator goes into the strlen function to determine the length of the obfuscated string. This strlen function is imported from an external library, and so the Emulator doesn’t have access to it.

There are at least two ways to get around this: Manual intervention, or creating custom sleigh code. Let’s take a look at both and we’ll start with manual intervention.

Manually skipping the strlen function

Start a new trace and continue until you’ve reached the call to strlen (0x10186c). Next, click the ‘Skip instruction’ button to jump to the next line without actually executing the instruction:

Skip instruction

Of course, since we skipped the strlen function, the correct value is not in x0. We can patch this manually by opening the Registers window and filtering on x. The current value of x0 is 0x103c00 (the location of the string) and we need to replace this with the length. The string1 variable is a null-terminated string (otherwise strlen wouldn’t work) so we can take a look at the memory location (0x103c00) and count the number of characters. We can also label it as a c-style string by right-mouse clicking > Data > TerminatedCString

Assign data type

You can now hover over the ds to see that the length of the string is 12 (0xc), but we have to subtract one for the null byte so the length is 11. Back in our Registers window, we can now change the value of x0 to 11. Before you modify the register, click the button at the top of the registers window to enable editing and then double click the value of the x0 register and update it to 11:

Edit registers

Emulating strlen

As an alternative, we can create some custom SLEIGH code that is run instead of the strlen function. First, close all the current traces via the Threads window. Next, start a new trace at 0x101860 and then add a breakpoint on the strlen call at 0x10186c. In the breakpoints window, right-mouse click on the breakpoint and choose ‘Set Injection (Emulator)’:

Adding breakpoint injection

As the injection, we’ll use the following code:

# Initialize counter variable
x8=0;
# Top of our for-loop
<loop>
# If we read a null-byte, we know the length
if (*:1 (x0+x8) == 0) goto <exit>;
# Increase the counter
x8 = x8+1;
# Jump back to the top
goto <loop>;
<exit>
# Assign counter to x0
x0=x8;
# Don't execute the current line in the listing
emu_skip_decoded();

Normally we would have to allocate some space on the stack to store the old value of x8, but since we know that x8 will be overwritten in the line after our strlen call, we know it’s free to use. This code is very similar to the code from the official Ghidra documentation, just adapted for AARCH64. You can also continue reading the official documentation to figure out how to make this SLEIGH injection work for any call to strlen rather than just this single occurrence.

Before stepping through the trace, choose Debugger > Configure Emulator > Invalidate Emulator Cache, just to make sure the Emulator will pick up on our custom SLEIGH code. Finally, we can step through the trace and this time it will skip over the strlen call and store the length of the string in x0:

Success!

Continuing with the trace

Continue with single steps until you get to line 0x10178c. When stepping over this stlxr instruction, Ghidra throws an error:

Sleigh userop 'ExclusiveMonitorPass' is not in the library ghidra.pcode.exec.ComposedPcodeUseropLibrary@73117252
ghidra.pcode.exec.PcodeExecutionException: Sleigh userop 'ExclusiveMonitorPass' is not in the library ghidra.pcode.exec.ComposedPcodeUseropLibrary@73117252
    at ghidra.pcode.exec.PcodeExecutor.step(PcodeExecutor.java:275)
    at ghidra.pcode.exec.PcodeExecutor.finish(PcodeExecutor.java:178)
    at ghidra.pcode.exec.PcodeExecutor.execute(PcodeExecutor.java:160)
    at ghidra.pcode.exec.PcodeExecutor.execute(PcodeExecutor.java:135)
    at ghidra.pcode.emu.DefaultPcodeThread.executeInstruction(DefaultPcodeThread.java:586)
    at ghidra.pcode.emu.DefaultPcodeThread.stepInstruction(DefaultPcodeThread.java:417)
    at ghidra.trace.model.time.schedule.Stepper$Enum$1.tick(Stepper.java:25)
    at ghidra.trace.model.time.schedule.TickStep.execute(TickStep.java:74)
    at ghidra.trace.model.time.schedule.Step.execute(Step.java:182)
    at ghidra.trace.model.time.schedule.Sequence.execute(Sequence.java:392)
    at ghidra.trace.model.time.schedule.TraceSchedule.finish(TraceSchedule.java:400)
    at ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin.doEmulateFromCached(DebuggerEmulationServicePlugin.java:722)
    at ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin.doEmulate(DebuggerEmulationServicePlugin.java:770)
    at ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin$EmulateTask.compute(DebuggerEmulationServicePlugin.java:261)
    at ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin$EmulateTask.compute(DebuggerEmulationServicePlugin.java:251)
    at ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin$AbstractEmulateTask.run(DebuggerEmulationServicePlugin.java:238)
    at ghidra.util.task.Task.monitoredRun(Task.java:134)
    at ghidra.util.task.TaskRunner.lambda$startTaskThread$0(TaskRunner.java:106)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: ghidra.pcode.exec.SleighLinkException: Sleigh userop 'ExclusiveMonitorPass' is not in the library ghidra.pcode.exec.ComposedPcodeUseropLibrary@73117252
    at ghidra.pcode.exec.PcodeExecutor.onMissingUseropDef(PcodeExecutor.java:578)
    at ghidra.pcode.emu.DefaultPcodeThread$PcodeThreadExecutor.onMissingUseropDef(DefaultPcodeThread.java:205)
    at ghidra.pcode.exec.PcodeExecutor.executeCallother(PcodeExecutor.java:562)
    at ghidra.pcode.exec.PcodeExecutor.stepOp(PcodeExecutor.java:249)
    at ghidra.pcode.emu.DefaultPcodeThread$PcodeThreadExecutor.stepOp(DefaultPcodeThread.java:182)
    at ghidra.pcode.exec.PcodeExecutor.step(PcodeExecutor.java:268)
    ... 20 more

---------------------------------------------------
Build Date: 2023-Sep-28 1301 EDT
Ghidra Version: 10.4
Java Home: /usr/lib/jvm/java-19-openjdk-amd64
JVM Version: Private Build 19.0.2
OS: Linux 5.15.0-76-generic amd64

Apparently the call to ExclusiveMonitorPass isn’t implemented for this Emulator so it doesn’t know what to do. The ExclusiveMonitorPass is there because of the atomic status flag which makes sure that different threads don’t interfere with each other while decrypting the string. We could simulate the call again with some custom SLEIGH code, but since our emulation is single-threaded anyway, let’s patch the code to remove the call altogether.

Patching the decryption function

Currently, the decompiled code looks like this:

void xorString(long param_1,long param_2,int param_3,int *param_4)
{
  int iVar1;
  char cVar2;
  bool bVar3;
  int local_30;

  do {
    iVar1 = *param_4;
    if (iVar1 != 1) break;
    cVar2 = 'x01';
    bVar3 = (bool)ExclusiveMonitorPass(param_4,0x10);
    if (bVar3) {
      *param_4 = 0;
      cVar2 = ExclusiveMonitorsStatus();
    }
  } while (cVar2 != '');
  if (iVar1 == 1) {
    for (local_30 = 0; local_30 < param_3; local_30 = local_30 + 1) {
      *(byte *)(param_1 + local_30) =
           *(byte *)(param_1 + local_30) ^ *(byte *)(param_2 + local_30 % 4);
    }
  }
  return;
}

The general flow is described in this ARMv8-A Synchronization primitives document which explains how ExclusiveMonitors work. While the decompiler understands the special exclusive stlxr and ldaxr commands, the emulator does not. In the snippet below, I’ve renamed (L) and retyped (CTRL+L) the variables, and added some constants and comments to make it a bit clearer:

void xorString(char *p_string,char *p_key,int p_length,int *p_status)
{
  int counter;
  bool wasAbleToStore;
  bool monitorIsReady;
  int status;
  int ENCRYPTED = 1;
  int DECRYPTED = 0;

  do {
    status = *p_status;
    // If the string isn't encrypted, no need to do more work
    if (status != ENCRYPTED) break;
    wasAbleToStore = true;

    // Check whether the given address is part of the Exclusive Monitor of the current PE
    monitorIsReady = (bool)ExclusiveMonitorPass(p_status,0x10);
    if (monitorIsReady) {
      // Try to store the value. This will also lift the exclusion in case the write was successful
      *p_status = DECRYPTED;
      // If the store was unsuccessful, wasAbleToStore will become false
      wasAbleToStore = (bool)ExclusiveMonitorsStatus();
    }
  } while (wasAbleToStore != false);

  // Only encrypt if status == ENCRYPTED
  if (status == ENCRYPTED) {
    for (counter = 0; counter < p_length; counter = counter + 1) {
      p_string[counter] = p_string[counter] ^ p_key[counter % 4];
    }
  }
  return;
}

The code will figure out if the string still needs to be decrypted and if so, try to update the status. If this status update succeeds, the thread will continue on to the decryption algorithm and start XORing the different characters. Since we are only using a single thread in the emulator, let’s just patch out the thread-specific logic while making as few modifications as possible.

If we look at the listing, the instruction that triggered the error is stlxr:

stlxr instruction

The stlxr instruction is the exclusive version of the non-exclusive str instruction which simply stores a value at a certain position. Let’s modify this instruction to str w12, [x11] by right-mouse clicking, choosing ‘Patch Instruction’ and entering the new instruction:

Patching stlxr

In the next line (0x101790) the w10 register, which is no longer there, is checked and we jump to the top of the block if it’s not equal to zero (cbnz). This means that if we nop-out the cbnz instruction, the flow would just continue as if the store was successful. So right-mouse click > Patch Instruction and choose the nop command:

Nopping cbnz

If we now look at the decompilation view, the code is much more straightforward:

void xorString(char *p_string,char *p_key,int p_length,int *p_status)
{
  int counter;
  bool wasAbleToStore;

  if (*p_status == ENCRYPTED) {
    *p_status = 0;
    for (counter = 0; counter < p_length; counter = counter + 1) {
      p_string[counter] = p_string[counter] ^ p_key[counter % 4];
    }
  }
  return;
}

Let’s trace through the first invocation again. Start a new trace, add the custom SLEIGH injection breakpoint and put a breakpoint at line 0x101890. Once the second breakpoint hits, examine address 0x103c0c in the Dynamic listing window. The string has successfully been decrypted and we can convert it into a normal C string using right-mouse click > Data > TerminatedCString. Note that the normal listing still has the obfuscated string, since it is not automatically updated based on the emulator result.

Decrypted strings

Now that we know that the string ‘Hello world’ is located at address 0x103c00, we can label it appropriately in the normal listing. Select the symbol name string1 and press L:

Assign label

The new label will automatically be used throughout the listing and the decompilation view:

Updated decompilation

The same technique can be used to decode the other two strings, but let’s just automate everything with some python to speed things up.

Automating with python

Automating Ghidra can be a bit tricky due to a few reasons:

  • We can choose between Python2 or Java, but no Python3
  • Not too many code examples
  • Documentation is limited

To solve the first problem, we could install Ghidraton or Ghidra bridge, but for simplicity, let’s just stick to importing a normal print function and using python2.

As for the actual code, the easiest solution by far (at least currently) is to use ChatGPT to generate it. It does a pretty good job and can quickly give you the necessary API calls for some prototyping.

There are a few different approaches we could take:

  • Create a script that we can trigger after selecting a specific call to xorString. We could even select all the relevant lines in the listing for each call so that the script knows exactly where to get the input from.
  • Find all references to the xorString function and try to find the correct input values automatically.

Let’s try the second approach for maximum convenience. This will allow us to run the script once and hopefully identify all obfuscated strings. The requirement is of course that Ghidra has identified all the correct cross-references to the xorString function.

The general structure is as follows:

  1. Retrieve all references to the chosen decryption function
  2. Check each reference to make sure it’s a function call
  3. Go up from the function call until we find correct values for all parameters (x0, x1, x2, x3)
  4. Extract correct values from memory (e.g. collect the byte array at x0)
  5. Use a custom python function to decrypt
  6. Assign the decrypted string as the label of the encrypted byte array in the listing

The most difficult part is definitely step 3 and will be very specific to your application. In the test application, it’s not too difficult. We actually only need x0 (string) and x1 (key) since we can calculate the length of the string ourselves and we don’t really need the status variable. x0 and x1 are defined across a few different statements, but we can actually make use of Ghidra’s calculations.

In the image below, we can see that at line 0x1018d4, Ghidra knows that x0 refers to string3, and at line 0x1018e0, Ghidra knows that x1 refers to key3. So let’s use that knowledge in our script, and search for the first occurrence (working backwards from the call) to where we have a resolved value for x0 and x1.

Interesting registers

One useful trick here is to select the line that has the information you want, and right-mouse click > Instruction Info.

Automatic address calculation

We can see that the value we are looking for (0x103c24: string3) can be accessed via the Address property of Operand-0. So we can scan the code looking for the first occurrence of x0 as Operand-0 and then extract the address.

The full script to resolve all the strings is given below. There might be better/faster ways to do this, but it works. This script can definitely fail for multiple reasons, but as a PoC it works very well. For each decrypted string, the label is updated and the data is converted into a TerminatedCString.

from __future__ import print_function
import os
import jarray
from ghidra.program.model.data import TerminatedStringDataType
from ghidra.program.model.mem import MemoryAccessException
from ghidra.program.model.symbol import SymbolTable, SourceType
from ghidra.program.model.data import CharDataType, ArrayDataType

global toAddr, getReferencesTo, getInstructionAt, currentProgram

def getCurrentProgram():
    return currentProgram

program = getCurrentProgram().getListing()
memory = getCurrentProgram().getMemory()

def main():
    decryptFunction = getState().getCurrentLocation().getAddress()
    functionStart = getStartOfFunction(decryptFunction)
    if decryptFunction != functionStart:
        print("Chosen instruction is inside of a function. Using first instruction of function instead")
        decryptFunction = functionStart

    print("Decrypt function: " + str(decryptFunction))

    # Obtain all references to the chosen function
    xrefs = getReferencesTo(decryptFunction)

    for xref in xrefs:

        # Find the caller, which is an address    
        caller = xref.getFromAddress()

        # Get the instruction at that address
        inst = getInstructionAt(caller)

        if inst:

            mnemonic = inst.getMnemonicString()
            # Interested in function calls
            if mnemonic == "bl":
                # Find x1, x2, x3 and x4
                x0 = getValue("x0", inst)
                x1 = getValue("x1", inst)
                x2 = getStringLength(x0)
                x3 = getValue("x3", inst)
                print("Found call at", caller,"Decoding with arguments: ", x0, x1, x2, x3);

                encryptedString = getMemoryBytes(x0, x2)
                status = getMemoryBytes(x3, 1)
                key = getMemoryBytes(x1, 4);
                decryptedValue = str(xorDecrypt(encryptedString, key))

                print("Decryption: ", decryptedValue, "n")
                assignPrimaryLabel(x0, "s_" + toCamelCase(decryptedValue))
                # Include the x00, so x2 + 1
                tagAsCharArray(x0, x2 + 1)

def assignPrimaryLabel(address, label_name):
    try:
        # Get the current program's symbol table
        symbolTable = getCurrentProgram().getSymbolTable()

        symbol = symbolTable.getPrimarySymbol(address)

        if symbol:
            symbol.setName(label_name, SourceType.USER_DEFINED)
        else:
            symbol = symbolTable.createLabel(address, label_name, SourceType.USER_DEFINED)
            symbol.setPrimary()

    except Exception as e:
        print("Error assigning label:", e)

def toCamelCase(input_string):
    words = input_string.split()
    # Capitalize the first letter of each word except the first one
    camelCaseString = words[0].lower() + ''.join(word.capitalize() for word in words[1:])
    return camelCaseString

def getValue(registerName, inst):
    # A safeguard to only go back 20 lines max
    c = 0
    while True:
        inst = inst.getPrevious()

        register = inst.getRegister(0)

        if register and register.getName() == registerName:
            primRef = inst.getPrimaryReference(0)
            if primRef:
                return primRef.getToAddress()
        c += 1
        if c > 20:
            return None

def assignString(addr, name):
    existingData = program.getDataContaining(addr)

    if not existingData or not isinstance(existingData.getDataType(), TerminatedStringDataType):
        program.clearCodeUnits(addr, addr, False)
        program.createData(addr, TerminatedStringDataType())

def tagAsCharArray(address, length):
    dataManager = getCurrentProgram().getListing()
    charDataType = CharDataType()  # Define the char data type
    charArrayDataType = ArrayDataType(charDataType, length, charDataType.getLength())  # Create an array of chars

    try:
        endAddress = address.add(length)
        dataManager.clearCodeUnits(address, endAddress, False)

        # Apply the char array data type at the given address
        dataManager.createData(address, charArrayDataType)
    except Exception as e:
        print("Error creating char array at address:", e)

def getStringLength(address):
    # print("String length of ", address)
    length = 0
    while True:
        # Read a single byte
        byteValue = memory.getByte(address)

        # Check if the byte is the null terminator
        if byteValue == 0:
            break

        # Move to the next byte
        address = address.add(1)
        length += 1
    return length

def getStartOfFunction(address):
    return program.getFunctionContaining(address).getEntryPoint()

def xorDecrypt(encodedString, key):
    result = bytearray()
    key_length = len(key)
    for i in range(len(encodedString)):
        result.append(encodedString[i] ^ key[i % key_length])
    return result

def getMemoryBytes(address, length):
    try:
        # Create a byte array to hold the memory contents
        byte_array = jarray.zeros(length, 'b')

        # Read memory into the byte array
        if memory.getBytes(address, byte_array) != length:
            print("Warning: Could not read the expected number of bytes.")

        return byte_array
    except MemoryAccessException as e:
        print("Memory access error:", e)
        return bytearray()
    except Exception as e:
        print("An error occurred:", e)
        return bytearray()

main();

Note: It’s not possible to use a bytearray as the second argument for memory.getBytes (see this Ghidra issue). Using a python bytearray will result in an empty bytearray.

To run this script, open the Script Manager (Window > Script Manager) and click ‘Create New Script’ in the top right. Choose Python and give it a name (e.g. xorStringDecrypter). Paste the content of the script, make sure you select a line of code somewhere inside the xorString function and finally click Run. You should see the following output:

xorStringDecrypter.py> Running...
Decrypt function: 00101754
Found call at 0010188c Decoding with arguments:  00103c00 00103c34 11 00103c0c
Decryption:  Hello World 

Found call at 001018bc Decoding with arguments:  00103c10 00103c38 15 00103c20
Decryption:  Auto Decryption 

Found call at 001018ec Decoding with arguments:  00103c24 00103c3c 11 00103c30
Decryption:  With Python 

xorStringDecrypter.py> Finished!

The decompiler output is automatically updated, and the data is tagged correctly:

Conclusion

There are many ways to solve the different problems listed in this mini-tutorial, and it’s always good to have multiple techniques in your toolbelt. Automation can help tremendously, but you’ll often have to write custom scripts that work for your specific problem.

Finally, if you know of some interesting additional techniques, or maybe faster ways to do something, leave a comment!

Jeroen Beckers

Jeroen Beckers is a mobile security expert working in the NVISO Software Security Assessment team. He is a SANS instructor and SANS lead author of the SEC575 course. Jeroen is also a co-author of OWASP Mobile Security Testing Guide (MSTG) and the OWASP Mobile Application Security Verification Standard (MASVS). He loves to both program and reverse engineer stuff.

Scaling your threat hunting operations with CrowdStrike and PSFalcon

13 December 2023 at 08:00

Introduction

Most modern day EDRs have some sort of feature which allows blue teamers to remotely connect to hosts with an EDR agent/sensor installed, to aid in their investigation of incidents. In CrowdStrike, this is called Real Time Response, and it provides a wide range of capabilities, from executing built-in commands like ipconfig and netstat to running your own PowerShell scripts.

In this blog post, I’ll showcase how CrowdStrike’s PSFalcon PowerShell module can be used to execute RTR commands on multiple hosts at once for the purpose of threat hunting. I’ll also be providing the code for the threat hunting script, and by the end of this blog you will be able to use the script to pull registry run keys, scheduled tasks, WMI subscriptions, startup folder files, and services from multiple machines, to uncover hidden persistence mechanisms. Attackers may establish persistence in your environment without being detected, as such hunting for some of the techniques they use could uncover a potential breach. You can find the Persistence-Hunter script here: https://github.com/NVISOsecurity/blogposts/blob/master/Persistence%20Hunter/Persistence-Hunter.ps1

Getting started

Before interacting with CrowdStrike’s Oauth2 API via PSFalcon, you will need PowerShell installed and a valid API client (which consists of a ClientID and a secret), that you can create via this link https://falcon.crowdstrike.com/api-clients-and-keys/clients. You can get an API client yourself if you have the Falcon administrator role, otherwise an administrator has to provide you with one. Make sure that your client has the Real time response (admin): Write permission enabled. After your API client is created, you have to install the PSFalcon module. You can find instructions on that through this link https://github.com/CrowdStrike/psfalcon/wiki/Installation,-Upgrade-and-Removal#use-the-powershell-gallery. Once that is done, run Show-FalconModule in a PowerShell prompt to verify that everything is correctly installed.

Since API keys could provide someone with a great deal of access, here are some best practices to keep in mind when handling them:

  1. Principle of least privilege; Don’t over assign privileges and rights to your key that are not necessary for the work you will be doing.
  2. Keep your key (ClientID and secret) stored somewhere safe, preferably a password manager.
  3. Don’t hardcode your key in a script, for obvious reasons.
  4. Don’t share your key with anyone, treat it as you would treat a password.

Workflow and script details

Persistence-Hunter.ps1 utilizes the PSFalcon module in order to query multiple hosts at once. It initiates Real Time Response sessions to each host in a specific group simultaneously and checks for persistence mechanisms that indicate if a host might be compromised or not.

Invoke-FalconRtr is used to initiates RTR sessions. Depending on the parameter provided, it will start a Real-time Response session with:

  • -GroupId: Members of the specified host group identifier
  • -HostId: A single host identifier
  • -HostIds: An array containing one or more host identifiers

Example command:

Invoke-FalconRtr -Command 'runscript' -Argument $Arguments -Timeout 60 -GroupId $group_ID

In this case, Invoke-FalconRtr was supplied with the with the following parameters:

  • runscript: To run a PowerShell script
  • Arguments: the Base64 commands about to be executed
  • Timeout: timeout limit
  • Group_ID: The ID of the host group

Sadly, PSFalcon does not understand host group names, so you need to convert the host group name to the corresponding host group identifier (GroupId).

How do I go from group name to GroupId?

You can do it manually via PSFalcon. Let’s say you want to find the GroupId of the following group:

Domain - Workstations

First, you need to turn all of the characters into lowercase:

$GroupName = 'Domain - Workstations'.ToLower()

You can get the value of GroupName in a PowerShell prompt to see if it worked by typing $GroupName:

domain - workstations

Then you need to use the following PSFalcon cmdlet:

$Id = Get-FalconHostGroup -Filter "name:'$GroupName'"

Once again you can get the value of id in a PowerShell prompt by typing $Id :

k43b…………………..d9120

The value of id is your GroupId, which is a 32-character long string. The GroupId parameter can then be supplied to the Persistence-Hunter script.

What are the commands that get executed via the script?

1. Registry keys (MITRE T1547.001):
gi -path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run\' -ea silentlycontinue | out-string ;
gi -path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce\' -ea SilentlyContinue | out-string;
gi -path 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run\' -ea silentlycontinue | out-string;
2. Scheduled tasks: (MITRE T1053.005):
schtasks /query /fo csv /v | convertfrom-csv | select TaskName, 'Task To Run', Author | fl | out-string;                                
3. Startup folder (MITRE T1547.001):
gci -path 'C:\Users\*\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\*' -ea silentlycontinue | select fullname,Length,CreationTime,LastWriteTime,LastAccessTime,Mode | fl | out-string;
gci -path 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\*' -ea silentlycontinue | select fullname,Length,CreationTime,LastWriteTime,LastAccessTime,Mode | fl | out-string;
4. WMI (MITRE T1546.003):
Get-WmiObject -namespace 'root\subscription' -class __EventConsumer | fl | out-string
Get-WmiObject -namespace 'root\subscription' -class __EventFilter | fl | out-string
Get-WmiObject -namespace 'root\subscription' -class __FilterToConsumerBinding | fl | out-string
5. Services (MITRE T1543.003):
gwmi win32_service |select name,pathname,state,status,startmode| fl | out-string;

How can I modify the commands or run other commands?

The commands need to be Base64 encoded since they are custom scripts to be executed. If you want to run your own PowerShell commands, you can use the following snippet to Base64 encode them:

$EncodedScript = [Convert]::ToBase64String(
        [System.Text.Encoding]::Unicode.GetBytes((Get-Content -Path $Path -Raw)))

Running the script

Before running the script, you’ll need to edit line 39 to include your group ID, and change the group name in line 42 as well. With that done, you are ready to run it via the command line. The script will produce 5 different csv files, one for each technique mentioned above. Happy hunting!

Conclusion

In this blog post, we looked into how the PSFalcon module can be leveraged in order to execute multiple commands in a group of hosts in CrowdStrike for threat hunting purposes.

This script has assisted me in the following use cases:

  1. Identify persistence via registry run keys in a host with a crypto miner infection, which was not detected by the CrowdStrike agent since it was a pre-sensor malware infection and the host had not been rebooted in order for a detection to have triggered.
  2. Identify plaintext credentials in backup related scheduled tasks, also something that CrowdStrike did not generate an alert on, since it is not malicious per se, but could be abused by attackers.

All in all, this code and the methodology presented could be modified to execute any PowerShell command in a group of hosts, so feel free to experiment with your own commands, whether it is threat hunting, system hardening or whatever else you want to do.

Dimitris Binichakis

Dimitris is a senior cybersecurity consultant at NVISO working as a Cyber Emergency Response Team (CERT) member. In his free time, he enjoys tinkering with code and skateboarding.

RPC or Not, Here We Log: Preventing Exploitation and Abuse with RPC Firewall

8 December 2023 at 08:00

Welcome, readers, to the first installment of our blog series “Preventing Exploitation and Abuse with the RPC Firewall”.
In this post, we’ll delve into how to create rules for the RPC firewall and how to deploy them onto our servers.
In the year 2024, we’ll release the second part of this series, where we’ll explore detection possibilities by analyzing the generated Windows events to further enhance your security posture.

Introduction

Remote Procedure Call (RPC) plays an important role in Windows environments today. RPC is a fundamental mechanism that enables communication between processes, allowing them to request services from one another across a network. In Windows, RPC is utilized extensively for various system functions, such as file and printer sharing, Active Directory authentication, and remote management. However, the widespread use of RPC also makes it an attractive target for attackers.

Why should we care?

By exploiting vulnerabilities in RPC implementations, malicious actors can gain unauthorized access, execute arbitrary code, and compromise the security and integrity of a Windows environment. Thus, it is of paramount importance to implement robust security measures to protect against RPC-based attacks, ensuring the confidentiality, availability, and integrity of critical systems and data.

Shoutout to the Zero Networks research team, who built a tool called “RPC Firewall“, a free and open source tool that allows the prevention and auditing of RPC calls.

What can the RPC Firewall do for me?

Remote RPC Attacks – Detection

When the RPC firewall configuration is configured to audit, RPC events are written to the Windows Event Log and allow for a forward to a central detection and analysis platform.

The created event entries can then be forwarded to the SIEM and used to create baselines of remote RPC traffic for various servers in the environment.

Once an abnormal RPC call is audited it can be used to trigger an alert for your SOC/CSIRT team for analysis.

RPC Firewall log entries can be found inside the Windows Event Viewer path “Application/RPCFW”.
RPC Filter events can be found inside the Windows Security log with the event ID 5712.

Remote RPC Attacks – Prevention

Besides logging, the RPC Firewall can be configured to block potentially malicious RPC calls.

All other RPC calls are not audited to reduce noise, save storage space and keep the performance impact minimal.

Once a potentially malicious RPC call is detected, it is blocked and audited/logged which can then be used to alert your SOC/CSIRT team, while keeping the servers protected.

Components of the RPC Firewall

The RPC Firewall has 3 main components:

The rpcFwManager.exe is the main executable that is being used by the RPC Firewall service in the deployment but is also used to reload the changed configuration.

The file rpcFireWall.dll is injected into the processes in order to allow auditing and filtering of the RPC calls.

The file rpcMessages.dll is a common library file used for logic that is shared between the other components and responsible for creating and writing the events to the Windows Event log.

The file RpcFw.conf is the configuration file containing our defined ruleset that will be used to protect and log legitimate use of RPC endpoints.

Components of the RPC Firewall

Create an RPC Filter/Firewall rule

Although the provided rules have been tested in our lab environment, we highly recommend to test the firewall rules in a test or pre-production environment before deploying them into a production network!

In order to create an RPC firewall rule (Firewall or Filter) we have to complete the following steps:

  • Identify the UUID of the RPC call we want to allow/block/audit
  • Identify the operation number (Opnum) of the method
  • Collect possible whitelisted IPs of endpoints that should be allowed to access the RPC methods
  • Decide if we want to block/allow and/or audit the call

Identify UUID and Operation number of an RPC call

For this example we are going to look at how the script “secretsdump.py” from the impacket toolkit executes the DCSync attack in order to create a rule that prevents it.

If we take a look at the script here:

https://github.com/fortra/impacket/blob/master/impacket/examples/secretsdump.py#L571

We can identify the method used by analyzing the following lines:

[...]
    def _DRSGetNCChanges(self, userEntry, dsName):
        if self.__drsr is None:
            self.__connectDrds()

        LOG.debug('Calling DRSGetNCChanges for %s ' % userEntry)
        request = drsuapi.DRSGetNCChanges()
[...]

Which means that impacket is using the RPC method “IDL_DRSGetNCChanges”, which is documented by Microsoft here:

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/b63730ac-614c-431c-9501-28d6aca91894

According to the Microsoft Documentation, “the IDL_DRSGetNCChanges method replicates updates from an NC replica on the server”.

As the following sidebar shows, it is part of the RPC interface called “drsuapi”.

Sidebar overview of the different RCP methods of the drsuapi interface

In order to create a rule we do need the UUID for the affected interface and we can find that information here:

ParameterValueReference
RPC interface UUID for drsuapi methodse3514235-4b06-11d1-ab04-00c04fc2dcd2Section 4.1.1 – section 4.1.29
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/063618ed-b2e2-4983-ab13-3ed056700641

This gives us the needed UUID for the RPC interface.

You might have noticed the operation number in the screenshot above and that is indeed what we are looking for and thus we do already have the operation number “3” for our rule.

DRSGetNCChanges -> 4.1.10 IDL_DRSGetNCChanges (Opnum 3)

Identifying allowed endpoints

We are now missing the IP addresses for which the DCSync actions should be allowed and/or audited.

In a general environment, DCSync is a part of the Active Directory replication process, and it allows a domain controller to request and pull the latest information about user accounts, security groups, and other objects from another domain controller. This synchronization is crucial for maintaining consistency and ensuring that all domain controllers have up-to-date information.

This also means that we do not want our RPC firewall to block legitimate RPC calls and thus prevent the DCSync and cause problems in our production environment.

For this example, let’s go with the following environment where we do have 2 domain controllers:

  • DC1.ecorp.local (IP: 10.0.31.5)
  • DC2.ecorp.local (IP: 10.0.31.6)
Overview how a DCSync works

This post assumes that, as part of the network configuration, the domain controllers are assigned a static IP address. If your network depends on domain controllers retrieving their IP addresses from a pool, the rule will not automatically update and thus block the DCSync actions sooner or later.

Identifying possible actions

In order to correctly configure the rules, we need to define what our use case of the RPC firewall will be. In this example we want:

  • Domain controllers to be able to synchronize with each other
  • Blocking of all other access to DCSync operations on the RPC endpoint
  • Logging of attempts to DCSync in order to detect malicious use or possible configuration problems

In order to archieve this, we will be using the actions “allow” and “deny” as well as the audit setting “true”.

Checking the current status of the Local Security Authority (LSA) Protection

Because the RPC Firewall protection interacts with the LSASS process on the server it is deployed on, we first have to identify if LSA Protection is enabled in order to decide if our rule set will be based on RPC filters or the RPC firewall rules.

The current status of the LSA Protection can be checked by using the registry or checking for a group policy that was created in order to set these values.
For more information please check the official Microsoft page: https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection#enable-lsa-protection-by-using-group-policy

Check by using the registry

  1. Open the Registry Editor RegEdit.exe, and navigate to the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa.
  2. Check the value of the registry key:
    • “RunAsPPL”=dword:00000000 = Disabled
    • “RunAsPPL”=dword:00000001 = Enabled with a UEFI variable
    • “RunAsPPL”=dword:00000002 = Enabled without a UEFI variable (only enforced on Windows 11 build 22H2 and higher)

Rule creation with LSA Protection enabled (RPC filters)

Because LSA Protection is enabled, the DLL cannot be injected into the LSASS process and thus the RPC firewall does not apply to operations happening inside the application space of the LSASS process. This means we can only rely on RPC filters.

Because of this restriction we will be working only with RPC filters. Which limitations this might have can be accessed here: https://github.com/zeronetworks/rpcfirewall#using-rpc-firewall-or-rpc-filters

For the RPC filters we will be making use of the additional parameters “prot” and “sid” in order to fine tune our rule set as the parameter “opnum” is not available when using the RPC filters.

The parameter “prot” specifies the protocol sequence the rule should match for.

In our example, we will be using “Connection-oriented named pipes” and this means a value of “ncacn_np” for the parameter “prot”.

Constant/valueDescription
ncacn_np Connection-oriented named pipesClient only: MS-DOS, Windows 3.x, Windows 95 Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
https://learn.microsoft.com/en-us/windows/win32/rpc/protocol-sequence-constants

The parameter “sid” is one of the Microsoft built-in security identifiers, which can be be found in an overview under the following URL: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings

In our example we will be using:

SDDL SID stringConstant in Sddl.hAccount alias and corresponding RID
BASDDL_BUILTIN_ADMINISTRATORSBuilt-in administrators. The corresponding RID is DOMAIN_ALIAS_RID_ADMINS.
SYSDDL_LOCAL_SYSTEMLocal system. The corresponding RID is SECURITY_LOCAL_SYSTEM_RID.

As “BA” should not be allowed in order to prevent local administrators from using DCSync on the target and “SY” should be allowed in order to allow the local system or machine account to call the RPC endpoint successfully.

By using the information prepared in the previous steps and our requirements the DCSync can be allowed from both the domain controllers using the following rules:

flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 prot:ncacn_np sid:BA addr:10.0.31.5 action:allow audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 prot:ncacn_np sid:BA addr:10.0.31.6 action:allow audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 sid:SY action:allow audit:true

Because we want to block local administrators from being able to DCSync even with the correct permissions and log any tries to do so, we add the following rule:

flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 sid:BA action:block audit:true

As we cannot make use of the operation number and just block one specific operation, we decided to allow but audit all further requests to the RPC endpoint. This can be reflected in the following rule:

flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 action:allow audit:true

This results in the following final RPC filter rule set for the file “RpcFw.conf”:

flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 prot:ncacn_np sid:BA addr:10.0.31.5 action:allow audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 prot:ncacn_np sid:BA addr:10.0.31.6 action:allow audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 sid:SY action:allow audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 sid:BA action:block audit:true
flt:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 action:allow audit:true

Rule creation with LSA Protection disabled (Firewall)

In order to allow the DCSync from domain controllers and audit the calls we will set the two following rules:

fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 addr:10.0.31.5 opnum:3 action:allow audit:true
fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 addr:10.0.31.6 opnum:3 action:allow audit:true

As our requirement was that we want to block all other access to DCSync operations on the RPC endpoint and we also wanted to log attempts to DCSync in order to detect malicious use or possible configuration problems, we will be adding this additional rule:

fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 opnum:3 action:block audit:true

This results in the final rule set for the configuration file “RpcFw.conf” being:

fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 addr:10.0.31.5 opnum:3 action:allow audit:true
fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 addr:10.0.31.6 opnum:3 action:allow audit:true
fw:uuid:e3514235-4b06-11d1-ab04-00c04fc2dcd2 opnum:3 action:block audit:true

Create the Configuration file “RpcFw.conf” in the same directory as the executable “RpcFwManager.exe” and insert your rules in there.
Whenever the configuration changes, you need to notify the rpcFirewall.dll via the update command: RpcFwManager.exe /update

Deployment

The following YouTube tutorial provided by the developers of the RPC firewall provides some insights in how to install the software:

References

https://github.com/zeronetworks/rpcfirewall

Picture of the author

Steffen Rogge

Steffen is a Cyber Security Consultant at NVISO, where he mostly conducts Purple & Red Team assessments with a special focus on TIBER engagements.

This enables companies to evaluate their existing defenses against emulated Advanced Persistent Threat (APT) campaigns.

Data Connector Health Monitoring on Microsoft Sentinel

6 December 2023 at 08:00

Introduction

Security information and event management (SIEM) tooling allows security teams to collect and analyse logs from a wide variety of sources. In turn this is used to detect and handle incidents. Evidently it is important to ensure that the log ingestion is complete and uninterrupted. Luckily SIEMs offer out-of-the-box solutions and/or capabilities to create custom health monitoring. In this blog post we will take a look at the health monitoring capabilities for log ingestion in Microsoft Sentinel.

Microsoft Sentinel

Microsoft Sentinel is the cloud-native Security information and event management (SIEM) and Security orchestration, automation, and response (SOAR) solution provided by Microsoft. It provides intelligent security analytics and threat intelligence across the enterprise, offering a single solution for alert detection, threat visibility, proactive hunting, and threat response. As a cloud-native solution, it can easily scale to accommodate the growing security needs of an organization and alleviate the cost of maintaining your own infrastructure.

Microsoft Sentinel utilizes Data Connectors to handle log ingestion. Microsoft Sentinel comes with out of the box connectors for Microsoft services, these are the service-to-service connectors. Additionally, there are many built-in connectors for third-party services, which utilize Syslog, Common Event Format (CEF) or REST APIs to connect the data sources to Microsoft Sentinel.

Besides logs from Microsoft services and third-party services, Sentinel can also collect logs from Azure VMs and non-Azure VMs. The log collection is done via the Azure Monitor Agent (AMA) or the Log Analytics Agent (MMA). As a brief aside, it’s important to note that the Log Analytics Agent is on a deprecation path and won’t be supported after August 31, 2024.

The state of the Data Connectors can be monitored with the out-of-the-box solutions or by creating a custom solution.

Microsoft provides two out-of-the-box features to perform health monitoring on the data connectors: The Data connectors health monitoring workbook & SentinelHealth data table.

Using the Data connectors health monitoring workbook

The Data collection health monitoring workbook is an out-of-the-box solution that provides insight regarding the log ingestion status, detection of anomalies and the health status of the Log Analytics agents.

The workbook consists of three tabs: Overview, Data collection anomalies & Agents info.

The Overview tab shows the general status of the log ingestions in the selected workspace. It contains data such as the Events per Second (EPS), data volume and time of the last log received. For the tab to function, the required Subscription and Workspace have to be selected at the top

Data connectors health monitoring workbook - Overview


The Data collection anomalies tab provides info for detecting anomalies in the log ingestion process. Each tab in the view presents a specific table. The General tab is a collection of a multiple tables.

We’re given a few configuration options for the view:

  • AnomaliesTimeRange: Define the total time range for the anomaly detection.
  • SampleInterval: Define the time interval in which data is sampled in the defined time range. Each time sample gets an anomaly score, which is used for the detection.
  • PositiveAlertThreshold: Define the positive anomaly score threshold.
  • NegativeAlertThreshold: Define the negative anomaly score threshold.

The view itself contains the expected amount of events, the actual amount of events & anomaly score per table. When a significant drop or rise in events is detected, a further investigation is advised. The logic behind the view can also be re-used to setup alerting when a certain threshold is exceeded.

Data connectors health monitoring workbook - Data Collection anomalies view

The Agent info tab contains information about the health of the AMA and MMA agents installed on your Azure and non-Azure machines. The view allows you to monitor System location, Heartbeat status and latency, Available memory and disk space & Agent operations. There are two tabs in the view to choose between Azure machines only and all machines.

Data connectors health monitoring workbook - Agent information view

You can find the workbook under Microsoft Sentinel > Workbooks > Templates, then type Data collection health monitoring in the search field. Click View Template to open the workbook. If you plan on using the workbook frequently, hit the Save button so it shows up under My Workbooks.

The SentinelHealth data table

The SentinelHealth data table provides information on the health of your Sentinel resources. The content of the table is not limited to only the data connectors, but also the health of your automation rules, playbooks and analytic rules. Given the scope of this blog post, we will focus solely on the data connector events.

Currently the table has support for following data connectors:

  • Amazon Web Services (CloudTrail and S3)
  • Dynamics 365
  • Office 365
  • Microsoft Defender for Endpoint
  • Threat Intelligence – TAXII
  • Threat Intelligence Platforms

For the data connectors, there are two types of events: Data fetch status change & Data fetch failure summary.

The Data fetch status change events contain the status of the data fetching and additional information. The status is represented by Success or Failure and depending on the status, different additional information is given in the ExtendedProperties field:

  • For a Success, the field will contain the destination of the logs.
  • For a Failure, the field will contain an error message describing the failure. The content of this message depends on the failure type.

These events will be logged once an hour if the status is stable (i.e. status doesn’t change from Success to Failure and vice versa). Once a status change is detected it will be logged immediately.

The Data fetch failure summary events are logged once an hour, per connector, per workspace, with an aggregated failure summary. They are only logged when the connector has experienced polling errors during the given hour. The event itself contains additional information in the ExtendedProperties field, such as all the encountered failures and the time period for which the connector’s source platform was queried.

Using the SentinelHealth data table

Before we can start using the SentinelHealth table, we first have to enable it. Go to Microsoft Sentinel > Settings > Settings tab > Auditing and health monitoring, press Enable to enable the health monitoring.

Once the SentinelHealth table contains data, we can start querying on it. Below you’ll find some example queries to run.

List the latest failure per connector

SentinelHealth
| where TimeGenerated > ago(7d)
| where OperationName == "Data fetch status change"
| where Status == "Failure"
| summarize TimeGenerated = arg_max(TimeGenerated,*) by SentinelResourceName, SentinelResourceId

Connector status change from Failure to Success

let success_status = SentinelHealth
| where TimeGenerated > ago(1d)
| where OperationName == "Data fetch status change"
| where Status == "Success"
| summarize TimeGenerated = arg_max(TimeGenerated,*) by SentinelResourceName, SentinelResourceId;
let failure_status = SentinelHealth
| where TimeGenerated > ago(1d)
| where OperationName == "Data fetch status change"
| where Status == "Failure"
| summarize TimeGenerated = arg_max(TimeGenerated,*) by SentinelResourceName, SentinelResourceId;
success_status
| join kind=inner (failure_status) on SentinelResourceName, SentinelResourceId
| where TimeGenerated > TimeGenerated1

Connector status change from Success to Failure

let success_status = SentinelHealth
| where TimeGenerated > ago(1d)
| where OperationName == "Data fetch status change"
| where Status == "Success"
| summarize TimeGenerated = arg_max(TimeGenerated,*) by SentinelResourceName, SentinelResourceId;
let failure_status = SentinelHealth
| where TimeGenerated > ago(1d)
| where OperationName == "Data fetch status change"
| where Status == "Failure"
| summarize TimeGenerated = arg_max(TimeGenerated,*) by SentinelResourceName, SentinelResourceId;
success_status
| join kind=inner (failure_status) on SentinelResourceName, SentinelResourceId
| where TimeGenerated > TimeGenerated1

Custom Solutions

With the help of built-in Azure features and KQL queries, there is the possibility to create custom solutions. The idea is to create a KQL query and then have it executed by an Azure feature, such as Azure Monitor, Azure Logic Apps or as a Sentinel Analytics Rule. Below you’ll find two examples of custom solutions.

Log Analytics Alert

For the first example, we’ll setup an alert in the Log Analytics workspace where Sentinel is running on. The alert logic will run on a recurring basis and alert the necessary people when it is triggered. For starters, we’ll go the the Log Analytics Workspace and and start the creation of a new alert.

Select Custom log search for the signal and we’ll use the Connector status change from Success to Failure query example as logic.

Log Analytics Alert - Query

Set both the aggregation and evaluation period to 1hr, so it doesn’t incur a high monthly cost. Next, attach an email Action Group to the alert, so the necessary people are informed of the failure.

Log Analytics Alert - Notification Group

Lastly, give the alert a severity level, name and description to finish off.

Log Analytics Alert - Overview

Logic App Teams Notification

For the second example, we’ll create a Logic App that will send an overview via Teams of all the tables with an anomalous score.

For starters, we’ll create a logic app and create a Workflow inside the logic app.

Logic App for Teams Notification - Overview

Inside the Workflow, we’ll design the logic for the Teams Notification. We’ll start off with a Recurrence trigger. Define an interval on which you’d like to receive notifications. In the example, an interval of two days was chosen.

Recurrence settings for Logic App

Next, we’ll add the Run query and visualize results action. In this action, we have to define the Subscription, Resource Group, Resource Type, Resource Name, Query, Time Range and Chart Type. Define the first parameters to select your Log Analytics Workspace and then use following query. The query is based on the logic from the Data Connector Workbook. The query looks back on the data of the past two weeks with an interval of one day per data sample. If needed, the time period and interval can be increased or decreased. The UpperThreshold and LowerThreshold parameter can be adapted to make the detection more or less sensitive.

let UpperThreshold = 5.0; // Upper Anomaly threshold score
let LowerThreshold = -5.0; // Lower anomaly threshold score
let TableIgnoreList = dynamic(['SecurityAlert', 'BehaviorAnalytics', 'SecurityBaseline', 'ProtectionStatus']); // select tables you want to EXCLUDE from the results
union withsource=TableName1 *
| make-series count() on TimeGenerated from ago(14d) to now() step 1d by TableName1
| extend (anomalies, score, baseline) = series_decompose_anomalies(count_, 1.5, 7, 'linefit', 1, 'ctukey', 0.01)
| where anomalies[-1] == 1 or anomalies[-1] == -1
| extend Score = score[-1]
| where Score >= UpperThreshold or Score <= LowerThreshold
| where TableName1 !in (TableIgnoreList)
| project TableName=TableName1, ExpectedCount=round(todouble(baseline[-1]),1), ActualCount=round(todouble(count_[-1]),1), AnomalyScore = round(todouble(score[-1]),1)

Lastly, define the Time Range and Chart Type parameter. For Time Range pick Set in query and for Chart Type pick Html Table.

Now that the execution of the query is defined, we can define the sending of a Teams message. Select the Post message in a chat or channel action and configure the action to send the body of the query to a channel/person as Flowbot.

Teams Notification settings in Logic App

Once the Teams action is defined, the logic app is completed. When the logic app runs, you should expect an output similar to the image below. The parameters in the table can be analysed to detect Data Connector issues.

Example Teams Notification

Conclusion

In conclusion, as stated in the intro, monitoring the health of data connectors is a critical part of ensuring an uninterrupted log ingestion process into the SIEM. Microsoft Sentinel offers great capabilities for monitoring the health of data connectors, thus enabling security teams to ensure the smooth functioning of log ingestion processes and promptly address any issues that may arise. The combination of the two out-of-the-box solutions and the flexibility to create custom monitoring solutions, makes Microsoft Sentinel a comprehensive and adaptable choice for managing and monitoring security events.

Frederik Meutermans Headshot

Frederik Meutermans

Frederik is a Senior Security Consultant in the Cloud Security Team. He specializes in the Microsoft Azure cloud stack, with a special focus on cloud security monitoring. He mainly has experience as security analyst and security monitoring engineer.

You can find Frederik on LinkedIn.

AI in Cybersecurity: Bridging the Gap Between Imagination and Reality

8 November 2023 at 08:00

Introduction

In today’s digital environment, we encounter a mix of evolving cyber systems and the complexities they introduce. One notable influence in this space is artificial intelligence (AI), alongside associated technologies such as machine learning, which offer promising avenues for reshaping cyber strategies.

Traditionally, cybersecurity has operated with definitive parameters, set boundaries, and post-event counteractions. Yet, given the growth in digital data and the evolving nature of threats, there’s a clear shift towards strategies that are not only responsive but also proactive. AI and machine learning serve this purpose, providing defenses that are not only immediate but also predictive.

It’s important to clarify that the discussion isn’t solely about AI as a standalone term. Within this broader term are technologies like machine learning, neural networks, and deep learning, each having its significance in cybersecurity. However, given the extensive scope, our focus will be on select areas, shedding light on how these technologies function and their practical implications in cybersecurity.

The goal here is to explore the roles of AI and machine learning without presenting them as the singular answer but rather to understand their potential and limitations within cybersecurity.

For this purpose, a well-known cybersecurity framework proposed by NIST was used to understand the solution categories needed to protect, detect, react and defend against cyberattacks

R. Kaur et al., Information Fusion 97 (2023) 101804
Pillars of NIST framework
The pillars of the NIST framework

The subsequent section provides a graphical representation that outlines specific areas in which AI and machine learning are applied in cybersecurity. These areas span from ‘Automated Security Control Validation’ to ‘Decision Support for Risk Planning’, showcasing the various types of technology that can be categorized under each domain. This visualization serves as a reference to understand the scope and classification of technologies discussed in the article.

Overview of potential use cases for AI
Created after:
Ramanpreet Kaur, Dušan Gabrijelčič, Tomaž Klobučar,
Artificial intelligence for cybersecurity: Literature review and future research directions, Information Fusion, Volume 97, 2023
https://doi.org/10.1016/j.inffus.2023.101804.

In the domain of cybersecurity, while much attention has been given to Protection and Detection, there are other areas that demand attention and hold significant promise (in our case that would be Identify, Respond, Recover), especially with the advent of AI technologies. Many of these areas have been sidelined in popular discussions but are now emerging as pivotal components in the evolving cybersecurity landscape. Among these areas, we have identified several noteworthy topics:

  • Automated Security Control Validation: This refers to the automated processes used to confirm that security measures are operating correctly within a given system.
  • Automated Risk Analysis and Impact Assessment: A process that uses automation to evaluate potential threats and the possible consequences they could have on an organization.
  • Decision Support for Risk Planning: Using technology to aid decision-makers in strategizing and planning for potential risks.
  • Automated Responsibility Allocation: A method that uses automation to assign roles and responsibilities within a system, ensuring that tasks are designated to the right entity or personnel.
  • Information Sharing Property Platform: A platform designed to facilitate the sharing of information across different entities in a secure manner.

The foundation of this stems from the previously cited publication, a meta study

resulted in 2395 studies, of which 236 were identified as primary. This article classifies the identified AI use cases based on a NIST cybersecurity framework using a thematic analysis approach.

https://doi.org/10.1016/j.inffus.2023.101804.

Why these topics?

In the current digital environment, businesses are required to anticipate and mitigate threats and vulnerabilities. The cyber landscape is continually changing. What was once considered secure can now be a weak point, underscoring the relevance of tools like Automated Security Control Validation and Risk Analysis. These tools enable companies to promptly detect and rectify vulnerabilities, staying abreast of the sophisticated techniques used by adversaries.

Business continuity is also crucial. Unresolved security issues can disrupt operations, affecting revenue and brand perception. Decision Support for Risk Planning offers a systematic approach for risk assessment and management, ensuring smooth operations amidst emerging threats.

Regulatory compliance adds another layer of complexity in many industries. Meeting these regulations often means implementing stringent security protocols. With updated practices such as automated responsibility allocation, companies can ensure adherence to legal requirements, minimizing legal repercussions.

As organizations grow, managing security across expansive infrastructures manually becomes complex. Automated solutions offer scalability in security, allowing businesses to expand without a corresponding rise in resources.

Information today represents a strategic asset. Platforms like an Information Sharing Property Platform could be essential, promoting a collective approach to cybersecurity, letting businesses share insights and strengthen overall security.

Building trust with customers, partners, and stakeholders is fundamental. Ensuring data security and safe interactions enhances this trust, providing a competitive advantage.

To sum up, in today’s digital world, for businesses to succeed, it’s imperative to embrace and stay updated with advanced security practices.

Evaluation of the studies under consideration

Below, we will examine each domain in detail, aiming to identify the pros and cons of employing AI and machine learning techniques, as derived from our analysis of the meta-study within the cybersecurity context.

Automated Security Control Validation

AdvantagesDisadvantages
Automated checks can be performed much faster than manual validations.Might occasionally flag legitimate configurations as violations.
Perform checks uniformly without human error.Might not always adapt swiftly to rapidly evolving threat landscapes or new security protocols.
Handle large infrastructures without additional resources.Over-dependence on AI might lead to negligence in manual checks.
Real-time validation, ensuring immediate detection of security misconfigurations.May not capture subtle nuances or instinctual insights that experienced human professionals might recognize in complex security scenarios.
Overview of advantages and disadvantages of the integration of AI in Automated Security Control Validation

Automated Risk Analysis and Impact Assessment

AdvantagesDisadvantages
Process vast amounts of data rapidly, providing insights quicker.Effective AI integration requires understanding and tuning of the models.
Predict potential future threats based on patterns.Excessive trust in AI’s recommendations might overshadow human judgment.
Algorithms can correlate data from various sources for a more comprehensive risk profile.Effectiveness is dependent on the quality of the data fed to it.
Overview of advantages and disadvantages of the integration of AI in Automated Risk Analysis and Impact Assessment

Decision Support for Risk Planning

AdvantagesDisadvantages
Simulation of various risk scenarios, aiding in decision-making.If historical data has biases, AI recommendations might inherit them.
Provide real-time updates based on changing data.It might be challenging to effectively implement AI in decision-making processes.
Overview of advantages and disadvantages of the integration of AI in Decision Support for Risk Planning

Automated Responsibility Allocation

AdvantagesDisadvantages
Immediate allocation of responsibilities during incidents.AI might not understand the nuances of every situation.
Reduce decision-making time during crises.Requires continuous updates to responsibility matrices and rules.
Decisions are based on data, not emotions or internal politics.Sole reliance on AI might lead to issues if the system fails during a critical incident.
Overview of advantages and disadvantages of the integration of AI in Automated Responsibility Allocation

Information Sharing Property Platform

AdvantagesDisadvantages
Shift through vast amounts of shared information to find relevant insights.AI processing shared information might raise privacy issues.
Spot emerging threats or vulnerabilities from shared data.Might misinterpret or take out of context certain shared information.
Generate summaries or insights from the shared information.If not properly secured, AI systems could be a target for malicious actors trying to manipulate shared data.
Overview of advantages and disadvantages of the integration of AI in Information Sharing Property Platform

When examining the pros and cons presented, are there recurring themes that allow us to draw overarching conclusions? INDEED, there are!

We can identify four distinct categories:

  1. Speed and Data
  2. Understanding and Decision Making
  3. Adaptability and Complexity
  4. Security and Information

But what does each category signify, and how do they influence the broader scope of AI and machine learning in cybersecurity?

Conclusion

In the context of AI and machine learning within cybersecurity, several themes are evident:

Regarding Speed and Data, AI facilitates rapid and uniform data processing. However, it’s important to note that the quality of outcomes is contingent on the integrity of the data input, and there exists a risk of over-reliance on this speed.

In the area of Understanding and Decision Making, AI offers expansive views, promoting more objective decision-making processes. Yet, AI systems might not always replicate the nuanced understanding characteristic of human cognition.

Within Adaptability and Complexity, AI’s strength lies in its scalability and its ability to model diverse scenarios. Nevertheless, these systems can face challenges adapting to rapid technological changes, and their deployment can be intricate.

For Security and Information, AI’s capability for real-time monitoring allows for immediate threat detection. However, the speed of processing might increase the probability of misinterpretations, potentially introducing security vulnerabilities.

In addition to these themes, the Human Factor remains pivotal. AI systems, despite their sophistication, cannot replace human judgment, especially in ambiguous situations that require intuitive reasoning. The human element brings a unique combination of experience, intuition, and ethical consideration, aspects that AI currently cannot replicate fully. Hence, while automating processes can enhance efficiency, the human oversight ensures that decisions align with organizational values and the broader context. Balancing the capabilities of AI with human expertise optimizes the cybersecurity framework, ensuring robustness and adaptability.

Overall, while AI and machine learning bring substantial advantages to cybersecurity, it’s crucial to consider their inherent limitations and dependencies.

Maurice Striek

Maurice Striek

Maurice Striek is a Consultant in the Cyber Security & Architecture Team (CSA) at NVISO. His expertise lies in risk analysis based on IT-Grundschutz and ISO 27001/2 standards, as well as data analysis and data management.

Generating IDA Type Information Libraries from Windows Type Libraries

7 November 2023 at 08:00

When working with IDA, a commonly leveraged feature are type information libraries (TIL). These libraries contain high-level type information such as function prototypes, type definitions, standard structures or enums; enabling IDA to convert statements such as movsxd rbx, dword ptr [r12+3Ch] into, for example, the more human-readable counterpart movsxd rbx, [r12+IMAGE_DOS_HEADER.e_lfanew].

On Windows, a similar concept called type libraries (TLB) exists to describe COM (Component Object Model) objects. In a nutshell, COM provides a language-independent interface to objects, abstracting how they have been implemented themselves.

In this quick-post, we’ll explore how to convert Windows type libraries (TLB) into IDA type information libraries (TIL). In particular, we’ll generate the necessary type information library to analyze .NET injection into unmanaged processes using mscoree.dll and mscorlib.dll (e.g., through _AppDomain). In a hurry? Grab the .NET type information library directly!

Abstract

Achieving TLB-to-TIL conversion can be done through an intermediary C++ conversion:

  1. First, the MSVC (Microsoft Visual C++) compiler can be leveraged to convert TLBs into their respective C++ header files.
  2. Once the C++ header files generated, IDAClang can be used to convert these into TILs.
A schema of MSVC converting TLBs into C++ as well as IDAClang converting C++ into TILs.
Figure 1: A schema of MSVC converting TLBs into C++ as well as IDAClang converting C++ into TILs.

Requirements

To achieve TLB-to-TIL conversion, this article requires the following tools:

  1. The MSVC (Microsoft Visual C++) compiler installed through Visual Studio*.
  2. The IDAClang command-line utility.

* For our example, we’ll be generating a type information library targeting the .NET Framework, hence also requiring header files part of the .NET Framework Developer Pack (a.k.a. SDK). The .NET Framework Developer Pack can be installed through Visual Studio.

Given both MSVC and IDAClang rely on a properly configured developer environment (e.g., a configured INCLUDE environment variable), this article assumes all commands are issued within a Visual Studio Developer Command Prompt such as the “x64 Native Tools Command Prompt”.

A capture of the “x64 Native Tools Command Prompt” within the Windows Start menu.
Figure 2: A capture of the “x64 Native Tools Command Prompt” within the Windows Start menu.

Converting Microsoft Type Libraries to C++ Headers

Windows type libraries are a Windows-specific feature which integrates seamlessly with the MSVC compiler through the #import statement.

#import creates two header files that reconstruct the type library contents in C++ source code. The primary header file is similar to the one produced by the Microsoft Interface Definition Language (MIDL) compiler, but with additional compiler-generated code and data. The primary header file has the same base name as the type library, plus a .TLH extension.

Source: learn.microsoft.com

As such, creating a C++ file to import a type library will generate its C++ header. Given we wish to convert mscorlib.tlb into C++ headers, we can create the following C++ file named, for example, til.cpp.

#import "mscorlib.tlb" raw_interfaces_only auto_rename

Once the C++ file has been created, we can rely on the MSVC compiler to generate the necessary headers. The beneath command will generate the mscorlib.tlh file.

CL.exe /c /D NDEBUG /D _CONSOLE /D _UNICODE /D UNICODE /permissive- /TP til.cpp

Converting C++ Headers to IDA Type Information Libraries

With the C++ headers generated, we can now proceed to create the IDA type information library. To do so, we can create a new C++ header file that will reference any standard headers (e.g., those from the .NET Framework Developer Pack) and generated headers (i.e., the previously generated mscorlib.tlh) we wish to use in IDA. The following is our example til.h.

// Include standard headers
// Example: Microsoft .NET Framework Developer Pack
#include <alink.h>
#include <clrdata.h>
#include <cordebug.h>
#include <corhlpr.h>
#include <corprof.h>
#include <corpub.h>
#include <corsym.h>
#include <fusion.h>
#include <gchost.h>
#include <ICeeFileGen.h>
#include <isolation.h>
#include <ivalidator.h>
#include <ivehandler.h>
#include <metahost.h>
#include <mscoree.h>
#include <openum.h>
#include <StrongName.h>
#include <tlbref.h>
#include <VerError.h>

// Include generated headers
// Example: Microsoft Common Language Runtime Class Library
//          The mscorlib.h generated from mscorlib.tlb
#include "mscorlib.tlh"

Once the C++ header file created, we can rely on IDAClang to generate the TIL.

idaclang.exe -x c++ -target x86_64-pc-windows -ferror-limit=0 --idaclang-tildesc "Example" --idaclang-tilname "example.til" til.h

MSVC and Clang (used in IDAClang) are two different C++ compilers. While they can mostly agree, compiling MSVC-generated C++ code using Clang is bound to generate non-fatal errors. While IDAClang may generate quite some errors as shown below, the TIL conversion should succeed after a moment.

IDACLANG: nonfatal: ./mscorlib.tlh:10774:1: error: enumeration previously declared with fixed underlying type
IDACLANG: nonfatal: ./mscorlib.tlh:11509:64: error: expected ';' after struct
IDACLANG: nonfatal: ./mscorlib.tlh:11509:1: error: declaration of anonymous struct must be a definition
IDACLANG: nonfatal: ./mscorlib.tlh:11510:11: error: expected unqualified-id

As an example, we published our .NET type information library mscoru.til.

Using the IDA Type Information Library

Once the TIL generated, we can proceed to make it available to IDA. To do so, copy the TIL to the appropriate folder which, in our example, could be the C:\Program Files\IDA Pro 8.3\til\pc directory. Once the TIL staged, IDA should display the new type information library and allow it to be loaded.

A capture of IDA’s Available Type Libraries.
Figure 3: A capture of IDA’s Available Type Libraries.

With the TIL loaded, we can now instruct IDA to import the new structures such as ICLRMetaHost and its ICLRMetaHost_vtbl virtual function table.

A capture of IDA’s ICLRMetaHost structure.
Figure 4: A capture of IDA’s ICLRMetaHost structure.
A capture of IDA’s ICLRMetaHost_vtbl structure.
Figure 5: A capture of IDA’s ICLRMetaHost_vtbl structure.

Once our structures imported, we can leverage IDA’s structure offsets to make raw offsets human-readable as observed in the following slider (left before, right after).

Figure 6: A capture of IDA’s ICLRMetaHost.GetRuntime call.

In our .NET injection analysis, this enables us to identify where the raw .NET assembly is loaded as observed in the beneath slider (left before, right after). Such information in turn allows us to identify from where it originates and where we could best intercept it for further analysis.

Figure 7: A capture of IDA’s _AppDomain.Load_3 call.

Conclusions & Lessons Learned

While tools such as OLEViewer alongside MIDL could in theory generate C++ code as well, we found these to be unreliable. Instead, working with MSVC and IDAClang provides a quick (and clean) approach to convert TLBs into TILs.

The above described process can be extended to other abused COM objects such as the Windows Script Host Object Model (with TLB %SystemRoot%\System32\wshom.ocx) or the Microsoft Management Console (with TLB %SystemRoot%\System32\mmc.exe).

By creating IDA type information libraries matching libraries used by adversaries we gain the capability to properly understand their tooling, how to analyze further stages and how to best defend against them.

References

  1. https://learn.microsoft.com/en-us/cpp/preprocessor/hash-import-directive-cpp
  2. https://hex-rays.com/tutorials/idaclang/
  3. https://thewover.github.io/Introducing-Donut/#disposable-appdomains
Maxime THIEBAUT

Maxime Thiebaut

Maxime Thiebaut is a GCFA-certified Incident Response & Digital Forensics Analyst within NVISO CSIRT. He spends most of his time performing defensive research and responding to incidents. Previously, Maxime worked on the SANS SEC699 course. Besides his coding capabilities, Maxime enjoys reverse engineering samples observed in the wild.

Introducing CS2BR pt. III – Knees deep in Binary

26 October 2023 at 11:00

Introduction

Over the span of the previous two blog posts in the series, I showed why the majority of Cobalt Strike (CS) BOFs are incompatible with Brute Ratel C4 (BRC4) and what you can do about it. I also presented CS2BR itself: it’s a tool that makes patching BOFs to be compatible with BRC4 a breeze. However, we also found some limitations to CS2BR’s current approach.

In this (final?) post in this series, we’ll take a look at one of CS2BR’s shortcomings: its reliance on source-code for patching. We’ll see how this can be resolved and – spoiler alert – why we couldn’t (yet!) but decided to pull the plug on it. That’s right: this blog post won’t present a fancy new solution but the challenges you’ll encounter when you go down this rabbit hole.

True story: I thought it would be an easier ride.

This post will get a bit more technical than its predecessors. Don’t worry though, I’ll try my best not to get lost in itty-bitty details. So feel free to grab a coffee and prepare for a journey into the wonderful world of object files, how you can mess with them, and what I did to them.

I. Underlying motivation

When I finished work on CS2BR’s source code patching, I realized that there were two major issues with it that caused me headaches and that I wasn’t happy with:

  1. Input arguments: Supplying BOFs with input arguments in BRC4 isn’t straightforward and requires you to figure out the number and format of arguments, feed them into a standalone Python script, and pass the output into BRC4.
  2. Source code: In order to make BOFs compatible with BRC4 in the first place, CS2BR patches a compatibility layer (and some extras) into a BOF’s source code. You’ll then need to recompile the BOF in order to use it in BRC4.

While the first issue is just somewhat awkward, the second one can be a real showstopper in some cases:

  • Third party BOFs: There are proprietary, commercial BOFs out there that you might like to use in BRC4, but can’t because they’re incompatible. Since you usually don’t have access to their source code, you can’t use CS2BR to patch them.
  • Compilation: Usually BOFs come with limited features and thus don’t require crazy compilation environments. Well, if they do, CS2BR’s source code patching can interfere with that and potentially screw up your compilation configuration. You’d then need to get into the depths of makefiles, build scripts and Visual Studio project configurations to troubleshoot.

So wouldn’t it be great if we didn’t need access to source code? And wouldn’t it be cool to avoid recompilation of BOFs? There surely has to be a way to do this, right?

II. The idea

Since BOFs are object files (hence the name, beacon object files) and CS2BR’s compatibility layer can be compiled into an object file, we might just be able to merge both of them into a single object file.

Imagine it was that easy.

And indeed, it appears that you can merge object files using ld, the GNU linker:

ld --relocatable cs2br.o bof.o -o brc4bof.o

That’s the basic premise. Before we continue with the details of this idea, let’s have a brief look at the “Common Object File Format” (COFF) that our object files come in.

About COFF

At their core, object files are an intermediate format of executables: they contain compiled code and data but aren’t directly executable. Here’s the source code of a simple BRC4 BOF that prints its input arguments:

#include "badger_exports.h"
void coffee(char** argv, int argc, WCHAR** dispatch) {
    int i = 0;
    for (; i < argc; i++) {
        BadgerDispatch(dispatch, "Arg #%i: \"%s\"\n", (i+1), argv[i]);
    }
}

This can be compiled into a COFF file using a C compiler such as mingw on a Linux machine:

$ x86_64-w64-mingw32-gcc -o minimal.o -c minimal.c

$ file minimal.o        
minimal.o: Intel amd64 COFF object file, no line number info, not stripped, 7 sections, symbol offset=0x216, 19 symbols, 1st section name ".text"

We can get detailed information about the compiled object file using the nd or objdump utilities:

objdump output
$ objdump -x minimal.o              

minimal.o:     file format pe-x86-64
minimal.o
architecture: i386:x86-64, flags 0x00000039:
HAS_RELOC, HAS_DEBUG, HAS_SYMS, HAS_LOCALS
start address 0x0000000000000000

Characteristics 0x4
        line numbers stripped

Time/Date               Wed Dec 31 19:00:00 1969
Magic                   0000
MajorLinkerVersion      0
MinorLinkerVersion      0
SizeOfCode              0000000000000000
SizeOfInitializedData   0000000000000000
SizeOfUninitializedData 0000000000000000
AddressOfEntryPoint     0000000000000000
BaseOfCode              0000000000000000
ImageBase               0000000000000000
SectionAlignment        00000000
FileAlignment           00000000
MajorOSystemVersion     0
MinorOSystemVersion     0
MajorImageVersion       0
MinorImageVersion       0
MajorSubsystemVersion   0
MinorSubsystemVersion   0
Win32Version            00000000
SizeOfImage             00000000
SizeOfHeaders           00000000
CheckSum                00000000
Subsystem               00000000        (unspecified)
DllCharacteristics      00000000
SizeOfStackReserve      0000000000000000
SizeOfStackCommit       0000000000000000
SizeOfHeapReserve       0000000000000000
SizeOfHeapCommit        0000000000000000
LoaderFlags             00000000
NumberOfRvaAndSizes     00000000

The Data Directory
Entry 0 0000000000000000 00000000 Export Directory [.edata (or where ever we found it)]
Entry 1 0000000000000000 00000000 Import Directory [parts of .idata]
Entry 2 0000000000000000 00000000 Resource Directory [.rsrc]
Entry 3 0000000000000000 00000000 Exception Directory [.pdata]
Entry 4 0000000000000000 00000000 Security Directory
Entry 5 0000000000000000 00000000 Base Relocation Directory [.reloc]
Entry 6 0000000000000000 00000000 Debug Directory
Entry 7 0000000000000000 00000000 Description Directory
Entry 8 0000000000000000 00000000 Special Directory
Entry 9 0000000000000000 00000000 Thread Storage Directory [.tls]
Entry a 0000000000000000 00000000 Load Configuration Directory
Entry b 0000000000000000 00000000 Bound Import Directory
Entry c 0000000000000000 00000000 Import Address Table Directory
Entry d 0000000000000000 00000000 Delay Import Directory
Entry e 0000000000000000 00000000 CLR Runtime Header
Entry f 0000000000000000 00000000 Reserved

The Function Table (interpreted .pdata section contents)
vma:                    BeginAddress     EndAddress       UnwindData
 0000000000000000:      0000000000000000 000000000000006a 0000000000000000

Dump of .xdata
 0000000000000000 (rva: 00000000): 0000000000000000 - 000000000000006a
        Version: 1, Flags: none
        Nbr codes: 3, Prologue size: 0x08, Frame offset: 0x0, Frame reg: rbp
          pc+0x08: alloc small area: rsp = rsp - 0x30
          pc+0x04: FPReg: rbp = rsp + 0x0 (info = 0x0)
          pc+0x01: push rbp

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000070  0000000000000000  0000000000000000  0000012c  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC
  3 .rdata        00000010  0000000000000000  0000000000000000  0000019c  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .xdata        0000000c  0000000000000000  0000000000000000  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .pdata        0000000c  0000000000000000  0000000000000000  000001b8  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  6 .rdata$zzz    00000020  0000000000000000  0000000000000000  000001c4  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
SYMBOL TABLE:
[  0](sec -2)(fl 0x00)(ty    0)(scl 103) (nx 1) 0x0000000000000000 minimal.c
File 
[  2](sec  1)(fl 0x00)(ty   20)(scl   2) (nx 1) 0x0000000000000000 coffee
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[  4](sec  1)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .text
AUX scnlen 0x6a nreloc 2 nlnno 0
[  6](sec  2)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[  8](sec  3)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 10](sec  4)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .rdata
AUX scnlen 0xf nreloc 0 nlnno 0
[ 12](sec  5)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .xdata
AUX scnlen 0xc nreloc 0 nlnno 0
[ 14](sec  6)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .pdata
AUX scnlen 0xc nreloc 3 nlnno 0
[ 16](sec  7)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .rdata$zzz
AUX scnlen 0x17 nreloc 0 nlnno 0
[ 18](sec  0)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000000 __imp_BadgerDispatch


RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000046 IMAGE_REL_AMD64_REL32  .rdata
0000000000000050 IMAGE_REL_AMD64_REL32  __imp_BadgerDispatch


RELOCATION RECORDS FOR [.pdata]:
OFFSET           TYPE              VALUE
0000000000000000 IMAGE_REL_AMD64_ADDR32NB  .text
0000000000000004 IMAGE_REL_AMD64_ADDR32NB  .text
0000000000000008 IMAGE_REL_AMD64_ADDR32NB  .xdata

This gave us quite a lot of information of which I’d like to highlight and unpack three particularly important bits:

  1. Sections: These are regions of arbitrary, binary data. Their content is indicated by a section’s name and flags. For example, the .text section with the CODE flag set will usually contain compiled code whereas the .rdata section with the READONLY and DATA flags will contain readonly data (such as strings used in the application).
  2. Symbols: Symbols are used to reference various things in object files, such as sections (e.g. .text), functions (e.g. coffee) and imports (e.g. __imp_BadgerDispatch).
  3. Relocations: An object file’s sections can contain references to symbols (and thus to other sections, functions, and imports). When the file is compiled or loaded into memory by a COFF loader such as BRC4, these references need to be resolved to actual relative or absolute memory addresses.
    For example, the above BadgerDispatch call references the string Arg #%i: \"%s\"\n which is located in the .rdata section. The first relocation entry in the .text section indicates that at offset 0x46 into the .text section, there is a reference to the .rdata symbol (which points to the .rdata section), which needs to be resolved as a relative address.
COFF section contents dumped using objdump
$ objdump -s -j .rdata minimal.o

minimal.o:     file format pe-x86-64

Contents of section .rdata:
 0000 41726720 2325693a 20222573 220a0000  Arg #%i: "%s"...
                                                                                                                                            
$ objdump -S -j .text minimal.o 

minimal.o:     file format pe-x86-64


Disassembly of section .text:

0000000000000000 <coffee>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 30             sub    $0x30,%rsp
   8:   48 89 4d 10             mov    %rcx,0x10(%rbp)
   c:   89 55 18                mov    %edx,0x18(%rbp)
   f:   4c 89 45 20             mov    %r8,0x20(%rbp)
  13:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
  1a:   eb 3e                   jmp    5a <coffee+0x5a>
  1c:   8b 45 fc                mov    -0x4(%rbp),%eax
  1f:   48 98                   cltq
  21:   48 8d 14 c5 00 00 00    lea    0x0(,%rax,8),%rdx
  28:   00 
  29:   48 8b 45 10             mov    0x10(%rbp),%rax
  2d:   48 01 d0                add    %rdx,%rax
  30:   48 8b 10                mov    (%rax),%rdx
  33:   8b 45 fc                mov    -0x4(%rbp),%eax
  36:   8d 48 01                lea    0x1(%rax),%ecx
  39:   48 8b 45 20             mov    0x20(%rbp),%rax
  3d:   49 89 d1                mov    %rdx,%r9
  40:   41 89 c8                mov    %ecx,%r8d
  43:   48 8d 15 00 00 00 00    lea    0x0(%rip),%rdx        # 4a <coffee+0x4a>
  4a:   48 89 c1                mov    %rax,%rcx
  4d:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # 54 <coffee+0x54>
  54:   ff d0                   call   *%rax
  56:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  5a:   8b 45 fc                mov    -0x4(%rbp),%eax
  5d:   3b 45 18                cmp    0x18(%rbp),%eax
  60:   7c ba                   jl     1c <coffee+0x1c>
  62:   90                      nop
  63:   90                      nop
  64:   48 83 c4 30             add    $0x30,%rsp
  68:   5d                      pop    %rbp
  69:   c3                      ret
  6a:   90                      nop
  6b:   90                      nop
  6c:   90                      nop
  6d:   90                      nop
  6e:   90                      nop
  6f:   90                      nop

These are the central parts of COFF files that are relevant to this blog post.

Merging object files

Continuing with the idea of merging object files, it turns out that it’s not just going to be a simple ld. Let’s compare a regular BOF in Cobalt Strike to a CS2BR BOF in BRC4:

Regular CS BOF

Pictured above is a regular CS BOF: It resides in a beacon, is executed via its go entrypoint and can make use of serveral CS BOF APIs. In order to execute the BOF, the beacon acts as a linker: it maps the BOF’s sections into memory, resolves CS BOF API imports to the beacon’s internal implementations and resolves relocations. That’s the regular flow of things.

CS2BR BOF

Here’s how the general CS2BR approach works: it provides the CS BOF APIs as part of its compatibility layer. This layer in turn uses the BRC4 BOF APIs which are implemented in the BRC4 badger. From our perspective, a badger loads & executes a BOF similar to how CS does.

When we patch a BOF’s source code via CS2BR and compile it afterwards, the coffee entrypoint will be included in the BOF and able to invoke the original go entrypoint (*). Additionally, calls to the CS BOF API will be “rerouted” to CS2BR’s compatibility layer (*). When both BOF and the CS2BR compatibility layer are compiled separately though, we need to ensure that those two connections are made when we merge the object files. For simplicity’s sake, let’s refer to the compiled CS BOF as bof.o and to the compiled CS2BR compatibility layer as cs2br.o:

  • Entrypoint: The coffee entrypoint in cs2br.o needs to reference the go entrypoint in bof.o. When the files are merged, this reference must be resolved.
  • APIs: The CS BOF APIs imported in bof.o must be “re-wired” so they don’t reference imports but cs2br.o‘s implementations instead.

Well, this doesn’t sound super complicated, does it?

III. Execution

Now it’s only a matter of putting everything together. We’ll start with the entrypoint:

Preparing the entrypoint

In order to reference bof.o‘s go entrypoint from cs2br.o, we can leverage the fact that such operations are precisely what object files and linkers are great at accomplishing: by defining go as an external symbol in cs2br.o, a linker will resolve it when also supplying it with bof.o which provides this exact symbol. So here’s the single line we add to CS2BR’s badger_stub.c that contains our custom coffee entrypoint:

extern void go(void *, int);

Now, when we compile CS2BR’s entrypoint in badger_stub.c and its compatibility layer beacon_wrapper.h, we observe the resulting cs2br.o‘s symbols. Also, for comparison, let’s also inspect bof.o‘s symbols:

$ objdump -x cs2br.o | grep go  
[ 52](sec  0)(fl 0x00)(ty   20)(scl   2) (nx 0) 0x0000000000000000 go

$ objdump -x bof.o | grep go
[  2](sec  1)(fl 0x00)(ty   20)(scl   2) (nx 1) 0x0000000000000000 go

We can use Microsoft’s documentation on the PE format (which also covers COFF) to better understand what those entries mean:

  • sec“The signed integer that identifies the section, using a one-based index into the section table. Some values have special meaning […].”
    • Value 0 (IMAGE_SYM_UNDEFINED): “[…] A value of zero indicates that a reference to an external symbol is defined elsewhere. […]”
  • ty“A number that represents type. Microsoft tools set this field to 0x20 (function) or 0x0 (not a function). […]”
  • The value (hex value before the symbol name): “The value that is associated with the symbol. The interpretation of this field depends on SectionNumber and StorageClass. A typical meaning is the relocatable address.”
  • scl“An enumerated value that represents storage class. […]”
    • Value 2 (IMAGE_SYM_CLASS_EXTERNAL): “[…] The Value field indicates the size if the section number is IMAGE_SYM_UNDEFINED (0). If the section number is not zero, then the Value field specifies the offset within the section.”

Using this information, we can deduct that:

  • cs2br.o‘s go symbol is an external symbol defined elsewhere.
  • bof.o‘s go symbol is located in section 1 (.text) and located right at the start of the section (offset 0).

When we merge them using ld (ld --relocatable bof.o cs2br.o -o brbof.o --oformat pe-x86-64) and inspect them in a disassembler like Ghidra, we see that the linking worked as expected and cs2br.o‘s coffee actually calls bof.o‘s go:

Resolved go entrypoint in Ghidra

Nice, the first thing is done. This was pretty easy!

Thanks, stackoverflow!

Rewiring CS BOF API imports

In the previous section we declared go as an external symbol in cs2br.o‘s source code. This allowed us to have the linker resolve the reference to the supplied bof.o‘s implementation of go.

Rewiring the CS BOF API imports of bof.o to cs2br.o‘s implementations isn’t as straightforward though. Let’s have a look at the symbols involved:

$ objdump -x cs2br.o | grep BeaconPrintf
[ 24](sec  1)(fl 0x00)(ty   20)(scl   2) (nx 0) 0x00000000000005e1 BeaconPrintf

$ objdump -x bof.o | grep BeaconPrintf                                                
[ 18](sec  0)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000000 __imp_BeaconPrintf
0000000000000027 IMAGE_REL_AMD64_REL32  __imp_BeaconPrintf

From this output we learn that:

  • cs2br.o exports BeaconPrintf as a symbol that
    • is contained in section #1 (.text)
    • is a function (ty 20)
    • is at offset 0x5e1 into its section
  • bof.o exports __imp_BeaconPrintf as a symbol that
    • has the __imp_ prefix, indicating that this function was declared using __declspec(import) and needs to be imported at runtime
    • is an external symbol (section value IMAGE_SYM_UNDEFINED)
    • is not a function (ty 0)
  • bof.o also references __imp_BeaconPrintf in a relocation in the .text section. Which makes sense considering that BeaconPrintf is imported from the CS BOF API and its implementation is not included in the BOF’s source code.

The fact that __imp_BeaconPrintf referes to an import makes it special and more tricky to handle:

Relative reference to pointer to BeaconPrintf
Pointer to BeaconPrintf

Contrary to how cs2br.o called go (which was a call to an address relative to the CALL statement), bof.o calls BeaconPrintf by absolute address that is read from the place in memory where __imp_BeaconPrintf is located. In other words, __imp_BeaconPrintf is a pointer to the actual implementation and a loader needs to calculate and populate this address at runtime.

If we wanted to make the linker resolve these references in bof.o like it did with the go symbol in cs2br.o above, we would need cs2br.o to export not the function implementations but pointers to those implementations. Then we’d still need to rename all the imported functions in bof.o so they don’t have the __imp_ prefix in their names anymore or else a loader might attempt to import them again (and fail doing so).

There are two major challenges to this though:

  • How can we modify parts (such as symbols) of object files? The GNU utilities I found so far only allowed me to read but not write them.
  • How can we debug merged object files? When we just execute any merged BOF via a BRC4 badger, the badger might just not output anything (in the best case) or straight up crash on us (in the worst case).

I’ll cover those next before continuing with the process of merging object files.

This wouldn't work out.

IV. Getting the right tools for the job

As outlined above, there are two major challenges related to the tooling I needed to overcome at this point.

Reading/writing COFF: structex

There are lots of COFF parsers out there that allow you to parse existing or create new COFF files. Only very few also allow for modification of existing files though. Since I wanted to stick with Python for the tooling for this project and couldn’t find a suitable solution for my needs, I decided to implement this functionality based on a Python library I programmed in the past: structex.

The idea of structex is that, as a developer, you don’t imperatively write down code to serialize or deserialize individual fields of data structures but instead describe the data structure to your application. The library then does the heavy-lifting and figures out which field is at what offset and does all the (de-)serialization for you. Then you can just have your application map data structures to some binary buffer and access fields of those structures like you access fields in Python classes. Here’s a brief example:

class MachineType(IntEnum):
    IMAGE_FILE_MACHINE_I386 = 0x014c
    IMAGE_FILE_MACHINE_IA64 = 0x0200
    IMAGE_FILE_MACHINE_AMD64 = 0x8664

class coff_file_header(Struct):
    # ...
    _machine: int = Primitive(uint16_t)
    NumberOfSections: int = Primitive(uint16_t)
    #...    

    @property
    def Machine(self) -> MachineType:
        return MachineType(self._machine)

# Load BOF into memory & parse header
memory = BufferMemory.from_file('bof.o')
bof_header = coff_file_header(memory, 0)

print(bof_header.Machine.name)
# Prints: IMAGE_FILE_MACHINE_AMD64
bof_header.NumberOfSections = 0

# Write modified BOF back to disk
memory.to_file('bof_modified.o')
# bof_modified.o has now set its NumberOfSections to 0

All that I needed to do then was write down the data structures used in COFF, add some property decorators for even easier handling, and implement some bits of custom logic (e.g. reading & modifying the COFF string table). This allowed me to easily parse, inspect and modify any BOF files.

Debugging BOFs: COFFLoader

Implants are mainly designed to operate covertly, leave very few traces, and avoid getting noticed (and for that matter, sometimes even actively evade detection). This can make them hard to locate, observe and make sense of – not exactly ideal conditions for debugging. So I went out looking for alternatives.

It’s safe to assume that any program that executes BOFs does that in a way that is somewhat similar to TrustedSec’s COFFLoader. So why not use COFFLoader then? Well, it doesn’t support BRC4’s BOF API. Considering that COFFLoader is open source and the BRC4 API is pretty limited (as shown in our first blog post TODO: Insert link), it wasn’t terribly difficult to implement that functionality. I basically only needed to

  • provide simple implementations of the BRC4 APIs,
  • update COFFLoader’s InternalFunctions array to point to the Badger* APIs,
  • update hardcoded uses of the length of InternalFunctions,
  • update the check for symbol prefixes to check for the Badger prefix and
  • update the signature and exact call of the BOF entrypoint.

Since I didn’t want to spend much time on this, I kept the implementations of the BRC4 APIs very simple and didn’t add any sanity checks (or even proper formatting):

size_t BadgerStrlen(CHAR* buf) { returnstrlen(buf); }
size_t BadgerWcslen(WCHAR* buf) { return wcslen(buf); }
void* BadgerMemcpy(void* dest, const void* src, size_t len) { return memcpy(dest, src, len); }
void* BadgerMemset(void* dest, int val, size_t len) { return memset(dest, val, len); }
int BadgerStrcmp(const char* p1, const char* p2) { return strcmp(p1, p2); }
int BadgerWcscmp(const wchar_t* s1, const wchar_t* s2) { return wcscmp(s1, s2); }
int BadgerAtoi(char* string) { return atoi(string); }
PVOID BadgerAlloc(SIZE_T length) { return malloc(length); }
VOID BadgerFree(PVOID* memptr) { free(*memptr); }
BOOL BadgerSetdebug() { return TRUE; }
ULONG BadgerGetBufferSize(PVOID buffer) { return 0; }

int BadgerDispatch(WCHAR** dispatch, const char* __format, ...) {
    va_list args;
    va_start(args, __format);
    vprintf(__format, args);
    va_end(args);
}
int BadgerDispatchW(WCHAR** dispatch, const WCHAR* __format, ...) {
    va_list args;
    va_start(args, __format);
    vwprintf(__format, args);
    va_end(args);
}

I’m not very familiar with using gbd for debugging and do most of my coding in Visual Studio and Visual Studio Code and debugging in x64dbg. That’s why I also used this opportunity to set up COFFLoader as a Visual Studio solution. Now I could use COFFLoader to run my BOFs and Visual Studio and x64dbg to debug both COFFLoader and my CS2BR BOFs, neat!

Compiling & debugging in Visual Studio

V. Finally: The CS2BR Binary Patching Workflow

RE: Rewiring CS BOF APIs

On the matter of actually rewiring CS BOF API imports, there are two things to consider:

  1. The relocations to the imports themselves are relative to the instruction using/calling them.
  2. The imports referenced by the code are pointers to the actual implementations.

Writing this, I realize that all of this sounds pretty abstract, so let’s have a look at an example:

Our bof.o sends text back to operators by using the BeaconPrintf API. Because of that, bof.o imports the API by defining a __imp_BeaconPrintf symbol. This symbol refers to a place in memory where a pointer to the actual BeaconPrintf is stored.

For binary patching in CS2BR this means that we need to overwrite these pointers in bof.o to the actual implementations somehow so they point to CS2BR’s methods. These pointers are set by the loader (e.g. COFFLoader) though and that’s something we can’t control before or even at compile-time. So the question becomes: How can we make the loader point imports to CS2BR’s methods instead?

After staring at Ghidra, x64dbg, objdump output and my Python source code for more days than I’m comfortable to admit, I worked out a solution to this problem. It consists of some preparations and two processing phases that I’ll further detail in the following paragraphs.

The general idea is pretty simple:

CS2BR binary patching

CS2BR defines pointers (prefixed with __cs2br_) to its compatibility layer’s methods. These pointers will also end up in its symbol table. After merging both object files, the __imp_ symbols (that originated from bof.o) to CS BOF APIs are replaced with the __cs2br_ symbols (provided by cs2br.o). This leaves us with symbols that are referenced relative to instructions and contain pointers to our desired CS2BR compatibility layer methods.

Here’s how the complete workflow is implemented in CS2BR:

1. Declaring the go entrypoint

As described earlier in this blog post, the compiled CS2BR object file needs to contain an external reference to the go entrypoint. To do so, I just added the a declaration of this method to CS2BR’s the stub: extern void go(void *, int);

This will make ld correctly resolve this symbol to the BOF’s entrypoint when we merge both object files.

2. Creating proxy symbols

Next, I added pointers to all of the CS BOF APIs implemented in CS2BR’s compatibility layer:

void* __cs2br_BeaconDataParse __attribute__((section(".data"))) = &BeaconDataParse;
void* __cs2br_BeaconDataInt __attribute__((section(".data"))) = &BeaconDataInt;
void* __cs2br_BeaconDataShort __attribute__((section(".data"))) = &BeaconDataShort;
// ...

3. Preprocessing the BOF

Before merging object files, CS2BR identifies all CS BOF API import symbols (named __imp_Beacon*) and reconfigures them:

for symbol_name in cs_patches:
  symbol = osrcbof.get_symbol_by_name(f"__imp_{symbol_name}")
  symbol.Value = 0
  symbol.SectionNumber = 0
  symbol.StorageClass = StorageClassType.IMAGE_SYM_CLASS_EXTERNAL
  symbol.Name = symbol_name
  symbol.Name = f"__cs2br_{symbol_name}"
  symbol._type = 0

This reconfiguration achieves that the symbols are

  • treated as external (section number 0, storage class IMAGE_SYM_CLASS_EXTERNAL, type 0) and
  • renamed from __imp_* to __cs2br_*, which alles ld to resolve them to cs2br.o‘s defined symbols upon merging.

Then CS2BR renames the symbols of windows APIs that are available to CS BOFs by default (LoadLibraryGetModuleHandleGetProcAddress and FreeLibrary) so they have the __imp_KERNEL32$ prefix. This ensures that, if any of those APIs are used by the BOF, BRC4 imports and links them before executing the BOF.

4. Merging both object files

Both object files (bof.o and cs2br.o) are merged using ld. The resulting object file contains the sections and symbols of both files.

5. Recalculating ADDR64 relocations

At this point, both COFFLoader and BRC4 should be able to load and execute the patched BOF. Instead, COFFLoader just crashed and BRC4 gave me the silent treatment.

It turned out that the relocations were flawed and presumably not recalculated by ldI’ll briefly describe that bug right now, you can skip to my workaround if you want to.

Broken relocations

Relocations are a tricky topic. In fact I don’t think I got my head fully wrapped around the topic myself. When I tested my BOFs at that point and saw COFFLoader crashing, I did a lot of manual investigation by debugging COFFLoader and tracing back why it crashed. Let’s have a look at an example:

We’ll execute a very simple BOF that only formats and outputs a string using BeaconPrintf:

#include <windows.h>
#include "beacon.h"

VOID go(IN PCHAR Args,  IN ULONG Length) {
    BeaconPrintf(CALLBACK_OUTPUT, "Hi from CS2BR %i\n", 1337);	
	return;
}

When executing the BOF in COFFLoader, it would end up executing some data, not actual instructions:

Silly COFFLoader executing data

Inspecting the address of RIP in the dump, we can see that RIP lies in the .rdata section of the BOF as we can clearly see the strings used in cs2br.o‘s entrypoint:

.radata in x64dbg

By restarting and carefully stepping through the program we see that the coffee entrypoint is invoked correctly, so that bit works just fine:

Proof that our entrypoint works!

It also reaches the go entrypoint:

Proof that go is called

The next call will fail though. It retrieves the address of the method to call from a pointer (mov rax, qword ptr ds:[7ff45d050068]) and calls that. Taking a look at the memory dump of the address of the pointer, we see that this is our .data section:

Proof that the pointers are garbage

The 0xDEADBEEFDEADBEF is a dummy value I made COFFLoader pass to the coffee entrypoint to use as the _dispatch variable. CS2BR saves this _dispatch variable as a global variable in .data as can be seen in the objdump output:

$ objdump -x minimal.BR_bin21.o | grep "sec  3"

[ 36](sec  3)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .data
...
 0x0000000000000068 __cs2br_BeaconPrintf
[ 43](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000050 __cs2br_BeaconFormatPrintf
[ 44](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000088 __cs2br_BeaconIsAdmin
[ 45](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000000 _dispatch
[ 46](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000070 __cs2br_BeaconOutput
[ 47](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0)
...

As expected, the call fails at this point as it jumps to 0x00007FF45D0705E1 which is just some random offset into a method:

Broken relocations jumping into random functions

It should be pointing to 0x00007FF45D070621 though, as the .text section is mapped to 0x00007FF45D070000 and BeaconPrintf‘s offset into this section is 0x621. Apparently, the value of the pointer to BeaconPrintf is a whopping 0x40 bytes short. This left me confused for quite a while. And just by accident, I noticed something in the objdump output:

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000dc0  0000000000000000  0000000000000000  000000b4  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
...
  5 .data         00000200  0000000000000000  0000000000000000  00001380  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
...

SYMBOL TABLE:
...
[  2](sec  1)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000000 .text
...
[ 28](sec  1)(fl 0x00)(ty   20)(scl   2) (nx 0) 0x0000000000000621 BeaconPrintf
...
[ 34](sec  1)(fl 0x00)(ty    0)(scl   3) (nx 1) 0x0000000000000040 .text
...
[ 42](sec  3)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x0000000000000068 __cs2br_BeaconPrintf
...


RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
...
0000000000000027 IMAGE_REL_AMD64_REL32  __cs2br_BeaconPrintf-0x0000000000000068
...


RELOCATION RECORDS FOR [.data]:
OFFSET           TYPE              VALUE
...
0000000000000068 IMAGE_REL_AMD64_ADDR64  .text-0x0000000000000040
...

Did you spot it? There are two .text symbols, of which one has an offset of 0x40 into the .text section. That same odd symbol is used in relocations of the __cs2br_* symbols.

The ADDR64 relocations for the entries in .data could be read as: “Read the relocation’s current value from its offset into .data (aka its ‘addend’), add to it the absolute address of the .text-0x40 symbol, and write the calculated sum back at the relocation entry’s offset in .data.” This doesn’t quite work though: these relocations aren’t relative to a symbol but to the section their symbols reside in. Thus, COFFLoader correctly resolves the relocation to the address of the .text section plus the relocation’s addend 5E1. We know the relocation’s addend is 5E1 by simply extracting it:

# 5096 is the decimal representation of 1380h (.data offset into the file) + 68h (relocation offset)
od -j 5096 -N 8 -t x8 minimal.BR_bin.o
0011750 00000000000005e1
0011760

Here’s where the workaround finally comes into play!

(Cont:) 5. Rebasing ADDR64 relocations

Lastly, CS2BR recalculates relocations that

  • are of type IMAGE_REL_AMD64_ADDR64 and
  • are associated to a symbol that doesn’t refer to a section but to an offset within a section (e.g. .text-0x40).

For each of those relocations, it will acquire their current addend, add to it the value of the associated symbol, and write the newly calculated addend back to the image, as can be seen here with the __cs2br_BeaconPrintf symbol:

[INFO] Pointing relocation .data:0x68 from .text:0x40+0x5e1 (=> __cs2br_BeaconPrintf) to .text:0x621 (=> BeaconPrintf)...

VI. Demo

Patching a BOF using cs2br is very simple. One only needs to compile the compatibility layer (cs2br.o) and supply & run the patchbin.py script with paths to the BOF file to patch and the cs2br.o:

CS2BR Binary Patcher

Running a BOF that was binary-patched by CS2BR works great in COFFLoader:

COFFLoader runs our patched BOFs!

Not so much in BRC4 though:

BRC4 doesn't

At this point, there wasn’t much I could do. I certainly didn’t feel like putting more work into it and testing against a black box didn’t make much sense.

That's it.

I did reach out to Chetan Nayak, the developer of BRC4, via Discord a couple of times during the project. Since this was an internal project at the time however, I couldn’t share CS2BR’s source code. Provided with a fully patched binary, they said the entrypoint was not found and never executed by the badger. Apparently, debugging this blob could take a lot of time and they can’t provide support for such BOFs.

This marks the end of my work on this project – for now.

VII. Conclusion & Outlook

This was one long blog post to write. Working on the tool and debugging BOFs certainly took a long time – I honestly underestimated the effort of documenting all of it in this post though. So, let’s have a look at what CS2BR accomplished:

CS2BR’s source-code patching approach works very well and enables operators to use well-known and battle-tested BOFs that were formerly (almost) exclusive to CS now in BRC4. While it requires access to source code and recompilation of BOFs, it does provide a solid compatibility layer.

In its current iteration, CS2BR is able to patch binary CS BOFs and make them (on paper!) compatible with BRC4. It works well in a modified COFFLoader that provides a simple BRC4 BOF API but doesn’t seem to work with BRC4’s badgers. The reason as to why it doesn’t is a mystery to me. As such, this iteration of CS2BR effectively isn’t usable. Since this is an open-source project, everyone is free to have a look for themselves and maybe someone finds a solution – in which case I would be thrilled to learn all about it!

Both approaches, the source-code and binary patching, make use of the same custom entrypoint which, depending on the exact BOF being executed, requires encoding input parameters with the provided Python script. It would be nice to automate parts of those by parsing the CNA scripts that accompany the BOFs and making use of the BRC4 Ratel Server API to simplify the process.

Afterthoughts

To me, this project was a rewarding, albeit intense and at times frustrating, journey and deep-dive into BOF development and the COFF format. I certainly learned a lot! To be frank though, the fact that this isn’t a success-story leaves me quite unsatisfied.

I’ll be laying down my work on this project for now and provide support for the source-code patching approach. Maybe some day Chetan finds some time to look into his BOF loader, though, and lets me know what’s wrong with CS2BR’s approach to patching.

Since you made it this far, I can only assume that you are very interested in the topic (or skipped a fair bunch of this blogpost). I would love to know your thoughts on the topic, so please leave a reply!

Moritz Thomas

Moritz is a senior IT security consultant and red teamer at NVISO.
When he isn’t infiltrating networks or exfiltrating data, he is usually knees deep in research and development, working on new techniques and tools in red teaming.

Most common Active Directory misconfigurations and default settings that put your organization at risk

26 October 2023 at 07:00

Introduction

In this blog post, we will go over the most recurring (and critical) findings that we discovered when auditing the Active Directory environment of different companies, explain why these configurations can be dangerous, how they can be abused by attackers and how they can be mitigated or remediated.

First, let’s start with a small introduction on what Active Directory is.
Active Directory (AD) is a service that allows organizations to manage users, computers and other resources within a network. It centralizes authentication and authorization mechanisms for Windows devices and applications, making it easier for administrators to control access to network resources, enforce security policies, manage device configuration, etc.

Setting up an AD environment can be simple as it can be difficult depending on the organization’s size and requirements. In any case, AD comes with default settings and configurations that can be considered as dangerous and that may not comply with the security policies of your company. Administrators should be aware of these default configurations and take action to secure their environment by implementing best practices and security measures that align with their organization’s needs and risk appetite.

However, it may be difficult to identify these insecure configurations as they are not always well known to administrators. Moreover, new vulnerabilities may be identified later, as in the case of Active Directory Certificate Services (ADCS) where default templates can be abused to escalate privileges.

In the past two years, we reviewed AD environments of about 40 companies. When reviewing these environments, we noticed that some findings were quite recurrent. Some of these misconfigurations (or default settings) can have a significant impact on the security posture of a company and allow attackers to gain access to privileged accounts or to compromise the entire domain.

Let’s look at the 6 most common misconfigurations that could be abused by attackers to gain access to other systems or to compromise the environment.

Misconfigurations

Administrator accounts are allowed for delegation

In Active Directory, accounts can be delegated by default. This means that an application can act on behalf of a user (Kerberos delegation), impersonate a user anywhere within the forest (unconstrained delegation), or only impersonate the user to a specific service on a specific computer (constrained delegation).

If a delegation has been configured and if an attacker has access to the delegated system or account, they could try to impersonate an administrator account and move laterally or compromise the domain.

We found that, in almost all organizations audited, there was at least one privileged account for which the “This account is sensitive and cannot be delegated” setting was not enabled.

To abuse this default configuration, we first need to enumerate delegations. This can be done by using the Active-Directory PowerShell module:

Get-ADUser -LdapFilter "(&(userAccountControl:1.2.840.113556.1.4.803:=16777216)(msDS-AllowedToDelegateTo=*))"
Figure 1: Output of the above "Get-ADUser" command
Figure 1: Output of the above command

Thanks to the above command, we know that a constrained delegation has been configured on the IIS account. We can now check the other properties of the IIS account:

Get-ADUser iis -Properties msDS-AllowedToDelegateTo
Figure 2: Output of the Get-ADUser iis -Properties msDS-AllowedToDelegateTo command
Figure 2: Output of the Get-ADUser iis -Properties msDS-AllowedToDelegateTo command

In this case, we can see that a constrained delegation has been configured on the IIS account to access the CIFS service of the WinServ-2022 server (Figure 2).

If we try to access the server using our low-privileged account, Bob, we get an error (Figure 3). This is expected because our account is not allowed to access this server.

Figure 3: Error message when trying to access the WinServ-2022 server with Bob
Figure 3: Error message when trying to access the WinServ-2022 server with Bob

As the IIS account is a service account, we can try to kerberoast the IIS account using Rubeus, for example (Figure 4). A kerberoast attack is a technique that attempts to retrieve the hash of an Active Directory account that has a Service Principal Name (also known as a service account). Note that in this example, we use the “rc4opsec” argument to only kerberoast service account that supports RC4 encryption, which is the default setting (we will go more in details in the “AES encryption not enforced on service accounts” section).

Figure 4: Kerberoasting of the IIS account
Figure 4: Kerberoasting of the IIS account

In this case, we were able to get the hash of the IIS account and crack the password using “John the Ripper”, which is “Password123”.

Figure 5: Generating the AES256 hash of the password
Figure 5: Generating the AES256 hash of the password

After generating the AES256 representation of the password, we can now use Rubeus to request an HTTP ticket to impersonate the domain administrator and gain access to the WinServ-2022 system. In this example, the HTTP ticket will allow us to run command on the WinServ-2022 server:

Figure 6: Generating an HTTP to impersonate the Administrator of the domain and gain access to WinServ-2022
Figure 6: Generating an HTTP to impersonate the Administrator of the domain and gain access to WinServ-2022

As mentioned before, we are allowed to impersonate the Administrator account because the “This account is sensitive and cannot be delegated” setting is not enforced by default.

After requesting and injecting the ticket that is used to impersonate the Administrator account in memory, we can access WinServ-2022 with the Administrator account:

Figure 7: Accessing WinServ-2022 and running command as the Domain Administrator
Figure 7: Accessing WinServ-2022 and running command as the Domain Administrator

This demonstrates that by compromising a poorly configured service account, any user can gain access to another system with domain administrator privileges. This could have been avoided by enabling the “This account is sensitive and cannot be delegated” setting on privileged accounts (e.g., Domain Admins, etc.), because the Administrator credentials would not be forwarded to another computer for authentication purposes.

The following dsquery command can be used to identify any user where the setting is not enabled:

dsquery * DC=LAB,DC=LOCAL -filter "(&(objectclass=user)(objectcategory=person)(!useraccountcontrol:1.2.840.113556.1.4.803:=1048576))"
Figure 8: Output of the above "dsquery" command
Figure 8: Output of the dsquery command

In this example, if this setting was enabled, the attacker would not have been able to gain access to WinServ-2022 as Administrator:

Figure 9: Enabling the flag for the Administrator account
Figure 9: Enabling the flag for the Administrator account
Figure 10: The attack fails when the flag is enabled
Figure 10: The attack fails when the flag is enabled

Another option is to add the accounts to the Protected Users group. The Protected Users is a group introduced in Windows Server 2012 R2. The goal of this group is to protect administrators against credential theft by not caching credentials in insecure ways. Adding accounts to this group will not only prevent any type of Kerberos delegations, but will also prevent:

  • CredSSO and Wdigest from being used;
  • NTLM authentication;
  • Kerberos from using RC4 or DES keys;
  • Renewal of TGT beyond a 4-hour lifetime.

Microsoft recommends adding a few users to this group first to avoid blocking all administrators in case of a problem. However, it is useless to add computers and service accounts to this group because credentials will always be present on the host machine.

Note that after adding administrators to this group, some organizations have experienced difficulties connecting to servers using RDP (Remote Desktop Protocol). This is because only the Fully Qualified Domain Name (FQDN) is supported when connecting to servers via RDP when the user has been added to the Protected Users group. In fact, when using an IP address to connect to a server with RDP, NTLM authentication is used instead of Kerberos. However, when the FQDN is used, Kerberos authentication will be used.

AES encryption not enforced on service accounts

When a user requests access to a service in Active Directory, a service ticket is created. This service ticket is encrypted using a specific encryption type and sent to the user. The user can then present this encrypted ticket to the server to access the service. There are different encryption types available, such as DES, RC4 and AES. The encryption type is defined by the msDS-SupportedEncryptionTypes attribute. By default, the attribute is not set and the domain controller will encrypt the ticket with RC4 to ensure compatibility. This could allow an attacker to perform a kerberoasting attack, as previously demonstrated.

This means that if AES encryption is not enabled on service accounts and RC4 is not specifically disabled, an attacker could try to request a Kerberos ticket for a specific SPN (Service Principal Name, which is used to associate a service to a specific account) and brute force its password. Then, if someone can retrieve the cleartext password, they will be able to impersonate the account and access all systems/assets to which the service account has access.

If weak encryption types are allowed, an attacker can try to kerberoast a service account without generating too much suspicious activity in the logs, and gain access to other systems within the environment as described above in the “Administrator accounts are allowed for delegation” section.

To identify the value of the msDS-SupportedEncryptionTypes attribute for all service accounts, the following dsquery command can be used:

dsquery * "DC=lab,DC=local" -filter "(&(objectcategory=user)(servicePrincipalName=*))" -attr msDS-SupportedEncryptionTypes samaccountname distinguishedName -limit 0 | FIND /i /v "KRBTGT" | SORT

It is important to note that if the value is blank or equal to 0, it will be interpreted as RC4_HMAC_MD5.

The msDS-SupportedEncryptionTypes attribute on service accounts should be modified to only allow AES instead of legacy protocols such as RC4 or DES. However, for backward compatibility or to validate everything is functional, the value of the attribute can be set to 28. This means that RC4, AES-128, and AES-256 will be allowed. Note that all clients should support AES encryption if systems are not running Windows 2000, Windows XP or Windows Server 2003.

Finally, after making sure everything is working as expected, the value can be modified to 24 to only allow AES-128 and AES-256, as shown on the following screenshot (Figure 11), or to 16 to only allow AES-256.

Figure 11: Output of the above "dsquery" command
Figure 11: Output of the dsquery command

Alternatively, you can edit the options of the account and check the following boxes (Figure 12). This will update the msDS-SupportedEncryptionTypes attribute.

Figure 12: Editing the account options to support Kerberos AES 128 and 256 encryption
Figure 12: Editing the account options to support Kerberos AES 128 and 256 encryption

If the attribute was set to 16 (meaning that only AES-256 is supported), we would not have been able to kerberoast the IIS account using the rc4opsec argument, as shown in Figure 13.

Figure 13: Comparison when the msDS-SupportedEncryptionTypes is set and not set
Figure 13: Comparison when the msDS-SupportedEncryptionTypes is set and not set

Moreover, if the rc4opsec argument is not used and the service account only allows AES encryption types, a 4769 event will be generated on the domain controller with the encryption type used (Figure 14). In this case, the encryption type is 0x12 (DES_CBC_MD5 and AES 256) which is not expected as the attribute is set to 0x10 (only AES-256).

Figure 14: Log showing the encryption type used
Figure 14: Log showing the encryption type used

A Blue team can use these events to identify kerberoasting activities on service accounts.

Finally, deprecated and insecure encryption types can be disabled via a GPO, as follows:

Figure 15: GPO allowing the secure encryption types and disabling the deprecated and insecure ones
Figure 15: GPO allowing the secure encryption types and disabling the deprecated and insecure ones

If an attacker tries to request an RC4 ticket for an account where only AES encryption types are allowed, the kerberoast attack will fail:

Figure 16: Kerberoast attack failing when the account only supports AES encryption types
Figure 16: Kerberoast attack failing when the account only supports AES encryption types

Note that the /usetgtdeleg parameter is used to request RC4 ticket for AES accounts.

Print spooler is enabled on Domain Controllers

According to Microsoft, the print spooler is an executable that manages the printing process by retrieving the location of the correct printer driver, loading the driver, scheduling the print job, etc.

In the past few years, the print spooler service has been affected by several zero-day vulnerabilities (such as PrintNightmare) allowing low privileged users to escalate their privilege, as the service is running with system level privileges. Many exploits are available, but we will not focus on these vulnerabilities.

The print spooler service can also be abused to gain access to the key of the kingdom, the hash of the KRBTGT account. By gaining access to the hash of this account, attackers will be able to forge Golden Tickets, meaning that they will gain almost unlimited access to the Active Directory domain (domain controllers, devices, files, etc.). An attacker can also perform a Skeleton Key attack to create persistence in the domain, as an example. This malware will inject itself inside the LSASS process and create a master password that will work for any account in the domain.

Indeed, when an unconstrained delegation has been configured on a server and when the print spooler service is running on at least one domain controller, it is possible to get the credentials of the domain controller where the service is running.

During our audits, we identified that more than 25% of organizations had configured unconstrained delegation on one or multiple machine accounts. In addition, the print spooler service was running on at least one domain controller in 75% of organizations.

Let’s see how an attacker could abuse these dangerous configurations.

First, we must find where an unconstrained delegation has been configured. This can be done using the Get-DomainComputer command from PowerView as follows:

Figure 17: List of computers where an unconstrained delegation has been configured
Figure 17: List of computers where an unconstrained delegation has been configured

Note that unconstrained delegation is enabled by default and required on domain controllers. In this example, WIN-7I6M16HF63I is the Domain Controller (DC).

We have already compromised the WinServ-2022 server where an unconstrained delegation has been configured. Moreover, the print spooler service is running by default on domain controllers. All the conditions are met to try to retrieve the hash of the KRBTGT account, so let’s give it a try!

We can use Rubeus on WinServ-2022 to extract all Ticket Granting Tickets (TGTs) and display any newly captured TGTs:

Figure 18: Using Rubeus to extract all captured TGTs
Figure 18: Using Rubeus to extract all captured TGTs

On our low privilege machine, we can use MS-RPRN, as an example, to force the domain controller to connect to WinServ-2022.

Figure 19: Forcing the DC to authenticated to WinServ-2022
Figure 19: Forcing the DC to authenticated to WinServ-2022

As expected, we captured a new TGT (Figure 20). The response from the DC contains the domain controller’s computer account Kerberos ticket.

Figure 20: Capturing the TGT from the DC using Rubeus
Figure 20: Capturing the TGT from the DC using Rubeus

We can now import this TGT to impersonate the DC:

Figure 21: Importing the TGT to impersonate the DC
Figure 21: Importing the TGT to impersonate the DC

Once the ticket has been imported, we can perform a DCSync attack using SharpKatz to get the KRBTGT hash.

Figure 22: DCSync attack using SharpKatz
Figure 22: DCSync attack using SharpKatz

We now have the hash of the KRBTGT account (Figure 22), which means that we successfully compromised the domain.

Thanks to the print spooler service running by default on DCs, we were able to trigger the service and make it authenticate to the WinServ-2022 service.

To mitigate this vulnerability, Microsoft recommends disabling the print spooler service on all domain controllers as a security best practice.

One way to identify domain controllers where the print spooler service is running is by using PingCastle, as shown in Figure 23. In this case, only the spooler module was executed and we can see that the service is active on the DC.

Figure 23: PingCastle scan returning all domain controllers where the Print Spooler service is running
Figure 23: PingCastle scan returning all domain controllers where the Print Spooler service is running

As mentioned above, the recommendation is to disable the print spooler service on domain controllers. This can be done using a GPO that will disable the service:

Figure 24: GPO to disable the Print Spooler service
Figure 24: GPO to disable the Print Spooler service

If the print spooler service was disabled, an attacker would not have been able to force the domain controller to connect to WinServ-2022.

Figure 25: Error message when the Print Spooler is disabled
Figure 25: Error message when the Print Spooler is disabled

Users can create machine accounts

First of all, let’s define what a machine account in Active Directory is. A machine account (or computer account) is an Active Directory object that represents a computer or a device connected to the domain. Like user accounts, machine accounts have different attributes that store information about the device, can be a member of security groups, can have Group Policies applied, etc.

By default, in Active Directory, everyone can create up to 10 machine accounts in the domain. This is due to the ms-DS-MachineAccountQuota attribute. According to the Microsoft documentation, this attribute is “the number of computer accounts that a user is allowed to create in the domain”.

This setting is defined in the Default Domain Controllers Policy.

Figure 26: Default value of the "Add workstation to domain" setting in the Default Domain Controllers Policy
Figure 26: Default value of the “Add workstation to domain” setting in the Default Domain Controllers Policy

Moreover, the current value of ms-DS-MachineAccountQuota can be found using this PowerShell command:

Get-ADObject ((Get-ADDomain).distinguishedname) -Properties ms-DS-MachineAccountQuota
Figure 27: Output of the above command
Figure 27: Output of the above command

In this example, the Default Domain Controller Policy Group Policy Object (GPO) and the attribute have not been modified and Authenticated users can create up to 10 computer accounts (Figure 26 and Figure 27).

To create a new machine account, the PowerMad module, written by Kevin Robertson, can be used as follows:

Figure 28: Creation of a new machine account using PowerMad
Figure 28: Creation of a new machine account using PowerMad

As expected, after creating 10 machine accounts, the user will no longer be able to create new machine accounts:

Figure 29: Error message when reaching the MachineAccountQuota limit
Figure 29: Error message when reaching the MachineAccountQuota limit

There is no attribute indicating the number of accounts already created by one specific user. However, the mS-DS-CreatorSID attribute of computer objects is used to determine how many computer accounts have been created by a specific user.

This information can be retrieved by using the Get-MachineAccountCreator command from the PowerMad module:

Figure 30: List of all machines accounts and their creator
Figure 30: List of all machines accounts and their creator

It is also possible to check who created a specific machine account by using the Active Directory PowerShell module:

Get-ADComputer MyComputer -Properties mS-DS-CreatorSID | Select-Object -Expandproperty mS-DS-CreatorSID | Select-Object -ExpandProperty Value | Foreach-Object {Get-ADUser -Filter {SID -eq $_}}
Figure 31: Information about the creator of the "MyComputer" machine account
Figure 31: Information about the creator of the “MyComputer” machine account

The user who created the machine account will be granted write access to different attributes such as msDS-AllowedToActOnBehalfOfOtherIdentity, ServicePrincipalNames, DnsHostName, and so on.

Tools like KrbRelayUp leverage this default setting to escalate privileges to NT\SYSTEM on a local system. An attacker can also change the msDS-AllowedToActOnBehalfOfOtherIdentity to abuse Resource-Based Constrained Delegation, for example.

If a Public Key Infrastructure (PKI) is present in the domain, an attacker can take advantage of the default Machine certificate template to perform a DCSync attack and dump hashes of all users and computers. Let’s take a look at how an attacker can proceed to retrieve the hashes.

After creating a new machine account, an attacker can modify the ServicePrincipalNames and the DnsHostName attributes. First, we remove the service principal names containing the initial DnsHostName and then we set the DnsHostname attribute to the domain controller FQDN, as follows:

Figure 32: Default values of the new machine account attributes
Figure 32: Default values of the new machine account attributes
Figure 33: Modification of the DNSHostName attribute of the machine account to the DC FQDN
Figure 33: Modification of the DNSHostName attribute of the machine account to the DC FQDN

After that, an attacker can request a certificate for the machine account using the Machine template and they will get a certificate for the domain controller. This will allow the attacker to retrieve the NT hash of the domain controller machine account.

Figure 34: Retrieval of the NT hash of the domain controller machine account
Figure 34: Retrieval of the NT hash of the domain controller machine account

The hash can then be used to perform a DCSync attack:

Figure 35: DCsync attack using secretsdump.py
Figure 35: DCsync attack using secretsdump.py

By creating a new computer object, editing its properties and abusing the default Machine template, we were able to dump the hashes of all users. The hashes can then be used to perform a “Pass-the-Hash” attack and move laterally to other systems.

This could have been avoided if some mitigation measures had been put in place.

First, computer objects created using the PowerMad tool will be stored in the Computers container as opposed to other computer objects created by IT administrators. Indeed, they should be put in specific OUs as Group Policies can’t be applied on the container. This can be used to identify any objects created by malicious users.

Moreover, it is recommended to create a new group (or a new account) that will be granted the required permissions to create new machine accounts. This way, only members of this group will be allowed to create new computer objects and malicious users will not be able to perform the attack.

This can be done by modifying the Default Domain Controller Policy. To do so, go to Computer configuration > Policies > Windows Settings > Security Settings > User Right Assignment > Add workstations to domain: Remove the ‘Authenticated Users’ group and add the new group or account previously created.

Authenticated users will no longer be able to create new machine accounts, as shown in Figure 36.

Figure 36: Error message when a user tries to create a new machine account (after removing the permission of the Authenticated Users group)
Figure 36: Error message when a user tries to create a new machine account (after removing the permission of the Authenticated Users group)

Unchanged GPOs are not reprocessed on Domain Controllers

All domain joined systems refresh and apply applicable group policies at specific intervals.

For security policy settings (https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/security-policy-settings), the Group Policy engine works differently and these settings are automatically re-applied every 16 hours even if the GPO has not been changed.

However, by default, most GPO settings are only applied when they are new or when they have been changed since the last time the client requested them. This could allow an attacker to modify a registry key that is normally managed through a GPO to disable specific security measures, for example.

In the following example, a company enforces the Windows Defender Real-Time Protection through a GPO:

Figure 37: GPO to enable Windows Defender Real-Time Protection
Figure 37: GPO to enable Windows Defender Real-Time Protection

If a user tries to download malicious files, Windows Defender will immediately quarantine the files:

Figure 38: Windows Defender alert when downloading a malicious file
Figure 38: Windows Defender alert when downloading a malicious file

If a user can modify the Windows Defender Real-Time Protection registry key, they will be able to download and run malicious tools on the system. In this case, by setting the value to 1, the user disables the Real-Time Protection feature:

Figure 39: Modification of the DisableRealtimeMonitoring registry key
Figure 39: Modification of the DisableRealtimeMonitoring registry key

As expected, the Real-Time Protection is now disabled and the user can download malicious files:

Figure 40: Comparison when downloading a malicious file with Real-Time Protection enabled and disabled
Figure 40: Comparison when downloading a malicious file with Real-Time Protection enabled and disabled

To mitigate this vulnerability, it is recommended to ensure that registry and security policy settings defined in GPOs are always enforced and re-applied on systems even if the GPO has not changed. This way, any unauthorized changes made locally will be overridden after 5 minutes to 16 hours.

In the Default Domain Controller policy, under Computer Configuration > Administrative Templates > System > Group Policy, configure the following two settings as follows:

  1. Configure security policy processing:
    • Process even if the Group Policy objects have not changed: Enabled
    • Do not apply during periodic background processing: Disabled
  2. Configure registry policy processing:
    • Process even if the Group Policy objects have not changed: Enabled
    • Do not apply during periodic background processing: Disabled

The following settings can also be re-applied even if they have not been changed:

  • Internet Explorer Maintenance
  • IP Security
  • Recovery Policy
  • Wireless Policy
  • Disk Quota
  • Scripts
  • Folder Redirection
  • Software Installation
  • Wired Policy

Moreover, enabling auditing for registry operations can help your organization identify suspicious changes.

To audit registry key modification, the “Audit object access” policy needs to be enabled using a GPO (Figure 41).

Figure 41: GPO for auditing registry key modifications
Figure 41: GPO for auditing registry key modifications

After that, auditing also needs to be enabled on the registry keys that you want to monitor (Figure 42).

Figure 42: Enable auditing on the registry keys in Regedit
Figure 42: Enable auditing on the registry keys in Regedit

In this case, each time the value of a registry key under Windows Defender is modified, an event will be generated in the Event Viewer and can be used by the Blue team to identify suspicious activities.

Modification to registry keys can now be detected by looking at different event IDs (4656, 4657, 4660 and 4663).

In our example, we can see that the value of the DisableRealTimeMonitoring registry key was changed to 1 instead of 0:

Figure 43: Log showing that the value of the registry key has been modified
Figure 43: Log showing that the value of the registry key has been modified

Password policy and least privilege

This section includes recommendations related to the password policy of service accounts and the KRBTGT account.

The recommendations included in this section should be adapted to your company policy, specific use cases and risk tolerance.

Service accounts

During the audits, we noticed that most of the time, there is no password policy for service accounts, allowing administrators to set weak passwords that can be easily brute forced. In a few cases, the password for service accounts was even included in their description.

As shown in the “Administrator accounts are allowed for delegation” section, we cracked the IIS account password because weak passwords are allowed as there is no password policy enforced for service accounts. This could have been prevented by configuring a proper password policy.

For example, Microsoft recommends using passwords of at least 25 characters for service accounts and implementing a process for changing them regularly. Moreover, it is also recommended to use a dedicated Organizational Unit to manage this kind of accounts, making it easier to administrators to manage security settings applied to these accounts.

Finally, we also noticed that some organizations tend to use personal administrator accounts as service accounts. This means that if someone achieves to compromise a service account used by an administrator, they will have all privileges associated to this account. As a best practice, service accounts should only be granted the permissions they need.

KRBTGT account

The KRBTGT account is a default account that exists in all Active Directory domains. Its main purpose is to act as the Key Distribution Center (KDC) service account, which handles all Kerberos requests in the domain. As mentioned above, if an attacker achieves to compromise this account, they will be able to forge Golden Tickets and gain access to domain resources.

We noticed that many organizations do not change the KRBTGT password on a regular basis. Based on 25 audits, we found that the KRBTGT password is changed every 1855 days on average, and two organizations did not change the password for more than 5500 days, that’s over 15 years!

This means that an attacker, who was able to compromise the KRBTGT hash and has not been detected yet, can maintain his access for 5 years on average (if they have not created a backdoor yet).

It is recommended to change the password of the KRBTGT account regularly, for example every 6 months or every year.

Note that the password must be changed twice because the password history value for this account is set to 2. This means that the two most recent passwords will be valid for all already existing tickets. Before changing the password for the second time, best practices recommend waiting at least 24 hours to avoid invalidating existing Kerberos tickets and requiring everyone and everything (computers, servers, service accounts, etc.) to re-authenticate.

However, if you suspect that the KRBTGT account has been compromised by an attacker, the password should also be reset. This will prevent anyone who has access to its hash from generating Golden Tickets, for example.

It is important to keep in mind that changing the KRBTGT password will not ensure the security of your organization. If someone managed to get its hash once, they will probably be able to compromise it a second time if no other security measures are implemented.

Conclusion

In this blog post, we went over the most common misconfigurations and default settings discovered when doing Active Directory assessments of different environments. These configurations can have a significant impact on the security of your organization and allow attackers to gain access to the key of your kingdom.

Therefore, it is important to know your environment. Moreover, there should be a security baseline which should always be followed and reviewed regularly.

  • Is this configuration still required?
  • Is there any potential risk, any new vulnerabilities associated to this service?
  • Is there a more secure approach?

These are some of the questions IT administrators must repeatedly ask themselves to maintain a certain security posture.

Moreover, some tools allow you to perform automatic auditing of your AD environment and identify settings that could put your organization at risk:

  • PingCastle: It scans your environment to identify security vulnerabilities and weaknesses. It includes checks for stale objects (legacy protocols, never expiring password, etc.), privileges accounts (Kerberoastable admin accounts, delegations, etc.) and anomalies (print spooler, ADCS, audit policy, etc.).
  • BloodHound: It allows you to visualize Active Directory attack paths. It can be used to identify potential security vulnerabilities that could be exploited by an attacker with Domain Users privileges to elevate their privileges to Domain Admins, as an example.
  • Testimo: It is a PowerShell Module created by EvotecIT that helps you identify security issues as well as other operational issues. It can also generate HTML reports showing the commands executed, their output, a description and links to external resources.

While PingCastle and Testimo are more defender oriented, BloodHound is more attacker oriented.

In addition to performing regular scans, IT administrators should always keep an eye on newly discovered vulnerabilities, as a configuration that is considered safe can be the cause of a disaster a few years later. Indeed, it is important to note that no tool can guarantee complete security for your AD environment.

NVISO can help you identify and remediate vulnerabilities and weaknesses in your environment by performing an adversary emulation assessment which simulate real-world threats, as an example. These assessments will help you improve your security posture and protect your organization from potential threats.

To learn more about how we can help you, feel free to reach out or to check our website.

Bastien Bossiroy

Bastien Bossiroy

Bastien is a Senior Security Consultant at NVISO where he is part of the Software Security and Assessment team. He focuses mainly on web applications testing and Active Directory environments auditing.

During his free time, Bastien enjoys testing different Active Directory configurations to understand how they work and how specific settings or misconfigurations can impact the security of the environment.

XOR Known-Plaintext Attacks

12 October 2023 at 07:00

In this blog post, we show in detail how a known-plaintext attack on XOR encoding works, and automate it with custom tools to decrypt and extract the configuration of a Cobalt Strike beacon. If you are not interested in the theory, just in the tools, go straight to the conclusion 🙂 .

A known-plaintext attack (KPA) is a cryptanalysis method where the analyst has the plaintext and ciphertext version of a message. The goal of the attack is to reveal the encryption algorithm and key.

XOR encoding intro

Let’s first agree on a notational convention:
Decimal integers are represented like this:

65

Hexadecimal integers are represented like this:

0x41

Let’s take the following plaintext message as example:

IT security company NVISO was founded in 2013!

And we XOR encode this with key ABC.
Like this:

Figure 1: XOR encoding with key ABC
Figure 1: XOR encoding with key ABC

This is how this works. Character per character, we perform an 8-bit XOR operation.

  • We take the first character of the plaintext message (I) and the first character of the key (A), we lookup their numeric value (according to the ASCII table): I is 0x49 and A is 0x41. Xoring 0x49 and 0x41 gives 0x08. In the ASCII table, 0x08 is a control character and thus unprintable (hence the thin rectangle in figure 1: this depicts unprintable characters).
  • We take the second character of the plaintext message (T) and the second character of the key (B), we lookup their numeric value (according to the ASCII table): T is 0x54 and B is 0x42. Xoring 0x54 and 0x42 gives 0x16. In the ASCII table, 0x16 is a control character and thus unprintable.
  • We take the third character of the plaintext message (space character) and the third character of the key (C), we lookup their numeric value (according to the ASCII table): is 0x20 and C is 0x43. Xoring 0x20 and 0x43 gives 0x63. In the ASCII table, 0x63 is lowercase letter c (lowercase and uppercase letters have different values: they have their 6th bit toggled).
  • We take the fourth character of the plaintext message (s) and the first character of the key (A), we lookup their numeric value (according to the ASCII table): s is 0x73 and A is 0x41. Xoring 0x73 and 0x41 gives 0x32. In the ASCII table, 0x32 is digit 2. Since our XOR key (ABC) is only 3 characters long, and the plaintext is longer than 3 characters, we start repeating the key. That is why we start again with the first character of the key (A) after having used the last character of the key (C) for the previous character. We roll the key.

This goes on, until we process the 46th character:

  • Character 1: we perform operation I xor A and that gives us unprintable character
  • Character 2: we perform operation T xor B and that gives us unprintable character
  • Character 3: we perform operation xor C and that gives us character c
  • Character 4: we perform operation s xor A and that gives us character 2
  • and so on ….
  • Character 46: we perform operation ! xor A and that gives us character `

This example explains how we XOR a plaintext message with a key that is shorter than the plaintext message: Plaintext XOR Key -> Ciphertext.

XOR cryptanalysis

XOR encoding has an interesting property, especially if you are interested in cryptanalysis. When you XOR the plaintext message with the ciphertext, you obtain the key: Plaintext XOR Ciphertext -> Key.

Figure 2: XORing plaintext and ciphertext gives keystream
Figure 2: XORing plaintext and ciphertext gives keystream

That’s how XOR encoding works. Let’s now see how we can decode this, without knowing the key, neither the complete plaintext, but just with a part of the plaintext.

In the case we are dealing with here, we have the complete ciphertext and partial plaintext. Under certain circumstances (explained later), it is also possible to recover the key in such a case.
Assume that the partial plaintext we have is NVISO: we know that the string NVISO appears in the plaintext (unknown to us), but we don’t know where.
So what we will do here, is repeat the string NVISO as many times as necessary to make it as long as the ciphertext (and hence as long as the plaintext). Like this:

NVISONVISONVISONVISONVISONVISONVISONVISONVISON

When we perform XOR operations with the ciphertext and the repeating NVISO string, we obtain this result:

Figure 3: XORing ciphertext with partial known plaintext
Figure 3: XORing ciphertext with partial known plaintext

What we see here, is a string of 3 characters (CAB) that starts to repeat itself (CA) and has exactly the same length as the partial plaintext: NVISO is 5 characters, CABCA is 5 characters.
This is how you can identify the key: you look for repetition, that is in total as long as the partial plaintext. Substring CABCA is the only string that satisfies this condition in the XOR result in our example. yy and bib are also repeating strings, but they are shorter than the partial plaintext (5 characters).
And this also illustrates the most important condition for a partial KPA attack on XOR encoding to succeed: the partial plaintext must be longer than the XOR key. If the partial plaintext is as long, or shorter, than the XOR key, we will not observe repetition, and thus we will not be able to identify the key.
The string that contains our XOR key is CABCA. Since we assume that we are dealing with a rolling XOR key, the key can be ABC, BCA or CAB.
Let’s expand that string that contains our XOR key, to the left and to the right, so that it is as long as the ciphertext:

Figure 4: expanding the keystream to the left and to the right
Figure 4: expanding the keystream to the left and to the right

And finally, use that as the key stream for the XOR operation:

Figure 5: XORing the ciphertext with the expanded keystream
Figure 5: XORing the ciphertext with the expanded keystream

And now we have recovered the complete plaintext, by knowing a part of it that is longer than the XOR key used to encode the message.
We have designed our example so, that the ciphertext and partial plaintext align, like this:

Figure 6: for the purpose of this example, the plaintext and partial known plaintext align
Figure 6: for the purpose of this example, the plaintext and partial known plaintext align

If the number of characters preceding the ciphertext of partial plaintext NVISO would not be an exact multiple of the length of plaintext NVISO (20 / 4 = 5), then ciphertext and partial plaintext would not align properly, and we would not calculate the correct key.
For example, like this (I removed the leading word IT):

Figure 7: more likely, the partial known plaintext will not align
Figure 7: more likely, the partial known plaintext will not align

But this can be solved by “rolling” the partial plaintext. If our partial plaintext is 5 characters long, we have to generate 5 partial plaintext streams that we have to check:

NVISO -> NVISONVISONVISONVISONVISONVISONVISONVISONVISON
VISON -> VISONVISONVISONVISONVISONVISONVISONVISONVISONV
ISONV -> ISONVISONVISONVISONVISONVISONVISONVISONVISONVI
SONVI -> SONVISONVISONVISONVISONVISONVISONVISONVISONVIS
ONVIS -> ONVISONVISONVISONVISONVISONVISONVISONVISONVISO
Figure 8: trying with the 4th potential keystream
Figure 8: trying with the 4th potential keystream

In this example, it’s the fourth stream we generated that will align, and will thus reveal the key.

A partial KPA attack on XOR encoded ciphertext with a rolling XOR key, requires a partial, known plaintext that is longer than the XOR key. The bigger the difference in length, the easier it will be to identify the XOR key.

Doing all this decoding manually is a very intensive process. That’s why we have a tool to automate this process: xor-kpa.py.

Let’s illustrate with our example.
This is the encoded file (ciphertext):

Figure 9: the ciphertext
Figure 9: the ciphertext

This is the file that contains the known plaintext:

Figure 10: the partial known plaintext
Figure 10: the partial known plaintext

Running tool xor-kpa.py with these 2 files as input gives:

Figure 11: xor-kpa.py lists potential keys
Figure 11: xor-kpa.py lists potential keys

We see that the tool recovers keystream CABCA, just like we did, and infers the right key from it: ABC.
Extra tells us how many characters are repeating (thus how many extra characters the partial plaintext has compared to the XOR key). The bigger this number, the more confident we can be that the correct key was recovered.
Divide tells us how many times the complete XOR key appears in the keystream.
And counts tells us how many times this ciphertext was found (in our example, that would mean that the word NVISO appears more than once in the plaintext).
xor-kpa.py can also do the decoding for us (using option -d):

Figure 12: decoding with xor-kpa.py
Figure 12: decoding with xor-kpa.py

And here is an example, where the partial known plaintext is longer (was founded in 2013):

Figure 13: longer partial known plaintext
Figure 13: longer partial known plaintext
Figure 14: longer keystream
Figure 14: longer keystream

As the recovered keystream is much longer, the probability to extract the correct key is higher.

Conclusion

NVISO regularly encounters malware or artefacts that use XOR encoding with a key longer than one byte. Tool xor-kpa.py helps us to decode such files.

The tool comes with some predefined plaintexts, that appear often in samples (like “This program cannot be run in DOS mode”). We also included plaintexts for Cobalt Strike beacons.

When you run tool xor-kpa.py with the help option, you get a list of predefined plaintexts:

Figure 15: xor-kpa's options, including the predefined known plaintexts
Figure 15: xor-kpa’s options, including the predefined known plaintexts

All the predefined plaintexts that start with cs-, are for Cobalt Strike.

As a last example, we try to decode a file suspected to be an encoded Cobalt Strike beacon (beacon.vir). We will use cs-key-dot: this is the invariable part of the public key stored inside the beacon configuration for Cobalt Strike version 4:

Figure 16: xor-kpa displaying potential keys
Figure 16: xor-kpa displaying potential keys

Several potential keys are recovered, and the most likely key is presented at the end of the output. Potential key output is sorted by the amount of repetition in the keystream: the more repetition, the likelier the key is correct, and therefore listed lower than the keystreams with less repetition.

We can now use option -d to decode the sample with the most probable key (the last one in the list), and pipe the output to 1768.py, a tool to extract beacon configurations:

Figure 17: xor-kpa decoding the beacon and 1768 extracting the beacon configuration
Figure 17: xor-kpa decoding the beacon and 1768 extracting the beacon configuration

Because of the decoding with xor-kpa, 1768.py is able to extract the proper configuration.

Didier

Didier Stevens

Didier Stevens is a malware expert working for NVISO. Didier is a SANS Internet Storm Center senior handler and Microsoft MVP, and has developed numerous popular tools to assist with malware analysis.

A Beginner’s Guide to Adversary Emulation with Caldera

25 August 2023 at 07:00
caldera logo

Target Audience

The target audience for this blog post is individuals who have a basic understanding of cybersecurity concepts and terminology and looking to expand their knowledge on adversary emulation. This post delves into the details of adversary emulation with the Caldera framework exploring the benefits it offers. By catering to a beginner to intermediate audience, the blog post aims to strike a balance between providing fundamental information for newcomers and offering valuable insights and techniques that can benefit individuals who are already familiar with the basics of cybersecurity.

What is Adversary Emulation

Adversary emulation is a methodology used to simulate the Tactics, Techniques, and Procedures (TTPs) used by known Advanced Persistent Threats (APTs), with the goal of identifying vulnerabilities in an organization’s security defenses. By emulating real-world attacks and incident response techniques, such as exploitation of vulnerabilities and lateral movement within a network, cybersecurity teams can gain a better understanding of their security posture and identify areas for improvement.

The Need for Adversary Emulation

Adversary emulation can help organizations test their security defenses against real-world threats. Some of the benefits the emulation offers are:

  • Identifying vulnerabilities: Adversary emulation assists organizations in identifying vulnerabilities, weaknesses or misconfigurations in their security defenses that might not have been detected through conventional security testing. This information can enhance the existing detection mechanisms by creating new alerts and rules that are triggered when similar activities are detected. The emulation results can also work as a guide in prioritizing mitigation and patching activities.
  • Improving security controls: By identifying weaknesses in their security defenses, organizations can make informed decisions about how to improve their security controls. This can include implementing new security technologies, updating security policies, or providing additional security awareness training to employees.
  • Measuring security effectiveness: Adversary emulation enables organizations to assess the effectiveness of their security defenses within a controlled environment. Through analyzing the emulation results, organizations can have a clearer understanding of how well their incidence response plan operates in real-world scenarios.If any gaps or inefficiencies are identified, the plan can be refined based on the new data.
  • Staying ahead of emerging threats: Adversary emulation exercises can help organizations stay ahead of emerging threats by testing their security defenses against new and evolving attack techniques. This can help organizations prepare for future threats and ensure that their security defenses are effective in protecting against them.

Emulation VS Simulation

Emulation involves creating a replica of a specific system or environment, such as an operating system, network, or application. It provides a more realistic testing environment, which can help identify vulnerabilities and test the effectiveness of security controls in a more accurate and reliable way. However, creating an emulation environment can be time-consuming and resource-intensive, and it may not always be feasible to replicate every aspect of a real-world environment.

Simulation, on the other hand, involves creating a hypothetical scenario that models a real-world attack. It is often quicker and easier to set up, and can be used to test response plans and procedures without the need for a complex emulation environment. However, simulations may not always provide a completely accurate representation of a real-world attack scenario, and the results may be less reliable than those obtained through emulation.

The Caldera Framework

MITRE’s Caldera project is an open-source platform that allows organizations to automatically emulate the tactics, techniques, and procedures (TTPs) used by real-world APTs. The platform is designed to be modular, which means that it can be customized to fit the specific needs of an organization. More information can be found in the official documentation and on GitHub. Red team operators can benefit from this by manually executing TTPs and blue team operators can run automated incident response actions. Caldera is also highly extensible, meaning that it can be integrated with other security tools to provide a comprehensive view of an organization’s security defenses. Moreover, it is built on the MITRE ATT&CK framework which is where the platform draws all the Tactics, Techniques and Procedures (TTPs) from.

Most common use cases of this framework include but not limited to:

  • Autonomous Red Team Engagements: This case is used to emulate the TTPs of known adversary profiles to discover gaps across an infrastructure, test the defenses currently in place and train operators on detection different threats.
  • Manual Red Team Engagements: This case allows red team operators to replace or extend the attack capabilities of a scenario, giving them more freedom and control over the current emulation.
  • Autonomous Incident Response: This case is used by blue team operators to perform automated incident response actions to aid them in identifying TTPs and threats that other security solutions may not detect and/or prevent.

Caldera consists of two main components:

  • The core system, which is the framework’s code, including an asynchronous command-and-control (C2) server with a REST API and a web interface.
  • Plugins which are separate repositories that expand the core framework capabilities and provide additional functionality. Examples include agents, GUI interfaces, collections of TTPs and more.

In Figure 1 below, we are greeted with when we login either as user red or blue and some basic terminology.

Caldera's Main Menu
Figure 1: Caldera’s Main Menu
  1. Agents: An agent is another name for Remote Access Trojan (RAT). These programs written in any language, execute an adversary’s instructions on compromised systems (victims). Often, an agent will communicate back to the adversary’s server through an internet protocol, such as HTTP, UDP or DNS. Agents also beacon into the C2 on a regular basis, asking the adversary if there are new instructions. If a beacon misses a regularly scheduled interval, there is a chance the agent itself has been discovered and compromised.
  2. Abilities: An ability is a specific set of instructions to be run on a compromised host by an agent immediately after sending the first beacon in.
  3. Adversaries: Adversary profiles are groups of abilities, representing the tactics, techniques, and procedures (TTPs) of known real-world APT groups. Adversary profiles are used when running an operation to determine which abilities will be executed.
  4. Operations: An operation is an attack scenario which uses the TTPs of pre-configured adversary profiles. An operation can be run automatically where the agents and the C2 server run without the operator’s interference and can only run tasks in the adversary profile. On the other hand, there is the manual mode where the operator approves every command before it is tasked to an agent and executed. Additionally in manual mode the operator can add extra TTPs. In order to run an operation at least one agent must be active.
  5. Plugins: They provide additional functionality over the usage of the framework.

Configuring an Agent

When we select “agents” from the figure 1 menu above, we are greeted with the figure 2 page.

Figure 2: Agent's Menu
Figure 2: Agent’s Menu

If we select the “Configuration” button, a new window opens where we can configure different options for all the agents created afterwards.

Agent's Configuration Menu
Figure 3: Agent’s Configuration Menu
  • Beacon Timer(s) = This fields sets the minimum and maximum amount of seconds the agent will take to beacon back home.
  • Watchdog Timer(s) = This field sets the number of seconds an agent has to wait, if the server is unreachable, before it is killed.
  • Untrusted Timer(s) = This field sets the number of seconds the server has to wait before marking a missing or unresponsive agent as untrusted. Furthermore, operations will not generate new links or send new instructions to untrusted agents.
  • Implant Name = This field sets the name for the newly created agents.
  • Bootstrap Abilities = This is a list of abilities to be run when a new agent beacons back to the server. By default, it runs a command which clears the command history.
  • Deadman Abilities = This is a list of abilities to be run immediately before an agent is killed.

To deploy an agent, we can press the “Deploy an Agent” button and we are greeted with this page. For this example, the agent Sandcat will be used.

By deploying the agent we refer to the process of installing and setting up the agent on the target system to enable it to perform specific actions or functions such as: monitoring, management, data collection, exploitation, reconnaissance and many more.

In figure 4, we can select the agent we want to deploy.

Agent Selection
Figure 4: Agent Selection

Next, in figure 5, we have to select the operating systems the agent will be deployed on.

Agent Platform Selection
Figure 5: Agent Platform Selection

In this example, the Linux operating system has been chosen and Caldera provides us with some options and some pre-built commands. These commands can be copied and run directly to the victim’s terminal for the agent to be deployed. There are different variations for the deployment of the selected agent such as:

  • It can be deployed as a red or blue agent.
  • It can be downloaded with a random name and start as a background process.
  • It can be deployed as a peer-to-peer (P2P) agent with known peers included in the compiled agent.

Moreover, the settings that can be modified are:

  • app.contact.http = This field is where the URL of the server’s address can be specified.
  • agents.implant_name = This field represents the name of the agent binary.
  • agent.extensions = This field takes a list of agent extensions to compile with the binary.
Agent's Deployment Options
Figure 6: Agent’s Deployment Options

After an agent has been deployed it will be shown in the agent’s window, as illustrated in Figure 7.

Active Agents
Figure 7: Active Agents

If an agent is selected, a new window opens that shows some settings that can be modified along with some information about the system the agent is installed on and a kill switch, as shown in figure 8.

Agent's Options After Deployment
Figure 8: Agent’s Options After Deployment
  • Contact = This field specifies the protocol in which the agent will communicate with the server.
  • Sleeper Timer = This is the same as the Beacon Timer(s).

Configuring an Adversary Profile

Caldera comes with pre-defined profiles to choose from, loaded with known TTPs. There is also the option to create a new profile with mixed TTPs, providing an operator more flexibility over the operation. An adversary profile can be created and configured in the “adversaries” window as shown below in figure 9.

Creating A New Adversary Profile
Figure 9: Creating A New Adversary Profile

After the “New profile” button is pressed, a name and a description for the new adversary profile will be asked.

A new ability can be added to the newly created profile by pressing the “add Ability” button.

Adding an Ability To an Adversary Profile
Figure 10: Adding an Ability To an Adversary Profile

Then a new window will open where the specific ability can be chosen and configured, as depicted in figure 11.

Configuring an Ability
Figure 11: Configuring an Ability

Here an already existing ability can be added by searching for it in the search bar or a new one can be configured by choosing a specific Tactic, Technique and Ability as shown above, along with all the details shown in the “Ability Details” section.

This newly created ability can be added to the TTPs of an already existing adversary profile by pressing the “Add Adversary” button. A new window will open to choose the appropriate profile.

Choosing an Adversary Profile
Figure 12: Choosing an Adversary Profile

Finally, by pressing the “Save Profile” button the new profile is created and can be added to an operation.

Save The New Profile
Figure 13: Save The New Profile

Configuring an Operation

An operation can be created and configured in the “operations” window.

Creating A New Operation
Figure 14: Creating A New Operation

After that a new window will open with all the modifiable settings.

Operation's Configuration
Figure 15: Operation’s Configuration
  • Operation Name = Specifies the name of the operation.
  • Adversary = Specifies a specific adversary profile to emulate along with the pre-configured TTPs associated with this profile.
  • Fact Source = In this field a fact source can be attached to the current operation. This means that the operations will start with some knowledge of the facts which can be used to fill in different variable inside some abilities. A fact is identifiable information about the target machine that can be used by some abilities, such as usernames, passwords, hostname etc.
  • Group = Specifies the collection of agents to run against
  • Planner = Specifies which logic library to use for the current operation. A planner is a Python module which contains logic that allows a running operation to make decisions about which abilities to use and in what order. The default planner is the “Atomic” which sends a single ability command to each agent in a group at a time. The order in which the commands are sent is the same as in the adversary’s profile.
  • Obfuscators = This field specifies which obfuscator to use to encode each command before they are sent to the agents. The available options are:
    • Base64 = Encodes the commands in base64
    • Base64jumble = Encodes the commands in base64 and then adds characters
    • Base64noPadding = Encodes the commands in base64 and then removes padding
    • Caesar cipher = Obfuscates the commands with the Caesar cipher algorithm
    • Plain text = No obfuscation
    • Steganography = Obfuscates the commands with image-based steganography
  • Autonomous = Specifies if the operations will run autonomously or manually. In manual mode the operator will have to approve each command.
  • Parser = Parsers are Python modules that are used to extract facts from command outputs. For instance, some reconnaissance commands can output file paths, usernames, passwords, shares etc. these facts can then be fed back into future abilities. Parsers can also be used to create facts with relationships between them, such as username and password facts.
  • Auto-close = This option automatically terminates the operation when there are no further actions left. Alternatively, it keeps the operation open until the operator terminates it manually.
  • Run state = This option pauses the operation on start or runs immediately
  • Jitter = Specifies the minimum and maximum number of seconds the agents will check in with the server while they are part of an active operations.
  • Visibility = This option specifies how visible should the operation be to the defense. Abilities with higher visibility than the operation’s will be skipped.

After the “start” button is pressed the operation will start and the results will be shown on the screen whether each task fails or succeeds. There is also the option to view each command and its result, as illustrated in figure 16.

Operation's results
Figure 16: Operation’s results

This was a red team operation, but in order to see the full picture some security solutions should also be running on the target systems to examine what was prevented and what went undetected.

Configure Automated Incident Response Plan

To form an incident response plan the “blue” user must be logged in.

The blue team’s main menu is a little different than the red team’s one. The main change is the “response plugin” which is a counterpart of the threat emulation plugins. At the time of writing this blog post, it contains 37 abilities and 4 defender profiles that focus on detection and response actions.

In the “Defenders” tab a new custom defender profile can be created and configured with the same way as the adversaries profile.

Incident Responder Section
Figure 17: Incident Responder Section

The profiles included in this plugin are:

  • Incident Responder
  • Elastic Hunter
  • Query Sysmon
  • Task Hunter

All available abilities for each defender profile can be viewed in the “abilities” section, after the specific profile has been chosen from the “response” tab, as shown in figure 17.

Defender Abilities
Figure 18: Defender Abilities

Defender abilities are classified by four different tactics:

  • Setup: These abilities prepare information to be used by other abilities
  • Detect: These abilities focus on finding suspicious behavior by continuously monitoring the ingested information and run as long as the operation is active.
  • Response: These abilities act autonomously once suspicious is detected. Such actions include, killing a process, modifying firewall rules, deleting of a file and so on.
  • Hunt: These abilities focus on searching for Indicators of Compromise (IOCs) via logs or file hashes.

Blue team operations are configured the same way as the red team operations. The main difference in the procedure is that the agent must be deployed as blue instead of red, in the “adversary” option a defender profile must be selected and in the Fact source section the “response” option must be selected.

Deploy Blue Agent
Figure 19: Deploy Blue Agent
Configuring A Blue Team Operation
Figure 20: Configuring A Blue Team Operation

The result structure is the same as the red team operation. The commands and their output are shown and whether they were successful or not.

Conclusion

In conclusion, leveraging the Caldera framework for adversary emulation presents a robust and proactive approach to enhancing cybersecurity defenses. Through the simulation of real-world attack scenarios, organizations can acquire invaluable insights into potential vulnerabilities and subsequently strengthen their incident response capabilities. The flexibility, modularity, and extensibility of Caldera establish it as an ideal tool for executing sophisticated emulation exercises.

By harnessing adversary emulation in conjunction with the Caldera framework, cybersecurity experts are equipped with the means to proactively safeguard their organizations against potential threats.

Profile photo

Konstantinos Pantazis

Konstantinos is a SOC analyst for NVISO security.
When he is not handling alerts, he is usually sharpening his skills for purple teaming.

Introducing BitSight Automation Tool

8 August 2023 at 07:00
BitSight Automation Featured Image
  1. Glossary
  2. Introduction
  3. BitSight
  4. Automation
    1. Operations
  5. Structure
  6. Installation
    1. Prerequisites
    2. Configuration
    3. Generating an API key for your BitSight account
    4. Adding the API Key to the BitSight Automation Tool
      1. Windows
      2. Linux
    5. The group_mapper.json file
    6. The guid_mapper.json file
    7. Configuring your Company’s structure
      1. The groups.conf file
      2. Letting BitSight Automation Tool handle the rest
    8. Binding into Executable
  7. Execution
    1. Usage
    2. Use Cases
      1. Functional Operation: Rating
      2. Functional Operation: Historical
      3. Functional Operations: Findings
      4. Functional Operation: Assets
      5. Functional Operation: Reverse Lookup
      6. Supplementary Operation: List
      7. Supplementary Operation: Update
    3. Task Scheduler / Cron Jobs
      1. Windows – Task Scheduler
      2. Linux – Cron Jobs
  8. Troubleshooting
    1. Total Risk Monitoring Subscription Required
    2. File not Found *.JSON
  9. Conclusion

Glossary

EntityA part of an organization that can be assessed as a single figure.
SubsidiarySame as an Entity on BitSight’s side.
Group ClusterA complex. It can contain entities/subsidiaries, or Groups, or more Group Clusters.
GroupA structure that can contain Entities.
Glossary

Introduction

In this blog post you will be introduced to the BitSight Automation Tool (https://github.com/NVISOsecurity/BitSight-Automation-Tool). BitSight Automation was developed to automate certain manual procedures and extract information such as ratings, assets, findings, etc. Automating most of these tasks is crucial for simplicity and time saving. Besides that, this tool also provides the possibility to collaborate with Scheduled Tasks and cronjobs. You can configure the tool to execute in certain intervals or dates, and retrieve the results from the desired folder without needing to interact with it.

BitSight

What is BitSight? BitSight is a solution that helps organizations perform three (3) main functions.

  1. Quantify their cyber risk
  2. Measure the impact of their security efforts
  3. Benchmark their performance against peers

It does all that by managing the company’s external facing infrastructure both automatically and manually, by allowing a company to provide updates to BitSight in order to keep their database up to date.

Other functions that are useful and provided by BitSight are:

  • Performing periodic vulnerability assessments on those assets to determine the risk factors and reports back the findings.
  • Identifies malicious activity such as botnet infections and much more that adds up to the risk factor.
  • Provides detailed remediation tips to remediate findings.

Automation

By utilizing parts of the BitSight API Python wrapper developed by InfosecSapper, we developed an open source tool for the community to use, that fully automates some of BitSight’s operations, which we have named BitSight Automation Tool. This tool has a lot of potential to expand further with even more operations based on the needs that might arise.

Operations

You might be wondering by this point, what operations can this tool automate? Currently we have 5 operations that can be automated + 2 supplementary to assist with the tool’s maintenance.

  1. Rating -> Retrieve the current score of an entity and confirm it’s above or equal to your company’s required security policies or digital mandate.
  2. Findings -> Generate a filtered list of vulnerabilities for an entity to remediate.
  3. Assets -> Retrieve the asset count and asset list of an entity, to validate your public IP space.
  4. Reverse Lookup -> Investigate where an IP, IP Range, domain or domain wildcard is attributed to and what IPs or domains it is associated with.
  5. Historical Ratings -> Sets up an overview of ratings for a given entity or group over a specified timeframe (maximum 1 year) to showcase in reports and review progress or regress.
  • List ->  Review the correlation between an entity’s custom given name and BitSight’s given name in a list for all defined entities.
  • Update -> Automatically update the tool and its respective JSON files.

Structure

The below image is a representation of the current state of the tool. At the time of writing the tool comes with the following structure.

  • BitSightAPI: This folder contains certain vital Python files from the BitSightAPI Python wrapper.
  • ArgumentsHandler.py: This file contains the instructions on how to parse the tool’s arguments.
  • README.md: This file is your friend. It contains all the information on how to execute with examples, as well as troubleshooting advice.
  • bitsight_automation.py: This file is the heart of the tool. You can execute this Python file and use it for your Scheduled tasks or cron jobs.
  • group_mapper.json: This file is a JSON structure which represents the mapping of the groups and entities within your organization. (More on this in a dedicated section)
  • guid_mapper.json: This file is a JSON structure which represents the mapping of the entities and their respective GUIDs assigned to them by BitSight. (A GUID is the unique handle used by BitSight to identify your subsidiary)
  • groups.conf: This file is the main configuration to define your groups. It defines the groups which the tool will interact with.
  • requirements.txt: This file indicates all the required libraries for the tool to operate.
Files Diagram
Figure 1: Files Diagram

Installation

Prerequisites

In order to use the tool, we first need to install it. Regardless of the Operating System you are using, you will need to have Python installed. The tool has been tested with Python 3.8 and 3.11 at the time of writing, so any Python 3.x.x version should work.

Note: When installing Python make sure to include it in your PATH and include the PIP package manager as well.

Next step would be to install the tool’s requirements. To do so, navigate to the tool’s directory within a command prompt / terminal or PowerShell window and execute the following command: `pip install -r requirements.txt`

All the prerequisites are installed at this point, but we still have a couple more steps to perform before we can use the tool.

Configuration

Now that you have installed the prerequisites, we are one step closer to utilizing the tool. Before we do so, we need to update a couple of files.

Generating an API key for your BitSight account

First you need to generate an API key from your account in BitSight. To do so,

  1. Login to your BitSight account
  2. On the top right corner of the UI, click on Settings.
  3. Select Account
  4. Scroll down until you see an “API Token” section
  5. Select “Generate API Token
  6. Copy the newly generated token.

Adding the API Key to the BitSight Automation Tool

In order for the BitSight Automation Tool to use this API key, you need to include it as an environmental variable in the system you will be running the tool on. We’ll do so below for both Windows and Linux.

Windows

For Windows systems,

  1. Open the search menu
  2. Search for “Edit the System Environment Variables
  3. Select that option
  4. Select “Environment Variables
  5. On the “User Variables” click “New
  6. In the “Variable Name” field, add the value “BITSIGHT_API_KEY
  7. In the “Variable Value” field, add the generated token you copied in the previous section.

Linux

For Linux systems,

  1. Open a terminal
  2. Replace the “{token}” section with your token, and execute the following command:
echo export BITSIGHT_API_KEY={token} >> ~/.bashrc

The group_mapper.json file

This file is the heart of the tool. It will be queried to retrieve entities for every operation.

An example of a group_mapper.json file can be found below.

{
    "Root":[
      {"Group1": "Single Entity"},
      {"Cluster Group2": ["EntityOne", "EntityTwo", "EntityThree"]},
      {"Bigger Cluster Group3": [
        {"SubCluster": ["Entity1", "Entity2"]},
        {"SubCluster2": ["EntityUno", "EntityDos"]}
      ]},
      "Random Entity that sits alone under the root"
    ]
}

The grouping subsidiaries can be organized like above. A few rules apply:

  • All of your subsidiaries must be under the “Root” subsidiary. The “Root” Subsidiary is your main subscription in BitSight that contains all the others (if any).
  • The Root subsidiary contains a list of other group subsidiaries or subsidiaries that are directly under the Root.
  • A Group or Group Cluster subsidiary can hold one, or more subsidiaries.
  • You can define bigger Cluster Group subsidiaries that contain even more group subsidiaries, which in turn can contain more subsidiaries.
  • You can create your own Group subsidiaries even if they don’t exist in BitSight for better structuring.
  • You can use your own naming conventions for all your subsidiaries and group subsidiaries without affecting BitSight or the retrieved information.

The guid_mapper.json file

This file is the tie between BitSight and the BitSight Automation Tool. This is where the magic happens where your naming conventions can relate back to specific subsidiaries within BitSight.

An example of a guid_mapper.json file can be found below.

{
    "Root": "463862495-ab29-32829-325829304823",
    "Group1": "463862495-ab29-32829-325829304824"
}

This structure is the most basic structure you can have. The only thing you have to do is to create a new line for each subsidiary and assign its GUID from BitSight.

Below you may find how to get the GUIDs for the subsidiaries. A few rules that apply:

  • The order doesn’t matter.
  • Make sure you use the exact naming convention you used on the group_mapper.json file. (It’s case sensitive)
  • Do not add any lists or other structures in this file. It should be one line for every subsidiary.
  • For groups you added in group_mapper.json file, that do not exist in BitSight, add a line like the following: “{your-group}”:”-“,

Configuring your Company’s structure

This step is mandatory for the tool to operate correctly. You can either use BitSight’s structure or you can create your own that suits best for your company.

Update Example
Figure 2: Update Example

The groups.conf file

Once you have completed the above steps, you need to modify one last item in the configuration of the tool.

The ‘groups.conf’ file structure should look like below (Figure 3)

Groups Modification
Figure 3: Groups Modification

You can add your groups one per line.

Note: Do not modify the first line. It should remain as is [Groups].

Letting BitSight Automation Tool handle the rest

You have completed the manual part of the configuration! Pretty simple, right?

Execute the tool with the update operation.

python bitsight_automation.py update

This will go through Bitsight and find any subsidiaries that are missing. It will then prompt you to include it into the configuration. Follow the steps, provide the required information and the tool will take care of the rest.

Example:

PS C:\Users\Konstantinos Pap\Desktop\BitSight Automation> python .\bitsight_automation.py update

Subsidiary Name – GUID not found in our configuration

Would you like to include it (Y/N)? y
What is the name of this entity? Test
Under which group should this entity fall under({your-groups}) ? myTestGroup
Adding Subsidiary Name with guid {GUID} as Test in myTestGroup

Configuration Updated

Binding into Executable

Before we dive into how to utilize this tool, let’s first dive into how we can make an executable bundle for this tool. Performing this step allows for easier sharing and makes the tool usable by anyone, from analyst to CISO, without any specific requirements. We can use Pyinstaller to create the .exe standalone file.

  1. First, open a terminal
  2. Now, we need to install pyinstaller. Use the `pip install pyinstaller` command to download and install pyinstaller.
  3. Afterwards, navigate into the tool’s directory from within the terminal window.
  4. Execute the following command: “pyinstaller bitsight_automation.py -p __pycache__ -F

Wait for a few seconds and notice there is a new directory created named “dist”. Grab the 2 .json files and the README.md file and copy them into the dist directory. Zip the contents of that directory and distribute that bundle on any Windows system. It will execute without any need for dependencies.

Note: You still have to export the environment variables to the new machines in order for the tool to be able to connect to BitSight.

Execution

Now that we installed and fully configured BitSight, we can go ahead and use its capabilities. As we already mentioned before, the tool allows for 5 different operations + 2 supplementary to assist with the tool’s maintenance.

We’ll first have a look at the usage menu of the tool and then we’ll navigate over a breakdown of each operation and how it works with examples.

Usage

Invoke the tool with its –help attribute.

PS ~/> python .\bitsight_automation.py --help

usage: bitsight_automation.py [-h] [-g {{your-groups}}] [-e ENTITY] [-v]
                              [-s {All,Critical-High,Critical,High,Low,Medium}]
                              [-so {alphanumerically,alphabetically}] [--search SEARCH] [--months MONTHS]
                              {rating,historical,findings,assets,reverse_lookup,list,update}

BitSight Automation tool to automate certain operations like historical report generation, findings categorization, asset list retrieval, reverse lookup of IP addresses and current ratings for entites

positional arguments:
  {rating,historical,findings,assets,reverse_lookup,list,update}
                        The operation to perform.

optional arguments:
  -h, --help            show this help message and exit
  -g {{your-groups}}, --group {{your-groups}} The group of entities you want to query data for.
  -e ENTITY, --entity ENTITY A specific entity you want to query data for
  -v, --verbose         Increase output verbosity
  -s {All,Critical-High,Critical,High,Low,Medium}, --severity {All,Critical-High,Critical,High,Low,Medium}
                        Level of Severity to be captured
  -so {alphanumerically,alphabetically}, --sort {alphanumerically,alphabetically}
                        Sort rating results either alphanumerically or alphabetically.
  --search SEARCH       IP or Domain to reverse lookup for.
  --months MONTHS       Add in how many months back you want to view data for. If you want 1 year, fill in 12 months.
                        Max is 12

For any questions or feedback feel free to reach out to [email protected]

Use Cases

Now we will go through a breakdown of all different use cases within BitSight Automation Tool. We’ll go through the functional first and we’ll leave the last 2 complementary at the end.

For every operation different arguments will be required or not needed. The tool will let you know if you missed something during runtime. Example output:

[-] You need to specify one of the arguments --country or --region.

Functional Operation: Rating

Use the rating operation to retrieve the current score of an entity or group in order to confirm if it’s above or equal to your policies. If a group is supplied this operation will output all of the subsidiaries under the specified group in the order you specified them in the JSON files (You also have the option to sort them alphanumerically)

Let’s try to fetch the current rating for our “Test” subsidiary.

PS ~/> python .\bitsight_automation.py rating -e Test

Test - 790
[+] Data saved to: 2023-03-17_bitsight_rating_Test.txt

Our Test Subsidiary has a score of 790. That’s an advanced score, so we can cross-verify with the company’s policies and take further action if needed. The results are also saved as a TXT file to allow easy copy/paste if required.

We can do the same thing for our Group and retrieve all the scores from all subsidiaries under our “Test Group”.

PS ~/> python .\bitsight_automation.py rating -g “Test Group”

[*] This may take a moment. Grab a coffee
Working on Test Group...
Test Group – 660
EntityOne - 620
Test Entity 2 - 760
EntityTwo - 770
[+] Data saved to: 2023-03-17_bitsight_rating_Test Group.txt

Notice we have retrieved ratings for all subsidiaries under “Test Group” in addition with the rating of “Test Group”. Some additional notes:

  • If “Test Group” didn’t have a GUID then it will not pull any data for it.
  • You can change the sorting algorithm using the -so argument.
  • You can retrieve the rating of “Test Group” without having to go through its subsidiaries. To do so, you can treat it like a normal entity, supplying it with the -e argument instead.
  • If your group is a big cluster group containing more groups that contain more subsidiaries, the tool will recursively query Bitsight for all those groups and subsidiaries under the cluster group and its respective groups and subsidiaries. (In order words, nothing will be skipped)
  • You can use -g {Root} to retrieve ratings for all the subsidiaries in your company. (Replace {Root} with the name you have given it)

Functional Operation: Historical

Use the historical operation to set up an overview of ratings for a given subsidiary or group over a specified timeframe (maximum 12 months) to showcase in reports and review progress or regress. Typically this operation is used with the -g argument but you can also utilize the -e argument for a given subsidiary only.

Let’s try to generate a report for our previous “Test Group” and its subsidiaries for the past year.

PS ~/> python .\bitsight_automation.py historical -g “Test Group” --months 12

Grab a coffee, this will take a while...
Working on Test Group...
[+] Data saved to 2023-03-17_Test Group_bitsight_historical_ratings_12_months.xlsx

Note: This command might take some time depending on the size of your organization + the number of subsidiaries it has to query data for. In any case, it is verbose enough to let you know in which group it is working on each time, so if you supplied a big cluster group you would have real time output of the progress.

The report:

Historical Report
Figure 4: Historical Report

There is a legend in the second sheet (tab) of the Excel file that denotes what these colors are and their scores – aligned with BitSight’s ratings and color coding.

Historical Score Indication
Figure 5: Historical Score Indication

Note: You can generate these types of reports with no limitation to a number of subsidiaries. You can even generate it for the entire organization using the Root subsidiary.

Functional Operations: Findings

Use the findings operation to generate a filtered list of vulnerabilities for a subsidiary to remediate. This operation works solely with subsidiaries and not groups! You also need to supply the severity level with the -s argument.

Note: Your subsidiaries need to have a ‘Total Risk Monitoring’ subscription for this command to work. Otherwise it will produce an error.

Let’s retrieve the findings for our ‘EntityOne’ subsidiary under ‘Test Group’ we used earlier. We will retrieve the Critical vulnerabilities only.

PS ~/> Python .\bitsight_automation.py findings -e EntityOne -s Critical

[+] Data saved to bitsight_Critical_findings_EntityOne_2023-03-17.csv

Critical findings were downloaded and saved to a file called ‘bitsight_Critical_findings_EntityOne_2023-03-17.csv’. You can now start working on remediating the findings or assign it to the proper internal team.

Functional Operation: Assets

Use the assets operation to retrieve the asset count and asset list of a subsidiary in order to validate your public IP space. This operation works solely with subsidiaries and not groups. This is a two-step process of querying. The operation first queries BitSight to retrieve the total count of public IPs in your subsidiary and then queries for the detailed asset list.

Note: This command requires a ‘Total Risk Monitoring’ subscription. If one is not available this command will produce an error.

Let’s attempt to retrieve the asset list for our ‘EntityOne’ subsidiary from the previous examples.

PS ~/ > python .\bitsight_automation.py assets -e EntityOne

EntityOne - 1410
*********** Asset List ************
[+] Asset List saved to: bitsight_asset_list_EntityOne_2023-03-17.csv

Note: This command will only fetch assets that are correctly attributed to this subsidiary. There’s a difference between correctly attributed by BitSight and internal/private Tagging.

Functional Operation: Reverse Lookup

Use this command to investigate where an IP, IP Range, domain or domain wildcard is attributed to and what IPs or domains it is associated with. This command only requires the –search argument.

Let’s attempt to find out where our test.com domain is attributed to and what public IPs it is associated with.

PS ~/> python .\bitsight_automation.py reverse_lookup --search test.com

test.com - ['<Redacted XX.XXX.XX.XXX>']: Found in: EntityOne

Supplementary Operation: List

Use this operation to review the correlation between an entity’s custom given name and BitSight’s given name in a list for all defined entities. This command does not require any arguments.

Let’s view our subsidiaries and their correlation to BitSight.

PS ~/> Python .\bitsight_automation.py list

Listing Configuration...
Root – My Test BitSight Organization
Group One – First Group Subsidiary
EntityOne- Entity1 Test
Test Entity 2- Entity 2 Test
EntityTwo – SSEntity 2

Note: The mapping is {my JSON representation – BitSight’s representation}. The two names are bound over the GUID unique value for a subsidiary.

Supplementary Operation: Update

Use this operation to automatically update the tool and its respective JSON files. We already saw how this command works in the configuration section.

Task Scheduler / Cron Jobs

As we already mentioned, we can either manually execute the BitSight Automation Tool or we can set it up to automatically execute on its own recurringly over a specified window of time. This is relatively easy to achieve in both Linux and Windows operating systems.

Windows – Task Scheduler

To achieve this in Windows we need to utilize the Task Scheduler utility provided by Microsoft itself. No need to download or install any additional software. Let’s configure it.

  1. Open the Task Scheduler.
  2. On the top left, select “Task Scheduler Library“. (Figure 6)
Task Scheduler Library
Figure 6: Task Scheduler Library
  1. On the top right, select “Create Basic Task
Create Basic Task
Figure 7: Create Basic Task
  1. Write down a name and description like below:
Creating Basic Task
Figure 8: Creating Basic Task
  1. Then click “Next
  2. Select a Monthly Trigger and click Next
Selecting Interval
Figure 9: Selecting Interval
  1. Next, select the dates you wish to execute. I will select all months, and run on every 1st Monday of the Month and click Next.
Selecting TimeFrame
Figure 10: Selecting Timeframe
  1. Choose “Start a Program” and click Next.
Selecting Action
Figure 11: Selecting Action
  1. Browse to your bitsight_automation.exe file you created earlier for the “Program/Tool”field. For the arguments field supply “historical -g {your-group} –months XX” and replace {your-group} with the group you wish to execute for, and how many months back you want. (Remember it’s up to 12 months maximum). For the “Start in (Optional)” field add in the path to the executable. This is required here because the BitSight Automation Tool expects the JSON files in the same directory it is executing from. Finally click Next.
Configuring the Program and Arguments
Figure 12: Configuring the Program and Arguments.
  1. Verify all is correct and click on Finish.

Your Scheduled task is ready. You can manually invoke it once to verify it’s working correctly from the right bar, selecting ‘Run

Running the task
Figure 12: Run

Note: You can follow this procedure for other tasks as well. (Update excluded as it requires manual intervention. However, the shell or prompt that will open will be interactive, so you can issue update comments on a daily basis anyway and if there are any, you can interact with the tool.)

Linux – Cron Jobs

The same process can be setup for Linux as well using the Cron Jobs it offers.

Write the following new line into the “/etc/crontab” file and replace ‘{your-tool-directory}’ with your tool’s directory (i.e. /opt/bitsight):

10 9 1 * * kali cd {your-tool-directory} && Python bitsight_automation.py historical -g Root –months 12

This will execute the tool every first of the month at 9:10 in the morning.

Troubleshooting

While executing this tool you might run into some issues here and there. This section will go over the 2 most common notifications you might encounter while using BitSight Automation.

Total Risk Monitoring Subscription Required

You may have noticed in the Execution section of a couple of operations a note saying “This operation requires a ‘Total Risk Monitoring’ subscription to work. Otherwise it will produce an error”. These types of errors are usually encountered in Findings and Assets operations. The output will look something like this.

If we remove the ‘Total Risk Monitoring’ subscription from EntityOne and execute the findings operation on it again, we will run into the following error:

PS ~/> Python .\bitsight_automation.py findings -c EntityOne -s Critical

It appears as there are no findings in EntityOne or there is something wrong with the API. Please validate the old fashioned way using your browser.
More Details: list index out of range
It might be the case you do not have a 'Total Risk Monitoring' subscription. The 'Risk Monitoring' subscription is unable to work with the API for this operation

Response: {'links': {'next': None, 'previous': None}, 'count': 0, 'results': []}

File not Found *.JSON

In case you execute the tool and it reports back with “File not found” it means that somehow the necessary files were deleted. In order to resolve this issue you need to create the files again with the text “{}”inside them.

PS ~/> Python .\bitsight_automation.py findings -c EntityOne -s Critical

File not found:  'group_mapper.json'. Please copy the  'group_mapper.json' to the same directory as this tool and try again.

Conclusion

This blogpost presented the BitSight Automation Tool as a valuable enhancement for organizations employing BitSight for performing external assessment and reducing exposure, as their solution.

Some key perks of this tool are as follow:

  1. Automates a lot of operations that otherwise are time consuming.
    1. Rating -> Retrieve the current score of an entity and confirm it’s above or equal to your company’s required security policies or digital mandate.
    2. Findings -> Generate a filtered list of vulnerabilities for an entity to remediate.
    3. Assets -> Retrieve the asset count and asset list of an entity, to validate your public IP space.
    4. Reverse Lookup -> Investigate where an IP, IP Range, domain or domain wildcard is attributed to and what IPs or domains it is associated with.
    5. Historical Ratings -> Sets up an overview of ratings for a given entity or group over a specified timeframe (maximum 1 year) to showcase in reports and review progress or regress.
  2. Allows the possibility to configure scheduled executions of the tool and create monthly/daily/yearly reports per your needs.
  3. Provides an easy to use command interface. It can also be compiled as an executable version to avoid having to install dependencies and to make it usable by anyone. (From analysts to CISO.)
Konstantinos Papanagnou

Konstantinos Papanagnou

Konstantinos is a Senior Cybersecurity Consultant at NVISO Security.

With a background in software engineering, he has an extensive set of skills in coding which helps him in day-to-day operations even in the Cybersecurity area. His motto; “Better spend 5 hours debugging your automation, than 5 minutes performing an automatable task”.

Unlocking the power of Red Teaming: An overview of trainings and certifications

31 July 2023 at 07:00
Title Image

NVISO enjoys an excellent working relationship with SANS and has been involved as Instructors and Course Authors for a variety of their courses:


As technology continues to evolve, so do the tactics and techniques used by cyber criminals. This means that staying up to date as a red team operator is crucial for protecting customers against the constantly changing threat landscape. Red team operators are tasked with simulating real-world attacks on a customer’s system to identify weaknesses and vulnerabilities before they can be exploited by malicious actors. By staying informed about the latest attack methods and trends, red team operators can provide more effective and relevant testing that accurately reflects the current threat landscape. Additionally, keeping up with emerging technologies and security measures can help red team operators develop new tactics and strategies to better protect customers from potential cyberattacks.

While red teams are primarily responsible for simulating attacks and identifying vulnerabilities, blue teams play a critical role in defending against these attacks and protecting an organization’s assets. Attending trainings that are typically attended by red teams can provide valuable insights and knowledge that blue teams can use to better defend their organization. By understanding the latest attack methods and techniques, blue teams can develop more effective defense strategies, identify potential vulnerabilities and patch them before they can be exploited by attackers. Additionally, attending these trainings can help blue teams better understand the tactics and tools used by red teams, allowing for more effective collaboration and communication between the two teams. Overall, attending red team training can help blue teams stay informed and prepared to defend against the constantly evolving threat landscape.

TL;DR;

If you do not have much time at hand, do not worry, the following tables may provide you a quick overview:

Certification NameBeginnerIntermediateExpert
Red Team Ops (CRTO1)🔑
Red Team Ops II (CRTO2)🔑
Certified Red Team Professional (CRTP)🔑
Certified Red Team Expert (CRTE)🔑
Certified Red Team Master (CRTM)🔑
Certified Az Red Team Professional (CARTP)🔑
Training NameBeginnerIntermediateExpert
Malware on Steroids🔑
Red Team Operations and Adversary Emulation (SEC565)🔑
Purple Team Tactics – Adversary Emulation for Breach Prevention & Detection (SEC699)🔑
RED TEAM Operator: Malware Development Essentials Course🔑
RED TEAM Operator: Malware Development Intermediate Course🔑
RED TEAM Operator: Malware Development Advanced – Vol.1🔑
Corelan “BOOTCAMP” – stack exploitation🔑

Disclaimer:

It is important to note that the certifications and trainings included in the review are not an exhaustive list of all the options available and are not in a specific order.
While the ones highlighted in the review are all excellent and worth considering, there may be other certifications and trainings that could also be beneficial for your specific needs and goals.
It is always essential to do your own research and carefully consider your options before deciding. Ultimately, the best certification or training for you will depend on your individual circumstances, interests, and career aspirations.

Certifications

Red Team Ops – CRTO1

The Red Team Ops 1 course is a very well done certification that teaches you the basic red team operator principles, adds handy tools for the beginning and shows techniques you will use as a red team operator.

You will learn how to start and configure the team server (in the course of the certification Cobalt Strike from FORTRA) as well as how to manage the listeners and touch the base of payload generation.

The certification is a must for beginners who want to learn how to go from the initial compromise, to moving laterally and in the end take over the whole domain.

Of course, Microsoft Defender (not Defender ATP/MDE), application whitelisting are also part of the course to prepare you for the much-needed evasion in the customer environments by using the artifact and resource kit available with Cobalt Strike.

Who should take this course?

If you are new to the game, this course is made for you! If you already have infrastructure security assessment experience, this course adds new attack paths to your inventory and includes some important tips for OPSEC which is a lot different in red team engagements to what you are known from an internal security assessment, where stealth is optional.

I enjoyed the exam a lot and in comparison to the price of SANS certifications, this is also a great opportunity for someone with a tighter budget, thanks Zeropoint Security!

Associated costs

365 GBP = 415,32 EUR = 452,89 USD (as of 04/04/2023)

The price includes the course materials as well as a voucher for the first exam attempt.

The RTO lab is sold as a subscription to those who have purchased the course.

The price is 20/40/60 GBP per month for 40/80/120 hours of runtime respectively.

Red Team Ops II – CRTO2

The Red Team Ops 2 course aims to build on the foundation of the Red Team Ops course in order to help you improve your OPSEC skills and show you ways to bypass more defense mechanisms.

Important to note here is, that this course is NOT a newer version or replacement of the first course.

The course will introduce the concept of public redirectors and rewrite rules to you, which can then be applied in the wild.

To help you understand the evasion techniques, some common Windows APIs are being covered as well as P/Invoke and D/Invoke which allow you to dynamically invoke unmanaged code and avoid API hooks.

Other indicators such as RWX memory regions and suspicious command lines will be treated with PPID and Command Line Spoofing.

Since Microsoft upped their game for security quite a bit, the Attack Surface Reduction should not be missed out on and as such is also included in this course with examples of how to bypass a subset of the default rules.

If you have struggled with Applocker in the past, welcome to the game. The bigger brother “Windows Defender Application Control (WDAC)” is waiting for you and allows the blue team to even better protect the environment.

The cherry on top of the course is the chapter treating different types of EDR hooks, syscalls and how to integrate goodies into the artifact kit.

Who should take this course?

If you already have completed the Red Team Ops 1 course this is a great addition to extend the knowledge gathered in the first round. In more mature environments you will face WDAC, EDRs from different providers and better blue team responses. Similar to the first course the price is very attractive and the hands-on experience in a lab and not just on paper is worth every dime.

If you think you already cover the first course with your knowledge, you can also jump to this one directly. The exam can cover parts of the first course to allow reconnaissance and privilege escalation/lateral movement, so I would not recommend going for CRTO2 without prior red teaming knowledge.

Associated costs

399 GBP = 453,86 EUR = 495,07 USD (as of 04/04/2023)

The price includes the course materials as well as a voucher for the first exam attempt.

The RTO II lab is sold as a subscription to those who have purchased the course.

The price is 15 GBP per month for 40 hours of runtime.

Certified Red Team Professional (CRTP)

The Certified Red Team Professional (CRTP) course provides you with a hands-on lab environment with multiple domains and forests to understand and practice cross trust attacks. This allows you to learn and understand the core concepts of well-known Windows and Active Directory attacks which are being used by threat actors around the globe.

Windows tools like PowerShell and others off the shelf features are being used for attacks to try scripts, tools and new attacks in a fully functional AD environment.

At the time of this blog post, the lab makes use of Microsoft Windows Server 2022 and SQL Server 2017 machines.

Lab environment (AD Attacks Lab (CRTP) (alteredsecurity.com))

Who should take this course?

If you are new to topics like Active Directory enumeration, how to map trusts of different domain, escalate privileges via domain attacks or Kerberos-based attacks like golden and silver tickets, this course is a good bet.

Additionally, the SQL server trusts and defenses as well as bypasses of defenses are covered.

Associated costs

The price depends on the practice lab access time that is bought:

30 Days – LAB ACCESS PERIOD – 249 USD ~ 227,58 EUR (as of 05/04/2023)

60 Days – LAB ACCESS PERIOD – 379 USD ~ 346,40 EUR (as of 05/04/2023)

90 Days – LAB ACCESS PERIOD – 499 USD ~ 456,08 EUR (as of 05/04/2023)

The course mentions the following content:

23 Learning Objectives, 59 Tasks, >120 Hours of Torture

https://www.alteredsecurity.com/adlab

Please keep in mind, that the certificate has an expiry time of three years and then needs to be renewed.

Certified Red Team Expert (CRTE)

After completing the Certified Red Team Professional (CRTP) you might be looking to explore more of Microsoft features that can be implemented in customer environments. This course will allow you to play with the Local Administrator Password Solution (LAPS), Group managed service accounts (gMSA) and the Active Directory Certificate Service (AD CS).

As customers often have resources in the cloud as well, Azure AD Integration (Hybrid Identity) and the attack paths therefore are presented in this course as well.

The person taking the course will learn to understand implemented defenses and how to bypass, for example: Just Enough Administration (JEA), Privileged Access Workstations (PAWs), Local Administrator Password Solution (LAPS), Selective Authentication, Deception, App Allowlisting, Microsoft Defender for Identity and more.

Lab environment (Windows Red Team Lab (CRTE) (alteredsecurity.com))

Who should take this course?

If you feel ready to dive into the more advanced defense mechanisms mentioned above, this course will certainly help you to identify these in an environment and navigate in a more mature environment covertly.

Associated costs

The price depends on the practice lab access time that is bought:

30 Days – LAB ACCESS PERIOD – 299 USD ~ 273,28 EUR (as of 05/04/2023)

60 Days – LAB ACCESS PERIOD – 499 USD ~ 456,08 EUR (as of 05/04/2023)

90 Days – LAB ACCESS PERIOD – 699 USD ~ 638,87 EUR (as of 05/04/2023)

The course mentions the following content:

28 Learning Objectives, 62 Tasks, >300 Hours of Torture

https://www.alteredsecurity.com/redteamlab

Please keep in mind, that the certificate has an expiry time of three years and then needs to be renewed.

Certified Red Team Master (CRTM)

The goal of this course is to compromise multiple forests with a minimal footprint, while gaining full control over the starting/home forest.

As consulting is more than just attacking infrastructure, the course also includes the submission of a report that contains details of attacks on target forests and details of security controls/best practices implemented on the starting/home forest.

Lab environment (Global Central Bank (CRTM) (alteredsecurity.com))

Who should take this course?

I would suggest this course if you want to put your technical knowledge to the test while also taking a step behind the lines of a blue team, as you need to document details of the security controls in place and how they could be mitigated best. This will help you to grow in the long term and make it possible to think like a defender in order to improve your evasion techniques.

Associated costs

The price depends on the practice lab access time that is bought:

30 Days – LAB ACCESS PERIOD – 399 USD ~ 364,68 EUR (as of 05/04/2023)

60 Days – LAB ACCESS PERIOD – 599 USD ~ 547,47 EUR (as of 05/04/2023)

90 Days – LAB ACCESS PERIOD – 749 USD ~ 684,57 EUR (as of 05/04/2023)

The course mentions the following content:

46 Challenges and >450 Hours of Torture

https://www.alteredsecurity.com/gcb

Please keep in mind, that the certificate has an expiry time of three years and then needs to be renewed.

Certified Az Red Team Professional (CARTP)

The Azure Active Directory is nowadays often used as an Identity and Access Management platform using the hybrid cloud model. It also allows on-prem Active Directory applications and infrastructure to be connected to the Azure AD. This step brings some very interesting opportunities to the plate, but with these also risks.

When talking about red teaming and penetration testing, these risks can be mapped onto the following phases: Discovery, Initial access, Enumeration, Privilege Escalation, Lateral Movement, Persistence and Data exfiltration. All of these phases are covered in the course. The most value for the customers results from not just identifying and abusing vulnerabilities in the environment, but also making clear suggestions for mitigations that can be implemented in the short or long term in the customer environment.

Lab environment (Attacking & Defending Azure AD Lab (CARTP) (alteredsecurity.com))

Who should take this course?

If you are a security professional trying to strengthen your skills in Azure cloud security, Azure Penetration testing or Red teaming in Azure environments, this is the right course for you!

Associated costs

The price depends on the practice lab access time that is bought:

30 Days – LAB ACCESS PERIOD – 449 USD ~ 410,38 EUR (as of 05/04/2023)

60 Days – LAB ACCESS PERIOD – 649 USD ~ 593,17 EUR (as of 05/04/2023)

90 Days – LAB ACCESS PERIOD – 849 USD ~ 775,97 EUR (as of 05/04/2023)

The course mentions the following content:

26 Learning Objectives, 77 tasks, 7 Live Azure Tenants, >140 hours of fun!

https://www.alteredsecurity.com/azureadlab

Please keep in mind, that the certificate has an expiry time of three years and then needs to be renewed.

Trainings

Malware on Steroids

https://0xdarkvortex.dev/training-programs/malware-on-steroids/

The course is dedicated to building your own C2 Infrastructure and Payload. To achieve that, an introduction towards Windows Internals which is followed by a full hands-on experience on building your own Command & Control architecture with different types of Initial Access payloads and their lifecycle such initial access, in-memory evasions, different types of payload injections including but not limited to reflective DLLs, shellcode injection, COFF injections and more, is being offered.

The course is offered in a time span of 4 days with 6-7 hours per day in an online interactive environment.

Lab environment (Dark Vortex (0xdarkvortex.dev)

Who should take this training?

If you always wanted to write your own C2 and create a dropper and stagers in x64 Assembly, C this course is perfect for you. Please keep in mind, that fundamental knowledge of programming with C/C++/Python3 and the familiarity with programming concepts such as pointers, references, addresses, data structures, threads and processes is listed as a requirement.

Associated costs

2,500 USD ~ 2281,95 EUR (as of 05/05/2023)

The price includes a certificate of completion, all the training materials including course PDFs/slides, content materials, source code for payloads and a python3 C2 built during the training program.

SEC565: Red Team Operations and Adversary Emulation

https://www.sans.org/cyber-security-courses/red-team-operations-adversary-emulation/

The SEC565 is one of the courses where you get to not only improve your technical abilities to abuse vulnerabilities, but also improve your skills around the whole engagement from planning to making sure the work you deliver follows a high quality and the best benefit for the customers.

The focus of the course is to learn how to plan and execute end-to-end Red Teaming engagements that leverage adversary emulation, including the skills to organize a Red Team, consume threat intelligence to map against adversary tactics, techniques, and procedures (TTPs), emulate those TTPs, report and analyze the results of the Red Team engagement, and ultimately improve the overall security posture of the organization.

The in person course is 6 days long for a reason. From planning the emulation to infrastructure and learning about initial access and persistence, the active directory attacks and ways to move from one compromised host to another is also included. As a red team documenting the abused vulnerabilities and obtaining the requested objectives is very important and therefore has a dedicated time slot as well.

The last block will contain a capture the flag red team lab consisting of 3 domains which includes Windows servers, workstations and databases as well as the active directory infrastructure to test the skills you learned earlier.

Who should take this course?

Defensive security professionals to better understand how Red Team engagements can improve their ability to defend by better understanding offensive methodologies, tools, tactics, techniques, and procedures.

Offensive security professionals looking to improve their craft and also improve their methodology around the technical part of the engagement (adversary emulation plan, safe sensitive data exfiltration, planning for retesting and more).

Associated costs

The course is being offered On-Demand (Online) and In Person.

The On Demand course is 8,275 USD ~ 7534.24 EUR (as of 02/05/2023)

The In Person course is priced at 7,695 EUR + OnDemand Bundle (785 EUR) = 8,480€ (as of 02/05/2023)

SEC699: Purple Team Tactics – Adversary Emulation for Breach Prevention & Detection

The SEC699 is one of the more unique courses where you get detailed insights into both red & blue team.

The course contents have been created by both blue teamers and red teamers and that is reflected in the detail of the course material.

The focus of the course is to learn how to emulate threat actors in a realistic enterprise environment and how to detect those actions.

As a proper purple teaming needs to follow a proper process, suitable tooling and planning, the course makes sure that these important parts are not missing. In-depth techniques such as Kerberos Delegation attacks, Attack Surface Reduction / AppLocker bypasses, AMSI, Process Injection, COM Object Hijacking and many more are being executed during the course and in order to grow on the challenge you will build SIGMA rules to detect these techniques.

Who should take this course?

Defensive security professionals looking to gain insights in the actual operation of carrying out attacks to understand the perspective of an attacker: Which tools are being used? What does a C2 setup look like? How does an attacker communicate with the C2 infrastructure? How can I use automation to my advantage?

Offensive security professionals looking to gain insights in logging & monitoring, which footprint and events are being generated using specific techniques and how the operational security can be improved to stay stealthier.

Associated costs

The course is being offered On-Demand (Online) and In Person.

The On Demand course is 7,785 USD ~ 7148.73 EUR (as of 04/04/2023)

The In Person course is priced at 7,170 EUR + OnDemand Bundle (785 EUR) = 7,955€ (as of 04/04/2023)

RED TEAM Operator: Malware Development Essentials

Malware, similar to software you use every day, has to be developed, and this course guides you through it.

Starting with what malware development is and how PE files are being structured, it helps you to understand how to encode and encrypt your payloads as well as how to store them inside a PE file.

Remote process injection as well as using an existing binary to backdoor is also being explained with hands-on code examples to follow and customize.

Who should take this training?

If you are getting started with developing your own loaders and stagers, this course is awesome to get the fundamentals right and gives you customizable source code that you can improve and build upon.

Associated costs

199 USD ~ 181,64 EUR (as of 05/04/2023)

A virtual machine with a complete environment for developing and testing your software, and a set of source code templates are included in the price.

RED TEAM Operator: Malware Development Intermediate

After the course “RED TEAM Operator: Malware Development Essentials” you might be wondering where to go next. This course uses the build foundation to extend the tooling with more code injection techniques, how you can build your own custom reflective binary as well as how to hook APIs in memory to monitor or evade functions.

Sooner or later, you have to migrate between processes that have loaded your shellcode so the section on how to migrate between 32- and 64-bit processes comes to the rescue. Finally, the course guides you on how to use IPC to control your payloads.

Who should take this training?

If you completed the course “RED TEAM Operator: Malware Development Essentials” and you are ready to take your skills to the next level, this course helps you to extend the kit you built in the first course.

Associated costs

229 USD ~ 209,03 EUR (as of 05/04/2023)

A virtual machine with a complete environment for developing and testing your software, and a set of source code templates are included in the price.

RED TEAM Operator: Malware Development Advanced – Vol.1

As the name of the course suggests, after the essentials and the intermediate course, the advanced course will teach you how to enumerate processes the modules and handles in order to identify a suitable process for injection. Payloads can not only be hidden in PE files and, as such, the course covers how to hide payloads in different parts of the NTFS, in the registry and in memory.

It demonstrates how any API (with any number of params) in a remote process can be called by using a custom “RPC” and how exception handlers can be abused.

You will learn how to build, parse, load and execute COFF objects in memory and much more.

Who should take this training?

After completing the Essentials and Intermediate course of the malware development series of Sektor7, I can only recommend this training to further strengthen your knowledge of how the Windows internals work and give you ideas for how to exploit them in the future.

Associated costs

239 USD ~ 218,15 EUR (as of 05/04/2023)

A virtual machine with a complete environment for developing and testing your software, and a set of source code templates are included in the price.

Corelan “BOOTCAMP” – stack exploitation

One thing to start with, the 2021 edition of the course is based on Windows 10/11 and contains an introduction to x64 stack-based exploitation in case you care for up-to-date material and operating systems.

Although the training is based on Windows 10/11, you have to start with the fundamentals by explaining the basics of stack buffer overflows and exploit writing.

The training provides you with a solid understanding of current stack-based exploitation techniques and memory protection bypass techniques. The training provider mentions that the course material is kept updated with current techniques, previously undocumented tricks and techniques, and details about research that was performed by the training author.

A small excerpt of the training contents:

  • The x86 environment
  • Stack Buffer Overflows
  • Egg hunters
  • ASLR
  • DEP
  • Intro to x64 stack-based exploitation

Who should take this training?

If you do like challenges, this training is for you. Anyone interested in exploit development or analysis is the target audience of this training.

The training itself does not provide solutions for any of the exercises that you will work through but instead provides help either during the course or after the course (via the student-only support system).

Associated costs

The In-Person training is listed at 2,500 EUR + 525 EUR VAT.

At the time of 05/04/2023 this is equal to 2738,89 USD + 575,17 USD VAT.

The path I chose to walk on

I started as a penetration tester / security consultant with a lot of self gained knowledge from home projects ranging from active directory setups at home to self built network attached storage and this helped me to have a good base with how to debug problems and general operating system usage.

During my security consulting path I then chose to start with the Offensive Security Certified Professional (OSCP) certification as this allowed me to understand some basic exploitation techniques and also get in contact with report writing and evidence collection.

Then there was a slight change in paths for dedicating my life to mobile security, but I always kept an eye on infrastructure security and did some projects in the mix.

After some years in the field, I knew I wanted a new challenge and decided to complete my CRTO1 certification.

I approached NVISO and after joining and the first larger projects I was hungry for more and completed my CRTO2 certification.

There are so many more trainings I have on my list, so keep it coming!

Education at NVISO

ARES assembles highly skilled expert professionals. This pool consists of people having 5+ years of experience in penetration testing and red team exercises, as well as blue team experts with knowledge on threat hunting and SOC operations.

The ARES team together currently holds the following certifications:

  • GPEN / GRID / GXPN / GCTI / GDAT / GCIA / GMOB
  • OSCP / OSEP / OSED / OSEE / OSWE / OSCE
  • CRTO1 / CRTO2
  • CRTP / CRTE / PACES / CARTP
  • eCPPTv2 / eWPTXv2
ARES Logo

Our ARES team at NVISO is dedicated to offer red team services to customers around the globe in order to identify gaps in the incident and response handling to improve the security posture of the companies many of us interact with daily.

See the ARES homepage for more information.

Steffen Rogge

Steffen is a Cyber Security Consultant at NVISO, where he mostly conducts Purple & Red Team assessments with a special focus on TIBER engagements.

This enables companies to evaluate their existing defenses against emulated Advanced Persistent Threat (APT) campaigns.

The SOC Toolbox: Analyzing AutoHotKey compiled executables

20 July 2023 at 07:00

One day, a long time ago, whilst handling my daily tasks, an alert was generated for an unknown executable that was flagged as malicious by Microsoft cloud app security.

When I downloaded the file through Microsoft security center, I immediately noticed that it might be an AutoHotKey script. Namely, by looking at the Icon, which is the AutoHotKey logo.

As with many unknown executables I like to inspect the executable in PE studio and look at the strings. URL patterns are a quick way to see if an executable could be exfiltrating if there was no obfuscation used.

In the strings section of PE studio there were multiple mentions of AutoHotKey, which confirmed my previous suspicions that this was indeed a AutoHotKey executable. A colleague of mine mentioned this YARA rule to detect AutoHotKey executables which could be used to identify this file.

AutoHotKey executable in PE studio

After a quick internet search I found the program Exe2Ahk (www.autohotkey.com/download/Exe2Ahk.exe) which promises to convert executables to AHK (AutoHotKey) scripts. However, this program did not work for me and I had to find another way to extract the AutoHotKey script.

Unsuccessful extraction using Exe2Ahk

Thanks to a form post on the Autohotkey forums. I found out that the uncompiled script is present in the RCDATA section of the executable. When inspecting the executable with 7zip, we notice that we can extract the script that is stored in the .rsrc\RCDATA folder. The AutoHotKey script is named: >AUTOHOTKEY SCRIPT<. The file can be extracted by simply dragging and dropping the file from the 7zip folder to any other folder on your pc.

RCDATA folder in 7Zip

Another website (where I unfortunately lost the URL to) mentioned that the same can be achieved via inspecting the file with Resource Hacker. Resource Hacker parses the PE file sections and can extract embedded files from those sections.

RCDATA folder in Resource Hacker

Once the file is extracted via your preferred method, you can open it in any text editor and start your analysis of the file, if you run in to any unknown methods or parameters used in the script or have difficulty with the syntax, the AutoHotKeys documentation can probably help you out.

In this case the file was not malicious, which is why we won’t go in more detail, but we have seen cases in the past where threat actors abused this tool to create malware.

Nicholas Dhaeyer

Nicholas Dhaeyer is a Threat Hunter for NVISO. Nicholas specializes in Threat Hunting, Malware analysis & Industrial Control System (ICS) / Operational Technology (OT) Security. Nicholas has worked in the NIVSO SOC solving security incidents for our MDR clients. You can reach out to Nicholas via Twitter or LinkedIn

Introducing CS2BR pt. II – One tool to port them all

17 July 2023 at 16:00

Introduction

In the previous post of this series we showed why Brute Ratel C4 (BRC4) isn’t able to execute most BOFs that use the de-facto BOF API standard by Cobalt Strike (CS): BRC4 implements their own BOF API which isn’t compatible with the CS BOF API. Then we also outlined an approach to solve this issue: by injecting a custom compatibility layer that implements the CS BOF API using the BRC4 API, we can enable BRC4 to support any BOF.

CS2BR really can port a whole bunch of BOFs!

I’m proud to finally introduce you to our tool CS2BR (“Cobalt Strike to Brute Ratel [BOF]”) in this blog post. We’ll cover its concept and implementation, briefly discuss its usage, show some examples of CS2BR in use and draw our conclusions.

I. The anatomy of CS2BR

The tool is open-source and published on GitHub. It consists of three components: the compatibility layer (based on TrustedSec’s COFFLoader), a source-code patching script implemented in Python and an argument encoder script (also based on COFFLoader). Let’s take a closer look at each of those individually:

The Compatibility Layer

As outlined in the first blog post, the compatibility layer provides implementations of the CS BOF API for the original beacons and also comes with a new coffee entrypoint that is invoked by BRC4, pre-processes BOF input parameters and calls the original BOF’s go entrypoint.

For practical reasons that become apparent further down this post, the layer is split into two files: one for the BOF API implementation (beacon_wrapper.h) and entrypoint (badger_stub.c), respectively.

The BOF API implementation borrows heavily from COFFLoader and adds some bits and pieces, such as the Win32 APIs imported by default by CS (GetProcAddress, GetModuleHandle, LoadLibrary and FreeLibrary) and a global variable for the __dispatch variable used by BRC4 BOFs for output. Note that as of this writing, CS2BR doesn’t implement the complete CS BOF API and lacks functions related to process tokens and injection, as those weren’t considered worthwhile pursuing yet.

The entrypoint itself, on the other hand, was built from scratch. Since BRC4’s coffee entrypoint can only be supplied with string-based parameters (whereas CS’ go takes arbitrary bytes), this custom one optionally base64-decodes an input string and forwards it to the CS go entrypoint. To generate the base64-encoded input argument, CS2BR comes with a Python script (encode_args.py, based on COFFLoader’s implementation) that assembles a binary blob of data to be passed to BOFs (such as integers, strings and files).

Patching source code

The compatibility layer alone only gets you so far though – it needs to be patched into a BOF somehow. That’s where the patcher comes in. It’s a Python script that injects the compatibility layer’s source code into any BOF’s source code. Its approach to this is simple and only consists of two steps:

  1. Identify original CS BOF API header files (default beacon.h) and replace their contents with CS2BR’s compatibility layer implementation beacon_wrapper.h.
  2. Identify files containing the original CS BOF go entrypoint and append CS2BR’s custom coffee entrypoint from badger_stub.c.

When I started working on the patcher’s implementation, I wasn’t sure just how tricky these two steps would be to implement: Would I need to come up with tons of RegEx’s to CS BOF API identify imports? Would I maybe need to parse the actual source code using the actual C grammar to find go entrypoints? Or would I need to compile individual object files and extract line-number information from their metadata?

Luckily, I didn’t have to deal with most of the above. The CS BOF API imports are consistently included as a separate header file called beacon.h, thus they can be found by name in most cases. To find the entrypoint, I wrote a single RegEx: \s+(go)\s*\(([^,]+?),([^\)]+?)\)\s*\{. Let’s briefly break it down using Cyril’s Regex Tester:

The regex used to identify the CS entrypoint in source code

The patterns matches:

  • “go” (optionally surrounded by whitespaces),
  • an open parenthesis denoting the start of the parameter list,
  • the first char* argument (which is any character but “,”),
  • the comma separating both arguments,
  • the second int argument (matching any character but the closing parenthesis),
  • the closed parenthesis denoting the end of the parameter list and
  • an open curly bracket denoting the start of the function definition.

This pattern allows CS2BR to identify the entrypoint, optionally rename it and reuse the exact parameter names and types. Once it identified the go entrypoint in a file, it simply appends the contents of badger_stub.c to the file. This stub contains forward-declarations of base64-decoding functions used in the custom coffee entrypoint, the new entrypoint itself, and the accompanying definitions of the base64-decoding functions. And that’s it – BOFs patched this way can now be recompiled and are ready to use in BRC4. If a BOF takes input from CNA scripts, one might need to use the argument encoder.

Encoding BOF Arguments

CS BOFs can be supplied with arbitrary binary data, and the first blog post showed that BRC4 BOFs can’t since their entrypoints are designed and invoked differently. To remedy this, CS2BR borrows a utility from COFFLoader and comes with a Python script that allows operators to encode input parameters for their BOFs in a way that can be passed via BRC4 into CSBR’s custom coffee entrypoint:

CS2BR's argument encoder

One drawback of using base64-encoding is the considerable overhead: base64 encodes 3 bytes of input into 4 bytes of ASCII, resulting in 33% overhead. As can be seen in the above screenshot, the raw data of about 6kB is encoded into about 8kB. The script also implements GZIP compression of input data, reducing the raw buffer to about 2.5kB and base64 data to about 3.5kB. As of this writing, however, CS2BR’s entrypoint doesn’t support decompression yet.

II. Using CS2BR

Using CS2BR is pretty straight-forward. You’ll need to patch & compile your BOFs only once and can then execute them via BRC4. If your BOFs accept input arguments, you’ll need to generate them via CS2BR’s argument encoder. Let’s have a look at the complete workflow.

1. Setup, Patching & Compilation

Again, we’ll use CS-Situational-Awareness (SA) as an example. First, clone SA and CS2BR:

git clone https://github.com/trustedsec/CS-Situational-Awareness-BOF
git clone https://github.com/NVISO-ARES/cs2br-bof/

Then, invoke the patcher from the cs2br-bof repo and specify the “CS-Situational-Awareness-BOF” directory you just cloned as the source directory (--src) to patch:

CS2BR's source code patcher

Finally, compile the BOFs as you would usually do:

cd CS-Situational-Awareness-BOF
./make_all.sh

That’s it, simple BOFs (such as whoami, uptime, …) that don’t require any input arguments can be executed directly through BRC4 now:

Executing a simple patched BOF without arguments

2. Encoding Arguments

In order to supply BOFs compiled with CS2BR with input arguments, we’ll use the encode_args script.

Let’s use nslookup as an exemplary BOF for this workflow. It expects up to three input parameters, lookup valuelookup server and type, as defined in CS-Situational-Awareness’ aggressor script:

alias nslookup {
	...
	$lookup = $2;
	$server = iff(-istrue $3, $3, "");
	$type = iff(-istrue $4, # ...
    ...
	$args = bof_pack($1, "zzs", $lookup, $server, $type);
	beacon_inline_execute($1, readbof($1, "nslookup", "Attempting to resolve $lookup", "T1018"), "go", $args);
}

The bof_pack call above assembles these variables into a binary blob according to the format “zzs” ($lookup and $server as null-terminated strings with their length prepended and $type as a 2-byte integer). This binary blob is disassembled by the BOF using the BeaconData* APIs.

BRC4 doesn’t support aggressor scripts, though, so CS2BR’s argument encoder serves as a workaround. As an example, let’s encode blog.nviso.eu for $lookup, 8.8.8.8 for $server and 1 for $type (to query A records, ref. MS documentation):

Encoding arguments for the nslookup BOF

The resulting base64 encoded argument buffer, DgAAAGJsb2cubnZpc28uZXUACAAAADguOC44LjgAAQA=, can then be passed to BRC4’s coffexec command and will be processed by CS2BR’s custom entrypoint and forwarded to the original BOF’s logic:

Running a patched BOF with generated input arguments

III. Where to go from here

Working on CS2BR has been a lot of fun and, frankly, also quite frustrating at times. After all, BRC4 isn’t an easy target system to develop for due to its black-box nature. This project has come a fairly long way nonetheless!

Conclusion

This blog post showed how CS2BR works and how it can be used. At this point, the tool allows you to run all your favorite open-source CS BOFs via BRC4. So in case you are used to a BOF-heavy workflow in CS and intend to switch to BRC4, now you got the tools to keep using the same BOFs.

Using CS2BR is straight-forward and doesn’t require special skills or knowledge for the most part. There are some caveats to it that should be considered before using it “in production” though:

  • Source code: CS2BR works only on a source code level. If you want to patch a BOF that you don’t have the source code for, this tool won’t be of much use to you.
  • API completeness: CS2BR does not (yet) support all of CS’s BOF C API: namely, the Internal APIs are populated with stubs only and won’t do anything. This mainly concerns BOFs utilizing CS’ user impersonation and process injection BOF API capabilities.
  • Usability: While CS2BR allows you to pass parameters to BOFs, you’ll still have to work out the number and types of parameters yourself by dissecting your BOF’s CNA. You’ll only need to figure this out once, but it’s a certain investment nonetheless.
  • Binary overhead: Patching the compatibility layer into source code results in more code getting generated, thus increasing the size of the compiled BOF. Also note that the compatibility layer code can get signatured in the future and thus become an IOC.

I’m convinced that most of those points don’t constitute actual practical problems, but rather academic challenges to tackle in the future. Overall, I think the benefit of being able to run CS BOFs in BRC4 outweighs CS2BR’s drawbacks.

Outlook

While I’m happy with the current implementation, I’m convinced it can be improved upon. Expect a third, final blog post about the next iteration of CS2BR. What is it going to be about, I hear you ask? Well, let me use a meme to tease you:

Teasing the next and final (?) blog post about CS2BR
That's me!

Moritz Thomas

Moritz is a senior IT security consultant and red teamer at NVISO.
When he isn’t infiltrating networks or exfiltrating data, he is usually knees deep in research and development, working on new techniques and tools in red teaming.

Transforming search sentences to query Elastic SIEM with OpenAI API

30 May 2023 at 09:48

(In the Blog Post, we will demonstrate a Proof-of-Concept on how to use a OpenAI’s Large Language Model to craft Elastic SIEM queries in an automated way. Be mindful of issues with accuracy and privacy before trying to replicate this Proof-of-Concept. More info in our discussion at the bottom of this article.)

Introduction
The primary task of a security analyst or threat hunter is to ask the right questions and then translate them into SIEM query languages, like SPL for Splunk, KQL for Sentinel, and DSL for Elastic. These questions are designed to provide answers about what actually happened. For example: “Identify failed login attempts, Search for a specific user’s login activities, Identify suspicious process creation, Monitor changes to registry keys, Detect user account lockouts, etc.”

The answers to these questions will likely lead to even more questions. Analysts will keep interrogating the SIEM until they get a clear answer. This allows them to piece together a timeline of all the activities and explain whether it is a false positive or an actual incident. To do this, the analysts need to know a bunch of things. First, they need to be familiar with several types of attacks. Next, they need to understand the infrastructure (cloud systems, on-premises, applications, etc.). And on top of all that, they must learn how to use these SIEM tools effectively.

Is GPT-3 capable of generating Elasticsearch DSL queries?
In this blog post, we will explore how a powerful language model by OpenAI can automate the last step and bridge the gap between human language questions and SIEM query language.

We will be presenting a brief demo of a custom chat web app that allows users to query Windows event logs using natural language and obtain results for incident handling. In our example, we used the TextDavinci-3 model from OpenAI and Elastic as a SIEM. We built the custom chat app, using vanilla JS for the client and NodeJS for the backend.

Architecture
In our design, we send the analysts question to OpenAI using their API within a custom prompt. Subsequently, the resulting Elastic Query is sent to the Elastic SIEM using its API. Lastly, the result from Elastic is returned to the user.

chat app openai api with elastic siem
Web app diagram

A: User asking in the chat
B: The web app sends the initial input, enhanced with a standard phrase, to guide the model in generating more relevant and coherent responses.
C: It gets back the response: corresponding Elasticsearch query
D: The web app sends the query to Elasticsearch, after some checks
E: Elasticsearch sends back the result to web app
F: Present the results to the user in table format

Demo

In this demo, we focused on querying a specific log source, namely the “winlogbeat” index. However, it is indeed possible to expand the scope of the query by incorporating a broader index pattern that includes a wider range of log sources, such as “Beats-*” (if we are utilizing Beats for log collectors). Another approach would be to perform a search across all available sources, assuming the implementation of the Elastic Common Schema (ECS) within Elasticsearch. For instance, if we have different log types, such as Windows event logs, Checkpoint logs, etc. and we want to retrieve these logs from a specific host name, we can utilize the “host.name” key in each log source (index). By specifying the desired host name, we can filter the logs and retrieve the relevant information from the respective log sources.

ecs example
Working with ECS

Deep drive
Below, we will go into detail on how we built the application.
To create this web app, the first thing we need is an API key from OpenAI. This key will give us access to the gpt-3 models and their functionalities.

create openai api key
Creating OpenAI API key

Next, we will utilize the OpenAI playground to experiment and interact with the TextDavinci-3 model. In this particular example, we made an effort to craft an optimal prompt that would yield the most desirable responses. Fortunately, the TextDavinci-3 model proved to be the ideal choice, providing us with excellent results. Also, the OpenAI API allows you to control the behavior of the language model by adjusting certain parameters:

  • Temperature: The temperature parameter controls the randomness of the model’s output. A higher temperature, like 0.8, makes the output more creative and random, while a lower temperature, like 0.1, makes it more focused and deterministic.
  • Max Tokens: The max tokens parameter allows you to limit the length of the model’s response. You can set a specific number of tokens to restrict the length of the generated text. Be aware that setting an extremely low value may result in the response being cut off and not making sense to the user.
  • Frequency Penalty: The frequency penalty parameter allows you to control the repetitiveness of the model’s responses. By increasing the frequency penalty (e.g., setting it to a value higher than 0), you can discourage the model from repeating the same phrases or words in its output.
  • Top P (Top Probability): The top_p parameter, also known as nucleus sampling or top probability, sets a threshold for the cumulative probability distribution of the model’s next-word predictions. Instead of sampling from the entire probability distribution, the model only considers the most probable tokens whose cumulative probability exceeds the top_p value. This helps to narrow down the possibilities and generate more focused and coherent responses.
  • Presence Penalty: The presence penalty parameter allows you to encourage or discourage the model from including specific words or phrases in its response. By increasing the presence penalty (e.g., setting it to a positive value), you can make the model avoid certain words or topics. Conversely, setting a negative value can encourage the model to include specific words or phrases.

Following that, we can proceed to export the code based on the programming language we are using for our chat web app. This will allow us to obtain the necessary code snippets tailored to our preferred language.

playground openai
OpenAI Playground code snippet

Also, it is worth mentioning that we stumbled upon an impressive attempt at dsltranslate.com, where you can check how ChatGPT translate a search sentence into an Elasticsearch DSL query (even SQL).

Returning to our experimental use case, our web app consists of two components: the client side and the server side. On the client side, we have a chat user interface (UI) where users can input their questions or queries. These questions are then sent to the server side for processing.

client custom chat app elasticsearch openaiai
UI chat

On the server side, we enhance the user’s questions by combining them with a predefined text to create a prompt. This prompt is then sent to the OpenAI API for processing and generating a response.

prompt send to openai
Backend code snippet- prompt OpenAI api

Once we receive the response, we perform some basic checks, such as verifying if it is a valid JSON object, before forwarding the query to our SIEM API, which in this case is Elastic. Finally, we send the reply back to the client by transforming the JSON response into an HTML table format.

Discussion
But many of the responses from OpenAI API are not correct…
You are absolutely right. Not all responses from the OpenAI API can be guaranteed to be correct or accurate. Fine-tuning the model is a valuable approach to improve the accuracy of the generated results.

Fine-tuning involves training the pre-trained language models like GPT-3 and TextDavinci-3 on specific datasets that are relevant to the desired task or domain. By providing a training dataset specific to our use case, we can enable the model to learn from and adapt to the context, leading to more accurate and tailored responses.

To initiate the fine-tuning process, we would need to prepare a training dataset comprising a minimum of 500 examples in any text format. This data set should cover a diverse range of scenarios and queries related to our specific use case. By training the model on this dataset, we can enhance its performance and ensure that it generates more accurate and contextually appropriate responses for our application.
Example:

{"prompt": "show me the last 5 logs from the user sotos", "completion": " {\n\"query\": {\n    \"match\": {\n..... "}
{"prompt": "...........", "completion": "................."}
....

Even if we invest efforts in fine-tuning the model and striving for improvement, it is important to acknowledge that new versions and functionalities are regularly integrated into the Elasticsearch query language. It is worth noting that the knowledge perspective of ChatGPT is limited to information available up until September 2021. Similar to numerous companies, Elastic has recently developed a plugin that enable ChatGPT to tap into Elastic’s up-to-date knowledge base and provide assistance with the latest features introduced by Elastic.

Everything seems perfect so far, but…what about security and privacy of data?

Indeed, privacy and security are important concerns when dealing with sensitive data, especially in scenarios where queries or requests might expose potentially confidential information. In the described scenario, the actual logs are not shared with OpenAI, but the queries themselves reveal certain information, such as specific usernames or host names (ex. “find the logs for the user mitsos” or “show me all the failed logon attempts from the host WIN-SOTO.”).

In accordance with the data usage policies of OpenAI API (in contrast to ChatGPT), it refrains from utilizing the data provided through its API to train its models or enhance its offerings. It is worth noting, however, that data transmitted to their APIs is handled by servers situated in the United States, and OpenAI retains the data you submit via the API for a period of up to 30 days for the purpose of monitoring potential abuses. Nevertheless, OpenAI grants you the ability to choose not to participate in this monitoring, thereby ensuring that your data remains neither stored nor processed. To exercise this option, you can make use of the provided form. Consequently, each API call initiates and concludes your data’s lifecycle. The data is transmitted through the API, and the API call’s response contains the resulting output. It does not retain or preserve any data transmitted during successive API requests.

In conclusion, by leveraging OpenAI’s language processing capabilities, organizations can empower security analysts to express their query intentions in a natural language format. This approach streamlines the SIEM query creation process, enhances collaboration, and improves the accuracy and effectiveness of security monitoring and incident response. With OpenAI’s assistance, bridging the gap between human language and SIEM query language becomes an achievable reality in the ever-evolving landscape of cybersecurity. Last but not least, the privacy issue surrounding ChatGPT and OpenAI API usage raises a significant point that necessitates thoughtful consideration, before creating new implementations.

nikos sam

Nikos Samartzopoulos

Nikos is a Senior Consultant in the SOC Engineer Team. With a strong background in data field and extensive knowledge of Elastic Stack, Nikos has cultivated his abilities in architecting, deploying, and overseeing Elastic SIEM systems that excel in monitoring, detecting, and swiftly responding to security incidents.

Enforce Zero Trust in Microsoft 365 – Part 3: Introduction to Conditional Access

24 May 2023 at 07:00
Enforce Zero Trust in Microsoft 365 - Part 3: Introduction to Conditional Access

This blog post is the third blog post of a series dedicated to Zero Trust security in Microsoft 365.

In the first two blog posts, we set the basics by going over the free features of Azure AD that can be implemented in an organization that starts its Zero Trust journey in Microsoft 365. We went over the Security Defaults, the per-user MFA settings and some Azure AD settings that allowed us to improve our default security posture when we create a Microsoft 365 environment.

Previous blog posts:

Introduction

In this blog post, we will see what Azure AD Conditional Access is, how it can be used to further improve security and introduce its integration capabilities with other services.

As a reminder, our organization has just started with Microsoft 365. However, we have decided to go for Microsoft 365 for our production environment. Therefore, we want to have a look at a more advanced feature, Azure AD Conditional Access policies. This feature requires an Azure AD Premium P1 license which comes as a standalone license or which is also included in some Microsoft 365 licenses (Microsoft 365 E3/A3/G3/F1/F3, Enterprise Mobility & Security E3, Microsoft 365 Business Premium, and higher licenses). Note that one license should be assigned to each user in scope of any Conditional Access policies.

Azure AD Conditional Access allows to take identity-driven signals to make decisions and enforce policies. They can be seen as if-then statements. For instance, if a user wants to access SharePoint Online, which is a Microsoft cloud application that can be integrated in such policies, the user, more specifically, the user’s request, is required to meet specific requirements, defined in those policies. Let’s now see what the capabilities of those policies are.

Conditional Access

This part will be more theoretical to make sure everyone has the basics. Therefore, if you are already familiar to Azure AD Conditional Access Policies, you can directly jump to the next section for the implementation where we go over some prerequisites and important actions that need to be done to avoid troubles when setting up those policies based on our hands-on experience.

Conditional Access signals

As we have seen, signals will be considered to make a decision. It is possible to configure the following signals:

  • User, group membership or workload identities (also known as service principals or managed identities in Azure): It is possible to target or exclude specific users, groups, or workload identities from a Conditional Access policy;
  • Cloud apps or actions: Specific cloud applications such as Office 365, the Microsoft Azure Management, Microsoft Teams applications, etc. can be targeted by a policy. Moreover, specific user actions like registering security information (registering to MFA or Self-Service Password Reset) or joining devices can be included as well. Finally, authentication context can also be included. Authentication contexts are a bit different as they can be used to protect specific sensitive resources accessed by users or user actions in the environment. We will discuss authentication contexts in details in later blog post;
  • Conditions: With an Azure AD Premium P1 license, specific conditions can be set. This includes:
    • The device platforms: Android, iPhone, Windows Phone, Windows, macOS and Linux;
    • The locations: Conditional Access works with Named Locations which can include country/countries or IP address(es) that can be seen as trusted or untrusted;
    • The client apps: client apps which support modern authentication: Browser and Mobile apps and desktop clients; and legacy authentication clients: Exchange ActiveSync clients and other clients;
    • Filter for devices: allows to target or exclude devices based on their attributes such as compliance status in the device management solution, if the device is managed in Microsoft Endpoint Manager or on-premises, or registered in Azure AD, as well as custom attributes that have been set on devices;
    • Note that these conditions need to be all matched for the policy to apply. If a condition such as the location is excluded and match an attempt to access an application, the policy will not apply. Finally, if multiple policies matched, they will all apply, and access controls will be combined (the most restrictive action will be applied in case of conflicts).

Conditional Access access controls

Then, we have the access controls which are divided into two main categories, the “grant” and the “session” controls. These access controls define the “then do this” part of the Conditional Access policy (if all conditions have matched as mentioned previously). They can be used to allow or block access, require MFA, require the device to be compliant or managed as well as other more specific controls.

Grant controls

  • Block access: if all conditions have matched, then block access;
  • Grant access: if all conditions have matched, then grant access and optionally apply one or more of the following controls:
    • No controls are checked: Single-Factor Authentication is allowed, and no other access controls are required;
    • Require Multi-Factor Authentications;
    • Require authentication strength: allows to specify which authentication method is required for accessing the application;
    • Require device to be marked as compliant: this control requires devices to be compliant in Intune. If the device is not compliant, the user will be prompted to make the device compliant;
    • Require Hybrid Azure AD joined devices: this control requires devices to be hybrid Azure AD joined meaning that devices must be joined from an on-premises Active Directory. This should be used if devices are properly managed on-premises with Group Policy Objects or Microsoft Endpoint Configuration Manager, formerly SCCM, for example;
    • Require approved client apps: approved client apps are defined by Microsoft and represent applications that supports modern authentication;
    • Require app protection policy: app protection policies can be configured in Microsoft Intune as part of Mobile Application Management. This control does not require mobile devices to be enrolled in Intune and therefore work with bring-your-own-device (BYOD) scenarios;
    • Require password change;
    • For multiple controls (when multiple of the aforementioned controls are selected):
      • Require all the selected controls;
      • Require one of the selected controls.

Session controls

  • Use app enforced restrictions: app enforced restrictions require Azure AD to pass device information to the selected cloud app to know if a connection is from a compliant or domain-joined device to adapt the user experience. This control only works with Office 365, SharePoint Online and Exchange Online. We will see later how this control can be used;
  • Use Conditional Access App Control: this is the topic of a later blog post, but it allows to enforce specific controls for different cloud apps with Microsoft Defender for Cloud Apps;
  • Sign-in frequency: this control defines how often users are required to sign in again every (x hours or days). The default period is 90 days;
  • Persistent browser session: when a persistent session is allowed, users remain signed in even after closing and reopening their browser window;
  • Customize continuous access evaluation: continuous access evaluation (CAE) allows access tokens to be revoked based on specific critical events in near real time. This control can be used to disable CAE. Indeed, CAE is enabled by default in most cases (CAE migration);
  • Disable resilience defaults: when enabled, which is the case by default, this setting allows to extend access to existing session while enforcing Conditional Access policies. If the policy can’t be evaluated, access is determined by resilience settings. On the other hand, if disabled, access is denied once the session expires;
  • Require token protection for sign-in sessions: this new capability has been designed to reduce attacks using token theft (stealing a token, hijacking or replay attack) by creating a cryptographically secure tie between the token and the device it is issued to. At the time of writing, token protection is in preview and only supports desktop applications accessing Exchange Online and SharePoint Online on Windows devices. Other scenarios will be blocked. More information can be found here.

Conditional Access implementation

Before getting started with the implementation of Conditional Access policies, there are a few important considerations. Indeed, the following points might determine if our Zero Trust journey is a success or a failure in certain circumstances.

Per-user MFA settings

If you decided to go for the per-user MFA settings during the first blog post, you might consider the following:

  • As mentioned before, Conditional Access policies can be used to enforce a sign-in frequency. However, this can also be achieved using the ‘remember multi-authentication’ setting. If both settings are configured, the sign-in frequency enforced on end users will be a mix of both configuration and will therefore lead to prompting users unexpectedly;
  • If trusted IPs, which require an Azure AD Premium P1 license, have been configured in the per-user MFA settings, they will conflict with named locations in Azure AD Conditional Access. Named locations allow you to define locations based on countries or IP address ranges that can then be used to allow or block access in policies. Besides that, if possible, named locations should be used because they allow more fine-grained configurations as they do not automatically apply to all users and in all scenarios;
  • Finally, before enforcing MFA with Conditional Access policies, all users should have their MFA status set to disabled.

Security Defaults

Moreover, if you opted for the Security Defaults, it needs to be disabled as they can’t be used together.

How and where to start?

Now that we have some concepts about Conditional Access and some considerations for the implementation, we can start with planning the implementation of our policies. First, we need to ensure that we know what we want to achieve and what the current situation is. In our case, we first want to enforce MFA for all users to prevent brute force and protect against simple phishing attacks.

However, there might be some user accounts used as services accounts in our environment, such as the on-premises directory synchronization account for hybrid deployments, which can’t perform multi-factor authentication. Therefore, we recommend identifying these accounts and excluding them from the Conditional Access policy. However, because MFA would not be enforced on these accounts, they are inherently less secure and prone to brute force attacks. For that purpose, Named Locations could be used to only allow these service accounts to login from a defined trusted location such as the on-premises network (this now requires an additional license for each workload identity that you want to protect: Microsoft Entra Workload Identities license). Except for the directory synchronization account, we do not recommend the use of user accounts as service accounts. Other solutions are provided by Microsoft to manage applications in Azure in a more secure way.

Our first policy could be configured as follows (note that using a naming convention for Conditional Access policies is a best practice as it eases management):

1. Assign the policy to all users (which includes all tenant members as well as external users) and exclude service accounts (emergency/break-the-glass accounts might also need to be excluded):

Conditional Access policy assignments
Assignments

2. Enforce the policy for all cloud applications:

Cloud applications
Cloud applications

3. Require MFA and enforce a sign-in frequency of 7 days:

Access controls
Access controls

4. Configure the policy in report-only first

Report-only mode
Report-only mode

We always recommend configuring Conditional Access policies in report-only mode before enabling them. The report-only feature will generate logs the same way as if the policies were enabled. This will allow us to assess any potential impact on service accounts, on users, etc. After a few weeks, if no impact has been discovered, the policy can be switched to ‘On’. Note that there might be some cases where you may want to shorten or even skip this validation period.

These logs can be easily access in the ‘Insights and reporting‘ panel in Conditional Access:

Conditional Access Insights and reporting
Conditional Access Insights and reporting

Conclusion

In this third blog post, we learned about Conditional Access policies by going over a quick introduction on Conditional Access signals and access controls. Then, we went over some implementation considerations to make sure our Zero Trust journey is a success by preventing unexpected behaviors and any impact on end users. Finally, we implemented our very first Conditional Access policy to require Multi-Factor Authentication on all users except on selected service accounts (which is not the best approach as explained above).

If you are interested to know how NVISO can help you planning your Conditional Access policies deployment and/or support you during the implementation, feel free to reach out or to check our website.

In my next blog post, we will see which policies can be created to enforce additional access controls without requiring user devices to be managed in Intune to further protect our environment.

About the author

Guillaume Bossiroy

Guillaume is a Senior Security Consultant in the Cloud Security Team. His main focus is on Microsoft Azure and Microsoft 365 security where he has gained extensive knowledge during many engagements, from designing and implementing Azure AD Conditional Access policies to deploying Microsoft 365 Defender security products.

Additionally, Guillaume is also interested into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

Introducing CS2BR pt. I – How we enabled Brute Ratel Badgers to run Cobalt Strike BOFs

15 May 2023 at 07:00

If you know all about CS, BRC4 and BOFs you might want to skip this introduction and get right into the problem statement. You can also jump right to the solution.

Introduction

When we conduct Red Team assessments at NVISO, we employ a wide variety of proprietary and open source tools. One central component in these assessments is the command & control (C2) framework we use to remotely interact with compromised machines and move laterally through our targets’ networks. They usually feature a C2 server for central access, implants (analogous to bots in botnets) that execute commands, and client interfaces that allow red team operators to interact with the implants. Among others, there are two popular C2 frameworks that we use: Cobalt Strike and Brute Ratel C4.

Both C2s are proprietary and they have a lot of features in common. A particular capability they share is execution of beacon object files (BOFs). Normally you work with object files during compilation of C and C++ programs as they contain the compiled code of individual C/C++ source files and are not directly executable.

CS and BRC4 provide a mechanism to send BOFs to implants and execute their code on the remote machines. And the best thing about it is: one can write their own BOFs and have implants execute them. This comes with quite a set of benefits:

  • Implants don’t need to implement a lot of capabilities as those capabilities can be streamed and executed on demand, reducing the implant’s footprint.
  • Using custom BOFs, operators have finer control over the exact way an implant interacts with the target system. They can choose to implement new features or operate more covertly and OPSEC-safe.
  • While the C2s might be proprietary, BOFs can be open-source and shared with everyone.

There are many open-source BOFs available, such as TrustedSec’s CS-Situational-Awareness, that can easily be used in various C2s like CS and sliver. Nearly all of these BOFs use Cobalt Strike’s de-facto BOF API standard – which isn’t compatible with Brute Ratel’s BOF API. Thus, the vast majority of available BOFs is incompatible with BRC4.

Turns out there are only very few BRC4 BOFs!

In this blog post, we present an approach to solve this problem that enables Brute Ratel’s implants (“badgers”) to run BOFs written for Cobalt Strike. The tool we developed based on this approach will be presented in a follow-up blog post.

I. So what’s the exact problem?

In theory any BOF can be executed by either C2 framework so long as they don’t make any use of the C2-specific APIs. Practically, this doesn’t make much sense since using these APIs is required for basic tasks such as sending back information to operators.

The following paragraphs break down both BOF APIs in order to help understand how they’re incompatible.

Cobalt Strike’s BOF C API

Cobalt Strike BOF C API

Cobalt Strike splits its APIs into roughly four distinct groups:

  • Data Parser API: provides utilities to parse data passed to the BOF. This allows BOFs to receive arbitrary data as input, such as regular string-based values but also arbitrary binary data like files.
  • Output API: lets BOFs output raw buffers and format output. The output is sent back to operators.
  • Format API: allows BOFs to format output in buffers for later transmission.
  • Internal APIs: feature several utilities related to user impersonation, privileges and process injection. BRC4 doesn’t currently have an equivalent API.

Furthermore, the signature of CS BOFs entrypoints is void go(char*, int) and explicitly expects binary data to be passed and to be used with the Data Parser API.

Brute Ratel C4’s BOF C API

Brute Ratel BOF C API

Brute Ratel C4’s API on the other hand comes as a loose list that I grouped in this diagram for simplicity:

  • Output API: contains output printf-like functions for regular ANSI-C strings and wide-char strings.
  • String API: features various strlen and strcmp functions for regular ANSI-C strings and wide-char strings.
  • Memory API provides convenient memory-related functions for allocating, freeing, copying and populating buffers.

The signature of BRC4 BOF entrypoints is void coffee(char**, int, WCHAR**) and explicitely expects string-based inputs (similar to regular executable’s void main(int argc, char* argv[])).

Comparison & Conclusion

When comparing their APIs, it becomes apparent that CS and BRC4 follow different approaches to their APIs:

  • While both C2s provide some convenience APIs, Cobalt Strike’s APIs feature a higher abstraction level. As a result, CS doesn’t only feature an API dedicated to output (as does BRC4) but also one for formatting output.
  • CS provides advanced APIs (e.g. the “internal” ones) while BRC4 provides mostly low-level APIs.
  • Both differ greatly in their approach to passing inputs to BOFs: Cobalt Strike allows passing arbitrary binary data and provides a separate API for this task while BRC4 sticks to the traditional main entrypoint and its GUI only allows operators to pass strings to BOFs.
CS BOFs are (almost) the same as BRC4 BOFs - or at least BRC4 would like you to think that.

Actually BRC4’s documentation makes porting BOFs from CS to BRC4 look like an easy task. Simply trying to map the CS’s BOF API to BRC4’s shows that this is a more intricate task:

Cobalt Strike and Bute Ratel C4 BOF API mapping

As you can see, there are only very few CS APIs that (more or less) can be mapped to BRC4’s APIs. What are the implications for porting CS BOFs to BRC4 then? Well, it’s going to require some engineering.

II. Working out a solution

Now that we know how BRC4’s and CS’s BOF APIs are different from each other, we can work out a solution. Well, I’d love to tell you that that was the approach I took: read up on the problem’s intricacies first and then work out a well-structured and thought out solution. Things went a little different though, and I’d like to show you.

Approach 1: The naïve way

My first approach at porting BOFs from CS to BRC4 was based on the BRC4 documentation and involved only three steps:

  • Replace the go(char*, int) entrypoint with coffee(char**, int, WCHAR**).
  • Remove CS API imports (“beacon.h”) and add BRC4 API imports (“badger_exports.h”).
  • Replace uses of CS APIs with BRC4 APIs.

That looks easy to do! So let’s test this using the DsGetDcNameA example they posted in the documentation:

BRC4 DsGetDcNameA BOF

Nice, that worked very well! How about a real world example of an open source BOF: Outflank’s Winver BOF grabs the exact windows version of the victim machine. Again we replace the entrypoint, API imports and API uses:

#include "badger_exports.h"

//snip

VOID coffee(char** Args, int len, WCHAR** dispatch) {
	// snip
	dwUBR = ReadUBRFromRegistry();
	if (dwUBR != 0) {
		BadgerDispatch(dispatch, "Windows version: %ls, OS build number: %u.%u\n", chOSMajorMinor, pPEB->OSBuildNumber, dwUBR);
	}
	else {
		BadgerDispatch(dispatch, "Windows version: %ls, OS build number: %u\n", chOSMajorMinor, pPEB->OSBuildNumber);
	}
	
	return;
}
Common output with BOFs: not much to work with!

Running this gives us… nothing! This is a problem you’ll encounter when working with BOFs: you won’t receive any feedback when they aren’t executed or crash, making debugging and identifying the root cause just a bit harder.

But what was the problem in this case? Well, I got stuck for a bit at this point and after digging into the compiled BOF I noticed that there were WinAPI calls in the code that were not explicitly declared as imports:

DWORD ReadUBRFromRegistry() {
	//snip
	_RtlInitUnicodeString RtlInitUnicodeString = (_RtlInitUnicodeString)
		GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlInitUnicodeString");

Cobalt Strike’s BOF documentation says that “GetProcAddress, LoadLibraryA, GetModuleHandle, and FreeLibrary are available within BOF files” and don’t need to be explicitly imported by BOFs. This doesn’t apply to BRC4 though so imports for those need to be added:

WINBASEAPI FARPROC WINAPI KERNEL32$GetProcAddress (HMODULE hModule, LPCSTR lpProcName);
WINBASEAPI HMODULE WINAPI KERNEL32$GetModuleHandleA (LPCSTR lpModuleName);
WINBASEAPI HMODULE WINAPI KERNEL32$GetModuleHandleW (LPCWSTR lpModuleName);
WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR lpLibFileName);
WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryW (LPCWSTR lpLibFileName);
WINBASEAPI BOOL  WINAPI KERNEL32$FreeLibrary (HMODULE hLibModule);

#ifdef GetProcAddress
#undef GetProcAddress
#endif
#define GetProcAddress KERNEL32$GetProcAddress
#ifdef GetModuleHandleA
#undef GetModuleHandleA
#endif
#define GetModuleHandleA KERNEL32$GetModuleHandleA
#ifdef GetModuleHandleW
#undef GetModuleHandleW
#endif
#define GetModuleHandleW KERNEL32$GetModuleHandleW
#ifdef LoadLibraryA
#undef LoadLibraryA
#endif
#define LoadLibraryA KERNEL32$LoadLibraryA
#ifdef LoadLibraryW
#undef LoadLibraryW
#endif
#define LoadLibraryW KERNEL32$LoadLibraryW
#ifdef FreeLibrary
#undef FreeLibrary
#endif
#define FreeLibrary KERNEL32$FreeLibrary

Note that the macros allow us to leave the original function calls in the BOF untouched. If we didn’t do that, we would need to prepend KERNEL32$ to every call of the functions listed above.

After adding those and recompiling, we can run the BOF again and now it runs just fine:

Running a CS BOF in BRC4 after adding default imports

That’s great! However this approach is pretty limited. Let’s have a look at some of its shortcomings.

Caveat

The naïve approach works great for very simple BOFs that don’t use any of CS’s higher-level APIs. Many of the advanced BOFs use some of those APIs though.

Let’s examine TrustedSec’s sc_enum as it’s a very useful BOF and great example: it allows an operator to enumerate Windows services on a target machine. If we were to perform our simple 3-step approach again we’d hit a roadblock:

VOID go( 
	IN PCHAR Buffer, 
	IN ULONG Length 
) 
{
	const char * hostname = NULL;
	const char * servicename = NULL;
	DWORD result = ERROR_SUCCESS;
	datap parser;
	init_enums();
	BeaconDataParse(&parser, Buffer, Length);
	hostname = BeaconDataExtract(&parser, NULL);
	//snip
	if ((gscManager = ADVAPI32$OpenSCManagerA(hostname, SERVICES_ACTIVE_DATABASEA, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

You can see that this BOF takes the hostname parameter from the “Data Parser API” (BeaconDataExtract) that isn’t present in BRC4. There’s no equivalent to this API in BRC4.

At this point I figured that instead of coming up with some hacky fix I’d work out a proper solution that works more reliably and is more flexible: after all, I didn’t want to manually edit all the BOFs I use on a regular basis and troubleshoot API replacements.

Approach 2: True Compatibility

Since replacing APIs was already tricky in some cases and just impossible for higher-level APIs, I was searching for a solution that allowed me to (ideally) not touch any API calls in BOFs’ source code at all. There are two challenges to solve: allowing BOFs to call CS APIs and transforming their entrypoint to BRC4’s signature.

Compatibility Layer

Luckily, I wasn’t the first one to attempt this: TrustedSec’s COFFLoader is able to execute arbitrary compiled CS BOFs which means that it treats BOFs as blackboxes and introduces a compatibility layer that implements the CS BOF API. With this approach in mind I modelled the following design:

The compatibility layer

The idea is simple:

The CS API definitions included in BOF source code, usually called beacon.h, are replaced with stubs that don’t import the actual API but use the compatibility layer code. The compatibility layer imports the BRC4 BOF API and calls it as needed. COFFLoader’s compatibility layer is very readable and straight-forward to understand. It implements all the higher-level concepts missing in the BRC4 API. One only needs to copy their implementation and swap out some bits that require imports, such as string or memory utilities. They should be replaced with BRC4’s equivalents (e.g. replacing memcpy with BadgerMemcpy) or, less ideally, with MSVCRT imports (e.g. vsnprintf for string formatting). For example, the BeaconFormatAlloc API can be implemented as follows:

void BeaconFormatAlloc(formatp* format, int maxsz) {
	if (format == NULL) return;
	format->original = (char*)BadgerAlloc(maxsz);
	format->buffer = format->original;
	format->length = 0;
	format->size = maxsz;
}

For the sake of completeness: the compatibility layer should also include imports to the WinAPI functions included by default in CS (GetProcAddress, LoadLibraryA, GetModuleHandle, and FreeLibrary).

As a result, following this approach won’t tamper with the BOFs’ original logic but lets us implement the CS API ourselves, which in turn allows our BOFs to run in BRC4 now. Well, almost: the entrypoint isn’t compatible yet, and that’s not necessarily trivial.

Wrapping the Entrypoint

As we saw in the first attempt, porting the entrypoint from CS to BRC4 BOFs isn’t really tricky as we only need to change the function signature. It does get tricky if our BOF uses its start parameters (and thereby CS’s Data Parser API) though:

This API allows passing arbitrary data to CS BOFs. To achieve this, CS BOFs can ship with CNA scripts that allow the CS client to query input data (such as files) from operators, which the CNA assembles into a binary blob. This blob is sent along with the BOF itself to the implant (“beacon”). The BeaconData* APIs (which make up the Data Parser API) allow BOFs to disassemble this blob into structured data again. BRC4 doesn’t have this scripting capability and its BOF entrypoint only allows passing string-based arguments instead.

Again, COFFLoader solved the same problem before: it comes with a Python script that encodes arbitrary input into a hex-string that can be deserialized to a byte-buffer and passed to CS BOF entrypoints. Following the same approach, I worked out the following rather simple addition to the design above:

Wrapped entrypoint

Once more, the idea is simple:

Operators encode their inputs to string and pass it to the BOF using BRC4’s coffexec command. A minimal BRC4 entrypoint is appended to the BOF source code. This entrypoint decodes supplied input strings to a buffer and passes that buffer to the original CS entrypoint.

Summary

In essence, this approach consists of only three steps:

  1. Replace CS API imports with compatibility layer implementations
  2. Wrap CS entrypoint with a custom BRC4 entrypoint that prepares input for the Data parser API
  3. Manually encode execution parameters

This still isn’t a perfect solution but leaves us with a couple of pros and cons:

  • ✅ Doesn’t touch the original BOF’s logic
  • ✅ Flexibility: the same approach works for most (if not all) BOFs out there
  • ❌ Requires (somewhat) elaborate compatibility implementation
  • ❌ Requires some way to inject the compatibility layer (e.g. via source code)

III. Coming up next

Now that we have a solid and flexible approach to run CS BOFs on BRC4, there’s only one thing missing – a tool that automates it all!

We will publish CS2BR – a tool that does just that – as an open source project on Github along with a follow-up blogpost all about it soon. Stay tuned!

Moritz Thomas

Moritz Thomas

Moritz is a senior IT security consultant and red teamer at NVISO.
When he isn’t infiltrating networks or exfiltrating data, he is usually knees deep in research and development, working on new techniques and tools in red teaming.

We’re celebrating our 10th anniversary!

15 May 2023 at 06:54


From 5 people to almost 250 people. From working from our founders’ apartment to five offices in four countries. From an unknown challenger to being a reference in multiple fields in cyber security.

As a company, NVISO has come a long way since 2013 and we want to take a moment to celebrate what we have accomplished together so far.

NVISO celebrates a decade of European cyber security expertise

In 2013, NVISO was founded by five young security professionals with a dream:
To build a home and a hub for cyber security experts, here in the heart of Europe.

  • A team built on strong values.
  • A place that prioritizes personal growth and encourages everyone to innovate.
  • A community of experts that strives to be the best at what they do.
  • All working towards the mission of protecting European society from potentially devastating cyber attacks.

Together, we made it a reality!

This would not have been possible without the trust of our clients & partners and, most crucially, the dedication of every single NVISO bird. Thank you all!

Over the past decade, our team has made significant contributions to the field of cybersecurity through research and innovative solutions.

So, let’s take a trip down memory lane and revisit ten of the most influential articles from our blog!

  1. ApkScan
    Back in 2013, our first research project was a scanner for APKs; that Android malware analysis tool was very successful, being cited in academic papers, and helped us rapidly build knowledge and experience with what was then a relatively new challenge, mobile security. (Read more)
  1. Intercept Flutter traffic on iOS and Android
    Mobile security remains one of our big focus points, and this blogpost offers practical guidance for other testers on how to bypass SSL pinning, intercept HTTPS traffic, and use ProxyDroid during their mobile security assessments. (Read more)
  1. My journey reaching #1 on Hack The Box Belgium – 10 tips, tricks and lessons learned
    Inspiring others by sharing a personal success story – in this case, reaching the #1 spot on Hack The Box Belgium – is something we really encourage our colleagues to do. Combining hands-on tips with a few motivational memes mixed was the recipe for this popular & often-shared blog post! (Read more)

  2. Painless Cuckoo Sandbox Installation
    Sharing hands-on practical tutorials on how to solve a certain problem we had to deal with ourselves, has proven to be a good source for blog posts: practical tutorials where we share source code are some of the most searched blog posts we publish. This particular blog post explains how to set up a Cuckoo sandbox for analyzing malware samples, which is useful for blue team members who need to analyze a suspected malware sample without submitting it to online malware analysis services that may alert adversaries. (Read more)
  1. A practical guide to RFID badge copying
    Deciding which information (not) to publish is always an important balancing act: on one hand, we want to share important information about vulnerabilities as much as possible, while also protecting potential victims without encouraging illicit use of the information. We decide to share this particular blog post to raise awareness about the potential security risks associated with RFID card reading systems, which are often the sole factor of security that prevents unauthorized access to buildings, server rooms, and offices. The post demonstrates how easy it is to clone and abuse RFID cards using specialized hardware, such as the Proxmark3, when the card reader security mechanism is insufficiently secured. (Read more)

  2. DeTT&CT: Mapping detection to MITRE ATT&CK 
    Detailed and hands-on guide on mapping your detection capabilities to MITRE ATT&CK using MITRE DeTT&CT. Using this it becomes easier to build and maintain rules, and spot your blind spots! (Read more)

  3. Another spin to Gamification: how we used Gather.town to build a (great!) Cyber Security Game
    People are at the heart of cybersecurity. In this blog post, we outline how we crafted an – if we may say so ourselves – fun and informative game using Gather.town to promote cybersecurity awareness, and tell you how you can too. (Read more)

  4. PowerShell Inside a Certificate? – Part 1
    Didier Stevens outlines in this blog post how we crafted YARA detection rules that don’t just detect things we know are bad, but also checks whether things actually have the format we expect them to. This way we found some PowerShell code hidden in Certificate files. (Read more)

  5. Detecting DDE in MS Office documents
    Didier Stevens shares in this blog post how to detect Dynamic Data Exchange, an old technology often abused to weaponize MS Office documents. We believe sharing tips and detection rules like this one makes us all more secure in the end! (Read more)

  6. Under the hood: Hiding data in JPEG images
    In this lighthearted blog post, we dive under the hood of how you can hide your secrets inside a JPEG file. We recommend using this as a party trick or as a fun challenge, not for your TLP Red stuff! (Read more)



Enforce Zero Trust in Microsoft 365 – Part 2: Protect against external users and applications

12 May 2023 at 07:00
Enforce Zero Trust in Microsoft 365 - Part 2: Protect against external users and applications

In the first blog post of this series, we have seen how strong authentication, i.e., Multi-Factor Authentication (MFA), could be enforced for users using a free Azure Active Directory subscription within the Microsoft 365 environment.

In this blog post, we will continue to harden the configuration of our Azure AD tenant to enforce Zero Trust security without any license requirement. Specifically, we will see how our organization can protect against external users and prevent malicious applications from accessing our tenant.

Previous blog post:

Settings hardening

Because some default settings in Azure Active Directory are not secure and might introduce security issues within our organization, I wanted to quickly go over them and see how they could be used by malicious actors.

Guest users

We haven’t discussed guest users for now. It is because access control for guest users can’t be enforced using an Azure AD free license. However, guest users might be the entry door for attackers to access our Microsoft 365 environment. Indeed, by compromising a user in a partner’s environment, adversaries will directly gain access to our environment because of this implicit trust relationship that is automatically setup when inviting guest users. Therefore, we can either assume that guest users are correctly protected in their home tenant (we will see in a later blog post that even if guest users have the appropriate security controls enforced in their home tenant, these security controls might not be enforced in certain circumstances to access our tenant (i.e., the resource tenant)), or restrict or disable guest user invitations. In any case, the way guest users will be managed is an important consideration for our Zero Trust approach. In our case, we will not simply block guest user invites because we think that collaboration with external parties is an important aspect for our business and will be required. Therefore, we want to take a proactive approach to this problem by setting a solid foundation before it is too late.

First, we want to ensure that no one in the organization, except authorized users, can invite guest users. Indeed, by default, all users in our organization, including guest users, can invite other guest users. This could represent a serious weakness in our Zero Trust approach. Therefore, we will only allow users assigned to specific administrator roles to invite guest users (this includes the Global Administrators, User Administrators and Guest Inviters roles).

Guest invite restrictions are configured in Azure AD. For that purpose, go to the Azure Portal > Azure Active Directory > Users > User Settings > Manage external collaboration settings under External users. Choosing the most restrictive option disables the ability to invite guest users.

Guest invite restrictions in Azure AD
Guest invite restrictions

Moreover, because our organization works with defined partners, users should only be able to collaborate with them. We can therefore further restrict invitations by specifying domains in the collaboration restrictions settings:

Collaboration restrictions
Collaboration restrictions

For those restrictions, a reliant process is required to clearly define who can manage guest users and external domains, especially if you regularly collaborate with different partners.

By default, guest users have extensive permissions. If an attacker takes over a guest account, the information to which the guest user has access, may be used for advanced attacks on our company. For this reason, we want to restrict them as much as possible. It might not be required for guest users to be able to enumerate resources in our Azure Active Directory tenant. This could allow adversaries, that compromised a guest user, to gain information on users within our tenant such as viewing our employees for sending (consent) phishing emails to gain initial access or viewing other partners to deceive them by impersonating our company or an employee. Therefore, we want to limit guest users permissions.

Guest user access restrictions in Azure AD
Guest user access restrictions

With these restrictions implemented for guest users, we have already decreased the potential impact that a compromised guest user could have in our environment. However, remember that with the current configuration, specific access controls, such as strong authentication for guest users, are not enforced to access our tenant. This means that a compromised guest user might still be used to access our environment.

External applications

In Azure Active Directory, applications can be integrated into Azure Active Directory to make them accessible to user. There are many types of applications that can be made accessible through Azure AD such as cloud applications, also known as pre-integrated applications, like Office 365, the Azure Portal, Salesforce, etc., custom applications, and on-premises applications.

Users can consent to applications to allow these applications to access organization data or a protected resource in the tenant on their behalf. Indeed, applications can request API permissions so that they can work properly. These API permissions include accessing a user’s profile, a user’s mailbox content, sending emails, etc. This can also be seen as an entry door for adversaries to gain access to information in our environment. For example, attackers could trick an employee by sending a consent link (consent phishing) to an employee for a malicious application. If the user consents, attackers would have the permissions the user has consented to. Even worse, an administrator might consent to an application for the entire organization. This means that a malicious application could potentially gain access to all directory objects.

Let’s abuse it!

If user consent is allowed in our Azure AD tenant, adversaries could send consent grant phishing to employees. Let’s see how this could be done.

First, because adversaries could access our Azure AD tenant because guest invitation restrictions were initially not configured, they could gather a list of our employees as well as their email address. Then, they used this list to create a phishing campaign for a Microsoft Advertising Certification study guide.

Phishing email
Phishing email

Because one employee was very eager to try out this new limited edition guide, they clicked the link and signed in with their credentials.

Application permissions request
Permission consent

Unfortunately, the employee had administrative permission in our tenant and could therefore grant consent on behalf of the entire organization. Everyone should benefit from this free offer, right?… Not really, no. Indeed, as shown in the above screenshot the application, which is not verified, requires a lot of access such as sending and viewing emails, read and write access to mailbox settings, and read access to notes, files, etc.

Once the user clicks, adversaries can retrieve information about the user as well as from the organization. Additionally, they can access the user’s mailbox, OneDrive files and notes.

For this demonstration, I used 365-Stealer from AlteredSecurity to setup the phishing page and to access users in the directory:

Phished users in 365-Stealer
365-Stealer

How to protect ourselves against consent grant phishing?

There are no bullet proof solutions to protect users from phishing, unless you disable the ability for users to receive emails and messages globally, which is very far from ideal. Indeed, even with Office 365 threat policies, such as anti-phishing policies, and user awareness, malicious actors are always finding new ways of bypassing these polices and tricking users. However, what we can do is disabling the ability to consent for applications in Azure AD.

To restrict user consent for applications, it is possible to disable or restrict applications and permissions that user can consent to. Unless it is required, it is highly recommended to disable user consent. This will be done for our organization tenant to prevent consent grant attacks.

Consent and permissions for users
Consent and permissions for users

This setting can be configured in Azure Portal > Azure Active Directory > Users > User settings > Manage how end users launch and view their applications under Enterprise applications > Consent and permissions.

Besides blocking this functionality, it is also possible to only allow users to consent for permissions classified as low impact. Microsoft provides the ability to define our own classification model for application permissions, ranging from low to high as show below. In that case, administrators can select the Allow user consent for apps from verified publishers, for selected permissions (Recommended) setting in the user consent settings page:

Permission classifications for applications in Azure AD
Permission classifications for applications in Azure AD

Conclusion

In this blog post, we went over different settings in Azure AD that can be restricted to prevent malicious users from being added to our tenant. Moreover, we have seen how application consent settings can be abused through consent grant phishing and how we can protect against it.

I have selected these settings among others because we usually see that they are not restricted in most environments during our security assessments. However, configuring only these settings is not enough to protect your environment against malicious and unauthorized actions. If you would like to know more about how NVISO can help you securing your environment, feel free to reach out or to check our website.

In the next blog post, we will go over Azure AD Conditional Access policies, see how they can be used to further increase the security posture of our environment and implement our Zero Trust security approach.

About the author

Guillaume Bossiroy

Guillaume is a Senior Security Consultant in the Cloud Security Team. His main focus is on Microsoft Azure and Microsoft 365 security where he has gained extensive knowledge during many engagements, from designing and implementing Azure AD Conditional Access policies to deploying Microsoft 365 Defender security products.

Additionally, Guillaume is also interested into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

Implementing Business Continuity on Azure

5 May 2023 at 07:00

There is a general misconception among cloud consumers that the availability of their resources in the cloud is always guaranteed. This is not true since all cloud providers, including Microsoft, offer specific SLAs for their products that almost never reach an availability target of 100%. For the consumers who have deployed critical resources and applications to the cloud, reaching the company-defined targets for Business Continuity can be technically challenging and confusing. The purpose of this blog post is to provide practical guidance on how Business Continuity is expressed on the cloud, how it can be implemented for many Azure IaaS and PaaS services and what real-world problems each solution attempts to solve.

Introduction

Before we dive into the technical Azure-specific details, let’s explain what Business Continuity is and what it involves.

Business Continuity is the capability of the organization to continue the delivery of products or services at acceptable predefined levels following a disruptive incident. According to ISO 22301, business continuity is not limited only to IT and it involves many enterprise aspects.

In this blog post, we will focus on the business continuity aspects related to IT. Each of them corresponds to a specific type of SLA that you may have internally or with your customers, so there are multiple aspects of Business Continuity that may be applicable to you.

If it’s important for you to keep your services always up and running, you should focus on High Availability. This is the ability of a system to be continuously operational, or, in other words, have an uptime percentage of near 100%. It is generally achieved by implementing redundant, mirrored copies of the hardware and data, so that if one component fails, another one takes over.

If fluctuating demand and bottlenecks cause your systems to struggle, then you may need to focus on Scalability. This is the ability of a system to scale up or scale down cloud resources as needed to meet fluctuating demand. It can be considered as an aspect of Business Continuity, since peaks in demand can be the result or the cause of an incident.

Finally, to protect data that are critical to your company’s functionality and need to be always available and recoverable, you should implement Backup. This is the duplication of data to a secondary location, so that if the primary copy is harmed or becomes unavailable, data from the other location can be retrieved and the system can be rolled back to a specific point in time.

The following diagram shows an analogy between the aforementioned terms and the problems they tackle.

Business Continuity: Mapping of problems and solutions

It is important to note that the implementation of any of the controls described in this blogpost should be based on a structured business continuity assessment/plan, and should be selected based on the requirements of your environment or application. Improvident implementation of controls could result in undue costs or in inefficient protection.

Implementing High Availability

Depending on the required uptime of your application or system and the scale of disaster you need to be able to recover from, there are many ways to implement high availability in Azure. When choosing the controls that will be implemented in your environment, you should always consider that the availability in a chain of resources is determined by the weakest link in the chain. For example, in the case of an application composed by a front-end server and a database, if the web server is spread across multiple availability zones but the database is single-instance, the whole application will not be available anymore if the availability zone of the database goes down. Having the above in mind, we present below the different options provided by Azure, sorted by increasing complexity and costs.

Protection against hardware failures

Small-scale technical or hardware issues may affect single-instance components. To avoid this, the component should be mirrored to a secondary hardware volume. On Azure, depending on your cloud computing state, this can be implemented as follows:

IaaS

When the component is a Virtual Machine (VM), this can be achieved by using availability sets. An availability set is a logical grouping of VMs that allows Azure to understand how your application is built to provide for redundancy and availability. While for single-instance VMs Azure guarantees a 99,9% uptime SLA, by using availability sets the uptime is increased to 99,95%. To use availability sets on Azure VMs, you need to perform the following steps:

  1. Create an availability set;
  2. Create new VMs; in the creation wizard, under “Availability options” choose “Availability set” and then select the previously created set.

Note: It is not possible to add existing VMs to an availability set after their creation.

PaaS

Azure PaaS components are protected against local hardware failures by design, guaranteeing higher uptime SLAs than IaaS. Specifically:

  • Storage Accounts: Microsoft ensures 3 instances of the service when using the default redundancy option (Locally redundant Storage – LRS). This offers an SLA of 99.999999999% (11 nines) uptime.
  • SQL Databases: By default, Microsoft ensures at least two instances of the service within the same data center, reaching 99,99% uptime.
  • Cosmos DB: By default, Microsoft provides three replicas (individual nodes) within a cluster, ensuring an SLA of 99,99% uptime.
  • App Service: Microsoft guarantees an SLA of 99.95% uptime for App Services, for tiers other than Free or Shared.

Protection against datacenter failures

To provide the option of protecting against failures that affect the whole datacenter, such as fire, power and cooling disruptions or flood, Microsoft has introduced the concept of availability zones. Availability zones are unique physical locations within an Azure region, each made up of one or more datacenters with independent power, cooling, and networking. The creation of multiple instances of services across two or more zones provides increased high availability, as it protects both against hardware and against datacenter failures.

Azure Availability Zones
Source: What are Azure regions and availability zones? | Microsoft Learn

Based on your Cloud computing model, such protection can be achieved as follows:

IaaS

Virtual machines can be deployed across multiple availability zones to provide an uptime SLA of 99,99%. This can be done with the following steps:

  1. Create a VM; under “Availability options” select “Availability zone” and specify a zone. This will be the primary zone of your VM.
  2. Open the VM.
  3. Under Operations, select “Disaster recovery” and set the option “Disaster Recovery between Availability Zones?” to “Yes”. Under “Advanced settings” you will be able to see or change the secondary zone.
  4. Click on “Review and start replication”.

PaaS

When it comes to PaaS services, it is generally easier to deploy them across multiple availability zones. Specifically:

  • Storage Accounts:
    • Microsoft ensures 3 instances of the service across three different availability zones (Zone redundant Storage – ΖRS). This offers an SLA of 99.9999999999% (12 nines) uptime over a given year.
    • The option can be enabled during the Storage Account creation, under Basics – Redundancy.
  • SQL Databases:
    • Zone redundancy available at the General purpose, Hyperscale, Business Critical and Premium Service tiers. The SLA depends on the tier and can reach 99.995% of uptime.
    • The option can be configured during the SQL DB creation, under the Service tier selection menu, or under Settings – Compute + storage for existing databases.
  • Azure Cosmos DB Accounts:
    • Enabling zone redundancy in an Azure Cosmos DB account can increase the uptime SLA to 99.995%.
    • The option can be selected during the Cosmos DB Account creation, under Global Distribution – Availability Zones.
  • App Service:
    • Zone redundancy is only available in either Premium v2 or Premium v3 App Service Plans for Web Apps and in Elastic P2 for Function Apps. At the time of writing, Microsoft has not published specific SLAs for zone-redundant App Services, but it guarantees at least three instances of the service.
    • The option can be enabled during the creation of the Service Plan, under Zone redundancy.

Note: It is not possible to enable availability zone support after the creation of any of the above components.

Protection against regional failures

Finally, to protect against regional failures that can affect many adjacent datacenters and can be caused by large-scale natural and man-made disasters (e.g., earthquake, tornados, war), Microsoft has introduced the concept of availability regions. Azure regions are physical regions all over the world, designed to offer protection against local disasters within availability zones and against regional or large geography disasters by making use of another region and replicating the workloads to that region. The secondary region could be considered as the disaster recovery site. Availability regions can be used independently of availability zones or in conjunction with them.

Azure Availability Regions
Source: What are Azure regions and availability zones? | Microsoft Learn

Based on the Cloud computing model of the application components that you want to protect, you have the following options:

IaaS

Virtual machines can be deployed across multiple availability regions to provide an uptime SLA of 99,99%. This capability is offered by the Azure Site Recovery service that orchestrates the replication, failover, and recovery of the VMs. Site Recovery can be implemented with the following steps:

  1. Open the VM for which you want to configure regional redundancy.
  2. Under Operations, select “Disaster recovery”, set the option “Disaster Recovery between Availability Zones?” to “No” and select a target region.
  3. Click on “Review and start replication”.

PaaS

PaaS services can be protected from regional disasters as follows:

  • Storage Accounts:
    • With the Geo redundant Storage option (GRS), Microsoft ensures that there are 3 instances of the Storage Account in the primary region and another 3 instances in the secondary region. This offers an SLA of 99.99999999999999% (16 nines) uptime over a given year. There is also the option of Geo-zone-Redundant Storage (GZRS) which spreads the instances of the primary region across 3 different availability zones, to enable fastest recovery times in case of a datacenter failure.
    • The option can be enabled under Data Management – Redundancy for an existing Storage Account.
  • SQL Databases:
    • An additional replica for read operations can be created in a secondary region and used as a disaster recovery site. The SLAs for the geo-redundant setup vary depending on the selected service tier.
    • The option can be configured for a given SQL DB, under the Service tier selection menu.
  • Azure Cosmos DB Accounts:
    • Enabling geo redundancy in an Azure Cosmos DB account can increase the SLA for read operations to 99.999%.
    • The option can be selected during the Cosmos DB Account creation, under Global Distribution – Geo-Redundancy, or for an existing Cosmos DB account under Settings – Replicate data globally.
  • App Service:
    • As of the time of writing, there is no geo-redundancy support for Azure App Service.

Before closing with High Availability, remember that a highly available system is a system that your customers and employees can rely on. It increases the credibility of your company, improves its reputation and offers peace of mind to your valuable users. Although costs may go up, depending on your implementation choices, it shall assist you to establish yourself as a trustworthy partner.

Implementing scalability

For systems whose load can abruptly increase or decrease, a problem arises: How can you guarantee the available level of resources during high periods, while at the same time keeping your costs to the minimum during the low periods? This is the essence of scaling, and in the cloud, achieving this balance is much easier than in traditional, on-premises infrastructures. There are two main ways that an application can scale: vertical scaling and horizontal scaling. Vertical scaling (scaling up) increases the capacity of a resource, for example, by increasing the VM size, CPU, memory, etc. Horizontal scaling (scaling out) adds new instances of a resource, such as VMs or database replicas.

Vertical vs horizontal scaling

While vertical scaling can be achieved more easily, and without making any changes to the application, at some point it hits a limit where the system cannot be scaled more. On the other hand, horizontal scaling is more flexible, cheaper, and applies to big, distributed workloads. It also enables autoscaling, which is the process of dynamically allocating resources to ensure performance. That is why, especially in the Cloud, horizontal scaling is the recommended option.

The options Azure provides you with are as follows:

IaaS

Scalability of Virtual Machines in Azure can be achieved through Virtual Machine Scale Sets (VMSS). These represent groups of load-balanced VMs that provide scalability to applications by automatically increasing or decreasing the number of VM instances in response to demand or a defined schedule. VMSS can be deployed into one Availability Zone, multiple Availability Zones or even regionally.

During the creation of a VMSS resource, the cloud consumer can specify the scalability options and minimum instance number, the Load Balancer (or Application Gateway, in case of HTTPS traffic) that will be used, and other networking and orchestration options.

PaaS

In most cases, managed PaaS services have horizontal scaling and autoscaling built in. The ease of scaling these services is a major advantage of using Azure PaaS services.

Specifically for App service, we should point out that the scaling options depend on the App Service plan (tier) and can reach a maximum of 100 instances when using the Isolated tier.

Implementing backups

Although HA solves the problem of small or extended failures, what happens if the unavailability of data originates from a malicious threat, such as a ransomware attack? In this case, having a highly available infrastructure will simply replicate the encrypted/corrupted files everywhere almost immediately, leaving no recovery options. Here is where the value of remote data copies, that are unaffected by real-time modifications, lies. With Azure Backup, regular backups and snapshots of workloads are taken, so that in case of unauthorized modification or deletion, the service can be restored to a specific point in time.

Backups in Azure can be implemented both for IaaS and for PaaS services, and the options are presented below.

IaaS

VM backups can be either locally redundant or zone redundant. Recovery from backups can be implemented in two ways: the standard option generates backups once a day and maintains instant restore snapshots for 2 days. The enhanced option generates multiple backups per day, maintains instant restore snapshots for 7 days and ensures that snapshots are spread across zones for increased resiliency. The second option applies to VMs of high criticality.

In an existing Azure VM, Backups can be configured under Operations – Backup.

PaaS

Multiple backup options also exist for different PaaS services:

  • Storage Accounts:
    • Azure provides the option to configure operational backups of the blobs of a Storage Account.  This is a local backup solution that maintains data for a specified duration in the source storage account itself. Although a Backup Vault is required to manage the backup, there is no copy of the data stored in the Vault. The backup is continuous and allows the reversion to a specific point in time in case of data corruption.
    • The option can be configured under Data management – Data protection for existing Storage Accounts, by selecting the checkbox “Enable operational backup with Azure Backup” and following the necessary steps presented in the portal.
  • SQL Databases:
    • Azure gives the option of locally redundant, zone-redundant or geo-redundant backups.
    • Configuration can be performed with the option “Backup storage redundancy” under Settings – Compute + storage for existing databases, or under Basics – “Backup storage redundancy” during the database creation.
  • Azure Cosmos DB Accounts:
    • Two options are offered for backup functionality, either periodic (LRS or ZRS or GRS) or continuous backup. When using the periodic backup mode, which is the default one for all accounts, backups are taken at periodic intervals and the data can only be restored by creating a request with Microsoft’s support team. On the contrary, continuous backup facilitates the restoration to any point of time within either 7 or 30 days (depending on the tier) through the portal.
    • Can be configured under “Backup Policy” during creation, or under the “Backup & Restore” pane for existing accounts.
  • App Service:
    • Azure provides the possibility to backup an App’s content, configuration and database by enabling and scheduling periodic backups, which are stored in a Storage Account.
    • Backup and restoration options can be configured under Settings – Backups for existing App Services.

Overall, it is important to find the balance between the frequency of backups and the amount of maintained past snapshots, in order to lose as less data as possible in case of an incident, be able to revert to a healthy past state and at the same time maintain costs to an acceptable level for your company.

Conclusion

To conclude, it all ends up to one question: Can you survive? Can you recover from disasters as small as power interruptions to as big as pandemics and earthquakes? Business Continuity is the key to the answer. And in the modern, distributed Cloud world, all the capabilities are there – it’s just up to you, your dedication and commitment to implement the ones that are essential to your business.

Elpida Rouka

Elpida is an Information Security Consultant, with expertise in Azure/O365 Security, SIEM, Identity & Access management, Risk management, Information Security Management Systems (ISMS) and Business Continuity planning (ISO22301). She is always eager to create innovative high-quality solutions that precisely meet business needs.

Stijn Wellens

Stijn is a manager with experience in cloud and network security. He is Solution Lead for Cloud Security Assessments and Microsoft Cloud Security Engineering at NVISO. Besides the technical challenges during Azure and Microsoft 365 security roadmap implementations, Stijn enjoys coaching the teams by sharing his knowledge and experience.

Enforce Zero Trust in Microsoft 365 – Part 1: Setting the basics

2 May 2023 at 07:00

This first blog post is part of a series of blog posts related to the implementation of Zero Trust approach in Microsoft 365. This series will first cover the basics and then deep dive into the different features such as Azure Active Directory (Azure AD) Conditional Access policies, Microsoft Defender for Cloud Apps policies, Information Protection and Microsoft Endpoint Manager, to only cite a few.

In this first part, we will go over the basics that can be implemented in a Microsoft 365 environment to get started with Zero Trust. For the purpose of the blog post, we will assume that our organization decided to migrate to the cloud. We just started investigating what are the quick wins that can be easily implemented, what are the features that will need to be configured to ensure security of identities and data, and what the more advanced features that could be used to meet specific use cases would be.

Of course, the journey to implement Zero Trust is not an easy one. Some important decisions will need to be made to ensure the relevant features are being used and correctly configured according to your business, compliance, and governance requirements without impacting user productivity. Therefore, the goal of this series of blog posts is to introduce you possible approaches to Zero Trust security in Microsoft 365.

Introduction

However, before starting we need to set the scene by quickly going over some principles.

First, what is a Zero Trust security approach? Well, this security model says that you should never trust anyone and that each request should be verified regardless of where the request originates or what the accessed resource is. In other words, this model will assume that each request comes from an uncontrolled or compromised network. Microsoft provides this nice illustration to represent the primary elements that contribute to Zero Trust in a Microsoft 365 environment:

Zero Trust approach in Microsoft 365
Zero Trust approach in Microsoft 365

We will go over these components as part of this blog post series.

You may wonder why I have decided to discuss Zero Trust in Microsoft 365. Because I think it is one of the most, if not the most, important aspects of a cloud environment. Indeed, with cloud environments, identities are considered as the new perimeter as these identities can be used to access Internet-facing administrative portals and applications from any Internet-connected device. 

Furthermore, even when security controls are enforced, it does not mean that the environment is secure. There were many attacks these past few months/years that allowed attackers to bypass security controls through social engineering, and phishing attacks, for example. Therefore, the goal is more to reduce the potential impact of a security breach on the environment than to prevent attacks from succeeding.

Finally, let’s go over some Microsoft 365 principles. When an organization signs up for a subscription of Microsoft 365, an Azure AD tenant is created as part of the underlying services. For data residency requirements, Microsoft lets you choose the logical region where you want to deploy your instance of Azure AD. This region will determine the location of the data center where your data will be stored. Moreover, Microsoft 365 uses Azure AD to manage user identities. Azure AD offers the possibility to integrate with an on-premises Active Directory Domains Services (AD DS) but also to manage integrated applications. Therefore, you should/must/have to understand that most of the work to set up a Zero Trust approach will be done in Azure AD.

Let’s get started!

Our organization just bought a paid Microsoft 365 subscription which comes with a free subscription to Microsoft Azure AD. The free Azure AD subscription includes some basic features that will allow us to get started with our journey. Let’s go over them!

Security Defaults

The first capability is the Azure AD Security Defaults. The Security Defaults are a great first step to improve the security posture by enforcing specific access controls:

  • Unified Multi-Factor Authentication (MFA) registration: All users in the tenant must register to MFA. With Security Defaults, users can only register for Azure AD Multi-Factory Authentication by using the Microsoft Authenticator app using a push notification. Note that once registered, users will have the possibility to use a verification code (Global Administrator will also have the possibility to register for phone call or SMS as second factor). Another important note is that disabling MFA methods may lead to locking users out of the tenant, including the administrator that configured the setting, if Security Defaults are being used;
  • Protection of administrators: Because users that have privileged access have increased access to an environment, users that have been assigned to specific administrator roles are required to perform MFA each time they sign in;
  • Protection of users: All users in the tenant are required to perform MFA whenever necessary. This is decided by Azure AD based on different factors such as location, device, and role. Note that this does not apply to the Azure AD Connect synchronization account in case of a hybrid deployment;
  • Block the use of Legacy Authentication Protocols: Legacy authentication protocols refer to protocols that do not support Multi-Factor Authentication. Therefore, even if a policy is configured to require MFA, users will be allowed to bypass MFA if such protocols are used. In Microsoft 365, legacy authentication is made from clients that don’t use modern authentication such as Office versions prior to Office 2013 a mail protocols such as IMAP, SMTP, or POP3;
  • Protection of privileged actions: Users that access the Azure Portal, Azure PowerShell or Azure CLI must complete MFA.

These features already allow to increase the security posture by enforcing strong authentication. Therefore, they can be considered a first step for our organization that just started to use Microsoft 365 and is still researching/evaluating/ the different possibilities.

If we want to enable Security Defaults, we go to the Azure Portal > Active Azure Directory > Properties > Manage Security Defaults:

Enable Security Defaults in Azure AD
Enabling Security Defaults

However, there are important deployment considerations to be respected before enabling Security Defaults. Indeed, it is a best practice to have emergency accounts. These accounts are usually assigned the Global Administrator role, the most privileged role in Azure AD/Microsoft 365 and are created to enable access to the environment when normal administrator accounts can’t be used. This could be the case if Azure AD MFA experiences outages. Because of the purpose of such accounts, these users should either be protected with a very strong first authentication method (e.g., strong password stored in secure location such as a physical vault that can only be accessed by a limited set of people under specific circumstances) or use a different second authentication factor than other administrators (e.g., if Azure AD MFA is used for administrator accounts used regularly, a third party MFA provider, such as hardware tokens, can be used). But here is the problem: this is not possible when using Security Defaults.

Per-user MFA settings

Note that the per-user MFA settings, also known as legacy multifactor authentication, will be deprecated on September 30th, 2024.

The second capability with an Azure AD free license is the per-user MFA settings. These settings can be used to require Multi-Factor Authentication for specific users each time they sign in. However, some exceptions are possible by turning on the ‘Remember MFA on trusted devices’. Note that when enabled this setting will allow users to mark their own personal or shared devices as trusted. This is possible, because this setting does not rely on any device management solution. Users will only be asked to reauthenticate every few days or weeks when selecting this option. The interval depends on the configuration.

We usually do not recommend using the ‘Remember MFA on trusted devices’ setting unless you do not want to use Security Defaults and do not have Azure AD Premium licenses. Indeed, this setting allows any user to trust any device, including shared and personal devices, for the specified number of days (between one and 365 days). However, these settings can be configured in the https://account.activedirectory.windowsazure.com portal.

In the user settings, MFA can be enabled for each individual user.

Per-user MFA settings in Azure AD
Per-user MFA users settings

Then, in the service settings, we can allow users to create app passwords for legacy applications that do not support MFA, select authentication methods that are available for all users, and allow or not users to remember Multi-Factor Authentication on trusted devices for a given period of time. Note that the trusted IP addresses feature requires an additional license (Azure AD Premium P1) that we do not have for the moment.

Legacy MFA settings in Azure AD
Per-user MFA service settings

Sum-up

These two features are quite different but allow us to achieve the same goal, to enforce strong authentication, i.e., MFA, for all or some users.

For our organization we will choose the Security Defaults for multiple reasons:

  • The per-user MFA settings can become unmanageable quickly. This is especially true for growingorganization.With more people and a complex environment, exceptions will be required, and it will become difficult to keep track of the configuration and keep a good baseline. Security Defaults, respectively,allow to enforce a standard baseline for all users;
  • By using per user MFA users will be prompted for MFA every time they sign in.. This badly affects user experience and productivity might be impacted;
  • Security Defaults blocks legacy authentication protocols that might be used to bypass MFA in some cases. This prevents identities, including administrators, from being targeted by brute force or password spraying attacks and help mitigating the risk of successful phishing attacks to a certain extent;
  • Multi-Factor Authentication registration is enforced with Security Defaults for all users meaning that all users will be capable of doing MFA if required.

By going that way we need to consider that exclusions are not possible. Therefore, emergency accounts or user accounts used as service accounts (which it is not recommended to have as they are inherently less secure than managed identities or service principals) might be blocked. Nevertheless, as we are just evaluating the Microsoft 365 products, we can accept that the environment and cloud applications are unavailable for a few hours without any major impact on business processes. However, this might be an crucial point in the future.

Finally, it is important to note that these two features do not allow to configure more granular controls as we will see later in this series.

Conclusion

In this first blog post, we have seen different possibilities to enforce access restrictions that can be implemented when an organization just starts its journey in Microsoft 365:

  • Per-user MFA settings: Allow to enforce MFA for specific users but can become quickly unmanageable and does not provide granular controls;
  • Security Defaults: Allow to enforce a strong authentication mechanism and to block legacy authentication protocols that may allow users to bypass MFA. This solution is recommended over the per-user MFA settings. However, note that MFA might not be required in most cases which is not ideal.

In brief, we can see that both solutions have limitations and will not be suitable for most organizations. Indeed, there are still many aspects, such as restricting access based on specific conditions, that are not covered by these capabilities. We will go over additional key features as well as our recommendations for the implementation of a Zero Trust approach in Microsoft 365 in future blog posts.

In the next blog post, we will see how we can protect our environment against external users and applications.

About the author

Guillaume Bossiroy

Guillaume is a Senior Security Consultant in the Cloud Security Team. His main focus is on Microsoft Azure and Microsoft 365 security where he has gained extensive knowledge during many engagements, from designing and implementing Azure AD Conditional Access policies to deploying Microsoft 365 Defender security products.

Additionally, Guillaume is also interested into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

An Innocent Picture? How the rise of AI makes it easier to abuse photos online.

4 April 2023 at 08:15

Introduction

The topic of this blog post is not directly related to red teaming (which is my usual go-to), but something I find important personally. Last month, I gave an info session at a local elementary school to highlight the risks of public sharing of children’s pictures at school. They decided that instead of their photos being publicly accessible, changes would be implemented to restrict access to a subset of people. However, there are many more instances of excessive sharing of information online; photographers’ portfolios, youth/sports clubs, sharenting on social media, etc.

There are many risks stemming from this type of information being openly available, and the potential risks have only increased with the rise of artificial intelligence. Since you are reading this post on the NVISO blog, I’m assuming you are more cyber-aware than the average person out there and therefore perfectly positioned to use the takeaways from this post and spread the word to others. Obligatory Simpsons reference:

Since the children themselves may not have a say in the matter yet and the people who do may not be aware of the possible dangers, it’s up to us to think of the children!

Traditional Risks

When thinking of the risks linked to the presence of children’s pictures online, an obvious threat is the type of person that might drive a van like this:

There are three traditional risks we will be discussing here:

  • Kidnapping
  • Digital Kidnapping
  • Pornographic Collections

Kidnapping

How does a picture of a child pose a risk for physical kidnapping? First of all, a picture could give away a physical location, for example due to the presence of street signs/names, recognizable elements such as shops, bars, monuments, schools, etc. If this is a location frequented by the child, a possible child predator could identify an opportunity for kidnapping there.

In case no identifiable elements are present, certain people might still giveaway the location due to oversharing. Imagine a picture on a Facebook profile that is publicly accessible with comments such as “birthday party at …”, “visiting grandma & grandpa in …”, “always a fun day when we go to …”. Often-visited locations can be deduced from comments like these.

Finally, a more technical approach is looking at the picture’s metadata, which often gives information about the type of camera that was used, shutter time, lens, etc. but can also contain an exact location where the picture was taken. No additional research is required to figure out where the child has been.

Digital Kidnapping

With digital kidnapping, the victim is affected by some type of identity fraud. Pictures of the child are stolen and reused by people online on their own social media, often pretending to be related to the children. An example could be an adoption fantasy, reposting pictures of the child for likes and comments without the child or its parents knowing about this.

Another, more dangerous form of digital kidnapping consists of a sexual predator reusing the victim’s pictures to target other possible victims. Someone could pretend to be a young child themselves to lure other children into meeting with them online or sharing potentially explicit pictures.

Pornographic Collections

Continuing on the topic of potentially explicit pictures, it is not a secret that the Dark Web is full of pornographic pictures of children. However, pictures that you or I would not consider to be risky or explicit could end up in such collections as well. Holiday pictures of children in swimsuits are happily shared by child predators in an attempt to fulfill their fantasies. They search through social media to identify such pictures, sharing them among each other along with sexual fantasies. With pictures of a certain child, they might search for pictures of lookalike children to add to their fantasy. With only a textual story, they might search for pictures of children that match the story.

However, these risks have been existent for a number of years already. What’s more dangerous is that the life of a child predator looking for pictures has been facilitated with rise of artificial intelligence.

Next-gen Risks

So what is the problem with public pictures? Not only can they be retrieved by anyone browsing the web, but they can and will also be gathered by automated systems through concepts called spidering and scraping. These activities aren’t particularly nefarious and actually part of the regular functioning of the web, used by search engines for example. However, other applications can make use of these same techniques and have already done so to create massive collections of pictures, even those you would not expect to be public, such as medical records

Facial Recognition

One such example is ClearView AI, which is aimed at law enforcement by applying its facial recognition algorithm to a huge collection of facial images to help with investigative leads. However, for the broader public, a similar application has become available, allowing anyone to upload a picture and receive an overview of other pictures with matching faces. While probably having legitimate use cases, PimEyes provides people with less honorable intentions an easy way to add a high-tech touch to the traditional risks mentioned above. If you haven’t heard about PimEyes yet, it allows to upload a picture of someone’s face, after which the application will provide you with a collection of matching pictures. The tool is already quite controversial, as evidenced by the articles below:

As an example, we provided PimEyes with the face of the middle child selected from the stock photo on the left below, which resulted in a set of pictures containing the same child:

Of course, the algorithm identifies the pictures that are part of the same set of stock pictures. When trying this out with a private picture of someone, the set of results contained distinct public pictures with the same person. The algorithm was able to identify them in pictures of low quality or with the person wearing a hat or mouth mask covering a large part of the face. Scary stuff, especially considering what you could be able to do with this output:

  • Imagine a picture of a child without any hints towards the location (e.g. stolen from Facebook or other social media). Upload it to PimEyes and you might be able to link the child’s face to other public pictures where a location can easily be deducted (such as a school website for example). You now know locations where the child may frequently be present.
  • Remember in one of the previous paragraphs where we said “With pictures of a certain child, they might search for pictures of lookalike children to add to their fantasy.” Well, this type of technology automates the task.
  • Resources above mention a woman having found sexually explicit content through facial recognition. Imagine your child falling victim to revenge porn in the future and having those pictures exposed. Through PimEyes it may even be possible that such pictures are shown in the results together with pictures of when the victim was still a child.

Of course, in addition to these “extreme cases”, in the future it may very well be that possible employers don’t just google your name, but also search your face before an interview. The results may consist of shameful pictures you would rather not have an employer see. There could be a psychological effect as well; maybe in the past you were struggling with certain physical conditions (e.g. being overweight) or affected by other conditions which are no longer relevant at the time when someone tries to find your older pictures. Being confronted with that type of past content may be a painful experience.

Generation of previously non-existent content

We’ve all been playing around and having a lot of fun with ChatGPT, DALL-E, and other AI models. While it is is possible to generate a picture from a textual prompt, it is also possible to take an existing image and swap out parts of the image based on a textual prompt. What could possibly go wrong? OpenAI does mention following protections having been put in place: “… we filtered out violent and sexual images from DALL·E 2’s training dataset. Without this mitigation, the model would learn to produce graphic or explicit images when prompted for them, and might even return such images unintentionally in response to seemingly innocuous prompts … “ Let’s see what we are able to do with some stock photos.

Starting off from the same stock photo, I erased the bottom part – very amateuristically I admit – so that it can be completed again by DALL-E:

Using a fairly innocent prompt (“modify the image to portray the children at the beach in swimming gear”), which could however be the type of picture child predators are after, we get the following possible images (note that we have blurred the resulting images):

Alright, these first two images do indeed look like a fun day at the beach, with an inflatable tire, bucket, and what looks like sand. The third image on the other hand, did surprise me a bit. This time, the girls have received shorts and the middle child even has some cleavage generated (adding to our decision of blurring the image). Do note that this is the result with an innocent prompt, specifically mentioning it is about children, and with mitigations against the generation of explicit content built-in by removing sexual images from the training set. Let’s leave it at this for this photo and try to generate something a bit more suggestive starting from this stock picture resulting from “business woman” as a search term. When asking to “turn this into a pin-up model”, starting from just the neck and head, we are able to receive some spicier results:

So this is what we can create from a completely random picture on the internet without having any photo editing skills. Now imagine this result applied to pictures of children and the risks are obvious.

Taking things a step further, other applications may not have the same limitations applied to their training data and are as a result clearly biased towards female nudity. The popular avatar app “Lensa” is known to return nude or semi-nude variations of photos for female users, even when uploading childhood pictures, as evidenced in following articles:

Taking things another step further, certain apps or services are specifically aimed at the creation of sexually explicit content in the form of deepfakes. Deepfakes are computer-generated images or videos that make use of machine learning to replace the face or voice of someone with that of someone else. Usually this consists of fake pornographic material targeting celebrities. However, deepfake content of adult women personally known to the people wanting to create deepfakes is on the rise, in part due to the ease with which you can create such content or request to have this content created.

However, applying deepfake technology to photo or video content of children is unlikely to remain off-limits for some people and the report above states that already some of the victims of the DeepNude telegram bot appear to be under 18.

There is no doubt that artificial intelligence and machine learning are here to stay. With all of their legitimate and highly useful applications, there is inevitably the potential for abuse as well. The only thing we can do as cybersecurity professionals, parents, friends, … is limiting the attack surface as much as possible and trying to make those close to us aware of the dangers.

Tips on reducing the risks

Some general tips we can take into account to protect ourselves and our children include:

  • Determine for yourself and your children what kind of information you are willing to share online and make this desire clear to others. Respect other people’s wishes in this regard. Some people may not like it when you post a picture of them or their children on your social media, even if it is a group picture.
  • Share pictures privately instead of via social media, e.g. mail pictures of the birthday party to a selection of recipients instead of posting online.
  • If you do want to post pictures on your social media, limit the target audience to friends or people you know. As an extension, make sure you only accept connections of people you know.
  • Avoid metadata and limit details regarding location and other information that could give away a location. Some additional guidance on removing metadata provided by Microsoft here.

Conclusion

Public pictures can easily be scraped into huge collections that are used for different purposes. While traditional risks (such as sharing on the Dark Web) linked to pictures of children are well-known, emerging technologies such as artificial intelligence and machine learning have opened Pandora’s Box for potential abuse. These collections of gathered pictures can be used for facial recognition or generation of new, possibly explicit content. The resulting dangers may not only manifest now, but perhaps years in the future. As such, it is not only about protecting the child they are today, but also the adult they will become.

About the author

You can find Jonas on LinkedIn

Jonas Bauters

Jonas Bauters is a manager within NVISO, mainly providing cyber resiliency services with a focus on target-driven testing.
As the Belgian ARES (Adversarial Risk Emulation & Simulation) solution lead, his responsibilities include both technical and non-technical tasks. While occasionally still performing pass the hash (T1550.002) and pass the ticket (T1550.003), he also greatly enjoys passing the knowledge.


OneNote Embedded URL Abuse

27 March 2023 at 07:00
OneNote Embedded URL Abuse

In my previous blogpost I described how OneNote is being abused in order to deliver a malicious URL. In response to this attack, helpnetsecurity recently reported that Microsoft is planning to release a fix for the issue in April this year. Currently, it’s still unknown what this fix will look like, but from helpnetsecurity’s post, it seems like Microsoft’s fix will focus on the OneNote embedded file feature.
During my testing, I discovered that there is another way to abuse OneNote to deliver malware: Using URLs. The idea is similar to how Threat Actors are already abusing URLs in HTML pages or PDFs. Where the user is presented with a fake warning or image to click on which would open the URL in their browser and loads a phishing page.

The focus of this blogpost will be on URLs withing a OneNote file that is delivered via an attachment. Not a URL that leads to OneNote online.

There are 3 ways to deliver URLs via a OneNote file.

  1. Just plainly paste your URL in the OneNote file (Clickable URL)
  2. Make some text (like “Open”) clickable with a malicious URL (Clickable text)
  3. Embed URLs in pictures (Clickable picture)

Now it is important to note that these 3 ways rely on social engineering and tricking the user to click your URL or picture, either via instructions or deceiving the user. We have seen this technique being used through OneDrive and SharePoint online already

So, let’s create some examples and see what this attack could look like.

URLs in OneNote

Clickable URLs

The most straightforward way is to just put a URL in a OneNote file. In an actual phishing email, the OneNote file will probably not just contain the URL alone. To make things more believable, Threat Actors could potentially write a small story or an “encrypted” message in the OneNote file (an example of this can be observed below). The idea would then be to convince the user into clicking the URL in order to “decrypt” the message. Once clicked on the URL, the user would then either have to download something or provide credentials to “log in”.

If you would like to read the message in the OneNote file, you would have to click the URL. Which could then lead to the download of a malicious file or a credential harvest page.
An example of such an “encrypted” message could be:

An example of a fake encrypted message where a user has to click a URL to decrypt it

Clickable text

Similar to clickable URLs, you can hide a URL behind normal text. Once you hover over the URL, you will see where it points towards. If the address points to wards a malicious domain that uses typo squatting (e.g. g00gle[.]com instead of google[.]com) then Threat Actors could fool the human eye.

The text “open” hiding a malicious URL


The issue here lies in the fact that once you click the “open” text, you will immediately be redirected to the website. There is no pop up asking if you really want to visit the website.
Taking this technique into account, it is also possible to use our “encrypted message” example from before and make the user think they will visit a legitimate page but embed a different URL:

The visible URL “https://microsoft.com&#8221; is hiding a malicious URL

Clickable Pictures

To create an embedded URL in a picture, right-click your picture, and Click “Link…”


Here you can put a URL to your malicious file or phishing page. Yes, you could spin this story so that you would have to authenticate and login, to your browser with a fake login website.
Do note that to open a URL that is embedded within a picture, you will need to hold the CTRL key and click the image. The phishing document will have to instruct the user to hold CTRL and click the picture; however, I do not see this as an obstacle for threat actors.

A picture with the button “open” that has an embedded malicious URL

Detection Capabilities

On OneNote Interaction

Opening the URL, will launch the default browser. This can be translated to OneNote spawning a child process, which is the browser. A full process flow could look something like this:

Process execution of explorer.exe > Outlook.exe > OneNote.exe > firefox.exe


Do note that, as typically done so by Outlook, once you click the file, it saves a copy in a temporary cache folder (depending on your version of outlook, this can be a slightly different place than is shown above here, but generally, you will have the name INetCache and Content.Outlook in the folder path.)

A quick hunting rule for this behaviour can be to look for the process tree that was observed before. This process tree can be adjusted to the needs of your environment, depending on what browser is being used (e.g. if you are running brave.exe, you should include this in the “FileName” section of the query)

DeviceProcessEvents
| where InitiatingProcessFileName contains "onenote.exe"
| where FileName has_any ("firefox.exe","msedge.exe","chrome.exe")

Now if you’d like a more “catch all” approach, the last line can be replaced with a query that looks at the command line and looks for http or other protocols like ftp, as both chromium & Firefox-based browsers accept URLs as a command line argument to open a specific website.

| where ProcessCommandLine has_any ("http","ftp")

On Email Delivery

During our tests, Microsoft Defender was unable to detect and extract the URLs that were embedded in the OneNote file, as can be observed in the screenshot below. Defender was unable to extract the URLs from the OneNote files, nor was it able to show that a URL was embedded in the file.

No URLs extracted from the OneNote Attachment


This also means that Microsoft does not create a safe link for the URL and thus a threat actor can bypass the “potential malicious URL clicked” alert which helps against phishing pages, as this looks at URL clicks, which is impossible if no URLs are detected

Conclusion

Whilst embedded files within OneNote are currently still a big threat, you shouldn’t forget that there are other ways of abusing OneNote features that can be used for malicious intent. As we observed, Microsoft does not extract the URLs from a OneNote file and there are multiple ways of avoiding detection & tricking the user into clicking a URL. From there, the same tactics are used to deliver second stage malware, be it via ISO file or ZIP file that contains malicious scripts.

Nicholas Dhaeyer

Nicholas Dhaeyer is a Threat Hunter for NVISO. Nicholas specializes in Threat Hunting, Malware analysis & Industrial Control System (ICS) / Operational Technology (OT) Security. Nicholas has worked in the NVISO SOC solving security incidents for our MDR clients. You can reach out to Nicholas via Twitter or LinkedIn

❌
❌