stek29.rocks

Is this blog?

Sandbox’s MACF label slot is a struct “sandbox”

its slot is 1, and it’s not always present

#define EXT_TABLE_SIZE 9

struct sandbox {
/* 0x00 */	void* profile;
/* 0x08 */	extension_hdr_t ext_table[EXT_TABLE_SIZE]; // zeroed on create
/* 0x58 */	lck_rw_t ext_lck;
/* 0x68 */	uint32_t unk2; // unk2 = (unk2&0xe0)|0x10 on create
/* 0x6c */	uint32_t pad0; // or not pad :)
/* 0x70 */	uint64_t unk3; // zeroed on create
/* 0x78 */	uint64_t unk4; // zeroed on create
/* 0x80 */	lck_rw_t lck1; 
/* 0x90 */	uint64_t unk5; // zeroed on create
/* 0x98 */	uint64_t unk6; // 0x128 + some profile name thing
/* 0xa0 */	uint64_t unk7;
/* 0xa8 */	uint32_t refcount; // = 1 on create
/* 0xac */	uint32_t pad1;
/* 0xb0 */	uint64_t unk8; // zeroed on create
/* 0xb8 */  lck_rw_t lck2; 
/* 0xc8 */
}

What we’re most interested in is ext_table

There is a normal “extension”, and what I call “extenstion_hdr”

This is a three level structure

struct extension_hdr {
/* 0x00 */	extension_hdr_t next;
/* 0x08 */	const char* desc;
/* 0x10 */	extension_t ext_lst;
/* 0x18 */
};
typedef struct extension_hdr* extension_hdr_t;

struct extension {
/* 0x00 */	extension_t next;
/* 0x08 */	uint64_t desc; // always 0xffffffffffffffff
/* 0x10 */	uint64_t ext_lst; // zero, since it's extension and not a header
/* 0x18 */	uint8_t something[32]; // zeroed from what I've seen
/* 0x38 */	uint32_t type; // see ext_type enum
/* 0x3c */	uint32_t subtype; // either 0 or 4 (or whatever unhex gave?..)
/* 0x40 */	void *data; // a c string, meaning depends on type and hdr which had this extension
/* 0x48 */	uint64_t data_len; // strlen(data)
/* 0x50 */	uint64_t unk0; // always 0
/* 0x58 */	uint64_t unk1; // always 0xdeadbeefdeadbeef
/* 0x60 */
};
typedef struct extension* extension_t;

enum ext_type {
	ET_FILE = 0,
	ET_MACH = 1,
	ET_IOKIT_REG_ENT = 2,
	ET_POSIX_IPC = 4,
	ET_PREF_DOMAIN = 5, // inlined in issue_extension_for_preference_domain
	ET_SYSCTL = 6, // inlined in issue_extension_for_sysctl
};

3 is assigned only in one place – in _hook_policy_syscall

3 is special, because it’s handled differently, see extension_destroy

There are some “tracking” memory management functions which are used everywhere:

void* smalloc_track(sandbox*, size_t size);
void* sstrdup_track(sandbox*, const char* str);
void sfree(void* ptr);

Now, when we’re somewhat familiar with “extension”s, let’s see how are they created

typedef int (*)(sandbox *, sb_creation_context *, char const*, void *) ent_exc_handler_t;
struct handler_map_entry {
	const char* entitlement_cstr;
	ent_exc_handler_t func;
	const OSSymbol* entitlement_sym;
}

There’s an array of handler_map_entry’s (Obviously it’s in const region).

const handler_map_entry handler_map[] = {
	// ...
	{
		"com.apple.security.exception.iokit-user-client-class",
		issue_extension_for_iokit_registry_entry,
		NULL
	}, 
	// ...
}

On sandbox’s kmod_start it initializes entitlement_sym’s:

struct handler_map_entry* entry = handler_map;
while (entry->func != 0) {
	entry->entitlement_sym = OSSymbol::withCStringNoCopy(entry->entitlement_cstr);
	++entry;
}

and another place where it’s used is cred_label_update_execve hook:

OSDictionary* entitlements = AppleMobileFileIntegrity::copyEntitlements(ucred);
struct handler_map_entry* entry = handler_map;
while (entry->func != 0) {
	OSObject* val = entitlements->getObject(entry->entitlement_sym);
	if (val != 0) {
		iterate_entitlement_value_strings(
			sbx, ctx, entry->entitlement_cstr,
			val, entry->entitlement_cstr,
			entry->func
		);
	}
	++entry;
}
entitlements->free();

