Skip to main content

Reversing ‘pkgutil’ to Verify PKGs

Background

Being a security company, designing a macOS endpoint security tool, security is rather important to us! As part of our product’s secure update mechanism, we wanted to validate our downloaded update packages (.pkg) …before blindly installing them!

What is a .pkg file?

“Files that contain the .pkg file extension are most commonly used by the Apple OS X operating system. The Apple PKG files usually contain installer scripts that are used by a variety of Mac programs for software installation purposes. The PKG files contain compressed installer files that are used to install Mac software applications onto a user’s hard drive.” -file.org

Our goal was (conceptually) simple: verify that downloaded update .pkgs are validly signed by Developer ID Installer: Digita Security, LLC.

Googling around turned up some initially promising results, such as a jamf blog post: “Verifying that a Package is Signed”. However all postings (that I came across), only provided a mechanism to verify packages via the commandline, by means of Apple’s pkgutil (/usr/sbin/pkgutil). That is to say, none provided a purely programmatic method to verify a package.

Note:

Yes, it would be rather trivial to programmatically execute pkgutil (e.g. via NSTask) and parse its output.

However, I’ve never been a fan of such “intermediate” solutions. Spawning external binaries and then ingesting and parsing their human-readble stdout output, always seems a little amateur (IMHO).

Since Apple’s pkgutil utility is able verify packages, it goes to follow - so should we! So let’s don our hacker hats and dive in.

Reversing pkgutil

As noted, one can use the built-in pkgutil to verify a package (.pkg). This is accomplished via the --check-signature commandline flag:

 $ pkgutil --check-signature update.pkg

Package "update.pkg":
 Status: signed by a certificate trusted by Mac OS X
 Certificate Chain:
 1. Developer ID Installer: Digita Security, LLC (S62KCC4ZPU)
 SHA1 fingerprint: 96 F6 56 99 9E D1 39 C6 DE 7C 98 FF 09 4C F9 A9 50 17 0D 33
 -----------------------------------------------------------------------------
 2. Developer ID Certification Authority
 SHA1 fingerprint: 3B 16 6C 3B 7D C4 B7 51 C9 FE 2A FA B9 13 56 41 E3 88 E1 86
 -----------------------------------------------------------------------------
 3. Apple Root CA
 SHA1 fingerprint: 61 1E 5B 66 2C 59 3A 08 FF 58 D1 4A E2 24 52 D1 98 DF 6C 60

And yes, while this is valid approach, our aim was a self-contained programmatic approach that did not rely on spawning external binaries nor parsing stdout.

Since (AFAIK) such an approach has not be discussed before, it means our only option was to dive into pkgutil, reversing the binary to uncover its secrets!

It’s easy to get ‘lost’ when reversing a binary. So it’s important to have a concise goal (i.e. “how does it verify a package?”) and to quickly zone into (compiled) code that likely implements such logic. Often the best way, is simple find code that references a string that corresponds to the logic of interest.

In the preceeding terminal output ($ pkgutil --check-signature update.pkg) note the string, “signed by a certificate trusted by Mac OS X” in the output. We start by assuming this hard-coded string exists within the pkgutil binary and is referenced within (or tangentially to) code that verifies packages.

Let’s first confirm the first part of this assumption; namely, does said string exist within the pkgutil binary (vs. it perhaps being dynamically generated?):

 $ strings -a /usr/sbin/pkgutil | grep "signed by a certificate trusted by Mac OS X"
signed by a certificate trusted by Mac OS X

Via the built-in strings utility (which as its name implies, dumps strings within a binary), we can see, yes indeed, pkgutil contains the hard-coded string “signed by a certificate trusted by Mac OS X

Loading up the pkgutil binary in my personal disassembler of choice, Hopper, we find the embedded “signed by…” string at address 0x0000000100009b00

 aSignedByACerti_100009b00:
0000000100009b00 db "signed by a certificate trusted by Mac OS X", 0 ; DATA XREF=_check_signature+969

