This article is mainly about the following points, which you should ignore if you already know:

  • Apple released iOS2 on July 11, 2008, with NSKeyedArchiver, NSKeyedUnarchiver, IOS12, released on September 10, 2018, scrapped almost all methods in NSKeyedArchiver, NSKeyedUnarchiver, and even init methods. It took Apple 10 years to discover that NSCoding was a pit.
  • NSSecureCoding launched by iOS6 is a high-configuration version of NSCoding, NSCoding is not suitable for you to use, please discard the pit as soon as possible. This is not your problem, it is NSCoder, NSKeyedArchiver, NSKeyedUnarchiver’s internal implementation. IOS6 fixes NSCoder, iOS12 fixes NSKeyedArchiver, NSKeyedUnarchiver.
  • UIKit shipped with ns scoding, Foundation shipped with NSSecureCoding.
  • Any class in Foundation can comply with NSCoding, but NSObject, when it came out of the factory, didn’t. So to achieve-initWithCoder:Cannot always call the parent class of[super initWithCoder:]Sometimes[super init].
  • Nib and Storyboard load UIViewController through-initWithNibName:bundle:To declare the file path,-initWithCoder:Load with NSKeyedUnarchiver. Knowing that helps you understand the UIViewController lifecycle.
  • When an object is initialized, the init method may not be executed, perhaps by execution-initWithCoder:Init, so only init, may lose data, attributes.-initWithCoder:Often overlooked.

In addition, the sample code in this article is objective-C, and new Codable documents are available for Swift developers. Use caution in this article.

NSCoding profile

NSCoding is a protocol designed to convert NSData to objects. Naming is also continuous tense, very image.

Any object that wants to freely switch identities with NSData needs to comply with NSCoding. When Apple designed NSCoding, it probably didn’t realize how many holes it was in.

After the pit was discovered, NSSecureCoding was launched in iOS6 to solve the abnormal problem of NSCoder decoding

Documentation- foundation-Archives and Serialization-NS secure recoding.

For NSSecureCoding usage, here are two usage scenarios for AFNetworking:

Scenario 1: General operations

#pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { self = [self init]; if (! self) { return nil; } self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))]; self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))]; [coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))]; }

Scenario 2: As we saw above, because it is possible to do -initWithcoder: instead of init, you need to do the full initialization process here.

//AFURLSessionManager #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; self = [self initWithSessionConfiguration:configuration]; if (! self) { return nil; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; }

IOS12 fully implement secure archiving, coping with file replacement attacks.

In the past, iOS mainly focused on network transmission strategy to prevent data tampering, but the data processing part still has the risk of being attacked. And filing, is the short board. All objects are archived, encoded and decoded in the same way, which is also very convenient for attackers.

This time WWDC Apple mentioned iOS Object Substitution attacks, in which attackers replace local archive files and falsify data. So as to achieve the purpose of data tampering and attack.

Predicting the XPC of interprocess communication should be a high-risk step. For an introduction to XPC, please refer to Apple-documentation – XPC, ObjC China-XPC.

Apple’s strategy is to add class name verification to archived codecs. That is, before codec, check whether the class of the attribute is the specified class, and then decide whether to codec. In iOS12, Apple scrapped almost all of the original methods of NSKeyedArchiver and NSKeyedUnarchiver, including init methods, and added class name verification. Increases the cost of tampering with data.

However, As Jonathan Zdziarski points out in Preventing Widespread Automated Attacks in ios-Part 2, If the user hooks the NSKeyedArchiver (or NSKeyedUnarchiver) codec method, runtime intercepts the data (there should also be a risk of tampering). Since I know little about reverse, I am not sure whether this iOS12 change can prevent this risk. But presumably in principle, the vulnerabilities Jonathan Zdziarski identified in iOS12 still exist. Expect more secure strategies to follow.

TODO List

Here are some things developers need to keep up with:

  • Custom classes, Objective-C developers, use NSSecureCoding, not NSCoding, as shown in the example code above. Swift developer, enabled Codable.
  • NSKeyedArchiver and NSKeyedUnarchiver use the new API to make the system verify the attribute class name before codec. Note that the new API must start from iOS11/iOS12 at least.

The appendix

Related API changes:

NSCoder:

// Foundation/NSCoder.h // Specify what the expected class of the allocated object is. If the coder responds YES to -requiresSecureCoding, then an exception will be thrown if the class to be decoded does not implement NSSecureCoding or is not isKindOfClass: of the argument. If the coder responds NO to -requiresSecureCoding, then the class argument is ignored and no check of the class of the decoded object is performed, exactly as if decodeObjectForKey: Had been called. - (nullable id)decodeObjectOfClass:(Class)aClass forKey:(NSString *)key API_AVAILABLE(macos(10.8), Ios (6.0), watchos (2.0), tvos (9.0));

NSKeyedArchiver

