├── .gitignore ├── EndpointSecurityDemo.xcodeproj ├── .xcodesamplecode.plist ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── EndpointSecurityDemo.xcscheme ├── EndpointSecurityDemo ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── EndpointSecurityDemo.entitlements ├── Info.plist └── main.m ├── LICENSE └── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | # Pods/ 40 | # 41 | # Add this line if you want to avoid checking in source code from the Xcode workspace 42 | # *.xcworkspace 43 | 44 | # Carthage 45 | # 46 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 47 | # Carthage/Checkouts 48 | 49 | Carthage/Build/ 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. 54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 57 | 58 | fastlane/report.xml 59 | fastlane/Preview.html 60 | fastlane/screenshots/**/*.png 61 | fastlane/test_output 62 | 63 | # Code Injection 64 | # 65 | # After new code Injection tools there's a generated folder /iOSInjectionProject 66 | # https://github.com/johnno1962/injectionforxcode 67 | 68 | iOSInjectionProject/ 69 | -------------------------------------------------------------------------------- /EndpointSecurityDemo.xcodeproj/.xcodesamplecode.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EndpointSecurityDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 072A30AE283A5DAB003AEF26 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 072A30AC283A5DAB003AEF26 /* README.md */; }; 11 | 0767D7D026C1B55600A0C0FB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0767D7CF26C1B55600A0C0FB /* Assets.xcassets */; }; 12 | 0767D7D626C1B55600A0C0FB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0767D7D526C1B55600A0C0FB /* main.m */; }; 13 | 07B0AF1C26C1BC44007FCE49 /* libbsm.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B0AF1B26C1BC44007FCE49 /* libbsm.tbd */; }; 14 | 07B0AF1E26C1BC4C007FCE49 /* libEndpointSecurity.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B0AF1D26C1BC4C007FCE49 /* libEndpointSecurity.tbd */; }; 15 | 07FE053B27C26BF200C70F27 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07FE053927C25E0700C70F27 /* UniformTypeIdentifiers.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 072A30AC283A5DAB003AEF26 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 20 | 072A30B2283A5F83003AEF26 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 21 | 0767D7C626C1B55600A0C0FB /* EndpointSecurityDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EndpointSecurityDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 0767D7CF26C1B55600A0C0FB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 0767D7D426C1B55600A0C0FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 0767D7D526C1B55600A0C0FB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | 0767D7D726C1B55600A0C0FB /* EndpointSecurityDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EndpointSecurityDemo.entitlements; sourceTree = ""; }; 26 | 07B0AF1B26C1BC44007FCE49 /* libbsm.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbsm.tbd; path = usr/lib/libbsm.tbd; sourceTree = SDKROOT; }; 27 | 07B0AF1D26C1BC4C007FCE49 /* libEndpointSecurity.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libEndpointSecurity.tbd; path = usr/lib/libEndpointSecurity.tbd; sourceTree = SDKROOT; }; 28 | 07FE053927C25E0700C70F27 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 0767D7C326C1B55600A0C0FB /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 07FE053B27C26BF200C70F27 /* UniformTypeIdentifiers.framework in Frameworks */, 37 | 07B0AF1E26C1BC4C007FCE49 /* libEndpointSecurity.tbd in Frameworks */, 38 | 07B0AF1C26C1BC44007FCE49 /* libbsm.tbd in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 072A30AF283A5F1A003AEF26 /* LICENSE */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 072A30B2283A5F83003AEF26 /* LICENSE.txt */, 49 | ); 50 | path = LICENSE; 51 | sourceTree = ""; 52 | }; 53 | 0767D7BD26C1B55600A0C0FB = { 54 | isa = PBXGroup; 55 | children = ( 56 | 072A30AC283A5DAB003AEF26 /* README.md */, 57 | 0767D7C826C1B55600A0C0FB /* EndpointSecurityDemo */, 58 | 0767D7C726C1B55600A0C0FB /* Products */, 59 | 07B0AF1A26C1BC44007FCE49 /* Frameworks */, 60 | 072A30AF283A5F1A003AEF26 /* LICENSE */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 0767D7C726C1B55600A0C0FB /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 0767D7C626C1B55600A0C0FB /* EndpointSecurityDemo.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 0767D7C826C1B55600A0C0FB /* EndpointSecurityDemo */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 0767D7CF26C1B55600A0C0FB /* Assets.xcassets */, 76 | 0767D7D426C1B55600A0C0FB /* Info.plist */, 77 | 0767D7D526C1B55600A0C0FB /* main.m */, 78 | 0767D7D726C1B55600A0C0FB /* EndpointSecurityDemo.entitlements */, 79 | ); 80 | path = EndpointSecurityDemo; 81 | sourceTree = ""; 82 | }; 83 | 07B0AF1A26C1BC44007FCE49 /* Frameworks */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 07FE053927C25E0700C70F27 /* UniformTypeIdentifiers.framework */, 87 | 07B0AF1D26C1BC4C007FCE49 /* libEndpointSecurity.tbd */, 88 | 07B0AF1B26C1BC44007FCE49 /* libbsm.tbd */, 89 | ); 90 | name = Frameworks; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 0767D7C526C1B55600A0C0FB /* EndpointSecurityDemo */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 0767D7DA26C1B55600A0C0FB /* Build configuration list for PBXNativeTarget "EndpointSecurityDemo" */; 99 | buildPhases = ( 100 | 0767D7C226C1B55600A0C0FB /* Sources */, 101 | 0767D7C326C1B55600A0C0FB /* Frameworks */, 102 | 0767D7C426C1B55600A0C0FB /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = EndpointSecurityDemo; 109 | productName = EndpointSecurityDemo; 110 | productReference = 0767D7C626C1B55600A0C0FB /* EndpointSecurityDemo.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 0767D7BE26C1B55600A0C0FB /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastUpgradeCheck = 1320; 120 | TargetAttributes = { 121 | 0767D7C526C1B55600A0C0FB = { 122 | CreatedOnToolsVersion = 12.5.1; 123 | }; 124 | }; 125 | }; 126 | buildConfigurationList = 0767D7C126C1B55600A0C0FB /* Build configuration list for PBXProject "EndpointSecurityDemo" */; 127 | compatibilityVersion = "Xcode 9.3"; 128 | developmentRegion = en; 129 | hasScannedForEncodings = 0; 130 | knownRegions = ( 131 | en, 132 | Base, 133 | ); 134 | mainGroup = 0767D7BD26C1B55600A0C0FB; 135 | productRefGroup = 0767D7C726C1B55600A0C0FB /* Products */; 136 | projectDirPath = ""; 137 | projectRoot = ""; 138 | targets = ( 139 | 0767D7C526C1B55600A0C0FB /* EndpointSecurityDemo */, 140 | ); 141 | }; 142 | /* End PBXProject section */ 143 | 144 | /* Begin PBXResourcesBuildPhase section */ 145 | 0767D7C426C1B55600A0C0FB /* Resources */ = { 146 | isa = PBXResourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 072A30AE283A5DAB003AEF26 /* README.md in Resources */, 150 | 0767D7D026C1B55600A0C0FB /* Assets.xcassets in Resources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXResourcesBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | 0767D7C226C1B55600A0C0FB /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 0767D7D626C1B55600A0C0FB /* main.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin XCBuildConfiguration section */ 168 | 0767D7D826C1B55600A0C0FB /* Debug */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | CLANG_ANALYZER_NONNULL = YES; 173 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 174 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 175 | CLANG_CXX_LIBRARY = "libc++"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_ENABLE_OBJC_WEAK = YES; 179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_COMMA = YES; 182 | CLANG_WARN_CONSTANT_CONVERSION = YES; 183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 196 | CLANG_WARN_STRICT_PROTOTYPES = YES; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu11; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | MACOSX_DEPLOYMENT_TARGET = 10.15; 220 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 221 | MTL_FAST_MATH = YES; 222 | ONLY_ACTIVE_ARCH = NO; 223 | SDKROOT = macosx; 224 | }; 225 | name = Debug; 226 | }; 227 | 0767D7D926C1B55600A0C0FB /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_ENABLE_OBJC_WEAK = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | COPY_PHASE_STRIP = NO; 261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 262 | ENABLE_NS_ASSERTIONS = NO; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | GCC_C_LANGUAGE_STANDARD = gnu11; 265 | GCC_NO_COMMON_BLOCKS = YES; 266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 268 | GCC_WARN_UNDECLARED_SELECTOR = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 270 | GCC_WARN_UNUSED_FUNCTION = YES; 271 | GCC_WARN_UNUSED_VARIABLE = YES; 272 | MACOSX_DEPLOYMENT_TARGET = 10.15; 273 | MTL_ENABLE_DEBUG_INFO = NO; 274 | MTL_FAST_MATH = YES; 275 | SDKROOT = macosx; 276 | }; 277 | name = Release; 278 | }; 279 | 0767D7DB26C1B55600A0C0FB /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 283 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 284 | CODE_SIGN_ENTITLEMENTS = EndpointSecurityDemo/EndpointSecurityDemo.entitlements; 285 | CODE_SIGN_IDENTITY = "Apple Development"; 286 | CODE_SIGN_STYLE = Automatic; 287 | COMBINE_HIDPI_IMAGES = YES; 288 | DEVELOPMENT_TEAM = ""; 289 | ENABLE_HARDENED_RUNTIME = YES; 290 | INFOPLIST_FILE = EndpointSecurityDemo/Info.plist; 291 | LD_RUNPATH_SEARCH_PATHS = ( 292 | "$(inherited)", 293 | "@executable_path/../Frameworks", 294 | ); 295 | MACOSX_DEPLOYMENT_TARGET = 10.15; 296 | PRODUCT_BUNDLE_IDENTIFIER = com.example.EndpointSecurityDemo; 297 | PRODUCT_NAME = "$(TARGET_NAME)"; 298 | PROVISIONING_PROFILE_SPECIFIER = ""; 299 | }; 300 | name = Debug; 301 | }; 302 | 0767D7DC26C1B55600A0C0FB /* Release */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 306 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 307 | CODE_SIGN_ENTITLEMENTS = EndpointSecurityDemo/EndpointSecurityDemo.entitlements; 308 | CODE_SIGN_IDENTITY = "Apple Development"; 309 | CODE_SIGN_STYLE = Automatic; 310 | COMBINE_HIDPI_IMAGES = YES; 311 | DEVELOPMENT_TEAM = ""; 312 | ENABLE_HARDENED_RUNTIME = YES; 313 | INFOPLIST_FILE = EndpointSecurityDemo/Info.plist; 314 | LD_RUNPATH_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "@executable_path/../Frameworks", 317 | ); 318 | MACOSX_DEPLOYMENT_TARGET = 10.15; 319 | PRODUCT_BUNDLE_IDENTIFIER = com.example.EndpointSecurityDemo; 320 | PRODUCT_NAME = "$(TARGET_NAME)"; 321 | PROVISIONING_PROFILE_SPECIFIER = ""; 322 | }; 323 | name = Release; 324 | }; 325 | /* End XCBuildConfiguration section */ 326 | 327 | /* Begin XCConfigurationList section */ 328 | 0767D7C126C1B55600A0C0FB /* Build configuration list for PBXProject "EndpointSecurityDemo" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | 0767D7D826C1B55600A0C0FB /* Debug */, 332 | 0767D7D926C1B55600A0C0FB /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | 0767D7DA26C1B55600A0C0FB /* Build configuration list for PBXNativeTarget "EndpointSecurityDemo" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | 0767D7DB26C1B55600A0C0FB /* Debug */, 341 | 0767D7DC26C1B55600A0C0FB /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | /* End XCConfigurationList section */ 347 | }; 348 | rootObject = 0767D7BE26C1B55600A0C0FB /* Project object */; 349 | } 350 | -------------------------------------------------------------------------------- /EndpointSecurityDemo.xcodeproj/xcshareddata/xcschemes/EndpointSecurityDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/EndpointSecurityDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.endpoint-security.client 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /EndpointSecurityDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // EndpointSecurityDemo 4 | // 5 | // Created by Omar Ikram on 17/06/2019 - macOS Catalina 10.15 Beta 1 (19A471t) 6 | // Updated by Omar Ikram on 15/08/2019 - macOS Catalina 10.15 Beta 5 (19A526h) 7 | // Updated by Omar Ikram on 01/12/2019 - macOS Catalina 10.15 (19A583) 8 | // Updated by Omar Ikram on 31/01/2021 - macOS Big Sur 11.1 (20C69) 9 | // Updated by Omar Ikram on 07/05/2021 - macOS Big Sur 11.3.1 (20E241) 10 | // Updated by Omar Ikram on 04/07/2021 - macOS Monterey 12 Beta 2 (21A5268h) 11 | // Updated by Omar Ikram on 08/01/2022 - macOS Monterey 12.1 (21C52) 12 | // Updated by Omar Ikram on 15/02/2022 - macOS Monterey 12.2.1 (21D62) 13 | // Updated by Omar Ikram on 04/01/2025 - macOS Sequoia 15.2 (24C101) 14 | // 15 | 16 | /* 17 | 18 | A demo of using Apple's EndpointSecurity framework - tested on macOS Sequoia 15.2 (24C101). 19 | 20 | Minimum supported version: macOS Catalina 10.15 21 | 22 | This demo is an update of previous demos, which has been updated to support the latest API changes 23 | Apple has made for macOS Sequoia 15. 24 | 25 | The demo has also been expanded significantly to include more detail and cover more of the API. 26 | 27 | The code, hopefully, should be self explanatory. Important details are marked by a comment 28 | starting with "Note:". 29 | 30 | Disclaimer: 31 | This code is provided as is and is only intended to be used for illustration purposes. This code is 32 | not production-ready and is not meant to be used in a production environment. Use it at your own risk! 33 | 34 | Setup: 35 | 1. Build with Xcode 16 (tested with Version 16.2 (16C5032a)), having the macOS deployment target set 36 | to 10.15 (or later) and the Hardened Runtime capability enabled. 37 | 38 | 2. Link with libraries: 39 | - libEndpointSecurity.tbd (Endpoint Security functions) 40 | - libbsm.tbd (Audit Token functions) 41 | - UniformTypeIdentifiers.framework (UTI functions, which is not available on macOS Catalina 10.15 42 | , so it needs to be optinally linked - e.g. with the '-weak_framework' linker option) 43 | 44 | 3. Codesign with entitlement 'com.apple.developer.endpoint-security.client'. 45 | 46 | If your Apple Developer account has been granted the entitlement from Apple, then the program needs 47 | to be compiled as an App (i.e. Application Bundle). This will allow you to assign a Provisioning 48 | Profile to the program, which you need to have associated the entitlement to it. 49 | 50 | If you have not been granted the entitlement. You can still build the program (as an App or Command 51 | Line Tool), but it will only be able to run on a machine which has SIP disabled (best to use a VM). 52 | 53 | Runtime: 54 | 1. Test environment should be a macOS 10.15+ machine. 55 | 2. Run the demo binary in a terminal as root (e.g. with sudo). 56 | i) Running with no arguments will display a simple usage message. 57 | ii) Running with the 'serial' argument will run the demo using 58 | the example serial event message handler. 59 | iii) Running with the 'asynchronous' argument will run the demo using 60 | the example asynchronous event message handler. 61 | iv) Adding the 'verbose' argument at the end will turn on verbose logging. 62 | 3. Terminal will display messages related to subscribed events. 63 | 4. The demo will demonstrate processing Endpoint Security event messages 64 | serially or asynchronously (depending on the selected command line argument given). 65 | 66 | The demo will also demonstrate using Endpoint Security Auth events to make the 67 | following Auth based decisions: 68 | i) Block the 'top' binary and 'Calculator' app bundle from running. 69 | ii) Block 'vim' binary from reading plain text files. 70 | 5. CTL-C to exit. 71 | 72 | */ 73 | 74 | #import 75 | #import 76 | #import 77 | #import 78 | #import 79 | #import 80 | #import 81 | #import 82 | #import 83 | 84 | #pragma mark Globals 85 | 86 | es_client_t *g_client = nil; 87 | NSSet *g_blocked_paths = nil; 88 | NSDateFormatter *g_date_formater = nil; 89 | 90 | // Endpoint Security event handler selected at startup from the command line 91 | es_handler_block_t g_handler = nil; 92 | 93 | // Used to detect if any events have been dropped by the kernel 94 | uint64_t g_global_seq_num = 0; 95 | NSMutableDictionary *g_seq_nums = nil; 96 | 97 | // Set to true if want to cache the results of an auth event response 98 | bool g_cache_auth_results = false; 99 | 100 | // Logs can become quite busy, especially when subscribing to ES_EVENT_TYPE_AUTH_OPEN events. 101 | // Only log all event messages when the flag is enabled; 102 | // otherwise only denied Auth event messages will be logged. 103 | bool g_verbose_logging = false; 104 | 105 | #pragma mark Helpers - Mach Absolute Time 106 | 107 | // This could be running on either Apple Silicon or Intel based CPUs. 108 | // We will need to apply timebase information when converting Mach absolute time to nanoseconds: 109 | // https://developer.apple.com/documentation/apple_silicon/addressing_architectural_differences_in_your_macos_code#3616875 110 | // 111 | // Note: Running x86_64 code running under Rosetta 2 will have timebase information for Intel CPUs. 112 | // This will cause discrepancies when converting Mach absolute time values from Endpoint Security Messages. 113 | // The best option would be to compile your client as a universal binary: 114 | // https://developer.apple.com/documentation/xcode/building_a_universal_macos_binary 115 | uint64_t MachTimeToNanoseconds(uint64_t machTime) { 116 | uint64_t nanoseconds = 0; 117 | static mach_timebase_info_data_t sTimebase; 118 | if(sTimebase.denom == 0) 119 | (void)mach_timebase_info(&sTimebase); 120 | 121 | nanoseconds = ((machTime * sTimebase.numer) / sTimebase.denom); 122 | 123 | return nanoseconds; 124 | } 125 | 126 | uint64_t MachTimeToSeconds(uint64_t machTime) { 127 | return MachTimeToNanoseconds(machTime) / NSEC_PER_SEC; 128 | } 129 | 130 | #pragma mark Helpers - Code Signing 131 | 132 | typedef struct { 133 | const NSString* name; 134 | int value; 135 | } CSFlag; 136 | 137 | #define CSFLAG(flag) {@#flag, flag} 138 | 139 | // Code signing flags defined in cs_blobs.h 140 | const CSFlag g_csFlags[] = { 141 | CSFLAG(CS_VALID), CSFLAG(CS_ADHOC), CSFLAG(CS_GET_TASK_ALLOW), 142 | CSFLAG(CS_INSTALLER), CSFLAG(CS_FORCED_LV), CSFLAG(CS_INVALID_ALLOWED), 143 | CSFLAG(CS_HARD), CSFLAG(CS_KILL), CSFLAG(CS_CHECK_EXPIRATION), 144 | CSFLAG(CS_RESTRICT), CSFLAG(CS_ENFORCEMENT), CSFLAG(CS_REQUIRE_LV), 145 | CSFLAG(CS_ENTITLEMENTS_VALIDATED), CSFLAG(CS_NVRAM_UNRESTRICTED), 146 | CSFLAG(CS_RUNTIME), CSFLAG(CS_LINKER_SIGNED), CSFLAG(CS_ALLOWED_MACHO), 147 | CSFLAG(CS_EXEC_SET_HARD), CSFLAG(CS_EXEC_SET_KILL), CSFLAG(CS_EXEC_SET_ENFORCEMENT), 148 | CSFLAG(CS_EXEC_INHERIT_SIP), CSFLAG(CS_KILLED), CSFLAG(CS_DYLD_PLATFORM), 149 | CSFLAG(CS_PLATFORM_BINARY), CSFLAG(CS_PLATFORM_PATH), CSFLAG(CS_DEBUGGED), 150 | CSFLAG(CS_SIGNED), CSFLAG(CS_DEV_CODE) 151 | }; 152 | 153 | NSString* codesigning_flags_str(const uint32_t codesigning_flags) { 154 | NSMutableArray *match_flags = [NSMutableArray new]; 155 | 156 | // Test which code signing flags have been set and add the matched ones to an array 157 | for(uint32_t i = 0; i < (sizeof g_csFlags / sizeof *g_csFlags); i++) { 158 | if((codesigning_flags & g_csFlags[i].value) == g_csFlags[i].value) { 159 | [match_flags addObject:g_csFlags[i].name]; 160 | } 161 | } 162 | 163 | return [match_flags componentsJoinedByString:@","]; 164 | } 165 | 166 | #pragma mark Helpers - Endpoint Security 167 | 168 | NSString* esstring_to_nsstring(const es_string_token_t es_string_token) { 169 | if(es_string_token.data && es_string_token.length > 0) { 170 | // es_string_token.data is a pointer to a null-terminated string 171 | return [NSString stringWithUTF8String:es_string_token.data]; 172 | } else { 173 | return @""; 174 | } 175 | } 176 | 177 | const NSString* event_type_str(const es_event_type_t event_type) { 178 | static const NSString *names[] = { 179 | // The following events are available beginning in macOS 10.15 180 | @"ES_EVENT_TYPE_AUTH_EXEC", @"ES_EVENT_TYPE_AUTH_OPEN", @"ES_EVENT_TYPE_AUTH_KEXTLOAD", 181 | @"ES_EVENT_TYPE_AUTH_MMAP", @"ES_EVENT_TYPE_AUTH_MPROTECT", @"ES_EVENT_TYPE_AUTH_MOUNT", 182 | @"ES_EVENT_TYPE_AUTH_RENAME", @"ES_EVENT_TYPE_AUTH_SIGNAL", @"ES_EVENT_TYPE_AUTH_UNLINK", 183 | @"ES_EVENT_TYPE_NOTIFY_EXEC", @"ES_EVENT_TYPE_NOTIFY_OPEN", @"ES_EVENT_TYPE_NOTIFY_FORK", 184 | @"ES_EVENT_TYPE_NOTIFY_CLOSE", @"ES_EVENT_TYPE_NOTIFY_CREATE", @"ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA", 185 | @"ES_EVENT_TYPE_NOTIFY_EXIT", @"ES_EVENT_TYPE_NOTIFY_GET_TASK", @"ES_EVENT_TYPE_NOTIFY_KEXTLOAD", 186 | @"ES_EVENT_TYPE_NOTIFY_KEXTUNLOAD", @"ES_EVENT_TYPE_NOTIFY_LINK", @"ES_EVENT_TYPE_NOTIFY_MMAP", 187 | @"ES_EVENT_TYPE_NOTIFY_MPROTECT", @"ES_EVENT_TYPE_NOTIFY_MOUNT", @"ES_EVENT_TYPE_NOTIFY_UNMOUNT", 188 | @"ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN", @"ES_EVENT_TYPE_NOTIFY_RENAME", @"ES_EVENT_TYPE_NOTIFY_SETATTRLIST", 189 | @"ES_EVENT_TYPE_NOTIFY_SETEXTATTR", @"ES_EVENT_TYPE_NOTIFY_SETFLAGS", @"ES_EVENT_TYPE_NOTIFY_SETMODE", 190 | @"ES_EVENT_TYPE_NOTIFY_SETOWNER", @"ES_EVENT_TYPE_NOTIFY_SIGNAL", @"ES_EVENT_TYPE_NOTIFY_UNLINK", 191 | @"ES_EVENT_TYPE_NOTIFY_WRITE", @"ES_EVENT_TYPE_AUTH_FILE_PROVIDER_MATERIALIZE", 192 | @"ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_MATERIALIZE", @"ES_EVENT_TYPE_AUTH_FILE_PROVIDER_UPDATE", 193 | @"ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_UPDATE", @"ES_EVENT_TYPE_AUTH_READLINK", @"ES_EVENT_TYPE_NOTIFY_READLINK", 194 | @"ES_EVENT_TYPE_AUTH_TRUNCATE", @"ES_EVENT_TYPE_NOTIFY_TRUNCATE", @"ES_EVENT_TYPE_AUTH_LINK", 195 | @"ES_EVENT_TYPE_NOTIFY_LOOKUP", @"ES_EVENT_TYPE_AUTH_CREATE", @"ES_EVENT_TYPE_AUTH_SETATTRLIST", 196 | @"ES_EVENT_TYPE_AUTH_SETEXTATTR", @"ES_EVENT_TYPE_AUTH_SETFLAGS", @"ES_EVENT_TYPE_AUTH_SETMODE", 197 | @"ES_EVENT_TYPE_AUTH_SETOWNER", 198 | 199 | // The following events are available beginning in macOS 10.15.1 200 | @"ES_EVENT_TYPE_AUTH_CHDIR", @"ES_EVENT_TYPE_NOTIFY_CHDIR", @"ES_EVENT_TYPE_AUTH_GETATTRLIST", 201 | @"ES_EVENT_TYPE_NOTIFY_GETATTRLIST", @"ES_EVENT_TYPE_NOTIFY_STAT", @"ES_EVENT_TYPE_NOTIFY_ACCESS", 202 | @"ES_EVENT_TYPE_AUTH_CHROOT", @"ES_EVENT_TYPE_NOTIFY_CHROOT", @"ES_EVENT_TYPE_AUTH_UTIMES", 203 | @"ES_EVENT_TYPE_NOTIFY_UTIMES", @"ES_EVENT_TYPE_AUTH_CLONE", @"ES_EVENT_TYPE_NOTIFY_CLONE", 204 | @"ES_EVENT_TYPE_NOTIFY_FCNTL", @"ES_EVENT_TYPE_AUTH_GETEXTATTR", @"ES_EVENT_TYPE_NOTIFY_GETEXTATTR", 205 | @"ES_EVENT_TYPE_AUTH_LISTEXTATTR", @"ES_EVENT_TYPE_NOTIFY_LISTEXTATTR", @"ES_EVENT_TYPE_AUTH_READDIR", 206 | @"ES_EVENT_TYPE_NOTIFY_READDIR", @"ES_EVENT_TYPE_AUTH_DELETEEXTATTR", @"ES_EVENT_TYPE_NOTIFY_DELETEEXTATTR", 207 | @"ES_EVENT_TYPE_AUTH_FSGETPATH", @"ES_EVENT_TYPE_NOTIFY_FSGETPATH", @"ES_EVENT_TYPE_NOTIFY_DUP", 208 | @"ES_EVENT_TYPE_AUTH_SETTIME", @"ES_EVENT_TYPE_NOTIFY_SETTIME", @"ES_EVENT_TYPE_NOTIFY_UIPC_BIND", 209 | @"ES_EVENT_TYPE_AUTH_UIPC_BIND", @"ES_EVENT_TYPE_NOTIFY_UIPC_CONNECT", @"ES_EVENT_TYPE_AUTH_UIPC_CONNECT", 210 | @"ES_EVENT_TYPE_AUTH_EXCHANGEDATA", @"ES_EVENT_TYPE_AUTH_SETACL", @"ES_EVENT_TYPE_NOTIFY_SETACL", 211 | 212 | // The following events are available beginning in macOS 10.15.4 213 | @"ES_EVENT_TYPE_NOTIFY_PTY_GRANT", @"ES_EVENT_TYPE_NOTIFY_PTY_CLOSE", @"ES_EVENT_TYPE_AUTH_PROC_CHECK", 214 | @"ES_EVENT_TYPE_NOTIFY_PROC_CHECK", @"ES_EVENT_TYPE_AUTH_GET_TASK", 215 | 216 | // The following events are available beginning in macOS 11.0 217 | @"ES_EVENT_TYPE_AUTH_SEARCHFS", @"ES_EVENT_TYPE_NOTIFY_SEARCHFS", @"ES_EVENT_TYPE_AUTH_FCNTL", 218 | @"ES_EVENT_TYPE_AUTH_IOKIT_OPEN", @"ES_EVENT_TYPE_AUTH_PROC_SUSPEND_RESUME", 219 | @"ES_EVENT_TYPE_NOTIFY_PROC_SUSPEND_RESUME", @"ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED", 220 | @"ES_EVENT_TYPE_NOTIFY_GET_TASK_NAME", @"ES_EVENT_TYPE_NOTIFY_TRACE", 221 | @"ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE", @"ES_EVENT_TYPE_AUTH_REMOUNT", @"ES_EVENT_TYPE_NOTIFY_REMOUNT", 222 | 223 | // The following events are available beginning in macOS 11.3 224 | @"ES_EVENT_TYPE_AUTH_GET_TASK_READ", @"ES_EVENT_TYPE_NOTIFY_GET_TASK_READ", 225 | @"ES_EVENT_TYPE_NOTIFY_GET_TASK_INSPECT", 226 | 227 | // The following events are available beginning in macOS 12.0 228 | @"ES_EVENT_TYPE_NOTIFY_SETUID", @"ES_EVENT_TYPE_NOTIFY_SETGID", @"ES_EVENT_TYPE_NOTIFY_SETEUID", 229 | @"ES_EVENT_TYPE_NOTIFY_SETEGID", @"ES_EVENT_TYPE_NOTIFY_SETREUID", 230 | @"ES_EVENT_TYPE_NOTIFY_SETREGID", @"ES_EVENT_TYPE_AUTH_COPYFILE", @"ES_EVENT_TYPE_NOTIFY_COPYFILE", 231 | 232 | // The following events are available beginning in macOS 13.0 233 | @"ES_EVENT_TYPE_NOTIFY_AUTHENTICATION", @"ES_EVENT_TYPE_NOTIFY_XP_MALWARE_DETECTED", 234 | @"ES_EVENT_TYPE_NOTIFY_XP_MALWARE_REMEDIATED", @"ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN", 235 | @"ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT", @"ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK", 236 | @"ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK", @"ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH", 237 | @"ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH", @"ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN", 238 | @"ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT", @"ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN", 239 | @"ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT", @"ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD", 240 | @"ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_REMOVE", 241 | 242 | // The following events are available beginning in macOS 14.0 243 | @"ES_EVENT_TYPE_NOTIFY_PROFILE_ADD", @"ES_EVENT_TYPE_NOTIFY_PROFILE_REMOVE", @"ES_EVENT_TYPE_NOTIFY_SU", 244 | @"ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_PETITION", @"ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_JUDGEMENT", 245 | @"ES_EVENT_TYPE_NOTIFY_SUDO", @"ES_EVENT_TYPE_NOTIFY_OD_GROUP_ADD", @"ES_EVENT_TYPE_NOTIFY_OD_GROUP_REMOVE", 246 | @"ES_EVENT_TYPE_NOTIFY_OD_GROUP_SET", @"ES_EVENT_TYPE_NOTIFY_OD_MODIFY_PASSWORD", 247 | @"ES_EVENT_TYPE_NOTIFY_OD_DISABLE_USER", @"ES_EVENT_TYPE_NOTIFY_OD_ENABLE_USER", 248 | @"ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_ADD", @"ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_REMOVE", 249 | @"ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_SET", @"ES_EVENT_TYPE_NOTIFY_OD_CREATE_USER", 250 | @"ES_EVENT_TYPE_NOTIFY_OD_CREATE_GROUP", @"ES_EVENT_TYPE_NOTIFY_OD_DELETE_USER", 251 | @"ES_EVENT_TYPE_NOTIFY_OD_DELETE_GROUP", @"ES_EVENT_TYPE_NOTIFY_XPC_CONNECT", 252 | 253 | // The following events are available beginning in macOS 15.0 254 | @"ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE" 255 | }; 256 | 257 | if(event_type >= ES_EVENT_TYPE_LAST) { 258 | return [NSString stringWithFormat:@"Unknown/Unsupported event type: %d", event_type]; 259 | } 260 | 261 | return names[event_type]; 262 | } 263 | 264 | NSString* events_str(size_t count, const es_event_type_t* events) { 265 | NSMutableArray *arr = [NSMutableArray new]; 266 | 267 | for(size_t i = 0; i < count; i++) { 268 | [arr addObject:event_type_str(events[i])]; 269 | } 270 | 271 | return [arr componentsJoinedByString:@", "]; 272 | } 273 | 274 | // On macOS Big Sur 11, Apple have deprecated es_copy_message in favour of es_retain_message 275 | es_message_t * copy_message(const es_message_t * msg) { 276 | if(@available(macOS 11.0, *)) { 277 | es_retain_message(msg); 278 | // simulate a copy 279 | return (es_message_t*) msg; 280 | } else { 281 | return es_copy_message(msg); 282 | } 283 | } 284 | 285 | // On macOS Big Sur 11, Apple have deprecated es_free_message in favour of es_release_message 286 | void free_message(es_message_t * _Nonnull msg) { 287 | if(@available(macOS 11.0, *)) { 288 | es_release_message(msg); 289 | } else { 290 | es_free_message(msg); 291 | } 292 | } 293 | 294 | #pragma mark Helpers - Misc 295 | 296 | NSString* fdtype_str(const uint32_t fdtype) { 297 | switch(fdtype) { 298 | case PROX_FDTYPE_ATALK: return @"ATALK"; 299 | case PROX_FDTYPE_VNODE: return @"VNODE"; 300 | case PROX_FDTYPE_SOCKET: return @"SOCKET"; 301 | case PROX_FDTYPE_PSHM: return @"PSHM"; 302 | case PROX_FDTYPE_PSEM: return @"PSEM"; 303 | case PROX_FDTYPE_KQUEUE: return @"KQUEUE"; 304 | case PROX_FDTYPE_PIPE: return @"PIPE"; 305 | case PROX_FDTYPE_FSEVENTS: return @"FSEVENTS"; 306 | case PROX_FDTYPE_NETPOLICY: return @"NETPOLICY"; 307 | default: return [NSString stringWithFormat:@"Unknown/Unsupported fdtype: %d", 308 | fdtype]; 309 | } 310 | } 311 | 312 | void init_date_formater(void) { 313 | // Display dates in RFC 3339 date and time format: https://www.ietf.org/rfc/rfc3339.txt 314 | g_date_formater = [NSDateFormatter new]; 315 | g_date_formater.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; 316 | g_date_formater.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZZZ"; 317 | g_date_formater.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 318 | } 319 | 320 | NSString* formatted_date_str(__darwin_time_t secs_since_1970) { 321 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:secs_since_1970]; 322 | return [g_date_formater stringFromDate:date]; 323 | } 324 | 325 | bool is_system_file(const NSString* path) { 326 | // For the purpose of this demo. A system file is a file that is under these directories: 327 | for(NSString* prefix in @[@"/System/", @"/usr/share/"]) { 328 | if([path hasPrefix:prefix]) { 329 | return true; 330 | } 331 | } 332 | 333 | return false; 334 | } 335 | 336 | bool is_plain_text_file(const NSString* path) { 337 | if(@available(macOS 11.0, *)) { 338 | UTType* utt = [UTType typeWithFilenameExtension:[path pathExtension]]; 339 | return [utt conformsToType:UTTypePlainText]; 340 | } else { 341 | return [[NSWorkspace sharedWorkspace] 342 | filenameExtension:[path pathExtension] 343 | isValidForType:@"public.plain-text"]; 344 | } 345 | } 346 | 347 | char* filetype_str(const mode_t st_mode) { 348 | switch(((st_mode) & S_IFMT)) { 349 | case S_IFBLK: return "BLK"; 350 | case S_IFCHR: return "CHR"; 351 | case S_IFDIR: return "DIR"; 352 | case S_IFIFO: return "FIFO"; 353 | case S_IFREG: return "REG"; 354 | case S_IFLNK: return "LINK"; 355 | case S_IFSOCK: return "SOCK"; 356 | default: return ""; 357 | } 358 | } 359 | 360 | #pragma mark - Logging 361 | 362 | #define BOOL_VALUE(x) x ? "Yes" : "No" 363 | 364 | int g_log_indent = 0; 365 | #define LOG_INDENT_INC() {g_log_indent += 2;} 366 | #define LOG_INDENT_DEC() {g_log_indent -= 2;} 367 | 368 | #define LOG_IMPORTANT_INFO(fmt, ...) NSLog(@"*** " @#fmt @" ***", ##__VA_ARGS__) 369 | #define LOG_INFO(fmt, ...) NSLog(@"%*s" @#fmt, g_log_indent, "", ##__VA_ARGS__) 370 | #define LOG_ERROR(fmt, ...) NSLog(@"ERROR: " @#fmt, ##__VA_ARGS__) 371 | 372 | #define LOG_VERBOSE_EVENT_MESSAGE(msg) { \ 373 | if(g_verbose_logging) { \ 374 | log_event_message(msg); \ 375 | } \ 376 | } 377 | 378 | #define LOG_NON_VERBOSE_EVENT_MESSAGE(msg) { \ 379 | if(!g_verbose_logging) { \ 380 | log_event_message(msg); \ 381 | } \ 382 | } 383 | 384 | void log_audit_token(const NSString* header, const audit_token_t audit_token) { 385 | LOG_INFO("%@:", header); 386 | LOG_INDENT_INC(); 387 | LOG_INFO("pid: %d", audit_token_to_pid(audit_token)); 388 | LOG_INFO("ruid: %d", audit_token_to_ruid(audit_token)); 389 | LOG_INFO("euid: %d", audit_token_to_euid(audit_token)); 390 | LOG_INFO("rgid: %d", audit_token_to_rgid(audit_token)); 391 | LOG_INFO("egid: %d", audit_token_to_egid(audit_token)); 392 | LOG_INDENT_DEC(); 393 | } 394 | 395 | API_AVAILABLE(macos(12.0)) 396 | bool log_muted_paths_events(void) { 397 | es_muted_paths_t *muted_paths = NULL; 398 | es_return_t result = es_muted_paths_events(g_client, &muted_paths); 399 | 400 | if(ES_RETURN_SUCCESS != result) { 401 | LOG_ERROR("es_muted_paths_events: ES_RETURN_ERROR"); 402 | return false; 403 | } 404 | 405 | if(NULL == muted_paths) { 406 | // There are no muted paths 407 | return true; 408 | } 409 | 410 | LOG_IMPORTANT_INFO("Muted Paths"); 411 | for(size_t i = 0; i < muted_paths->count; i++) { 412 | es_muted_path_t muted_path = muted_paths->paths[i]; 413 | LOG_INFO("muted_path[%ld]: %@", i, esstring_to_nsstring(muted_path.path)); 414 | 415 | if(g_verbose_logging) { 416 | LOG_INDENT_INC(); 417 | LOG_INFO("type: %s", (muted_path.type == ES_MUTE_PATH_TYPE_PREFIX) ? "Prefix" : "Literal"); 418 | LOG_INFO("event_count: %ld", muted_path.event_count); 419 | LOG_INFO("events: %@", events_str(muted_path.event_count, muted_path.events)); 420 | LOG_INDENT_DEC(); 421 | } 422 | } 423 | 424 | es_release_muted_paths(muted_paths); 425 | return true; 426 | } 427 | 428 | bool log_subscribed_events(void) { 429 | // Log the subscribed events 430 | size_t count = 0; 431 | es_event_type_t *events = NULL; 432 | es_return_t result = es_subscriptions(g_client, &count, &events); 433 | 434 | if(ES_RETURN_SUCCESS != result) { 435 | LOG_ERROR("es_subscriptions: ES_RETURN_ERROR"); 436 | return false; 437 | } 438 | 439 | LOG_IMPORTANT_INFO("Subscribed Events: %@", events_str(count, events)); 440 | 441 | free(events); 442 | return true; 443 | } 444 | 445 | void log_file(const NSString* header, const es_file_t* file) { 446 | if(!file) { 447 | LOG_INFO("%@: (null)", header); 448 | return; 449 | } 450 | 451 | LOG_INFO("%@:", header); 452 | LOG_INDENT_INC(); 453 | LOG_INFO("path: %@", esstring_to_nsstring(file->path)); 454 | LOG_INFO("path_truncated: %s", BOOL_VALUE(file->path_truncated)); 455 | 456 | LOG_INFO("stat.st_dev: %d", file->stat.st_dev); 457 | LOG_INFO("stat.st_ino: %llu", file->stat.st_ino); 458 | LOG_INFO("stat.st_mode: %u (%s)", file->stat.st_mode, filetype_str(file->stat.st_mode)); 459 | LOG_INFO("stat.st_nlink: %u", file->stat.st_nlink); 460 | 461 | LOG_INFO("stat.st_uid: %u", file->stat.st_uid); 462 | LOG_INFO("stat.st_gid: %u", file->stat.st_gid); 463 | 464 | LOG_INFO("stat.st_atime: %@", formatted_date_str(file->stat.st_atime)); 465 | LOG_INFO("stat.st_mtime: %@", formatted_date_str(file->stat.st_mtime)); 466 | LOG_INFO("stat.st_ctime: %@", formatted_date_str(file->stat.st_ctime)); 467 | LOG_INFO("stat.st_birthtime: %@", formatted_date_str(file->stat.st_birthtime)); 468 | 469 | LOG_INFO("stat.st_size: %lld", file->stat.st_size); 470 | LOG_INFO("stat.st_blocks: %lld", file->stat.st_blocks); 471 | LOG_INFO("stat.st_blksize: %d", file->stat.st_blksize); 472 | LOG_INFO("stat.st_flags: %u", file->stat.st_flags); 473 | LOG_INFO("stat.st_gen: %u", file->stat.st_gen); 474 | LOG_INDENT_DEC(); 475 | } 476 | 477 | void log_proc(uint32_t msg_version, const NSString* header, const es_process_t* proc) { 478 | if(!proc) { 479 | LOG_INFO("%@: (null)", header); 480 | return; 481 | } 482 | 483 | LOG_INFO("%@:", header); 484 | LOG_INDENT_INC(); 485 | log_audit_token(@"proc.audit_token", proc->audit_token); 486 | LOG_INFO("proc.ppid: %d", proc->ppid); 487 | LOG_INFO("proc.original_ppid: %d", proc->original_ppid); 488 | 489 | if(msg_version >= 4) { 490 | log_audit_token(@"proc.responsible_audit_token", proc->responsible_audit_token); 491 | log_audit_token(@"proc.parent_audit_token", proc->parent_audit_token); 492 | } 493 | 494 | LOG_INFO("proc.group_id: %d", proc->group_id); 495 | LOG_INFO("proc.session_id: %d", proc->session_id); 496 | LOG_INFO("proc.is_platform_binary: %s", BOOL_VALUE(proc->is_platform_binary)); 497 | LOG_INFO("proc.is_es_client: %s", BOOL_VALUE(proc->is_es_client)); 498 | LOG_INFO("proc.signing_id: %@", esstring_to_nsstring(proc->signing_id)); 499 | LOG_INFO("proc.team_id: %@", esstring_to_nsstring(proc->team_id)); 500 | 501 | if(msg_version >= 3) { 502 | LOG_INFO("proc.start_time: %@", formatted_date_str(proc->start_time.tv_sec)); 503 | } 504 | 505 | LOG_INFO("proc.codesigning_flags: %x (%@)", 506 | proc->codesigning_flags, codesigning_flags_str(proc->codesigning_flags)); 507 | 508 | // proc.cdhash 509 | NSMutableString *hash = [NSMutableString string]; 510 | for(uint32_t i = 0; i < CS_CDHASH_LEN; i++) { 511 | [hash appendFormat:@"%02x", proc->cdhash[i]]; 512 | } 513 | LOG_INFO("proc.cdhash: %@", hash); 514 | 515 | log_file(@"proc.executable", proc->executable); 516 | 517 | if(msg_version >= 2 && proc->tty) { 518 | log_file(@"proc.tty", proc->tty); 519 | } 520 | 521 | LOG_INDENT_DEC(); 522 | } 523 | 524 | void log_command_line_arguments(const es_event_exec_t* exec) { 525 | uint32_t arg_count = es_exec_arg_count(exec); 526 | LOG_INFO("event.exec.arg_count: %u", arg_count); 527 | LOG_INDENT_INC(); 528 | 529 | // Extract each argument and log it out 530 | for(uint32_t i = 0; i < arg_count; i++) { 531 | es_string_token_t arg = es_exec_arg(exec, i); 532 | LOG_INFO("arg[%d]: %@", i, esstring_to_nsstring(arg)); 533 | } 534 | 535 | LOG_INDENT_DEC(); 536 | } 537 | 538 | void log_environment_variable(const es_event_exec_t* exec) { 539 | uint32_t env_count = es_exec_env_count(exec); 540 | LOG_INFO("event.exec.env_count: %u", env_count); 541 | LOG_INDENT_INC(); 542 | 543 | // Extract each env and log it out 544 | for(uint32_t i = 0; i < env_count; i++) { 545 | es_string_token_t arg = es_exec_env(exec, i); 546 | LOG_INFO("env[%d]: %@", i, esstring_to_nsstring(arg)); 547 | } 548 | 549 | LOG_INDENT_DEC(); 550 | } 551 | 552 | void log_file_descriptors(const es_event_exec_t* exec) { 553 | if(@available(macOS 11.0, *)) { 554 | uint32_t fd_count = es_exec_fd_count(exec); 555 | LOG_INFO("event.exec.fd_count: %u", fd_count); 556 | LOG_INDENT_INC(); 557 | 558 | // Extract each fd and log it out 559 | for(uint32_t i = 0; i < fd_count; i++) { 560 | // Pointer must not outlive event 561 | const es_fd_t *arg = es_exec_fd(exec, i); 562 | 563 | LOG_INFO("fd[%d].fd: %d", i, arg->fd); 564 | LOG_INFO("fd[%d].fdtype: %@", i, fdtype_str(arg->fdtype)); 565 | 566 | if(PROX_FDTYPE_PIPE == arg->fdtype) { 567 | LOG_INFO("fd[%d].fd: %llu", i, arg->pipe.pipe_id); 568 | } 569 | } 570 | 571 | LOG_INDENT_DEC(); 572 | } 573 | } 574 | 575 | void log_event_exec(uint32_t msg_version, const es_event_exec_t* exec) { 576 | log_proc(msg_version, @"event.exec.target", exec->target); 577 | log_command_line_arguments(exec); 578 | log_environment_variable(exec); 579 | log_file_descriptors(exec); 580 | 581 | if(msg_version >= 2 && exec->script) { 582 | log_file(@"event.exec.script", exec->script); 583 | } 584 | 585 | if(msg_version >= 3) { 586 | log_file(@"event.exec.cwd", exec->cwd); 587 | } 588 | 589 | if(msg_version >= 4) { 590 | LOG_INFO("event.exec.last_fd: %d", exec->last_fd); 591 | } 592 | } 593 | 594 | void log_event_open(const es_event_open_t* open) { 595 | NSMutableArray *match_flags = [NSMutableArray new]; 596 | 597 | if((open->fflag & FREAD) == FREAD) { 598 | [match_flags addObject:@"FREAD"]; 599 | } 600 | 601 | if((open->fflag & FWRITE) == FWRITE) { 602 | [match_flags addObject:@"FWRITE"]; 603 | } 604 | 605 | LOG_INFO("event.open.fflag: %d (%@)", 606 | open->fflag, [match_flags componentsJoinedByString:@", "]); 607 | log_file(@"event.open.file", open->file); 608 | } 609 | 610 | // Logs the top level datatype sent by Endpoint Security subsystem to its clients 611 | void log_event_message(const es_message_t *msg) { 612 | LOG_INFO("--- EVENT MESSAGE ----"); 613 | LOG_INFO("event_type: %@ (%d)", event_type_str(msg->event_type), msg->event_type); 614 | 615 | // Note: Apple have designed the Endpoint Security structures to support additional fields 616 | // in the future. Always check the version of the message before using a field, in the message 617 | // or sub-structure, which has been added to a later version of Endpoint Security. 618 | // Only new fields are added. Existing fields should be available in future revisions. 619 | uint32_t version = msg->version; 620 | LOG_INFO("version: %u", version); 621 | 622 | LOG_INFO("time: %@", formatted_date_str(msg->time.tv_sec)); 623 | LOG_INFO("mach_time: %lld", msg->mach_time); 624 | 625 | // Note: It's very important that an auth event is processed within the deadline: 626 | // https://developer.apple.com/documentation/endpointsecurity/es_message_t/3334985-deadline 627 | // From an Apple Security Engineer: 628 | // "You must respond by the deadline. 629 | // It is not configurable. 630 | // It won't get longer, but it will get shorter." 631 | // https://developer.apple.com/forums/thread/649552?answerId=615802022#615802022 632 | LOG_INFO("deadline: %llu", msg->deadline); 633 | 634 | uint64_t deadlineInterval = msg->deadline; 635 | 636 | if(deadlineInterval > 0) { 637 | deadlineInterval -= msg->mach_time; 638 | } 639 | 640 | LOG_INFO("deadline interval: %llu (%llu seconds)", 641 | deadlineInterval, MachTimeToSeconds(deadlineInterval)); 642 | 643 | // Note: You can use the seq_num field to detect if the kernel had to drop any event messages, 644 | // for an event type, to the client. 645 | if(version >= 2) { 646 | LOG_INFO("seq_num: %lld", msg->seq_num); 647 | } 648 | 649 | // Note: You can use the global_seq_num field to detect if the kernel had to drop any event 650 | // messages to the client. 651 | if(version >= 4) { 652 | LOG_INFO("global_seq_num: %lld", msg->global_seq_num); 653 | } 654 | 655 | if(version >= 4 && msg->thread) { 656 | LOG_INFO("thread_id: %lld", msg->thread->thread_id); 657 | } 658 | 659 | LOG_INFO("action_type: %s", (msg->action_type == ES_ACTION_TYPE_AUTH) ? "Auth" : "Notify"); 660 | log_proc(version, @"process", msg->process); 661 | 662 | // Event specific logging 663 | switch(msg->event_type) { 664 | case ES_EVENT_TYPE_AUTH_EXEC: { 665 | log_event_exec(version, &msg->event.exec); 666 | } 667 | break; 668 | 669 | case ES_EVENT_TYPE_AUTH_OPEN: { 670 | log_event_open(&msg->event.open); 671 | } 672 | break; 673 | 674 | case ES_EVENT_TYPE_NOTIFY_FORK: { 675 | log_proc(version, @"event.fork.child", msg->event.fork.child); 676 | } 677 | break; 678 | 679 | case ES_EVENT_TYPE_LAST: 680 | default: { 681 | // Not interested 682 | } 683 | } 684 | 685 | LOG_INFO(""); 686 | } 687 | 688 | // Demonstrates detecting dropped event messages from the kernel, by either 689 | // using the using the seq_num or global_seq_num fields in an event message 690 | void detect_and_log_dropped_events(const es_message_t *msg) { 691 | uint32_t version = msg->version; 692 | 693 | // Note: You can use the seq_num field to detect if the kernel had to 694 | // drop any event messages, for an event type, to the client. 695 | if(version >= 2) { 696 | uint64_t seq_num = msg->seq_num; 697 | 698 | const NSString *type = event_type_str(msg->event_type); 699 | NSNumber *last_seq_num = [g_seq_nums objectForKey:type]; 700 | 701 | if(last_seq_num != nil) { 702 | uint64_t expected_seq_num = [last_seq_num unsignedLongLongValue] + 1; 703 | 704 | if(seq_num > expected_seq_num) { 705 | LOG_ERROR("EVENTS DROPPED! seq_num is ahead by: %llu", 706 | (seq_num - expected_seq_num)); 707 | } 708 | } 709 | 710 | [g_seq_nums setObject:[NSNumber numberWithUnsignedLong:seq_num] forKey:type]; 711 | } 712 | 713 | // Note: You can use the global_seq_num field to detect if the kernel had to 714 | // drop any event messages to the client. 715 | if(version >= 4) { 716 | uint64_t global_seq_num = msg->global_seq_num; 717 | 718 | if(global_seq_num > ++g_global_seq_num) { 719 | LOG_ERROR("EVENTS DROPPED! global_seq_num is ahead by: %llu", 720 | (global_seq_num - g_global_seq_num)); 721 | g_global_seq_num = global_seq_num; 722 | } 723 | } 724 | } 725 | 726 | #pragma mark - Endpoint Secuirty Demo 727 | 728 | // Clean-up before exiting 729 | void sig_handler(int sig) { 730 | LOG_IMPORTANT_INFO("Tidying Up"); 731 | 732 | if(g_client) { 733 | es_unsubscribe_all(g_client); 734 | es_delete_client(g_client); 735 | } 736 | 737 | LOG_IMPORTANT_INFO("Exiting"); 738 | exit(EXIT_SUCCESS); 739 | } 740 | 741 | void print_usage(const char *name) { 742 | printf("Usage: %s (serial | asynchronous) (verbose)\n", name); 743 | printf("Arguments:\n"); 744 | printf("\tserial\t\tUse serial message handler\n"); 745 | printf("\tasynchronous\tUse asynchronous message handler\n"); 746 | printf("\tverbose\t\tTurns on verbose logging\n"); 747 | } 748 | 749 | // An example handler to make auth (allow or block) decisions. 750 | // Returns either an ES_AUTH_RESULT_ALLOW or ES_AUTH_RESULT_DENY. 751 | es_auth_result_t auth_event_handler(const es_message_t *msg) { 752 | // NOTE: You should ignore events from other ES Clients; 753 | // otherwise you may trigger more events causing a potentially infinite cycle. 754 | if(msg->process->is_es_client) { 755 | return ES_AUTH_RESULT_ALLOW; 756 | } 757 | 758 | // Ignore events from root processes 759 | if(0 == audit_token_to_ruid(msg->process->audit_token)) { 760 | return ES_AUTH_RESULT_ALLOW; 761 | } 762 | 763 | // Block exec if path of process is in our blocked paths list 764 | if(ES_EVENT_TYPE_AUTH_EXEC == msg->event_type) { 765 | NSString *path = esstring_to_nsstring(msg->event.exec.target->executable->path); 766 | 767 | if(![g_blocked_paths containsObject:path]) { 768 | return ES_AUTH_RESULT_ALLOW; 769 | } 770 | 771 | // Process is in our blocked list 772 | LOG_IMPORTANT_INFO("BLOCKING EXEC: %@", path); 773 | return ES_AUTH_RESULT_DENY; 774 | } 775 | 776 | // Block vim from accessing plain text files 777 | if(ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) { 778 | NSString *processPath = esstring_to_nsstring(msg->process->executable->path); 779 | 780 | if(![processPath isEqualToString:@"/usr/bin/vim"]) { 781 | // Not vim 782 | return ES_AUTH_RESULT_ALLOW; 783 | } 784 | 785 | NSString *filePath = esstring_to_nsstring(msg->event.open.file->path); 786 | 787 | if(is_system_file(filePath)) { 788 | // Ignore System files 789 | return ES_AUTH_RESULT_ALLOW; 790 | } 791 | 792 | if(!is_plain_text_file(filePath)) { 793 | // Not a text file 794 | return ES_AUTH_RESULT_ALLOW; 795 | } 796 | 797 | // Process is vim trying to access a text file 798 | LOG_IMPORTANT_INFO("BLOCKING OPEN: %@", filePath); 799 | return ES_AUTH_RESULT_DENY; 800 | } 801 | 802 | // All good 803 | return ES_AUTH_RESULT_ALLOW; 804 | } 805 | 806 | // Sends a response back to Endpoint Security for an auth event 807 | // Note: You must always send a response back before the deadline expires. 808 | void respond_to_auth_event(es_client_t *clt, const es_message_t *msg, es_auth_result_t result) { 809 | // Only log ES_AUTH_RESULT_DENY results when verbose logging is disabled 810 | if(ES_AUTH_RESULT_DENY == result) { 811 | LOG_NON_VERBOSE_EVENT_MESSAGE(msg); 812 | } 813 | 814 | // Note: You use es_respond_auth_result() to respond to auth events, 815 | // except for ES_EVENT_TYPE_AUTH_OPEN events, which require a response 816 | // using es_respond_flags_result() instead. 817 | if(ES_EVENT_TYPE_AUTH_OPEN == msg->event_type) { 818 | uint32_t authorized_flags = 0; 819 | 820 | if(ES_AUTH_RESULT_ALLOW == result) { 821 | authorized_flags = msg->event.open.fflag; 822 | } 823 | 824 | es_respond_result_t res = 825 | es_respond_flags_result(clt, msg, authorized_flags, g_cache_auth_results); 826 | 827 | if(ES_RESPOND_RESULT_SUCCESS != res) { 828 | LOG_ERROR("es_respond_flags_result: %d", res); 829 | } 830 | 831 | } else { 832 | es_respond_result_t res = 833 | es_respond_auth_result(clt, msg, result, g_cache_auth_results); 834 | 835 | if(ES_RESPOND_RESULT_SUCCESS != res) { 836 | LOG_ERROR("es_respond_auth_result: %d", res); 837 | } 838 | } 839 | } 840 | 841 | // Example of an event message handler to process event messages serially from Endpoint Security. 842 | es_handler_block_t serial_message_handler = ^(es_client_t *clt, const es_message_t *msg) { 843 | // Endpoint Security, by default, calls a event message handler serially for each message. 844 | 845 | LOG_VERBOSE_EVENT_MESSAGE(msg); 846 | 847 | // NOTE: It is important to process events in a timely manner. 848 | // The kernel will start to drop events for the client if they are not responded to in time. 849 | detect_and_log_dropped_events(msg); 850 | 851 | // Auth events require a response sent back before the deadline expires 852 | if(ES_ACTION_TYPE_AUTH == msg->action_type) { 853 | respond_to_auth_event(clt, msg, auth_event_handler(msg)); 854 | } 855 | }; 856 | 857 | // Example of an event message handler to process event messages asynchronously from Endpoint Security 858 | es_handler_block_t asynchronous_message_handler = ^(es_client_t *clt, const es_message_t *msg) { 859 | // Endpoint Security, by default, calls a event message handler serially for each message. 860 | // We copy/retain the message so that we can process and respond to auth events asynchronously. 861 | 862 | LOG_VERBOSE_EVENT_MESSAGE(msg); 863 | 864 | // NOTE: It is important to process events in a timely manner. 865 | // The kernel will start to drop events for the client if they are not responded to in time. 866 | detect_and_log_dropped_events(msg); 867 | 868 | // Copy/Retain the event message so that we process the event asynchronously 869 | es_message_t *copied_msg = copy_message(msg); 870 | 871 | if(!copied_msg) { 872 | LOG_ERROR("Failed to copy message"); 873 | return; 874 | } 875 | 876 | // Demonstrates handling events out of order, by processing 'ES_ACTION_TYPE_AUTH' events on 877 | // a separate thread. Sleep for 20s for 'ES_EVENT_TYPE_AUTH_EXEC' events if the result 878 | // is an ES_AUTH_RESULT_DENY. 879 | if(ES_ACTION_TYPE_AUTH == copied_msg->action_type) { 880 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){ 881 | es_auth_result_t result = auth_event_handler(copied_msg); 882 | 883 | if(ES_AUTH_RESULT_DENY == result && 884 | ES_EVENT_TYPE_AUTH_EXEC == copied_msg->event_type) { 885 | [NSThread sleepForTimeInterval:20.0]; 886 | } 887 | 888 | // Auth events require a response sent back before the deadline expires 889 | respond_to_auth_event(clt, copied_msg, result); 890 | free_message(copied_msg); 891 | }); 892 | 893 | return; 894 | } 895 | 896 | // Free/release the message 897 | free_message(copied_msg); 898 | }; 899 | 900 | es_handler_block_t get_message_handler_from_commandline_args(int argc, const char * argv[]) { 901 | if(argc < 2) { 902 | // No command line argument was given 903 | return nil; 904 | } 905 | 906 | // check if verbose logging argument was given 907 | if(argc > 2) { 908 | NSString *verbose = [[NSString stringWithUTF8String:argv[2]] lowercaseString]; 909 | g_verbose_logging = [verbose isEqualToString:@"verbose"]; 910 | } 911 | 912 | // Try and find an event message handler that matches the first command line argument 913 | NSString *arg = [[NSString stringWithUTF8String:argv[1]] lowercaseString]; 914 | 915 | NSDictionary *handlers = @{ 916 | @"serial" : serial_message_handler, 917 | @"asynchronous" : asynchronous_message_handler 918 | }; 919 | 920 | return [handlers objectForKey:arg]; 921 | } 922 | 923 | // On macOS Monterey 12, Apple have deprecated es_mute_path_literal in favour of es_mute_path 924 | bool mute_path(const char* path) 925 | { 926 | es_return_t result = ES_RETURN_ERROR; 927 | 928 | if(@available(macOS 12.0, *)) { 929 | result = es_mute_path(g_client, path, ES_MUTE_PATH_TYPE_LITERAL); 930 | } else { 931 | result = es_mute_path_literal(g_client, path); 932 | } 933 | 934 | if(ES_RETURN_SUCCESS != result) { 935 | LOG_ERROR("mute_path: ES_RETURN_ERROR"); 936 | return false; 937 | } 938 | 939 | return true; 940 | } 941 | 942 | // Note: This function shows the boilerplate code required to setup a connection to Endpoint Security 943 | // and subscribe to events. 944 | bool setup_endpoint_security(void) { 945 | // Create a new client with an associated event message handler. 946 | // Requires 'com.apple.developer.endpoint-security.client' entitlement. 947 | es_new_client_result_t res = es_new_client(&g_client, g_handler); 948 | 949 | if(ES_NEW_CLIENT_RESULT_SUCCESS != res) { 950 | switch(res) { 951 | case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED: 952 | LOG_ERROR("Application requires 'com.apple.developer.endpoint-security.client' entitlement"); 953 | break; 954 | 955 | case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED: 956 | LOG_ERROR("Application lacks Transparency, Consent, and Control (TCC) approval " 957 | "from the user. This can be resolved by granting 'Full Disk Access' from " 958 | "the 'Security & Privacy' tab of System Preferences."); 959 | break; 960 | 961 | case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED: 962 | LOG_ERROR("Application needs to be run as root"); 963 | break; 964 | 965 | default: 966 | LOG_ERROR("es_new_client: %d", res); 967 | } 968 | 969 | return false; 970 | } 971 | 972 | // Explicitly clear the cache of previous cached results from this demo or other ES Clients 973 | es_clear_cache_result_t resCache = es_clear_cache(g_client); 974 | if(ES_CLEAR_CACHE_RESULT_SUCCESS != resCache) { 975 | LOG_ERROR("es_clear_cache: %d", resCache); 976 | return false; 977 | } 978 | 979 | // Subscribe to the events we're interested in 980 | es_event_type_t events[] = { 981 | ES_EVENT_TYPE_AUTH_EXEC 982 | , ES_EVENT_TYPE_AUTH_OPEN 983 | , ES_EVENT_TYPE_NOTIFY_FORK 984 | }; 985 | 986 | es_return_t subscribed = es_subscribe(g_client, events, sizeof events / sizeof *events); 987 | 988 | if(ES_RETURN_ERROR == subscribed) { 989 | LOG_ERROR("es_subscribe: ES_RETURN_ERROR"); 990 | return false; 991 | } 992 | 993 | // All good 994 | return log_subscribed_events(); 995 | } 996 | 997 | int main(int argc, const char * argv[]) { 998 | signal(SIGINT, &sig_handler); 999 | 1000 | @autoreleasepool { 1001 | // Init global vars 1002 | g_handler = get_message_handler_from_commandline_args(argc, argv); 1003 | 1004 | if(!g_handler) { 1005 | print_usage(argv[0]); 1006 | return 1; 1007 | } 1008 | 1009 | init_date_formater(); 1010 | g_seq_nums = [NSMutableDictionary new]; 1011 | 1012 | // List of paths to be blocked. 1013 | // For this demo we will block the top binary and Calculator app bundle. 1014 | g_blocked_paths = [NSSet setWithObjects: 1015 | @"/usr/bin/top", 1016 | @"/System/Applications/Calculator.app/Contents/MacOS/Calculator", 1017 | nil]; 1018 | 1019 | if(!setup_endpoint_security()) { 1020 | return 1; 1021 | } 1022 | 1023 | // Note: Endpoint Security have a set of es_mute* functions to suppress events for a process. 1024 | // Uncomment the 'mute_path' line below to stop receiving events from the 'vim' binary. 1025 | // This program will then stop receiving 'ES_EVENT_TYPE_AUTH_OPEN' events for vim and will no 1026 | // longer be able to block vim from opening plain text files. 1027 | // mute_path("/usr/bin/vim"); 1028 | 1029 | if(@available(macOS 12.0, *)) { 1030 | // Note: Endpoint Security for performance reasons will automatically mute a set of paths 1031 | // on creation of new clients ('es_new_client'). 1032 | // macOS Monterey 12 now has the 'es_muted_paths_events' function, which can be used to 1033 | // inspect the muted paths. It is possible to unmute these paths (e.g. by using 1034 | // 'es_release_muted_paths'), but Apple advises against this. 1035 | log_muted_paths_events(); 1036 | } else { 1037 | // ES on macOS Monterey 12 implicitly mutes events from cfprefsd. We need to explicitly do 1038 | // this on older versions of macOS to prevent deadlocks in this program. This is because 1039 | // UTType and NSDate objects, used in parts of this program, may implicitly 1040 | // make NSUserDefaults calls which will generate ES events for cfprefsd. 1041 | mute_path("/usr/sbin/cfprefsd"); 1042 | } 1043 | 1044 | // Start handling events from Endpoint Security 1045 | dispatch_main(); 1046 | } 1047 | 1048 | return 0; 1049 | } 1050 | -------------------------------------------------------------------------------- /LICENSE/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2022 Omar Ikram 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Endpoint Security Demo 2 | 3 | This is a complete Xcode project of the Endpoint Security Demo gist: 4 | 5 | ## Building 6 | 7 | To build the project the 'Signing & Capabilities' section of the target needs to have a Team specified, but doing so will only allow the program to run on a SIP disabled machine (best use a VM to be safe). 8 | 9 | To build the project to run on a SIP protected machine, you need to update the 'Signing & Capabilities' section of the target with with a Team and Provisioning Profile that has been granted the 'com.apple.developer.endpoint-security.client' entitlement from [Apple](https://developer.apple.com/contact/request/system-extension/). 10 | 11 | - Note: Assigning the 'com.apple.developer.endpoint-security.client' entitlement to a Provisioning Profile cannot be done directly via Xcode. It must first be done through the [Apple Developer Account](https://developer.apple.com/account/resources/profiles) and then have Xcode import the Provisioning Profile into project. 12 | 13 | The build produces a command line program packaged as bundle. This is required so that the program has the Provisioning Profile for the System to validate against during runtime. 14 | 15 | ## Running 16 | 17 | The program needs to be run as root from the Terminal, which has been granted Full Disk access (best use a VM to be safe). 18 | 19 | The program is treated as a command line program, but running it requires launching the binary inside the bundle: 20 | ```bash 21 | sudo ./EndpointSecurityDemo.app/Contents/MacOS/EndpointSecurityDemo serial 22 | ``` 23 | --------------------------------------------------------------------------------