Selecting this address, (0x0000000100009b00), and pressing x in Hopper, brings up the “xref” (cross references) window, which contains code within the binary that references this string:

There is only one “xref” that falls within a method named _check_signature:

 0000000100004715 _check_signature: ; CODE XREF=_main+2118
...

loc_100004ade: ; "signed by a certificate trusted by Mac OS X"
0000000100004ade lea rdi, qword [aSignedByACerti_100009b00] 
0000000100004ae5 jmp loc_100004b14

Note:

The _check_signature contains an xref to the pkgutil’s main() function (meaning it’s invoked directly from the programs main method).

This is important to note, though it rather unsurprising, as generally commandline utilities will parse various commmandline flags within the main method, then invoke some function to execute the appropriate logic for the flag.

Here for example in pkgutil’s main() function, the commandline parsing logic likely invokes _check_signature function the when it encounters the --check-signature commandline flag.

Let’s take a peak at the (slightly abridged) decompilation of the _check_signature method:

 int _check_signature(int arg0) {
 r12 = arg0;
 rbx = *_objc_msgSend;
 r14 = [[NSAutoreleasePool alloc] init];
 r13 = [NSString stringWithUTF8String:r12];
 r15 = rbx;
 if ([[NSFileManager defaultManager] fileExistsAtPath:r13] == 0x0) goto loc_10000487f;

loc_100004792:
 rdx = r13;
 rax = [PKArchive archiveWithPath:rdx];
 if (rax == 0x0) goto loc_100004892;

loc_1000047b2:
 rbx = rax;
 r12 = r15;
 rax = (r12)(rbx, @selector(archiveSignatures), rdx);
 rax = (r12)(rax, @selector(count), rdx);
 var_40 = 0x0;
 var_50 = r14;
 var_38 = rbx;
 if (rax != 0x0) {
 rbx = (r12)((r12)(rbx, @selector(archiveSignatures)), @selector(objectAtIndex:), 0x0, 0x0);
 rdx = 0x0;
 rax = (r12)(rbx, @selector(verifySignedDataReturningError:), rdx, 0x0);
 rcx = 0x0;
 var_30 = rcx;
 if (rax != 0x0) {
 r15 = (r12)(rbx, @selector(verificationTrustRef), 0x0, 0x0);
 r14 = (r12)(@class(PKTrust), @selector(alloc), 0x0, 0x0);
 if (r15 != 0x0) {
 rbx = (r12)(rbx, @selector(verificationTrustRef), 0x0, 0x0);
 rax = (r12)(var_38, @selector(archiveSignatureDate), 0x0, 0x0);
 rsi = @selector(initWithSecTrust:usingAppleRoot:signatureDate:);
 }
 else {
 rbx = (r12)(rbx, @selector(certificateRefs), 0x0, 0x0);
 rax = (r12)(var_38, @selector(archiveSignatureDate), 0x0, 0x0);
 rsi = @selector(initWithCertificates:usingAppleRoot:signatureDate:);
 }
 rcx = 0x1;
 r8 = rax;
 rbx = (r12)(r14, rsi, rbx, rcx);
 r14 = var_50;
 rdx = 0x0;
 (r12)(rbx, @selector(evaluateTrustReturningError:), rdx, rcx);
 var_40 = (r12)(rbx, @selector(trustLevel), rdx, rcx);
 var_30 = (r12)((r12)((r12)(rbx, @selector(certificateChain), rdx, rcx), @selector(retain), rdx, rcx), @selector(autorelease), rdx, rcx);
 (r12)(rbx, @selector(release), rdx, rcx);
 }
 }
 else {
 rcx = 0x0;
 var_40 = 0x64;
 var_30 = 0x0;
 }
 printf("Package \"%s\":\n", (r12)((r12)((r12)(@class(NSFileManager), @selector(defaultManager), rdx, rcx, r8), @selector(displayNameAtPath:), r13, rcx, r8), @selector(UTF8String), r13, rcx, r8));
 printf(" Status: ");
 if ((r12)(var_38, @selector(verifyReturningError:), 0x0, rcx, r8) == 0x0) goto loc_100004a1c;

Ok, yes - a lot of code, and assembly registers (rax, r12 etc.), but not to worry! We’ll walk thru this, piece by piece to reveal exactly how pkgutil verifies .pkgs …and how we can implement similar logic in our own code.

Let’s start with the function declaration: int _check_signature(int arg0). Breaking this down, we have a function named check_signature that returns an integer (int). It takes a single argument, which Hopper thinks it also an integer (int arg0).

When reversing a function, it’s always good to try to identify the actual meaning of the arguments (int arg0 isn’t all that telling).

Via static analysis (i.e reading the disassembly), we can confidentially infer that what is passed in to the _check_signature function is a pointer to a C-string which contains the path to the package (.pkg) to validate. How? By understanding the following snippet of decompiled code, from the start of the check_signature function:

 int _check_signature(int arg0) {

 r12 = arg0;

 r13 = [NSString stringWithUTF8String:r12];
 
 if ([[NSFileManager defaultManager] fileExistsAtPath:r13] == 0x0) goto loc_10000487f;


 loc_10000487f:
 rsi = "Package does not exist: %s\n";
 goto loc_1000048a3;
}
  1. The argument (arg0) is moved into the r12 register.
  2. It is then passed (via the r12) register to the [NSString stringWithUTF8String:] method … which creates a string object from a “standard” NULL-terminated C-string and stores it into the r13 register.
  3. The string object (in r13) is then passed to the [[NSFileManager defaultManager] fileExistsAtPath:] method, which checks if a file exists.
  4. If this method fails (returns NO, or 0x0), the code jumps to loc_10000487f which stores an error message into the rsi register.

…see, reversing doesn’t isn’t all that hard once you break it down in to more manageable pieces!

We can also dynamically confirm this, by setting a breakpoint on the _check_signature function (at address 0x0000000100004715), and dumping the value of arg0:

 $ lldb pkgutil -- --check-signature update.pkg

//set breakpoint on _check_signature (address 0x0000000100004715)
(lldb) br s -a 0x0000000100004715

//run!
(lldb) r

//breakpoint is hit
Process 698 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
 frame #0: 0x0000000100004715 pkgutil`check_signature

pkgutil`check_signature:
-> 0x100004715 <+0>: pushq %rbp

//print arg0 (in rdi)
(lldb) x/s $rdi
0x7ffeefbffbb2: "update.pkg"

In the debugger, we can confirm that the argument passed in is indeed a string, specifically the package to verify.

Moving on, we see the code creating a PKArchive from the path to the .pkg (that was passed into the function). This is accomplished by calling the class method archiveWithPath:

 r12 = arg0;
r13 = [NSString stringWithUTF8String:r12];

rdx = r13;
rax = [PKArchive archiveWithPath:rdx];

The PKArchive class is not documented…that is to say it’s a private class:

Good news is neither the lack of documentation nor private nature of the classes is really not an obstacle at all. We simply need to locate the binary that contains the PKArchive class definition and implementation, and then extract it. This is discussed in the subsequent section.

After the _check_signature function creates an instance of the PKArchive class (via the archiveWithPath: method), the decompilation shows it checking if the package contains any signatures. This is accomplished by accessing the count, of what appears to be (private) instance variable named archiveSignatures:

 rax = [PKArchive archiveWithPath:rdx];
rbx = rax;

rax = (r12)(rbx, @selector(archiveSignatures), rdx);
rax = (r12)(rax, @selector(count), rdx);
if (rax != 0x0) {

 //verify signatures

}

Once pkgutil has invoked the archiveSignatures method in a debugger (lldb) we can dynamically examine the object to ascertain both its type and contents:

 (lldb) po $rax
<__NSSingleObjectArrayI 0x1001121e0>(
PKXARArchiveSignature type RSA with certificates:
 - Developer ID Installer: Digita Security, LLC (S62KCC4ZPU)
 - Developer ID Certification Authority
 - Apple Root CA
)

Looks like (as somewhat expected), it’s an array of the signatures (PKXARArchiveSignature) that signed the .pkg.

If the package is signed (that is to say, archiveSignatures has one or more signatures), signature verification commences. Specifically, the first signature in the archiveSignatures array is extracted. This is an PKXARArchiveSignature object, which represents the ‘final’ (leaf) item in the certificate chain that signed the package. First the verifySignedDataReturningError: method is then invoked, to verify (some component?) of this signature:

 //extract first item from `archiveSignatures` array
rbx = (r12)((r12)(rbx, @selector(archiveSignatures)), @selector(objectAtIndex:), 0x0, 0x0);

//verify 
rax = (r12)(rbx, @selector(verifySignedDataReturningError:), 0x0, 0x0);

Assuming the verifySignedDataReturningError: does not return an error, verification continues.

After allocating an instance of another private class, PKTrust, the code either invokes the initWithSecTrust:usingAppleRoot:signatureDate: or initWithCertificates:usingAppleRoot:signatureDate: method (depending on whether the package contains a verificationTrustRef).

Let’s take a closer look at invocation of the initWithCertificates:usingAppleRoot:signatureDate: method. The decompilation is not the “cleanest”, but reveals that this method is invoked with the signature’s certificateRefs, a boolean (set to YES/0x1), and the date the archive was signed (archiveSignatureDate):

 rbx = (r12)(rbx, @selector(certificateRefs), 0x0, 0x0);
rax = (r12)(var_38, @selector(archiveSignatureDate), 0x0, 0x0);
rsi = @selector(initWithCertificates:usingAppleRoot:signatureDate:);

rcx = 0x1;
r8 = rax;
rbx = (r12)(r14, rsi, rbx, rcx);

Perhaps fully converting the decompiled binary code to Objective-C, will make this clearer:

 //alloc pk trust
pkTrust = [PKTrust alloc];

//complete init
pkTrust = [pkTrust initWithCertificates:signature.certificateRefs 
 usingAppleRoot:YES signatureDate:archive.archiveSignatureDate];

Assuming the initWithCertificates... method succeeds, a fully initialized instance of a PKTrust class is returned.

Finally, the code invokes the evaluateTrustReturningError method on the instance of a PKTrust class, to set a ‘trust level’ instance variable:

 (r12)(rbx, @selector(evaluateTrustReturningError:), rdx, rcx);
var_40 = (r12)(rbx, @selector(trustLevel), rdx, rcx);

This trust level variable is (finally!) what brings us back to the “signed by a certificate trusted by Mac OS X” string that pkgutil displayed when we verifed our signed .pkg (update.pkg):

 trustLevel = (r12)(rbx, @selector(trustLevel), rdx, rcx);
printf(" Status: ");

rbx = trustLevel;
...

if(rbx == 0xca) 
{
 rdi = "signed by a certificate trusted by Mac OS X";
 printf(rdi);
}

At this point we have a comprehensive understanding of how pkgutil verifies packages.

To summarize:

  1. The _check_signature method is invoked with the path of the .pkg to verify
  2. An instance of a PKArchive class is created via the archiveWithPath: method
  3. After checking the package contains one or more signatures (archiveSignatures), the signature data is verified via the verifySignedDataReturningError: method
  4. An instance of a PKTrustclass is created via the initWithSecTrust... or initWithCertificates... method
  5. A trust level is evaluated via the evaluateTrustReturningError: method
  6. Assuming all the verification checks pass, the trust level will indicate whether the package is validly signed (by a certificate chain the OS trusts).

Hooray, let’s code this up ourselves so that we can programmatically verify .pkg files - without having to rely on pkgutil.

Programmatically Verifying Packages

Ok, time to write some code to programmatically verify the cryptographic signature of .pkg files.

Though we now understand exactly how pkgutil verifies packages, the classes and methods it utilizes are private. As noted, this means we first must find the binary that contains the private class definitions (such as PKArchive), so we can extract them for our personal use.

Using the class-dump utility, we can dump embedded Objective-C classes from compiled binaries. We start by looking within the pkgutil binary. Unfortunately, it does not appear to contain the classes we’re interested in:

 $ class-dump /usr/sbin/pkgutil | grep PKArchive | wc

0 0 0

This is not too surprising; private classes are often implemented in private frameworks. Using jtool, we can dump the frameworks that pkgutil is dynamically linked against (use the -L commandline flag):

 $ jtool -L /usr/sbin/pkgutil 
 /System/Library/PrivateFrameworks/Bom.framework/Versions/A/Bom
 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
 /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/PackageKit
 /usr/lib/libxar.1.dylib
 /System/Library/Frameworks/Security.framework/Versions/A/Security
 /usr/lib/libobjc.A.dylib
 /usr/lib/libSystem.B.dylib

Based on its name, the /System/Library/PrivateFrameworks/PackageKit.framework looks like a likely candidate! And indeed, it is the binary that contains the PKArchive class:

 $ class-dump /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/PackageKit | grep -A 10 PKArchive

@interface PKArchive : NSObject
{
}

+ (id)archiveWithPath:(id)arg1;
+ (id)_allArchiveClasses;
- (BOOL)closeArchive;
- (BOOL)fileExistsAtPath:(id)arg1;
- (BOOL)verifyReturningError:(id *)arg1;
- (id)fileAttributesAtPath:(id)arg1;
- (BOOL)extractItemAtPath:(id)arg1 toPath:(id)arg2 error:(id *)arg3;

Note:

Notice the archiveWithPath: class method in the class-dump output.

Recall this is the method invoked by pkgutil to initialize a archive object.

And good news, all the other private classes (i.e. PKTrust, etc.) that we need, are also found within PackageKit framework, and thus can similarly be extracted via class-dump.

Armed with private classes definitions, we can (finally) start writing some code!

First, in our (Xcode) project, we add all the definitions of the private classes extracted from PackageKit:

 //
// main.m
// verifyPKG
//

#import <dlfcn.h>
#import <Foundation/Foundation.h>

@interface PKArchive : NSObject
{
}

+ (id)archiveWithPath:(id)arg1;
+ (id)_allArchiveClasses;
- (BOOL)closeArchive;
- (BOOL)fileExistsAtPath:(id)arg1;
...

Second, we must dynamically load the (private) PackageKit framework, so that at runtime we can actually invoke the various package verification methods (such a archiveWithPath:, verifySignedDataReturningError, etc.). This is trivial to accomplish via the dlopen function:

 //(private framework)
void* packageKit = NULL;

//load framework
packageKit = dlopen("/System/Library/PrivateFrameworks/PackageKit.framework/PackageKit", RTLD_LAZY);
if(NULL == packageKit)
{
 //bail
 goto bail;
}

Note:

We are loading a private framework (PackageKit.framework).

…as such it’s wise to check that call to dlopen succeeds, returning a non-NULL value.

Now the PackageKit framework has been loaded in our address space, we can start invoking its classes and their methods!

Due to the introspective nature of the Objective-C (and Swift) programming language, it’s simple to utilize private classes and invoke private methods. To access a private class (that is implemented in a loaded framework), use the NSClassFromString method. Apple notes this method: “obtains a class by name”. For example, the following code obtain the PKArchive class:

 //class
Class PKArchiveCls = nil;

//obtain class
PKArchiveCls = NSClassFromString(@"PKArchive");
if(nil == PKArchiveCls)
{
 //bail
 goto bail;
}

Note:

Again, we are dealing with private classes and methods.

As such, it’s always wise to check return values, to ensure the method (i.e. NSClassFromString) succeeded!

With a ‘reference’ to the PKArchive class, we can invoke its archiveWithPath: method (just as pkgutul did). However, before invoking this method, it’s wise to ensure that the class actually implements it (or in Objective-C parlance, “responds to the selector”). Via the respondsToSelector: one can check and ensure that an object implements a method. Apple documents this method, stating:

“respondsToSelector:

Returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.”

Here’s a code snippet that invokes the respondsToSelector: method to ensure the private PKArchive class (still) implements the archiveWithPath: method:

 //sanity check
if(YES != [PKArchiveCls respondsToSelector:@selector(archiveWithPath:)])
{
 //bail
 goto bail;
}

Easy, ya? As we now have a “reference” to the PKArchive class and have verified it responds to the archiveWithPath: method, we are free to invoke the method - just as if it was a documented public method:

 //init archive from .pkg
archive = [PKArchiveCls archiveWithPath:PKG_PATH];
if(nil == archive)
{
 //bail
 goto bail;
}
 

Note:

If one invokes a method on an object, and that object does respond to a method (selector), it will crash with a “unrecognized selector sent to class” exception.

As we are leveraging private classes, we validate that the objects (PKArchive, PKTrust, etc) responds to each method before invoking it.

Our remaining code that implements package verification, is as follows (note: certain error checking has been removed):

 //basic validation
 // this checks checksum, etc
 if(YES != [archive verifyReturningError:&error])
 goto bail;
 
 //extract signatures
 signatures = archive.archiveSignatures;
 if(0 == signatures.count)
 goto bail;
 
 //validate leaf (child?) signature
 if(YES != [signatures.firstObject verifySignedDataReturningError:&error])
 goto bail;
 
 //dbg msg
 NSLog(@"passed: \"am i signed\" validation");

 //alloc pk trust
 pkTrust = [PKTrustCls alloc];
 
 //init pk trust
 pkTrust = [pkTrust initWithCertificates:signatures.firstObject.certificateRefs usingAppleRoot:YES signatureDate:archive.archiveSignatureDate];
 goto bail;
 
 //validate
 if(YES != [pkTrust evaluateTrustReturningError:&error])
 goto bail;
 
 //dbg msg
 NSLog(@"passed: \"am i trusted\" validation (trust level: %#x)", pkTrust.trustLevel);

This code allows us to programmatically determine that a package has been validly signed. Hooray!

However, we also wanted to ensure that the .pkg was signed by Digita proper (Developer ID Installer: Digita Security, LLC (S62KCC4ZPU)). Since the certificates used to signed the package may be accessed via the signatures.firstObject.certificateRef variable, it’s trivial to add this final check:

 //grab (leaf) certificate
 certificate = (__bridge SecCertificateRef)([signature.certificateRefs firstObject]);
 
 //extract name
 if(errSecSuccess != SecCertificateCopyCommonName(certificate, &certificateName))
 goto bail;
 
 //name match?
 if(YES != [(__bridge NSString*)certificateName isEqualToString:DIGITA_CERT])
 goto bail;
 
 //dbg msg
 NSLog(@"passed: \"i am digita\" validation (cert name: %@)", certificateName);

In short, we simply extract the name of the certificate via the SecCertificateCopyCommonName function, then compare it with the name of the our (Digita’s) certificate.

Note:

One can also extract and validate the/any certificate’s SHA thumbprint.

Conclusion

We started by asking the simple question: “how can one programmatically validate .pkgs?

By reverse-engineering Apple’s pkgutil binary, we uncovered various private classes and methods (within PackageKit) that anybody can use to validate packages to their hearts delight!

 $ ./verifyPKG update.pkg

passed: basic validation
passed: "am i signed" validation
passed: "am i trusted" validation (trust level 0xca: "signed by a certificate trusted by Mac OS X")
passed: "i am digita" validation (cert name: "Developer ID Installer: Digita Security, LLC (S62KCC4ZPU)"")