Code Signature的一些理解(升级版)

UPDATED 20210805:结合最近看的一些codesign的内容,发现之前的理解有一些问题。这里做一下更新。

Code Signature data的结构

签名后的程序会多出一个LC_CODE_SIGNATURE load command,它会指出Code Signature data在Mach-O文件的位置和大小。那么Code Signature data的结构是怎样的呢?看下面的图就会十分清楚了。
code_signature_data_structure
有几点说明:

  • 紫色表示Code Signature data。其大小比真正的数据大一点,是因为有16bit对齐的需求(另一方面,使用codesign工具进行签名得出的Code Signature data,在满足16bit对齐的需求下,还会多出好一些0x00字节。这是为什么呢??);
  • 真正的数据就是由blob结构组成的:1个索引blob + n个功能blob。每个blob由header和data两部分组成。不同blob的header包含的字段会不一样,但前两个字段肯定为magic字段和length字段。magic字段表示blob的类型;length字段表示blob的大小(header+data)。数据是big endian编码的;
  • Super blob就是索引blob。蓝色表示Super blob,其length字段表示真正的数据大小。header里有字段表示功能块的个数,以及各个功能块的相对于Super blob开始处(即,Code Signature data开始处)的偏移;
  • 一般情况下,功能blob有Code Directory blob,Requirements blob,Entitlements blob,Signature blob。最最最重要的功能blob就是Code Directory blob和Signature blob,后面会详细说明。Requirements blob表示一些internal requirements,需要满足这些requirements,程序才能成功启动,详细内容可以查看Code Signing Requirement Language。Entitlements blob即为Entitlements文件的内容。

Code Directory blob

数字签名,简单来说就是:private_key_encode(hash(content))。Code Directory blob就是存放hash的地方。(我的一个误解:之前还以为Code Directory blob存放的是私钥加密后的hash,这是不对的。这里的hash就是hash算法后的hash)。hash不是对整个程序文件的内容直接进行hash,因此,不是只有一个hash,而是有许多个hash。Code Directory blob里的存放的hash分为两组:“一般的hash”和“特殊的hash”。

  • 一般的hash:程序文件(不包括Code Signature data)各个页(4K)的hash;
  • 特殊的hash:其他数据块的hash:
    1)嵌入的info.plist(__TEXT.__info_plist)的hash;(嵌入的info.plist是以前的MacOS X程序有用到??)
    2)Requirements blob的hash;
    3)资源目录的hash(即,bundle里的_CodeSignature/CodeResources文件的hash??);
    4)程序专有的hash。看到的资料说现在基本没有使用了;
    5)Entitlements blob的hash。

Code Directory blob的详细结构可以查看codesign.hldid.cpp

根据上面的描述,Code Directory blob里只是存放各个hash,那么密钥加密在哪呢?另外,Code Directory blob是没有被hash的,如何保证其不被修改呢?这些相关的内容就在Signature blob里。

Signature blob

Signature blob的header就只有magic(CSMAGIC_BLOBWRAPPER = 0xfade0b01)和length,紧接的就是其数据内容了。那么其数据内容是什么呢?简单来说,就是存放Code Directory blob的数字签名(private_key_encode(hash(Code Directory blob)))。但更准确来说,是存放CMS(Cryptographic Message Syntax)的signedData类型的数据,即,根据Code Directory blob,生成CMS的signedData类型的数据。有关CMS标准可以查看RFC5652。我们只需要知道CMS的signedData包含了以下信息:

  • 数字签名!!
  • Code Directory blob的hash!!(为什么会有hash,下面会有说明)
  • 签名证书
  • 签名时间

CMS的signedData是支持包含或不包含数字签名对应的原始内容的。因为Code Directory blob就存在旁边,肯定没必要再包含了。根据CMS标准,当signedData没有包含原始内容时,需要在signedAttrs增加两个属性:内容类型以及内容hash。这时,数字签名是这样得到的:private_key_encode(hash(signedAttrs))。因为signedAttrs里包括了内容hash,所以得出的数字签名也会间接的保障原始输入的内容。所以,更更准确来说,Signature blob的数据内容是CMS的signedData类型的数据,其中的数字签名为private_key_encode(hash(signedAttrs))。

另一方面,既然是CMS的signedData,如果将Signature blob的数据部分提取出来,是不是可以直接使用security工具(security cms -D - i)直接解析?我的尝试是解析失败的,提示message has no digests和problem decoding。按我的分析,可能是因为Signature blob的CMS signedData并不是完全标准的。根据ldid的代码,signedAttrs里没有标准规定要有的内容hash属性,而是有一个私有的属性(1.2.840.113635.100.9.1),属性内容是一个xml字符串,里面包含一个hash数组,在这里保存内容hash。根据ldid旧版本的代码是没有这个私有属性的,增加这个私有属性就可以支持多个不同内容的hash了。这样就可以支持多个Code Directory blob(当然需要具有不同的magic)(当有多种hash算法时,例如,SHA1和SHA256,就需要多个Code Directory blob)。

