Reversing the AMD Secure Processor (PSP) - Part 2: Cryptographic Co-Processor (CCP)
2023-4-23 03:21:30 Author: dayzerosec.com(查看原文) 阅读量:29 收藏

设计mysql数据表时,通常用户名、密码的类型为varchar或者char,可以利用Mysql varchar或char类型同数字比较的自动转换机制,构造最新过狗(安全狗V3.5.12048)新型万能密码。先上一个payload尝鲜:'|0-- --,详细分析过程如下:

由结果可知,Mysql数据库中,varchar与数字比较时,会强制转换varchar为数字再进行比较(类似php语言中的intval函数处理)非数字开头的varchar字符串会转换为0再进行比较,数字开头的varchar字符串转化为开头对应数字部分的值再进行比较,所以当username和0进行比较时,会返回所有不是数字开头的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}

$sql = "SELECT * FROM user where username='".$_GET['username']."' and password='".$_GET['password']."'" ;
var_dump($sql);

$result = $conn->query($sql);
if ($result and $result->num_rows>0) {
while($row = mysqli_fetch_assoc($result)) {
echo "id:".$row["id"]. "; username:".$row["username"]. "; ocal), as the CCP treats SMN/Syshub addresses as local PSP memory.

We'll look at the ccp_passthrough() function for doing a direct regular DMA copy and mostly skip over ccp_zlib_inflate() for compressed data since it's very similar.

void ccp_mmio_write(struct ccp_mmio_req* req, int queue_idx, int a3)
{
	uint32_t* mmio_reg = (uint32_t*) (0x3001000 + (queue_idx * 0x1000));

	do {
		// busy wait on queue to be free
	} while (mmio_req[0] << 0x1F)
	// ...

	mmio_req[0] = req->ctrl;
	mmio_req[1] = req->tail;
	mmio_req[2] = req->head;
}

int ccp_passthrough(void* src, void* dest, uint32_t size, int src_type, int dest_type, int a6)
{
	struct ccp_passthrough_req req;
	struct mmio_ccp_req mmio_req;

	if (src == NULL || dest == NULL)
		return BL_ERR_INVALID_PARAMETER;

	bzero(&req, sizeof(struct ccp_passthrough_req)); // size = 0x20
	bzero((void*) 0xE680, 0x80);
	
	// Control
	CCP5_CMD_SOC(&req) = 1;
	CCP5_CMD_EOM(&req) = 1;
	CCP5_CMD_FUNCTION(&req) = CCP_ENGINE_PASSTHRU;

	CCP5_CMD_LEN(&req) = size;

	// Source
	if (src_type == CCP_MEMTYPE_LOCAL)
		CCP5_CMD_SRC_LO(&req) = sub_b0a0(src);
	else
		CCP5_CMD_SRC_LO(&req) = src;
	CCP5_CMD_SRC_MEM(&req) = src_type;

	// Dest
	if (dest_type == CCP_MEMTYPE_LOCAL)
		CCP5_CMD_DST_LO(&req) = sub_b0a0(dest);
	else
		CCP5_CMD_DST_LO(&req) = dest;
	CCP5_CMD_DST_MEM(&req) = dest_type;

	// Copy and submit mmio write
	memcpy((void*) 0xE680, &req, sizeof(struct ccp_passthrough_req));
	// ...

	mmio_req.ctrl = CMD5_Q_RUN;
	mmio_req.head = (void*) 0xE680;
	mmio_req.tail = (void*) 0xE6A0;
	// ...

	*(uint32_t*) (0x3006000) = 1;
	ccp_mmio_write(&mmio_req, queue_idx: 0, 6);
	// ...
	if (ccp_wait_status_update(queue_idx: 0))
		return BL_ERR_CCP_PASSTHR;
	return 0;
}

What we have here is a fairly standard register-based Memory Mapped I/O (MMIO) request, which is used to submit the CCP requests via ringbuffer. Each queue gets a 0x1000 byte MMIO region, with the first three dwords being used for the control bits, tail, and head respectively. The head points to the setup request, and the tail to zero'd / NOP data.

As far as I've seen, all CCP requests made by the IPL will use command queue #0. This makes sense, as the IPL is relatively simple and doesn't need to use all the queues. Here's a diagram giving an overview of the setup for sending requests to the CCP:

img

One of the main annoying features the PSP supports (from a research perspective at least), is the ability to have the firmware on the flash encrypted. In these cases, the firmware blobs are encrypted with what some other researchers have dubbed a Component Key (cK), which is embedded in the firmware's header contents. Of course, this key is encrypted with an Intermediate Key Encryption Key (iKEK), which is stored on the flash as well and is also encrypted with a Root Key (rK). Decryption involves first decrypting the iKEK with the Root Key (rK), to then decrypt the cK to finally decrypt the plaintext firmware. The root key stays in a locked CCP slot, and allegedly [2] can't easily be dumped even if you have code execution at this stage.

img

Note: "Imm.Secret" = Immutable + Secret / Non-Readable

The "One Glitch to Rule Them All" paper [2] describes this at a high level, but let's go down the rabbit hole on the code responsible for retrieving and decrypting this component key and the firmware. It starts in what I've tentatively named _bootloader_enter_c_main() after most of the bootloader initialization is complete.

int _bootloader_enter_c_main() {
	// ...
	char wrapped_ikek[0x10] = {0};
	err = spiReadPspDirEntry(entry_id: 0x21, dest: &wrapped_ikek, size: 0x10);
	if (err) { /* ... */ }
	err = ccp_aes_ecb_decrypt(
		key: 0x80,
		key_type: CCP_MEMTYPE_SB,
		key_size: 0x10,
		src: &wrapped_ikek,
		src_type: CCP_MEMTYPE_LOCAL,
		len: 0x10,
		dest: (void*) 0xF2C0,
		dest_type: CCP_MEMTYPE_LOCAL
	);
	// ...
}

The component key is stored at 0xF2C0 in this case, which will be used by various functions in the IPL when user-space requests a binary to be loaded via syscall. You'll notice the key of 0x80 is an LSB address (which resolves to slot 4 in LSB #0). Assumingly this region is reserved and locked by the CCP, and you can't just read it out.

Both ccp_aes_ecb_decrypt() and ccp_aes_ecb_encrypt() wrap around what I've named ccp_aes_ecb_crypt() and just call it with one slightly different argument indicating if it's an encrypt or decrypt operation.

int ccp_aes_ecb_crypt(
	uint32_t key,
	int key_type,
	uint32_t key_size,
	uint32_t a4,
	void* src,
	int src_type,
	uint32_t size,
	void* dest,
	int dest_type,
	int a10,
	int is_encrypt) {
	int aes_type;
	int function;
	struct ccp_aes_req req;
	struct mmio_ccp_req mmio_req;

	if (src == NULL || dest == NULL || key_type == CCP_MEMTYPE_SYSTEM)
		return BL_ERR_INVALID_PARAMETER;

	switch (key_size) {
	case 0x10:
		aes_type = 0; // aes-128
		break;
	case 0x18:
		aes_type = 1; // aes-192
		break;
	case 0x20:
		aes_type = 2; // aes-256
		break;
	default:
		return BL_ERR_INVALID_PARAMETER;
	}

	bzero(&req, sizeof(struct ccp_aes_req)); // size = 0x20
	bzero((void*) 0xE680, 0x80);

	// Control
	CCP_AES_ENCRYPT(&req) = is_encrypt;
	CCP_AES_MODE(&req) = CCP_AES_MODE_ECB;
	CCP_AES_TYPE(&req) = aes_type;

	CCP5_CMD_SOC(&req) = 1;
	CCP5_CMD_EOM(&req) = 1;
	CCP5_CMD_LEN(&req) = size;

	// Key
	if (key_type == CCP_MEMTYPE_LOCAL)
		CCP5_CMD_KEY_LO(&req) = sub_b0a0(key);
	else
		CCP5_CMD_KEY_LO(&req) = key;
	CCP5_CMD_KEY_MEM(&req) = key_type;

	// Source
	if (src_type == CCP_MEMTYPE_LOCAL)
		CCP5_CMD_SRC_LO(&req) = sub_b0a0(src);
	else
		CCP5_CMD_SRC_LO(&req) = src;
	CCP5_CMD_SRC_MEM(&req) = src_type;

	// Dest
	if (dest_type == CCP_MEMTYPE_LOCAL)
		CCP5_CMD_DST_LO(&req) = sub_b0a0(dest);
	else
		CCP5_CMD_DST_LO(&req) = dest;
	CCP5_CMD_DST_MEM(&req) = dest_type;

	// Copy and submit mmio write
	memcpy((void*) 0xE680, &req, sizeof(struct ccp_passthrough_req));
	// ...

	mmio_req.ctrl = CMD5_Q_RUN;
	mmio_req.head = (void*) 0xE680;
	mmio_req.tail = (void*) 0xE6A0;
	// ...

	ccp_mmio_write(&mmio_req, queue_idx: 0, 6);
	// ...
	if (ccp_wait_status_update(queue_idx: 0))
		return BL_ERR_CCP_AES;
	return 0;
}

Looking at this function, we can see it's somewhat similar to the DMA copy passthrough, though with a lot more context provided where AES operations are more complex. Most of these CCP handlers look fairly similar, utilizing their respective macros to initialize the control bits and set up the request descriptor, so I won't do a deep dive on all of them.

Now that the IKEK is decrypted and stored in memory, we can see where it's used for decrypting firmware. The syscall handler for SVC_ENTER (which loads a firmware blob from the flash firmware filesystem) will eventually call what I've dubbed fw_copy(). Note that srcdest contains both the source data already loaded and where to write the finalized contents to.

int component_key_decrypt(char* enc_key, char* dec_key) {
	return ccp_aes_ebc_crypt(
		key: (void*) 0xF2C0, key_type: CCP_MEMTYPE_LOCAL, key_size: 0x10,
		src: &enc_key, src_type: CCP_MEMTYPE_LOCAL, size: 0x10,
		dest: dec_key, dest_type: CCP_MEMTYPE_LOCAL
	);
}

int fw_body_decrypt(
	char* key,
	int key_type,
	uint32_t key_size,
	char* iv,
	char* src,
	int src_type,
	uint32_t size,
	char* dest,
	int dest_type) {
	return ccp_aes_cbc_crypt(
		key, key_type, key_size,
		iv, src, src_type, size,
		dest, dest_type, 1, is_encrypt: 0);
}

int fw_copy(char* srcdest, /* ... */) {
	int err;
	char enc_component_key[0x10];
	char component_key[0x10];
	// ...
	if (srcdest[0x18] == 1) {                               // PSP header 'is_encrypted'
		memcpy(&enc_encomponent_key, &srcdest[0x80], 0x10); // PSP header 'wrapped_key'
		err = component_key_decrypt(&enc_encomponent_key, &component_key);
		// ...
		err = fw_body_decrypt(
			key: &component_key,
			key_type: CCP_MEMTYPE_LOCAL,
			key_size: 0x10,
			iv: &srcdest[0x20],                             // PSP header 'iv'
			src: &srcdest[0x100],
			src_type: CCP_MEMTYPE_LOCAL,
			size: (uint32_t) (srcdest[0x14]),               // PSP header 'body_size'
			dest: &srcdest[0x100],
			dest_type: CCP_MEMTYPE_LOCAL
		);
	}
}

As expected, fw_copy() will first fetch and decrypt the Component Key, fetch other information from the PSP file header (such as the IV), and decrypt the file body via AES-128 CBC.

There is a good amount of other code relevant to firmware loading such as dealing with compression and verifying the firmware, but we'll skip that for this post at least.

While the CCP is a vital component of modern-day AMD systems, the interface to it isn't too complicated once you break it down. It seems the PSP uses basically the same request structures and IDs as what's exposed to the x86 kernel by the Secure OS, which is fair as there's no point in reinventing the wheel needlessly. I think the fact that it can support three different types of memory (PSP SRAM/local, local storage blocks, and system DRAM) is pretty cool, and you can get some interesting interactions between memory types. It also supports a lot of different types of operations which are fundamental to various encryption systems and key derivation.

Finally, the CCP holds some secrets of its own which are opaque even to the PSP itself, such as the Root Key. I'd be interested in how this locking mechanism is implemented exactly, however, that's getting into hardware territory that I frankly don't understand well. I am curious though on how much the concept of locking slots and keeping them exclusive to a subset of command queues is leveraged for defense-in-depth. There could be some very interesting research opportunities if a particular system doesn't lock these down well, such as leakage of keys and other sensitive data. This will require study of the Secure OS though... perhaps we'll go into this in a future blog post!


文章来源: https://dayzerosec.com/blog/2023/04/22/reversing-the-amd-secure-processor-psp-part-2-cryptographic-co-processor-ccp.html
如有侵权请联系:admin#unsafe.sh