So, the flow looks like this:

See more manually rewritten from disasm code below with some comments :)

// most functions return 0xC on error :
#define RET_ERR 0xC
#define RET_OK  0x0

int extension_create(extension_t* saveto, sandbox* sb) {
	extension_t ext = (extension_t) smalloc_track(sb, sizeof(extension_t));
	*saveto = ext;

	if (ext == NULL) {
		return RET_ERR;
	}

	ext->desc = 0xFFFFFFFFFFFFFFFF;
	bzero(&ext->something, sizeof(ext->something));

	return RET_OK;
}

void extension_destroy(extension_t ext) {
	switch(ext->type) {
		case ET_FILE:
		case ET_MACH:
		case ET_IOKIT_REG_ENT:
		case ET_POSIX_IPC:
		case ET_PREF_DOMAIN:
		case ET_SYSCTL:
			sfree(ext->data);
			// fallthrough
		default:
			sfree(ext);
	}
}

// subtype is 0 for "com.apple.security.application-groups"

int extension_create_file(extension_t* saveto, sandbox* sb, const char* path, size_t path_len, uint32_t subtype) {
	int ret;

	ret = extension_create(saveto, sb);
	if (ret) {
		return ret;
	}

	extension_t ext = *saveto;

	ext->type = ET_FILE;
	ext->subtype = subtype;

	ext->data = smalloc_track(sb, path_len);

	if (ext->data == NULL) {
		sfree(ext);
		*saveto = NULL;
		return RET_ERR;
	}

	memcpy(ext->data, path, path_len);

	// ; x22 is saveto
	// ; x19 is path_len
	// ; [X22,#0x40] is (*saveto)->data
	// ; i dont understand this
	// LDR             X8, [X22,#0x40]
	// STRB            WZR, [X8,X19]

	ext->data_len = path_len;
	ext->unk0 = 0;

	return RET_OK;
}

// for entitlement 
// "com.apple.security.exception.mach-(lookup|register).(local|global)-name"
// subtype is 0
// --- or ---
// for entitlement
// "com.apple.security.application-groups"
// subtype is 4
int extension_create_mach(extension_t* saveto, sandbox* sb, const char* name, uint32_t subtype) {
	int ret;

	ret = extension_create(saveto, sb);
	if (ret) {
		return ret;
	}

	extension_t ext = *saveto;

	ext->type = ET_MACH;
	ext->subtype = subtype;

	ext->data = sstrdup_track(sb, path_len);

	if (ext->data == NULL) {
		sfree(ext);
		*saveto = NULL;
		return RET_ERR;
	}

	ext->data_len = strlen(ext->data);

	return RET_OK;
}

// for entitlement
// "com.apple.security.application-groups"
// subtype is 4
int extension_create_posix_ipc(extension_t* saveto, sandbox* sb, const char* name, uint32_t subtype) {
	int ret;

	ret = extension_create(saveto, sb);
	if (ret) {
		return ret;
	}

	extension_t ext = *saveto;

	ext->type = ET_POSIX_IPC;
	ext->subtype = subtype;

	ext->data = sstrdup_track(sb, path_len);

	if (ext->data == NULL) {
		sfree(ext);
		*saveto = NULL;
		return RET_ERR;
	}

	ext->data_len = strlen(ext->data);

	return RET_OK;
}

// for entitlement
// "com.apple.security.exception.iokit-user-client-class"
// subtype is 0
int extension_create_iokit_registry_entry_class(extension_t* saveto, sandbox* sb, const char* name, uint32_t subtype) {
	int ret;

	ret = extension_create(saveto, sb);
	if (ret) {
		return ret;
	}

	extension_t ext = *saveto;

	ext->type = ET_IOKIT_REG_ENT;
	ext->subtype = subtype;

	ext->data = sstrdup_track(sb, path_len);

	if (ext->data == NULL) {
		sfree(ext);
		*saveto = NULL;
		return RET_ERR;
	}

	ext->data_len = strlen(ext->data);

	return RET_OK;
}


