no_cluely_driver/
lib.rs

1use std::ffi::{CStr, CString};
2use std::os::raw::{c_char, c_int, c_void};
3use std::ptr;
4
5// Core Graphics and Core Foundation bindings
6#[link(name = "CoreGraphics", kind = "framework")]
7#[link(name = "CoreFoundation", kind = "framework")]
8#[link(name = "ApplicationServices", kind = "framework")]
9extern "C" {
10    fn CGWindowListCopyWindowInfo(option: u32, relative_window_id: u32) -> *const c_void;
11    fn CFArrayGetCount(array: *const c_void) -> isize;
12    fn CFArrayGetValueAtIndex(array: *const c_void, index: isize) -> *const c_void;
13    fn CFDictionaryGetValue(dict: *const c_void, key: *const c_void) -> *const c_void;
14    fn CFStringCreateWithCString(
15        allocator: *const c_void,
16        c_str: *const c_char,
17        encoding: u32,
18    ) -> *const c_void;
19    fn CFStringGetCStringPtr(string: *const c_void, encoding: u32) -> *const c_char;
20    fn CFStringGetCString(
21        string: *const c_void,
22        buffer: *mut c_char,
23        buffer_size: isize,
24        encoding: u32,
25    ) -> bool;
26    fn CFNumberGetValue(number: *const c_void, number_type: c_int, value_ptr: *mut c_void) -> bool;
27    fn CFRelease(cf_type: *const c_void);
28    fn CFGetTypeID(cf_type: *const c_void) -> usize;
29    fn CFStringGetTypeID() -> usize;
30    fn CFNumberGetTypeID() -> usize;
31}
32
33// Constants
34const K_CG_WINDOW_LIST_OPTION_ALL: u32 = 0;
35const K_CF_STRING_ENCODING_UTF8: u32 = 0x08000100;
36const K_CF_NUMBER_INT_TYPE: c_int = 9;
37const WINDOW_OWNER_NAME: &str = "kCGWindowOwnerName";
38const WINDOW_SHARING_STATE: &str = "kCGWindowSharingState";
39const WINDOW_LAYER: &str = "kCGWindowLayer";
40const WINDOW_NUMBER: &str = "kCGWindowNumber";
41
42/// Detailed detection result with evasion techniques
43#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct ClueLyDetectionResult {
46    pub is_detected: bool,
47    pub window_count: u32,
48    pub screen_capture_evasion_count: u32, // Windows avoiding screen capture
49    pub elevated_layer_count: u32,         // Windows using elevated layers
50    pub max_layer_detected: i32,           // Highest layer number found
51}
52
53/// Window information for detailed analysis
54#[derive(Debug)]
55struct WindowInfo {
56    owner: String,
57    window_id: i32,
58    sharing_state: i32,
59    layer: i32,
60}
61
62fn create_cfstring(s: &str) -> *const c_void {
63    let c_str = CString::new(s).unwrap();
64    unsafe { CFStringCreateWithCString(ptr::null(), c_str.as_ptr(), K_CF_STRING_ENCODING_UTF8) }
65}
66
67fn cfstring_to_string(cf_string: *const c_void) -> String {
68    if cf_string.is_null() {
69        return String::new();
70    }
71
72    unsafe {
73        let c_str_ptr = CFStringGetCStringPtr(cf_string, K_CF_STRING_ENCODING_UTF8);
74        if !c_str_ptr.is_null() {
75            return CStr::from_ptr(c_str_ptr).to_string_lossy().into_owned();
76        }
77
78        let mut buffer = vec![0u8; 1024];
79        let success = CFStringGetCString(
80            cf_string,
81            buffer.as_mut_ptr() as *mut c_char,
82            buffer.len() as isize,
83            K_CF_STRING_ENCODING_UTF8,
84        );
85
86        if success {
87            let c_str = CStr::from_ptr(buffer.as_ptr() as *const c_char);
88            c_str.to_string_lossy().into_owned()
89        } else {
90            String::new()
91        }
92    }
93}
94
95fn get_dict_string(dict: *const c_void, key: &str) -> String {
96    unsafe {
97        let cf_key = create_cfstring(key);
98        let value = CFDictionaryGetValue(dict, cf_key);
99        CFRelease(cf_key);
100
101        if value.is_null() {
102            return String::new();
103        }
104
105        if CFGetTypeID(value) == CFStringGetTypeID() {
106            cfstring_to_string(value)
107        } else {
108            String::new()
109        }
110    }
111}
112
113fn get_dict_int(dict: *const c_void, key: &str) -> i32 {
114    unsafe {
115        let cf_key = create_cfstring(key);
116        let value = CFDictionaryGetValue(dict, cf_key);
117        CFRelease(cf_key);
118
119        if value.is_null() {
120            return 0;
121        }
122
123        if CFGetTypeID(value) == CFNumberGetTypeID() {
124            let mut result: i32 = 0;
125            CFNumberGetValue(
126                value,
127                K_CF_NUMBER_INT_TYPE,
128                &mut result as *mut i32 as *mut c_void,
129            );
130            result
131        } else {
132            0
133        }
134    }
135}
136
137fn is_cluely_process(owner: &str) -> bool {
138    let owner_lower = owner.to_lowercase();
139
140    // Exclude our own detection tool
141    if owner_lower.contains("no-cluely") {
142        return false;
143    }
144
145    // Look for actual Cluely processes
146    owner_lower.contains("cluely")
147        || owner_lower.contains("clue.ly")
148        || owner_lower.contains("com.cluely")
149        || owner_lower.contains("io.cluely")
150        || owner_lower.contains("co.cluely")
151}
152
153fn analyze_cluely_windows() -> (Vec<WindowInfo>, ClueLyDetectionResult) {
154    let mut cluely_windows = Vec::new();
155    let mut result = ClueLyDetectionResult {
156        is_detected: false,
157        window_count: 0,
158        screen_capture_evasion_count: 0,
159        elevated_layer_count: 0,
160        max_layer_detected: 0,
161    };
162
163    unsafe {
164        let window_list = CGWindowListCopyWindowInfo(K_CG_WINDOW_LIST_OPTION_ALL, 0);
165
166        if window_list.is_null() {
167            return (cluely_windows, result);
168        }
169
170        let count = CFArrayGetCount(window_list);
171
172        for i in 0..count {
173            let window_dict = CFArrayGetValueAtIndex(window_list, i);
174            if window_dict.is_null() {
175                continue;
176            }
177
178            let owner = get_dict_string(window_dict, WINDOW_OWNER_NAME);
179            if is_cluely_process(&owner) {
180                let window_id = get_dict_int(window_dict, WINDOW_NUMBER);
181                let sharing_state = get_dict_int(window_dict, WINDOW_SHARING_STATE);
182                let layer = get_dict_int(window_dict, WINDOW_LAYER);
183
184                let window_info = WindowInfo {
185                    owner,
186                    window_id,
187                    sharing_state,
188                    layer,
189                };
190
191                result.is_detected = true;
192                result.window_count += 1;
193
194                // Check for specific evasion techniques
195                if sharing_state == 0 {
196                    result.screen_capture_evasion_count += 1;
197                }
198
199                if layer > 0 {
200                    result.elevated_layer_count += 1;
201                    if layer > result.max_layer_detected {
202                        result.max_layer_detected = layer;
203                    }
204                }
205
206                cluely_windows.push(window_info);
207            }
208        }
209
210        CFRelease(window_list);
211    }
212
213    (cluely_windows, result)
214}
215
216/// Main detection function - returns detailed result
217/// This is the primary Rust API for detection
218pub fn detect_cluely_rust() -> ClueLyDetectionResult {
219    let (_, result) = analyze_cluely_windows();
220    result
221}
222
223/// Simple boolean check function for Rust API
224pub fn is_cluely_running_rust() -> bool {
225    let result = detect_cluely_rust();
226    result.is_detected
227}
228
229/// Get the number of Cluely windows detected (Rust API)
230pub fn get_cluely_window_count_rust() -> u32 {
231    let result = detect_cluely_rust();
232    result.window_count
233}
234
235/// C API - Main detection function (for compatibility)
236///
237/// # Safety
238/// This function is safe to call from Swift/C
239#[no_mangle]
240pub extern "C" fn detect_cluely() -> ClueLyDetectionResult {
241    let (_, result) = analyze_cluely_windows();
242    result
243}
244
245/// C API - Simple boolean check (for compatibility)
246///
247/// # Safety
248/// This function is safe to call from Swift/C
249#[no_mangle]
250pub extern "C" fn is_cluely_running() -> c_int {
251    if detect_cluely().is_detected {
252        1
253    } else {
254        0
255    }
256}
257
258/// C API - Get window count (for compatibility)
259///
260/// # Safety
261/// This function is safe to call from Swift/C
262#[no_mangle]
263pub extern "C" fn get_cluely_window_count() -> u32 {
264    detect_cluely().window_count
265}
266
267/// Generate a detailed text report of Cluely detection
268/// Returns a pointer to a C string that must be freed with free_cluely_report
269///
270/// # Safety
271/// This function is safe to call from Swift/C
272/// The returned string must be freed with free_cluely_report
273#[no_mangle]
274pub extern "C" fn get_cluely_report() -> *mut c_char {
275    let (windows, result) = analyze_cluely_windows();
276
277    let mut report = String::new();
278
279    if result.is_detected {
280        report.push_str("🚨 CLUELY EMPLOYEE MONITORING DETECTED\n");
281        report.push_str("=====================================\n\n");
282
283        report.push_str("📊 Summary:\n");
284        report.push_str(&format!(
285            "   • Total Cluely windows: {}\n",
286            result.window_count
287        ));
288        report.push_str(&format!(
289            "   • Screen capture evasion: {}\n",
290            result.screen_capture_evasion_count
291        ));
292        report.push_str(&format!(
293            "   • Elevated layer usage: {}\n",
294            result.elevated_layer_count
295        ));
296        if result.max_layer_detected > 0 {
297            report.push_str(&format!(
298                "   • Highest layer detected: {}\n",
299                result.max_layer_detected
300            ));
301        }
302        report.push('\n');
303
304        report.push_str("🔍 Evasion Techniques Detected:\n");
305        if result.screen_capture_evasion_count > 0 {
306            report.push_str(&format!(
307                "   ⚠️  {} window(s) configured to avoid screen capture\n",
308                result.screen_capture_evasion_count
309            ));
310        }
311        if result.elevated_layer_count > 0 {
312            report.push_str(&format!(
313                "   ⚠️  {} window(s) using elevated display layers\n",
314                result.elevated_layer_count
315            ));
316        }
317        report.push('\n');
318
319        report.push_str("📋 Window Details:\n");
320        for (i, window) in windows.iter().enumerate() {
321            report.push_str(&format!(
322                "   {}. Window ID: {} [{}]\n",
323                i + 1,
324                window.window_id,
325                window.owner
326            ));
327            report.push_str(&format!(
328                "      - Sharing State: {} {}\n",
329                window.sharing_state,
330                if window.sharing_state == 0 {
331                    "(avoiding screen capture)"
332                } else {
333                    "(normal)"
334                }
335            ));
336            report.push_str(&format!(
337                "      - Layer: {} {}\n",
338                window.layer,
339                if window.layer > 0 {
340                    "(elevated - potential overlay)"
341                } else {
342                    "(normal)"
343                }
344            ));
345
346            let mut techniques = Vec::new();
347            if window.sharing_state == 0 {
348                techniques.push("Screen capture evasion");
349            }
350            if window.layer > 0 {
351                techniques.push("Elevated layer positioning");
352            }
353
354            if !techniques.is_empty() {
355                report.push_str(&format!("      - Techniques: {}\n", techniques.join(", ")));
356            }
357            report.push('\n');
358        }
359
360        report.push_str("⚠️  WARNING:\n");
361        report.push_str("   This software is designed to monitor employee activity\n");
362        report.push_str("   while remaining hidden during screen sharing sessions.\n");
363        report.push_str("   Your activities may be recorded even when sharing your screen.\n");
364    } else {
365        report.push_str("✅ NO CLUELY MONITORING DETECTED\n");
366        report.push_str("================================\n\n");
367        report.push_str("No Cluely employee monitoring software found.\n");
368        report.push_str("Your system appears to be free from this monitoring tool.\n");
369    }
370
371    // Convert to C string
372    let c_string = CString::new(report).unwrap();
373    c_string.into_raw()
374}
375
376/// Free memory allocated by get_cluely_report
377///
378/// # Safety
379/// This function is safe to call from Swift/C
380/// Only call this with pointers returned by get_cluely_report
381#[no_mangle]
382#[allow(clippy::missing_safety_doc)]
383pub unsafe extern "C" fn free_cluely_report(ptr: *mut c_char) {
384    if !ptr.is_null() {
385        unsafe {
386            let _ = CString::from_raw(ptr);
387        }
388    }
389}