操作系统如何使用Code Signature data

操作系统是如何使用Code Signature data,来进行安全验证的?简单来说,有两个方面。

第一个方面:保证Code Directory blob的完整性。如果Code Directory blob是完整的,没有被修改过的,那么Code Directory blob里的各个hash值就是完整的,没有被修改过的。按照上面的描述,应该有以下过程:

  • 计算出Code Directory blob的hash,即,hash(Code Directory blob);
  • 确保Signature blob里的signature所对应的内容(根据上面的描述,即signedAttrs)的完整性。即,确保public_key(signature) == hash(signedAttrs)。如果是完整的,没有修改过,那么就可以信任signedAttrs里的内容;
  • 确保hash(Code Directory blob) == “signedAttrs里的对应Code Directory blob的hash”。

第二个方面:保证程序内容的完整性。这时,就需要使用Code Directory blob里的各个hash。简单来说,就是在相应内容使用前进行hash值比较。特别地,对于程序的代码和数据,会在Page Fault时机来确保hash(page) == “Code Directory blob里的一般的hash里对应页的hash”。

其他一些说明

  • 什么是Signature=adhoc?简单来说,就是没有Signature blob,而且Code Directory blob的flags有一个CS_ADHOC(0x0000002)标志。另外,Code Signature data的其他功能blob该有的还是有的。系统内置的程序就属于这种。对于这种情况,系统内置了这些系统程序的Code Directory blob的hash值,程序加载时直接比较。
  • 什么是pseudo signing?简单来说,就是没有Signature blob。另外,Code Signature data的其他功能blob该有的还是有的。ldid的初衷就是为了实现pseudo signing。对于越狱的iOS系统,可以将上面描述的验证完整性的“第一个方面”做一些处理,从而让没有Signature blob的程序通过验证;但是对“第二个方面”就没有做处理(可以做得到,但是逻辑比较分散,可维护性太差,而实现pseudo signing比较简单),所以还是需要Code Signature data的其他功能blob。

更多内容

通过上面的描述,应该可以对Code Signature相关内容有个总体的认识。如果想继续了解,可以查看下面的内容:

好的工具:


ORIGINAL 20160207:删除线的文字表示不正确的内容,蓝色的文字表示新增的说明。

这两天看了一下ldid的源代码,对codesign的一些理解,简单记录一下。

1、数字签名。简单来说就是HASH算法+私钥加密。从而可以确保文件的来源以及完整性。mac os对于程序的codesign并不只是简单的对整个文件进行数字签名,而是对程序文件以页大小为单位进行数字签名,加上对codesign部分的各个blob进行数字签名(根据ldid的代码,没有对codedirectory blob进行数字签名?)。 Code Directory blob只是存放各个hash,数字签名相关内容在Signature blob里。

2、codesign功能。确保文件的来源以及完整性只是codesign的其中一个(最主要的)功能,签名者还可以加上各种requirement,验证者(操作系统和各个程序)可以按需使用(具体可以查看下面参考文档CodeSigningGuide)。另一个功能就是包含entitlement文件了。

3、codesign数据。如果程序被codesign了,程序会多出一个codesign load command,以及对应的codesgin数据。codesign load command里有表明codesign数据在程序文件里的偏移量(dataoff)和大小(datasize)。codesign数据,简单来说就是由一个“索引块”和多个”功能块“组成,每个“功能块”表示某一种用途。
SuperBlob(索引)
–CodeDirectory(程序的bundle id,各个数字签名各个hash
–Requirements(这里应该就是苹果文档里对应的internal requirements,各个requirement使用codesign requirement language表示,最终内容是text->binary的形式,即compiled后的内容)
–Entitlements(这里就是entitlement文件了)
–Signature(CMS的signedData:数字签名,hash,证书,签名时间)
具体各个blob的结构表示,可以参考后面的codesign.h。

4、其他一些点。
1)ldid里对各个部分使用sha1的hash,没有使用私钥加密(因为Code Directory blob里的hash就是hash)。sha1出来的hash位数为20个字节。
2)ldid里,重签步骤里还会修改__LINKEDIT segment的filesize。这是为什么呢?应该是因为:重签会修改到codesign load command和codesign数据部分。那就说明codesing数据部分是属于linkedit segment的,因此,需要修改linkedit segment的大小。从这里也可以解释为什么codesign load command的结构体是linkedit data command(cmd+cmdsize+dataoff+datasize)了,因为codesing数据就是其中一种linkedit数据。
3)ldid里,重签时的requirement的内容\xfa\xde\x0c\x01\x00\x00\x00\x0c\x00\x00\x00\x00这12个字节是什么意思?当时猜测这个应该是codesign requirement language的compiled后的数据。后来通过使用csreq -r file -t(***file**内容就为这12个字节),命令可以成功执行,但是,返回/ no requirements in set */,表示没有内容。那么,可能就是最小的没有requirement的表示吧。简单来说,就是4个字节是internal requirements blob的magic,4个字节是blob长度,4个字节是内容(因为没有内容所以全为\x00)。
4)ldid重签,codesgin数据部分只会包括CodeDirectry, Requirements(上面提到的那12个字节)以及entitlement文件(如果有指定的话)。这就是pseudo signing的目标:去掉Signature blob。另一方面,ldid也是支持生成Signature blob功能的。
5)结合codesign.h里的数据结构的定义,ldid的源代码,以及hex编辑器来理解。