// get 64 higher bits of 64bit int multiplication
// https://stackoverflow.com/a/28904636
// ofc in asm it's done with 1 instruction huh
uint64_t mulhi(uint64_t a, uint64_t b) {
	uint64_t    a_lo = (uint32_t)a;
	uint64_t    a_hi = a >> 32;
	uint64_t    b_lo = (uint32_t)b;
	uint64_t    b_hi = b >> 32;

	uint64_t    a_x_b_hi =  a_hi * b_hi;
	uint64_t    a_x_b_mid = a_hi * b_lo;
	uint64_t    b_x_a_mid = b_hi * a_lo;
	uint64_t    a_x_b_lo =  a_lo * b_lo;

	uint64_t    carry_bit = ((uint64_t)(uint32_t)a_x_b_mid +
	                         (uint64_t)(uint32_t)b_x_a_mid +
	                         (a_x_b_lo >> 32) ) >> 32;

	uint64_t    multhi = a_x_b_hi +
	                     (a_x_b_mid >> 32) + (b_x_a_mid >> 32) +
	                     carry_bit;

	return multhi;
}

int hashing_magic(const char *desc) {
	// inlined into exception_add
	uint64_t hashed = 0x1505;

	// if desc == NULL, then returned value would be 8
	// APPL optimizes it for some reason
	// but meh, desc should never be NULL or you get
	// null dereference in exception_add
	// if (desc == NULL) return 8;

	if (desc != NULL) {
		for (const char* dp = desc; *dp != '\0'; ++dp) {
			hashed += hashed << 5;
			hashed += (int64_t) *dp;
		}
	}

	uint64_t magic = 0xe38e38e38e38e38f;

	uint64_t hi = mulhi(hashed, magic);
	hi >>= 3;
	hi = (hi<<3) + hi;

	hashed -= hi;

	return hashed;
}

int extension_add(extension_t ext, sandbox* sb, const char* desc) {
	int ret = RET_OK;
	int idx = hashing_magic(desc);

	lck_rw_lock_exclusive(sb->ext_lck);

	extension_hdr_t insert_at = sb->ext_table[idx];

	if (insert_at == NULL) {
		insert_at = smalloc_track(sb, sizeof(extension_hdr));
		if (insert_at == NULL) {
			ret = RET_ERR;
			goto end;
		}

		insert_at->next = NULL;

		insert_at->desc = sstrdup_track(sb, desc);

		if (insert_at->desc == NULL) {
			sfree(insert_at);
			ret = RET_ERR;
			goto end;
		}

		insert_at->ext_lst = NULL;

		sb->ext_table[idx] = insert_at;
	}

	while (insert_at->next != NULL) {
		// APPL doesn't check for (insert_at->desc != NULL)
		// but extension_add is never called with desc=NULL so meh
		if (strcmp(insert_at->desc, desc) == 0) {
			break;
		}

		insert_at = insert_at->next;
	}

	ext->next = insert_at->ext_lst;
	insert_at->ext_lst = ext;

	ret = 0;

end:;
	lck_rw_unlock_exclusive(sb->ext_lck);

	return ret;
}

int iterate_entitlement_value_strings(sandbox *sb, sb_creation_context *ctx, const char* entitlement_cstr, const OSObject* val, void *data, ent_exc_handler_t f) {
	{
		const char *c_val = NULL;

		OSString* as_str = OSDynamicCast(OSString, val);
		if (as_str != NULL) {
			c_val = as_str->getCStringNoCopy();
		} else {
			OSBoolean* as_bool = OSDynamicCast(OSBoolean, val);
			if (as_bool != NULL && as_bool->isTrue()) {
				c_val = "true";
			}
			// false is considered invalid...
		}

		if (c_val != NULL) {
			return (*f)(sb, ctx, c_val, data);
		}
	}

	{
		OSArray *as_arr = OSDynamicCast(OSArray, val);

		if (as_arr == NULL) {
			return 8;
		}

		unsigned int cnt = as_arr->getCount();

		for (unsigned int i = 0; i != cnt; ++i) {
			OSObject* obj = as_arr->getObject(i);
			OSString* str = OSDynamicCast(OSString, obj);

			if (str == NULL) {
				// value #%u for entitlement %s is type %s, expected string
				return 8;
			}

			c_val = str->getCStringNoCopy();

			int ret = (*f)(sb, ctx, c_val, data);
			if (ret) {
				return ret;
			}
		}

		return 0;
	}
}

// sample handler
int issue_extension_for_iokit_registry_entry(sandbox *sb, sb_creation_context *ctx, const char* entry_name, void *desc) {
	extension_t ext;

	int ret = extension_create_iokit_registry_entry_class(&ext, sb, entry_name, 0);

	if (ret == 0) {
		ret = extension_add(ext, sb, desc);

		if (ret) {
			extension_destroy(ext);
		}
	}

	return ret;
}