// Foundation/NSKeyedArchiver.h /** Initializes the receiver for encoding an archive, optionally disabling secure coding. If \c NSSecureCoding cannot be used, \c requiresSecureCoding may be turned off here;  for improved security, however, \c requiresSecureCoding should be left enabled whenever possible. \c requiresSecureCoding ensures that all encoded objects conform to \c NSSecureCoding, preventing the possibility of encoding objects which cannot be decoded later. To produce archives whose structure matches those previously encoded using \c +archivedRootDataWithObject, encode the top-level object in your archive for the \c NSKeyedArchiveRootObjectKey. */ - (instancetype) initRequiringSecureCoding: (BOOL) requiresSecureCoding API_AVAILABLE (macos (10.13), the ios (11.0), watchos (4.0), Tvos (11.0)); /** Returns an \c NSData object containing the encoded form of the object graph whose root object is given, optionally disabling secure coding. If \c NSSecureCoding cannot be used, \c requiresSecureCoding may be turned off here;  for improved security, however, \c requiresSecureCoding should be left enabled whenever possible. \c requiresSecureCoding ensures that all encoded objects conform to \c NSSecureCoding, preventing the possibility of encoding objects which cannot be decoded later. If the object graph cannot be encoded, returns \c nil and sets the \c error out parameter. */ + (nullable NSData *)archivedDataWithRootObject:(id)object RequiringSecureCoding :(BOOL)requiresSecureCoding error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), Tvos watchos (4.0), (11.0)); /// Initialize the archiver with empty data, ready for writing. - (instancetype)init API_DEPRECATED("Use -initRequiringSecureCoding: Home, "macosx (10.12, 10.14), the ios (10.0, 12.0), watchos (3.0, 5.0), tvos (10.0, 12.0)); - (instancetype)initForWritingWithMutableData:(NSMutableData *)data API_DEPRECATED("Use -initRequiringSecureCoding: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0)); + (NSData *)archivedDataWithRootObject:(id)rootObject API_DEPRECATED("Use +archivedDataWithRootObject:requiringSecureCoding:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0)); + (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path API_DEPRECATED("Use +archivedDataWithRootObject:requiringSecureCoding:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0));
// @property (readwrite) NSDecodingFailurePolicy API_AVAILABLE(MacOS (10.11), ios(9.0), Tvos watchos (2.0), (9.0));

NSKeyedUnarchiver

/** Initializes the receiver for decoding an archive previously encoded by \c NSKeyedUnarchiver. Enables \c requiresSecureCoding by default. If \c NSSecureCoding cannot be used, \c requiresSecureCoding may be turned off manually; for improved security, \c requiresSecureCoding should be left enabled whenever possible. Sets the unarchiver's \c decodingFailurePolicy to \c NSDecodingFailurePolicySetErrorAndReturn. Returns \c nil if the given data is not valid, and sets the \c error out parameter. */ - (nullable instancetype)initForReadingFromData:(NSData *)data error:(NSError **)error API_AVAILABLE(MacOS (10.13), ios(11.0), Watchos (4.0), tVOs (11.0)); /** Decodes the root object of the given class from the given archive, previously encoded by \c NSKeyedArchiver. Enables \c requiresSecureCoding and sets the \c decodingFailurePolicy to \c NSDecodingFailurePolicySetErrorAndReturn. Returns \c nil if the given data is not valid or cannot be decoded, and sets the \c error out parameter. */ + (nullable id)unarchivedObjectOfClass:(Class)cls fromData:(NSData *)data Error :(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) NS_REFINED_FOR_SWIFT; /** Decodes the root object of one of the given classes from the given archive, previously encoded by \c NSKeyedArchiver. Enables \c requiresSecureCoding and sets the \c decodingFailurePolicy to \c NSDecodingFailurePolicySetErrorAndReturn. Returns \c nil if the given data is not valid or cannot be decoded, and sets the \c error out parameter. */ + (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes FromData :(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), NS_REFINED_FOR_SWIFT tvos (11.0)); - (instancetype)init API_DEPRECATED("Use -initForReadingFromData:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0)); - (instancetype)initForReadingWithData:(NSData *)data API_DEPRECATED("Use -initForReadingFromData:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0)); + (nullable id)unarchiveObjectWithData:(NSData *)data API_DEPRECATED("Use +unarchivedObjectOfClass:fromData:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0)); + (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error API_DEPRECATED("Use +unarchivedObjectOfClass:fromData:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), Tvos (9.0, 12.0)) NS_SWIFT_UNAVAILABLE (" Use 'unarchiveTopLevelObjectWithData (_) throws' home "); + (nullable id)unarchiveObjectWithFile:(NSString *)path API_DEPRECATED("Use +unarchivedObjectOfClass:fromData:error: Home, "macosx (10.2, 10.14), the ios (2.0, 12.0), watchos (2.0, 5.0), tvos (9.0, 12.0));

reference

  • Data You Can Trust
  • Hacking and Securing iOS Applications
  • preventing widespread automated attacks in ios -part-1
  • preventing widespread automated attacks in ios -part-2
  • Review and Exploit Neglected

    Attack Surface in iOS 8