improved launch d&a plugin

-check for 'Program' key first
 -start/calendar interval
added macho parser to classify files as encrypted/packed
fixed bug in signing check (just for ad-hoc, should've bailed if file not signed)
suppress stderr in execTask()
improved sanity checks in various spots
This commit is contained in:
Patrick Wardle
2016-05-31 21:33:58 -10:00
parent 850cadee02
commit 01c29e7c00
69 changed files with 218 additions and 67 deletions
Regular → Executable
View File
+5 -1
View File
@@ -34,7 +34,6 @@ NSString * const SUPPORTED_PLUGINS[] = {@"AuthorizationPlugins", @"BrowserExtens
@synthesize categoryTableController;
@synthesize resultsWindowController;
//center window
// ->also make front
-(void)awakeFromNib
@@ -55,6 +54,11 @@ NSString * const SUPPORTED_PLUGINS[] = {@"AuthorizationPlugins", @"BrowserExtens
// ->main entry point
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
/*
extractSigningInfo(@"/Users/patrick/objective-see/tbd/DerivedData/tbd/Build/Products/Debug/tbd");
return;
*/
//app's (self) signing status
OSStatus signingStatus = -1;
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+1 -4
View File
@@ -20,7 +20,6 @@
//plugin search directories
NSString * const LAUNCHITEM_SEARCH_DIRECTORIES[] = {@"/System/Library/LaunchDaemons/", @"/Library/LaunchDaemons/", @"/System/Library/LaunchAgents/", @"/Library/LaunchAgents/", @"~/Library/LaunchAgents/"};
//enumerate all 'shared' items
// ->that is to say, items that multiple plugins scan/process
-(void)start
@@ -130,8 +129,6 @@ NSString * const LAUNCHITEM_SEARCH_DIRECTORIES[] = {@"/System/Library/LaunchDaem
//exec system profiler
taskOutput = execTask(SYSTEM_PROFILER, @[@"SPApplicationsDataType", @"-xml", @"-detailLevel", @"mini"]);
//sanity check
if(nil == taskOutput)
{
//bail
@@ -151,7 +148,7 @@ NSString * const LAUNCHITEM_SEARCH_DIRECTORIES[] = {@"/System/Library/LaunchDaem
@catch (NSException *exception)
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: serialized output not formatted as expected");
//NSLog(@"OBJECTIVE-SEE ERROR: serialized output not formatted as expected");
//bail
goto bail;
Regular → Executable
View File
Regular → Executable
+48 -4
View File
@@ -147,6 +147,10 @@
// ->this can be a File, Command, or Extension obj
ItemBase* item = nil;
//string for name
// ->for File objs, can be attributed w/ packed/encrypted
NSMutableAttributedString* customizedItemName = nil;
//signature icon
NSImageView* signatureImageView = nil;
@@ -158,10 +162,10 @@
VTButton* vtButton;
//attribute dictionary
NSMutableDictionary *stringAttributes = nil;
NSMutableDictionary* stringAttributes = nil;
//paragraph style
NSMutableParagraphStyle *paragraphStyle = nil;
NSMutableParagraphStyle* paragraphStyle = nil;
//tracking area
NSTrackingArea* trackingArea = nil;
@@ -282,7 +286,8 @@
//set main text
// ->name
[itemCell.textField setStringValue:item.name];
//[itemCell.textField setStringValue:item.name];
itemCell.textField.attributedStringValue = [[NSMutableAttributedString alloc] initWithString:item.name];
//only have to add tracking area once
// ->add it the first time
@@ -323,7 +328,7 @@
itemCell.imageView.image = getIconForBinary(((File*)item).path, ((File*)item).bundle);
//set signature icon
signatureImageView.image = getCodeSigningIcon((File*)item);//signatureStatus;
signatureImageView.image = getCodeSigningIcon((File*)item);
//show signature icon
signatureImageView.hidden = NO;
@@ -397,6 +402,7 @@
vtDetectionRatio = [NSString stringWithFormat:@"%lu/%lu", (unsigned long)[((File*)item).vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue], (unsigned long)[((File*)item).vtInfo[VT_RESULTS_TOTAL] unsignedIntegerValue]];
//known 'good' files (0 positivies)
// ->(re)set to black/gray
if(0 == [((File*)item).vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue])
{
//(re)set title black
@@ -476,6 +482,44 @@
[[itemCell viewWithTag:TABLE_ROW_VT_BUTTON+1] setHidden:YES];
}
//add 'packed' / 'encrypted' in red
// ->done here since VT stuff (above) sets name globally
if( (YES == ((File*)item).isPacked) ||
(YES == ((File*)item).isEncrypted) )
{
//init task string
customizedItemName = [[NSMutableAttributedString alloc] initWithString:@""];
//add existing name
// ->uses existing color (red or black)
[customizedItemName appendAttributedString:[[NSMutableAttributedString alloc] initWithString:itemCell.textField.stringValue attributes:@{NSForegroundColorAttributeName:itemCell.textField.textColor}]];
//add '('
// ->color, light gray
[customizedItemName appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" (" attributes:@{NSForegroundColorAttributeName:[NSColor lightGrayColor]}]];
//add 'encrypted'
if(YES == ((File*)item).isEncrypted)
{
//add
[customizedItemName appendAttributedString:[[NSAttributedString alloc] initWithString:@"encrypted" attributes:@{NSForegroundColorAttributeName:[NSColor redColor]}]];
}
//add 'packed'
// ->can't be both; and encryption takes precedence
else
{
//add
[customizedItemName appendAttributedString:[[NSAttributedString alloc] initWithString:@"packed" attributes:@{NSForegroundColorAttributeName:[NSColor redColor]}]];
}
//close string with ')'
// ->color; light gray
[customizedItemName appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@")" attributes:@{NSForegroundColorAttributeName:[NSColor lightGrayColor]}]];
//update name
itemCell.textField.attributedStringValue = customizedItemName;
}
}//file(s)
//EXTENSIONS
Regular → Executable
View File
Regular → Executable
View File
@@ -2,4 +2,38 @@
<Bucket
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Plugins/LoginItems.m"
timestampString = "472463796.155503"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "221"
endingLineNumber = "221"
landmarkName = "-enumSandboxItems"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Plugins/BrowserExtensions.m"
timestampString = "480742986.24468"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "512"
endingLineNumber = "512"
landmarkName = "-scanExtensionsFirefox:"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,15 +38,18 @@
ReferencedContainer = "container:KnockKnock.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -59,13 +62,18 @@
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+2 -3
View File
@@ -64,9 +64,8 @@
//exec cron
// ->just for current user
taskOutput = execTask(CRONTAB, @[@"-l"]);
//sanity check
if(nil == taskOutput)
if( (nil == taskOutput) ||
(0 == taskOutput.length) )
{
//bail
goto bail;
Regular → Executable
View File
Regular → Executable
+4 -2
View File
@@ -126,13 +126,14 @@
//load launch item's plist
plistContents = [NSDictionary dictionaryWithContentsOfFile:launchItemPlist];
/*
//skip disabled launch items
if(YES == [disabledItems containsObject:plistContents[@"Label"]])
{
//skip
continue;
}
//skip items that aren't auto launched
// ->neither 'RunAtLoad' *and* 'KeepAlive' is set to YES
if( (YES != [plistContents[@"RunAtLoad"] isKindOfClass:[NSNumber class]]) ||
@@ -146,6 +147,7 @@
continue;
}
}
*/
//extact environment vars dictionary
enviroVars = plistContents[LAUNCH_ITEM_DYLD_KEY];
@@ -167,7 +169,7 @@
dylibPath = enviroVars[@"DYLD_INSERT_LIBRARIES"];
}
//grab dylib path
// ->will be in '__XPC_DYLD_INSERT_LIBRARIES'
// ->check in '__XPC_DYLD_INSERT_LIBRARIES'
else
{
//grab
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+45 -12
View File
@@ -86,7 +86,13 @@
launchItemPath = nil;
//load plist contents
// ->skip any that error out
plistContents = [NSDictionary dictionaryWithContentsOfFile:launchItemPlist];
if(nil == plistContents)
{
//skip
continue;
}
//skip non-auto run items
if(YES != [self isAutoRun:plistContents])
@@ -95,9 +101,17 @@
continue;
}
//attempt to extact path to launch item
// ->first, via 'ProgramArguments'
if(nil != plistContents[@"ProgramArguments"])
//extact path to launch item
// ->first, check 'Program' key
if(nil != plistContents[@"Program"])
{
//extract path
launchItemPath = plistContents[@"Program"];
}
//extact path to launch item
// ->second, via 'ProgramArguments' (sometimes just has args)
else if(nil != plistContents[@"ProgramArguments"])
{
//should (usually) be an array
// ->extract & grab first item
@@ -116,14 +130,6 @@
}
}
//attempt to extact path to launch item
// ->second, via 'Program'
else if(nil != plistContents[@"Program"])
{
//extract path
launchItemPath = plistContents[@"Program"];
}
//skip paths that don't exist
if( (nil == launchItemPath) ||
(YES != [[NSFileManager defaultManager] fileExistsAtPath:launchItemPath]))
@@ -233,6 +239,12 @@
return;
}
//TODO: just copy launchd's logic (old open source versions?)
// start/cal interval - DONE
// what does: '<key>ServiceIPC</key><true/>' do?
// what does 'MachServices' and 'Sockets' do?
// are alias followed? (simply google if 'dictionaryWithContentsOfFile' follows aliases...it really should!
//
//checks if an item will be automatically run by the OS
-(BOOL)isAutoRun:(NSDictionary*)plistContents
{
@@ -251,6 +263,9 @@
// ->default to -1 for not found
NSInteger onDemand = -1;
//flag for start interval
BOOL startInterval = NO;
//skip launch items overriden with 'Disable'
if(YES == [self.disabledItems containsObject:plistContents[@"Label"]])
{
@@ -292,7 +307,17 @@
onDemand = [plistContents[@"OnDemand"] boolValue];
}
//first check 'RunAtLoad'/'KeepAlive'
//set 'StartInterval' flag
// ->check both and 'StartInterval' and 'StartCalendarInterval'
if( (nil != plistContents[@"StartInterval"]) ||
(nil != plistContents[@"StartCalendarInterval"]) )
{
//set
startInterval = YES;
}
//CHECK 0x1: 'RunAtLoad' / 'KeepAlive'
// ->either of these set to ok, means auto run!
if( (YES == runAtLoad) ||
(YES == keepAlive) )
@@ -301,6 +326,14 @@
isAutoRun = YES;
}
//CHECK 0x2: 'StartInterval' / 'StartCalendarInterval'
// ->either set, means will auto run (at some point)
else if(YES == startInterval)
{
//auto
isAutoRun = YES;
}
//when neither 'RunAtLoad' and 'KeepAlive' not found
// ->check if 'OnDemand' is set to false (e.g. HackingTeam)
else if( ((-1 == runAtLoad) && (-1 == keepAlive)) &&
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+6
View File
@@ -36,6 +36,12 @@
//signing info
@property(nonatomic, retain)NSDictionary* signingInfo;
//is packed
@property BOOL isPacked;
//is encrypted
@property BOOL isEncrypted;
/* VIRUS TOTAL INFO */
//dictionary returned by VT
Regular → Executable
+30 -2
View File
@@ -8,6 +8,7 @@
#import "File.h"
#import "MachO.h"
#import "Consts.h"
#import "Utilities.h"
#import "AppDelegate.h"
@@ -19,17 +20,21 @@
@synthesize plist;
@synthesize bundle;
@synthesize hashes;
@synthesize isPacked;
@synthesize isEncrypted;
@synthesize signingInfo;
@synthesize vtInfo;
//init method
-(id)initWithParams:(NSDictionary*)params
{
//flag for directories
BOOL isDirectory = NO;
//mach-O parser
MachO* machoParser = nil;
//super
// ->saves path, etc
self = [super initWithParams:params];
@@ -58,7 +63,7 @@
if(nil == (bundle = [NSBundle bundleWithPath:params[KEY_RESULT_PATH]]))
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: couldn't create bundle for %@", params[KEY_RESULT_PATH]);
//NSLog(@"OBJECTIVE-SEE ERROR: couldn't create bundle for %@", params[KEY_RESULT_PATH]);
//set self to nil
self = nil;
@@ -102,6 +107,29 @@
//call into filter object to check if file is known
// ->apple-signed or whitelisted
self.isTrusted = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).filterObj isTrustedFile:self];
//alloc macho parser iVar
// ->new instance for each file!
machoParser = [[MachO alloc] init];
//parse
// ->also do packed/encryption checks
if(YES == [machoParser parse:self.path classify:YES])
{
//unset 'packed' flag for apple signed binaries
// ->as apple doesn't pack binaries, but packer algo has some false positives
if(YES == [self.signingInfo[KEY_SIGNING_IS_APPLE] boolValue])
{
//unset
machoParser.binaryInfo[KEY_IS_PACKED] = NO;
}
//set packed flag
self.isPacked = [machoParser.binaryInfo[KEY_IS_PACKED] boolValue];
//set encrypted flag
self.isEncrypted = [machoParser.binaryInfo[KEY_IS_ENCRYPTED] boolValue];
}
}
//bail
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+25 -29
View File
@@ -147,26 +147,28 @@ NSDictionary* extractSigningInfo(NSString* path)
//(re)save signature status
signingStatus[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//if file is signed
// ->grab signing authorities
if(STATUS_SUCCESS == status)
//bail if not signed/errors
if(STATUS_SUCCESS != status)
{
//grab signing authorities
status = SecCodeCopySigningInformation(staticCode, kSecCSSigningInformation, &signingInformation);
//sanity check
if(STATUS_SUCCESS != status)
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: SecCodeCopySigningInformation() failed on %@ with %d", path, status);
//bail
goto bail;
}
//determine if binary is signed by Apple
signingStatus[KEY_SIGNING_IS_APPLE] = [NSNumber numberWithBool:isApple(path)];
//bail
goto bail;
}
//grab signing authorities
status = SecCodeCopySigningInformation(staticCode, kSecCSSigningInformation, &signingInformation);
//(re)save signature status
signingStatus[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//sanity check
if(STATUS_SUCCESS != status)
{
//bail
goto bail;
}
//determine if binary is signed by Apple
signingStatus[KEY_SIGNING_IS_APPLE] = [NSNumber numberWithBool:isApple(path)];
//init array for certificate names
signingStatus[KEY_SIGNING_AUTHORITIES] = [NSMutableArray array];
@@ -253,9 +255,6 @@ BOOL isApple(NSString* path)
status = SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode);
if(STATUS_SUCCESS != status)
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: SecStaticCodeCreateWithPath() failed on %@ with %d", path, status);
//bail
goto bail;
}
@@ -266,9 +265,6 @@ BOOL isApple(NSString* path)
if( (STATUS_SUCCESS != status) ||
(requirementRef == NULL) )
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: SecRequirementCreateWithString() failed on %@ with %d", path, status);
//bail
goto bail;
}
@@ -691,7 +687,7 @@ NSMutableAttributedString* setStringColor(NSAttributedString* string, NSColor* c
NSData* execTask(NSString* binaryPath, NSArray* arguments)
{
//task
NSTask *task = nil;
NSTask* task = nil;
//output pipe
NSPipe *outPipe = nil;
@@ -700,7 +696,7 @@ NSData* execTask(NSString* binaryPath, NSArray* arguments)
NSFileHandle* readHandle = nil;
//output
NSMutableData *output = nil;
NSMutableData* output = nil;
//init task
task = [NSTask new];
@@ -723,6 +719,9 @@ NSData* execTask(NSString* binaryPath, NSArray* arguments)
//set task's output
[task setStandardOutput:outPipe];
//ignore stderr
[task setStandardError:[NSFileHandle fileHandleWithNullDevice]];
//launch the task
[task launch];
@@ -924,9 +923,6 @@ OSStatus verifySelf()
status = SecStaticCodeCheckValidity(secRef, kSecCSDefaultFlags, NULL);
if(noErr != status)
{
//err msg
NSLog(@"OBJECTIVE-SEE ERROR: failed to validate application bundle (%d)", status);
//bail
goto bail;
}
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
-1
View File
@@ -265,7 +265,6 @@
// ->need this for (re)queries
scanID = result[VT_RESULTS_SCANID];
//TOOD: FIX LOGIC - only remove if not still flagged
//if file was flagged
// ->remove it from list of plugin's flagged
if(0 != [self.fileObj.vtInfo[VT_RESULTS_POSITIVES] unsignedIntegerValue])
Regular → Executable
View File
Regular → Executable
View File
+1
View File
@@ -37,6 +37,7 @@
"/usr/libexec/ntpd-wrapper":["baf0968c6df24734e079baeb01851221"],
"/usr/libexec/postfix/check-aliases.sh":["ff8fb1f04e944737d2cd15e410f6de06"],
"/Library/Spotlight/Microsoft Office.mdimporter/Contents/MacOS/Microsoft Office":["d9a23fc4585c30448bccf46213c26028"],
"/usr/libexec/gkreport":["a9355083f340d5a1f72127f8d1f08e44"],
"/etc/periodic/daily/110.clean-tmps":["3f4a4eb220706b891260f1e22dff34d5"],
"/etc/periodic/daily/130.clean-msgs":["f6367dd6876c2a06a5d6728d80ea19c3"],
"/etc/periodic/daily/140.clean-rwho":["aecff6f0a6eb42bf5de8bcb6cd42276e"],
Regular → Executable
View File
+5 -5
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<development version="5000" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -43,10 +43,10 @@
</menuItem>
</items>
</menu>
<window title="KnockKnock (UI)" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" showsToolbarButton="NO" animationBehavior="default" id="371">
<window title="KnockKnock" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" showsToolbarButton="NO" animationBehavior="default" id="371">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="1203" height="597"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1057"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<value key="minSize" type="size" width="1000" height="597"/>
<value key="maxSize" type="size" width="2000" height="1000"/>
<view key="contentView" id="372">
@@ -428,7 +428,7 @@
<action selector="scanButtonHandler:" target="494" id="836"/>
</connections>
</button>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" alphaValue="0.10000000000000001" translatesAutoresizingMaskIntoConstraints="NO" id="8im-ux-tdL">
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8im-ux-tdL">
<rect key="frame" x="960" y="552" width="245" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="245" id="mlg-aC-l0f"/>
Regular → Executable
View File
Regular → Executable
View File