5、一些命令。
查看codesign load command
otool -l binary | grep -A 5 SIGNATURE

查看签名数据内容
codesign -dvvv binary

查看compiled codesign requirement language的文本内容
csreq -r file -t

查看entitlement内容
ldid -e binary

修改entitlement内容
ldid -S***entitlement.xml*** binary

6、ldid一些代码片段。
1)显示entitlement文件内容。

if (flag_e) {
	_assert(signature != NULL);

	uint32_t data = mach_header.Swap(signature->dataoff);

	uint8_t *top = reinterpret_cast<uint8_t *>(mach_header.GetBase());
	uint8_t *blob = top + data;
	struct SuperBlob *super = reinterpret_cast<struct SuperBlob *>(blob);

	for (size_t index(0); index != Swap(super->count); ++index)
		if (Swap(super->index[index].type) == CSSLOT_ENTITLEMENTS) {
			uint32_t begin = Swap(super->index[index].offset);
			struct Blob *entitlements = reinterpret_cast<struct Blob *>(blob + begin);
			fwrite(entitlements + 1, 1, Swap(entitlements->length) - sizeof(struct Blob), stdout);
		}
}

2)normal部分的签名(即除去codesign数据部分的文件内容的签名,以页大小的单位签)(属于CodeDirectory部分的数据)

// 这里是normal部分的签名!!
if (pages != 1)
	for (size_t i = 0; i != pages - 1; ++i)
		sha1(hashes[i], top + 0x1000 * i, 0x1000);
if (pages != 0)
	// 最后一个特殊处理,因为可能不满一个页面大小
	sha1(hashes[pages - 1], top + 0x1000 * (pages - 1), ((data - 1) % 0x1000) + 1);

3)special部分的签名(即codesign数据部分的文件内容的签名,以blob为单位签)(属于CodeDirectory部分的数据)

for (size_t index(0); index != count; ++index) {
	uint32_t type = Swap(super->index[index].type);
	if (type != 0 && type <= special) {
		uint32_t offset = Swap(super->index[index].offset);
		struct Blob *local = (struct Blob *) (blob + offset);
		// 这里是specail部分的签名!!
		// 长度为blob的长度
		sha1((uint8_t *)(hashes - type), (uint8_t *)local, Swap(local->length));
	}
}

4)签名个数(属于CodeDirectory部分的数据)

uint32_t special = xmld == NULL ? CSSLOT_REQUIREMENTS : CSSLOT_ENTITLEMENTS;
// number of special hash slots
// special部分的签名slot
directory->nSpecialSlots = Swap(special);

uint8_t(*hashes)[20] = reinterpret_cast<uint8_t(*)[20]>(blob + offset);
memset(hashes, 0, sizeof(*hashes) * special);

offset += sizeof(*hashes) * special;
// 注意,签名是normal部分+special部分的!!!!
hashes += special;

uint32_t pages = (data + 0x1000 - 1) / 0x1000;
// number of ordinary (code) hash slots
// normal部分的签名slot
directory->nCodeSlots = Swap(pages);

5)bundle id(属于CodeDirectory部分的数据)

// offset of identifier string
directory->identOffset = Swap(offset - begin);
strcpy(reinterpret_cast<char *>(blob + offset), name);
offset += strlen(name) + 1;

参考:
http://gitweb.saurik.com/ldid.git
http://www.saurik.com/id/8
http://www.opensource.apple.com/source/xnu/xnu-3248.20.55/bsd/sys/codesign.h
https://developer.apple.com/library/mac/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值