├── .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 |
--------------------------------------------------------------------------------