Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure-Ruby Code Directory/Code signing parsing and manipulation #262

Open
woodruffw opened this issue Sep 29, 2020 · 7 comments · May be fixed by #265
Open

Pure-Ruby Code Directory/Code signing parsing and manipulation #262

woodruffw opened this issue Sep 29, 2020 · 7 comments · May be fixed by #265
Assignees

Comments

@woodruffw
Copy link
Member

I'm going to use this issue as a dumping ground as I explore a pure-Ruby alternative to #260.

At a high level:

  • If a binary already contains an LC_CODE_SIGNATURE, we need to erase it and replace it with our own (ad-hoc) signature
  • If a binary doesn't contain an LC_CODE_SIGNATURE, we need to add a new load command containing one

That's not the end of things:

  • LC_CODE_SIGNATURE references the signing data, but doesn't actually contain it. It's actually hiding in the __LINKEDIT segment. That means that we'll need to rewrite (and probably resize) __LINKEDIT.
@woodruffw woodruffw self-assigned this Sep 29, 2020
@woodruffw
Copy link
Member Author

woodruffw commented Sep 29, 2020

Kicking things off: here's the difference between the headers of an unsigned libgettextlib and an ad-hoc signed one:

12 -> 13 corresponds to the increase in load commands, E0 07 00 00 -> F0 07 00 00 corresponds to the increase in sizeofcmds (16, exactly one linkedit_data_command).

(Thanks @mistydemeo for the samples!)

@woodruffw
Copy link
Member Author

Here's how codesign.c futzes with that lc_code_signature (within __LINKEDIT):

CS_SuperBlob *sb = (CS_SuperBlob*)lc_code_signature;

and the structs:

typedef struct __SuperBlob {
	uint32_t magic;					/* magic number */
	uint32_t length;				/* total length of SuperBlob */
	uint32_t count;					/* number of index entries following */
	CS_BlobIndex index[];			/* (count) entries */
	/* followed by Blobs in no particular order as indicated by offsets in index */
} CS_SuperBlob;

typedef struct __BlobIndex {
	uint32_t type;					/* type of entry */
	uint32_t offset;				/* offset of entry */
} CS_BlobIndex;

where type is presumably this enum:

enum {
	CSMAGIC_REQUIREMENT	= 0xfade0c00,		/* single Requirement blob */
	CSMAGIC_REQUIREMENTS = 0xfade0c01,		/* Requirements vector (internal requirements) */
	CSMAGIC_CODEDIRECTORY = 0xfade0c02,		/* CodeDirectory blob */
	CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */
	CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */
	
	CSSLOT_CODEDIRECTORY = 0,				/* slot index for CodeDirectory */
};

Apparently this enum isn't complete: comex found two more in their attempt:

        CSMAGIC_ENTITLEMENT = 0xfade7171, # actually, this is kSecCodeMagicEntitlement, and not defined in the C version :psyduck:
        CSMAGIC_BLOBWRAPPER = 0xfade0b01, # and this isn't even defined in libsecurity_codesigning; it's in _utilities

So it's pretty standard TLV parsing, nothing outside of what ruby-macho can already do. Presumably each type corresponds to a structure of fixed (or inferrable) size.

@woodruffw
Copy link
Member Author

Oh, maybe type isn't that enum. Might be something else. Anyways, here's the struct for CSMAGIC_CODEDIRECTORY:

typedef struct __CodeDirectory {
	uint32_t magic;					/* magic number (CSMAGIC_CODEDIRECTORY) */
	uint32_t length;				/* total length of CodeDirectory blob */
	uint32_t version;				/* compatibility version */
	uint32_t flags;					/* setup and mode flags */
	uint32_t hashOffset;			/* offset of hash slot element at index zero */
	uint32_t identOffset;			/* offset of identifier string */
	uint32_t nSpecialSlots;			/* number of special hash slots */
	uint32_t nCodeSlots;			/* number of ordinary (code) hash slots */
	uint32_t codeLimit;				/* limit to main image signature range */
	uint8_t hashSize;				/* size of each hash in bytes */
	uint8_t hashType;				/* type of hash (cdHashType* constants) */
	uint8_t spare1;					/* unused (must be zero) */
	uint8_t	pageSize;				/* log2(page size in bytes); 0 => infinite */
	uint32_t spare2;				/* unused (must be zero) */
	/* followed by dynamic content as located by offset fields above */
} CS_CodeDirectory;

@woodruffw woodruffw linked a pull request Oct 6, 2020 that will close this issue
@woodruffw
Copy link
Member Author

Ongoing in #265.

@BrewTestBot
Copy link
Member

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@rickmark
Copy link
Contributor

raises hand - I'm planning to do some work in this space - initially entitlements to replace the aging j's database

@woodruffw
Copy link
Member Author

Please do! I've done some initial work in #265; feel free to crib from that if it helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants