BD Drive Reverse Engineering: Difference between revisions

From PS3 Developer wiki
Jump to navigation Jump to search
mNo edit summary
m (Getting your Console's BD-Drive Name via ATAPI command in GameOS)
Line 1,112: Line 1,112:


TODO
TODO
=Inquiry GameOS=
* Here is my code for a simple program to send the ATAPI command 0x12 Inquiry in order to read the drive's name.
* It was tested via Game OS on 4.21
* This is NO full source, but it is enough to copy&paste into your own code and modify for getting it to work.
==Program==
<pre>
struct lv2_atapi_cmnd_block {
    uint8_t pkt[0x20]; /* packet command block          */
    uint32_t pktlen; 
    uint32_t blocks;
    uint32_t block_size;
    uint32_t proto; /* transfer mode                  */
    uint32_t in_out; /* transfer direction            */
    uint32_t unknown;
} __attribute__((packed));
int ps3rom_lv2_get_inquiry(int fd, uint8_t *buffer) {
    int res;
    struct lv2_atapi_cmnd_block atapi_cmnd;
    init_atapi_cmnd_block(&atapi_cmnd, 0x3C, 1, 1);
    atapi_cmnd.pkt[0] = 0x12;
    atapi_cmnd.pkt[1] = 0;
    atapi_cmnd.pkt[2] = 0;
    atapi_cmnd.pkt[3] = 0;
    atapi_cmnd.pkt[4] = 0x3C;
    res = sys_storage_send_atapi_command(fd, &atapi_cmnd, buffer);
    return res;
}
int sys_storage_send_atapi_command(uint32_t fd, struct lv2_atapi_cmnd_block *atapi_cmnd, uint8_t *buffer)
{
    uint64_t tag;
    system_call_7(0x25C, fd, 1, (uint32_t) atapi_cmnd , sizeof (struct lv2_atapi_cmnd_block), (uint32_t) buffer, atapi_cmnd->block_size, (uint32_t) & tag);
    return_to_user_prog(int);
}
void main ()
{
// this poke allows us to use storage open on 4.21
lv2_poke(0x8000000000017B2CULL,0x386000014e800020ULL );
print("lv2 poked...\n");
int fd;
int ret;
uint8_t buf[0x38];
memset(buf,0,0x38);
// open Blu Ray Drive
ret = sys_storage_open(0x101000000000006ULL,&fd);
if(ret != 0)
{
printf("sys_storage_open failed (0x%x)...\n", ret);
return;
}
// inquiry command
ret = ps3rom_lv2_get_inquiry(fd,buf);
if(ret != 0)
{
printf("sys_storage_send_device_command failed (0x%x)...\n", ret);
return;
}
// close device
sys_storage_close(fd);
// dump result to file
FILE * pFile;
pFile = fopen ( "/dev_hdd0/game/myfile.bin" , "wb" );
fwrite (buf , 1 , sizeof(buf) , pFile );
fclose (pFile);
print("file written...\n");
}
</pre>
== Result ==
Here is a dump taken from a CECHJ04:
<pre>
00000000 05 80 00 33 9B 00 00 00 53 4F 4E 59 20 20 20 20 ...3....SONY   
00000010 50 53 2D 53 59 53 54 45 4D 20 20 20 33 30 32 52 PS-SYSTEM  302R
00000020 34 31 35 34 20 20 20 20 20 20 20 20 20 20 20 20 4154           
00000030 20 20 20 20 20 20 20 00                                .
</pre>
== structure ==
{|class="wikitable"
|-
! Address !! Size !! Value !! Description !! Observations
|-
| 0x0 || 0x8 ||  || scsi information  || [[http://en.wikipedia.org/wiki/SCSI_Inquiry_Command SCSI Inquiry Command]]
|-
| 0x8 || 0x8 || "Sony    " || Vendor identification ||
|-
| 0x10 || 0x20 || "PS-SYSTEM  302R" || Product identification ||
|-
| 0x20 || 0x4 || "4154" || Product revision level ||
|}

Revision as of 22:22, 11 February 2013

Introduction

  • The following information was reverse engineered from Storage Manager which runs in LPAR1 and from sv_iso_spu_module.self.

Information about EID4

  • EID4 contains 2 128bit keys which are necessary to establish a secure communication channel to BD drive for sending vendor specific security commands.
  • EID4 is encrypted with AES-CBC-256 algorithm.
  • EID4 is of size 0x30 bytes: 0x0-0xf bytes = 1st key, 0x10-0x1f - 2nd key, 0x20-0x2f - CMAC-OMAC1 of EID4
  • The first key is used for encrypting data sent from host to BD drive.
  • The second key is used for decrypting data sent from BD drive to host.

Dumping EID4 IV and Key

  • I modified sv_iso_spu_module.self to dump EID4 IV and key.
  • I used my spuisofs Linux kernel module and the below SPU program to dump EID4 IV key on PS3 Linux.
  • After dumping EID4 key use CMAC-OMAC1 algorithm to check the CMAC of EID4. If the EID4 key you got is correct then the CMAC should match.
  • dump_eid4_key.tar.gz: http://www.multiupload.nl/6AM6B6P05B

SPU Program

My program to dump EID4 AES-CBC-256 IV and key to PPU memory:

/*
 * Dump EID4 IV and key to EA with MFC
 *
 * Copyright (C) 2012 glevand <[email protected]>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published
 * by the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

.text

start:

	ila		$2, 0x3dfa0
	lr		$sp, $2

	ila		$80, 0x3e000
	lr		$81, $3

	stqd		$7, 0($80)
	stqd		$8, 0x10($80)	# store EID4 IV
	stqd		$9, 0x20($80)	# store upper 16bytes of EID4 key
	stqd		$10, 0x30($80)	# store lower 16bytes of EID4 key
	stqd		$11, 0x40($80)
	stqd		$12, 0x50($80)

	lr		$3, $80
	lr		$4, $81
	il		$5, 0x60
	il		$6, 0x7
	il		$7, 0x20
	brsl		$lr, 0x10	# mfc_dma_xfer

	il		$3, 0x7
	brsl		$lr, 0x28	# mfc_dma_wait

	stop		0x666		# our evil stop code :)

/*
 * r3 - LSA
 * r4 - EA
 * r5 - size
 * r6 - tag
 * r7 - cmd
 */
mfc_dma_xfer:

	wrch		$ch16, $3
	wrch		$ch17, $4
	shlqbyi		$4, $4, 4
	wrch		$ch18, $4
	wrch		$ch19, $5
	wrch		$ch20, $6
	wrch		$ch21, $7

	bi		$lr

/*
 * r3 - tag
 */
mfc_dma_wait:

	il		$2, 0
	nop		$127
	hbra		2f, 1f
	wrch		$ch23, $2

1:

	rchcnt		$2, $ch23
	ceqi		$2, $2, 1
	nop		$127
	nop		$127
	nop		$127
	nop		$127
	nop		$127

2:

	brz		$2, 1b
	hbr		3f, $lr
	rdch		$2, $ch24
	il		$2, 1
	shl		$2, $2, $3
	wrch		$ch22, $2
	il		$2, 2
	wrch		$ch23, $2
	rdch		$2, $ch24
	nop		$127

3:

	bi		$lr

Result

  • Test run with spuisofs.
[glevand@arch dump_eid4_key]$ ./dump_eid4_key ../dump_eid4_key.self ../eid4
spuisofs found at /mnt
arg1 kernel virtual address d000000000722000
shadow: spe_execution_status 7
priv2: puint_mb_R 2
shadow: spe_execution_status b
problem: spu_status_R 6660082
[glevand@arch dump_eid4_key]$ hexdump -C /mnt/arg1
...
Here should be your EID4 IV and key
IV is at offset 0x10 (16 bytes)
Key is at offset 0x20 (32 bytes)
...

Establish Secure Communication Channel

  • With both keys from EID4 we are now able to establish a secure communication channel with BD drive and send vendor-specific ATAPI commands to it.
  • Keys from EID4 are used only to derive a session key. After that they are not used anymore. A similar procedure is used e.g. to establish a secure communication channel with ENCDEC device.
  • ATAPI commands SEND_KEY and REPORT_KEY are used to exchange random number between host and BD drive.
  • Exchanged random numbers are used to derive the session key which is used later to send vendor-specific ATAPI commands (0xE0 and 0xE1) to BD drive.
  • The same procedue is follwed e.g. by Storage Manager which runs in LPAR1.
  • 3DES-CBC with 2 keys is used to encrypt commands sent to BD drive.

Session Key

TODO

Get Version

  • Here is an example of a simple program which establishes a secure communication channel with BD drive and reads BD FW version by using vendor-specific ATAPI commands 0xE0 and 0xE1.
  • It was tested on PS3 OtherOS++ 3.55 and Linux kernel 3.5.1.
  • The program uses Linux SCSI Generic driver to send ATAPI commands to BD drive.

Program

/*-
 * Copyright (C) 2012 glevand <[email protected]>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification, immediately at the beginning of the file.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include <scsi/sg.h>
#include <scsi/scsi_ioctl.h>

#include <openssl/aes.h>
#include <openssl/des.h>

static const unsigned char iv1[AES_BLOCK_SIZE] = {
	0x22, 0x26, 0x92, 0x8d, 0x44, 0x03, 0x2f, 0x43, 0x6a, 0xfd, 0x26, 0x7e, 0x74, 0x8b, 0x23, 0x93,
};

static const unsigned char iv2[DES_KEY_SZ] = {
	0xe8, 0x0b, 0x3f, 0x0c, 0xd6, 0x56, 0x6d, 0xd0,
};

static const unsigned char iv3[AES_BLOCK_SIZE] = {
	0x3b, 0xd6, 0x24, 0x02, 0x0b, 0xd3, 0xf8, 0x65, 0xe8, 0x0b, 0x3f, 0x0c, 0xd6, 0x56, 0x6d, 0xd0,
};

static const unsigned char key3[AES_BLOCK_SIZE] = {
	0x12, 0x6c, 0x6b, 0x59, 0x45, 0x37, 0x0e, 0xee, 0xca, 0x68, 0x26, 0x2d, 0x02, 0xdd, 0x12, 0xd2,
};

static const unsigned char key4[AES_BLOCK_SIZE] = {
	0xd9, 0xa2, 0x0a, 0x79, 0x66, 0x6c, 0x27, 0xd1, 0x10, 0x32, 0xac, 0xcf, 0x0d, 0x7f, 0xb5, 0x01,
};

static const unsigned char key5[AES_BLOCK_SIZE] = {
	0x19, 0x76, 0x6f, 0xbc, 0x77, 0xe4, 0xe7, 0x5c, 0xf4, 0x41, 0xe4, 0x8b, 0x94, 0x2c, 0x5b, 0xd9,
};

static const unsigned char key6[AES_BLOCK_SIZE] = {
	0x50, 0xcb, 0xa7, 0xf0, 0xc2, 0xa7, 0xc0, 0xf6, 0xf3, 0x3a, 0x21, 0x43, 0x26, 0xac, 0x4e, 0xf3,
};

static const unsigned char cmd_4_14[] = {
	0x1e, 0x79, 0x18, 0x8e, 0x09, 0x3b, 0xc8, 0x77, 0x95, 0xb2, 0xcf, 0x2a, 0xe7, 0xaf, 0x9b, 0xb4,
	0x86, 0x80, 0x18, 0x28, 0xc2, 0xca, 0x05, 0xba, 0xd1, 0xf2, 0x78, 0xf1, 0x80, 0x1f, 0xea, 0xcb,
	0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x8d, 0xb3, 0x46, 0x93, 0x42, 0x64, 0x81, 0x60, 0x16, 0x8f, 0x51, 0xd1, 0x93, 0x76, 0x23, 0x95,
};

static struct option long_opts[] = {
	{ "device",	no_argument, NULL, 'd' },
	{ "verbose",	no_argument, NULL, 'v' },
	{ "rnd1",	no_argument, NULL, 'r' },
	{ "key1",	no_argument, NULL, 'k' },
	{ "key2",	no_argument, NULL, 'l' },
	{ NULL, 0, NULL, 0 }
};

static const char *device = "/dev/sr0";
static int verbose;
static unsigned char rnd1[AES_BLOCK_SIZE] = {
	0x06, 0xd7, 0x33, 0xcb, 0x22, 0x4a, 0x83, 0x56, 0xa3, 0xe8, 0x39, 0x78, 0x66, 0xe4, 0x3e, 0xc2,
};
static unsigned char key1[AES_BLOCK_SIZE];
static int key1_length;
static unsigned char key2[AES_BLOCK_SIZE];
static int key2_length;

static int
parse_hex(const char *s, unsigned char *b, int maxlen)
{
	int len;
	char c;
	int i;

	len = strlen(s);
	if (len % 2)
		return (-1);

	for (i = 0; (i < (len / 2)) && (i < maxlen); i++) {
		if (!isxdigit(s[2 * i + 0]) || !isxdigit(s[2 * i + 1]))
			return (-1);

		b[i] = 0;

		c = tolower(s[2 * i + 0]);
		if (isdigit(c))
			b[i] += (c - '0') * 16;
		else
			b[i] += (c - 'a' + 10) * 16;

		c = tolower(s[2 * i + 1]);
		if (isdigit(c))
			b[i] += c - '0';
		else
			b[i] += c - 'a' + 10;
	}

	return (i);
}

static int
parse_opts(int argc, char **argv)
{
	int c;
	char *endptr;

	while ((c = getopt_long(argc, argv, "d:vtr:s:k:l:", long_opts, NULL)) != -1) {
		switch (c) {
		case 'd':
			device = optarg;
		break;
		case 'v':
			verbose++;
		break;
		case 'r':
			if (parse_hex(optarg, rnd1, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
				fprintf(stderr, "invalid rnd1 specified: %s\n", optarg);
				return (-1);
			}
		break;
		case 'k':
			key1_length = parse_hex(optarg, key1, 2 * AES_BLOCK_SIZE);
			if (key1_length != AES_BLOCK_SIZE) {
				fprintf(stderr, "invalid key1 specified: %s\n", optarg);
				return (-1);
			}
		break;
		case 'l':
			key2_length = parse_hex(optarg, key2, 2 * AES_BLOCK_SIZE);
			if (key2_length != AES_BLOCK_SIZE) {
				fprintf(stderr, "invalid key2 specified: %s\n", optarg);
				return (-1);
			}
		break;
		default:
			fprintf(stderr, "invalid option specified: %c\n", c);
			return (-1);
		break;
		}
	}

	if (key1_length <= 0) {
		fprintf(stderr, "no key1 specified\n");
		return (-1);
	}

	if (key2_length <= 0) {
		fprintf(stderr, "no key2 specified\n");
		return (-1);
	}

	return (0);
}

static void
aes_cbc_encrypt(const unsigned char *iv, const unsigned char *key, int key_length,
	const unsigned char *data, int data_length, unsigned char *out)
{
	AES_KEY aes_key;
	unsigned char cbc[AES_BLOCK_SIZE];
	int i;

	AES_set_encrypt_key(key, key_length, &aes_key);

	memcpy(cbc, iv, AES_BLOCK_SIZE);

	while (data_length >= AES_BLOCK_SIZE) {
		for (i = 0; i < AES_BLOCK_SIZE; i++)
			out[i] = cbc[i] ^ data[i];

		AES_encrypt(out, out, &aes_key);

		memcpy(cbc, out, AES_BLOCK_SIZE);

		data += AES_BLOCK_SIZE;
		out += AES_BLOCK_SIZE;
		data_length -= AES_BLOCK_SIZE;
	}
}

static void
aes_cbc_decrypt(const unsigned char *iv, const unsigned char *key, int key_length,
	const unsigned char *data, int data_length, unsigned char *out)
{
	AES_KEY aes_key;
	unsigned char cbc[AES_BLOCK_SIZE];
	unsigned char buf[AES_BLOCK_SIZE];
	int i;

	AES_set_decrypt_key(key, key_length, &aes_key);

	memcpy(cbc, iv, AES_BLOCK_SIZE);

	while (data_length >= AES_BLOCK_SIZE) {
		memcpy(buf, data, AES_BLOCK_SIZE);

		AES_decrypt(data, out, &aes_key);

		for (i = 0; i < AES_BLOCK_SIZE; i++)
			out[i] ^= cbc[i];

		memcpy(cbc, buf, AES_BLOCK_SIZE);

		data += AES_BLOCK_SIZE;
		out += AES_BLOCK_SIZE;
		data_length -= AES_BLOCK_SIZE;
	}
}

static void
triple_des_cbc_encrypt(const unsigned char *iv, const unsigned char *key,
	const unsigned char *data, int data_length, unsigned char *out)
{
	DES_cblock kcb1;
	DES_cblock kcb2;
	DES_cblock ivcb;
	DES_key_schedule ks1;
	DES_key_schedule ks2;

	memcpy(kcb1, key, DES_KEY_SZ);
	memcpy(kcb2, key + DES_KEY_SZ, DES_KEY_SZ);
	memcpy(ivcb, iv, DES_KEY_SZ);

	DES_set_key_unchecked(&kcb1, &ks1);
	DES_set_key_unchecked(&kcb2, &ks2);

	DES_ede2_cbc_encrypt(data, out, data_length, &ks1, &ks2, &ivcb, DES_ENCRYPT);
}

static void
triple_des_cbc_decrypt(const unsigned char *iv, const unsigned char *key,
	const unsigned char *data, int data_length, unsigned char *out)
{
	DES_cblock kcb1;
	DES_cblock kcb2;
	DES_cblock ivcb;
	DES_key_schedule ks1;
	DES_key_schedule ks2;

	memcpy(kcb1, key, DES_KEY_SZ);
	memcpy(kcb2, key + DES_KEY_SZ, DES_KEY_SZ);
	memcpy(ivcb, iv, DES_KEY_SZ);

	DES_set_key_unchecked(&kcb1, &ks1);
	DES_set_key_unchecked(&kcb2, &ks2);

	DES_ede2_cbc_encrypt(data, out, data_length, &ks1, &ks2, &ivcb, DES_DECRYPT);
}

static unsigned char
cksum(const unsigned char *data, int len)
{
	unsigned short sum = 0;

	while (len--)
		sum += *data++;

	return (~sum);
}

static void
hex_fprintf(FILE *fp, const char *buf, size_t len)
{
	int i;

	if (len <= 0)
		return;

	for (i = 0; i < len; i++) {
		if ((i > 0) && !(i % 16))
			fprintf(fp, "\n");

		fprintf(fp, "%02x ", buf[i]);
	}

	fprintf(fp, "\n");
}

static int
test_unit_ready(const char *device, unsigned int *req_sense)
{
	int fd;
	struct sg_io_hdr io_hdr;
	char cmd[256], sense[32];
	int ret;

	fd = open(device, O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return (-1);

	memset(cmd, 0, 6);

	memset(&io_hdr, 0, sizeof(io_hdr));
	io_hdr.interface_id = 'S';
	io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
	io_hdr.timeout = 20000;
	io_hdr.cmdp = cmd;
	io_hdr.cmd_len = 6;
	io_hdr.dxferp = NULL;
	io_hdr.dxfer_len = 0;
	io_hdr.sbp = sense;
	io_hdr.mx_sb_len = sizeof(sense);

	ret = ioctl(fd, SG_IO, &io_hdr);
	if (ret) {
		close(fd);
		return (-1);
	}

	close(fd);

	if (io_hdr.status) {
		fprintf(stderr, "TEST UNIT READY failed: status %d host status %d driver status %d\n",
		    io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
		fprintf(stderr, "sense buffer: ");
		hex_fprintf(stderr, sense, 0x10);
		*req_sense = (sense[2] << 16) | (sense[12] << 8) | sense[13];
		return (-1);
	}

	*req_sense = 0;

	return (0);
}

static int
send_key(const char *device, unsigned char key_class, unsigned short param_list_length,
	unsigned char agid, unsigned char key_fmt, unsigned char *param_list,
	unsigned int *req_sense)
{
	int fd;
	struct sg_io_hdr io_hdr;
	char cmd[256], sense[32];
	int ret;

	fd = open(device, O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return (-1);

	cmd[0] = 0xa3;
	cmd[1] = 0x00;
	cmd[2] = 0x00;
	cmd[3] = 0x00;
	cmd[4] = 0x00;
	cmd[5] = 0x00;
	cmd[6] = 0x00;
	cmd[7] = key_class;
	*(uint16_t *) (cmd + 8) = param_list_length;
	cmd[10] = ((agid & 0x3) << 6) | (key_fmt & 0x3f);
	cmd[11] = 0x00;

	if (verbose) {
		fprintf(stdout, "cdb: ");
		hex_fprintf(stdout, cmd, 12);
	}

	memset(&io_hdr, 0, sizeof(io_hdr));
	io_hdr.interface_id = 'S';
	io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
	io_hdr.timeout = 20000;
	io_hdr.cmdp = cmd;
	io_hdr.cmd_len = 12;
	io_hdr.dxferp = param_list;
	io_hdr.dxfer_len = param_list_length;
	io_hdr.sbp = sense;
	io_hdr.mx_sb_len = sizeof(sense);

	ret = ioctl(fd, SG_IO, &io_hdr);
	if (ret) {
		close(fd);
		return (-1);
	}

	close(fd);

	if (io_hdr.status) {
		fprintf(stderr, "SEND KEY failed: status %d host status %d driver status %d\n",
		    io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
		fprintf(stderr, "sense buffer: ");
		hex_fprintf(stderr, sense, 0x10);
		*req_sense = (sense[2] << 16) | (sense[12] << 8) | sense[13];
		return (-1);
	}

	*req_sense = 0;

	return (0);
}

static int
report_key(const char *device, unsigned char key_class, unsigned int alloc_length,
	unsigned char agid, unsigned char key_fmt, unsigned char *buf,
	unsigned int *req_sense)
{
	int fd;
	struct sg_io_hdr io_hdr;
	char cmd[256], sense[32];
	int ret;

	fd = open(device, O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return (-1);

	cmd[0] = 0xa4;
	cmd[1] = 0x00;
	cmd[2] = 0x00;
	cmd[3] = 0x00;
	cmd[4] = 0x00;
	cmd[5] = 0x00;
	cmd[6] = 0x00;
	cmd[7] = key_class;
	*(uint16_t *) (cmd + 8) = alloc_length;
	cmd[10] = ((agid & 0x3) << 6) | (key_fmt & 0x3f);
	cmd[11] = 0x00;

	if (verbose) {
		fprintf(stdout, "cdb: ");
		hex_fprintf(stdout, cmd, 12);
	}

	memset(&io_hdr, 0, sizeof(io_hdr));
	io_hdr.interface_id = 'S';
	io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
	io_hdr.timeout = 20000;
	io_hdr.cmdp = cmd;
	io_hdr.cmd_len = 12;
	io_hdr.dxferp = buf;
	io_hdr.dxfer_len = alloc_length;
	io_hdr.sbp = sense;
	io_hdr.mx_sb_len = sizeof(sense);

	ret = ioctl(fd, SG_IO, &io_hdr);
	if (ret) {
		close(fd);
		return (-1);
	}

	close(fd);

	if (io_hdr.status) {
		fprintf(stderr, "REPORT KEY failed: status %d host status %d driver status %d\n",
		    io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
		fprintf(stderr, "sense buffer: ");
		hex_fprintf(stderr, sense, 0x10);
		*req_sense = (sense[2] << 16) | (sense[12] << 8) | sense[13];
		return (-1);
	}

	*req_sense = 0;

	return (0);
}

static int
send_e1(const char *device, unsigned char param_list_length, const unsigned char cdb[8],
	unsigned char *param_list, unsigned int *req_sense)
{
	int fd;
	struct sg_io_hdr io_hdr;
	char cmd[256], sense[32];
	int ret;

	fd = open(device, O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return (-1);

	memset(cmd, 0, 12);
	cmd[0] = 0xe1;
	cmd[1] = 0x00;
	cmd[2] = param_list_length;
	cmd[3] = 0x00;
	memcpy(cmd + 4, cdb, 8);

	if (verbose) {
		fprintf(stdout, "cdb: ");
		hex_fprintf(stdout, cmd, 12);
	}

	memset(&io_hdr, 0, sizeof(io_hdr));
	io_hdr.interface_id = 'S';
	io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
	io_hdr.timeout = 20000;
	io_hdr.cmdp = cmd;
	io_hdr.cmd_len = 12;
	io_hdr.dxferp = param_list;
	io_hdr.dxfer_len = param_list_length;
	io_hdr.sbp = sense;
	io_hdr.mx_sb_len = sizeof(sense);

	ret = ioctl(fd, SG_IO, &io_hdr);
	if (ret) {
		close(fd);
		return (-1);
	}

	close(fd);

	if (io_hdr.status) {
		fprintf(stderr, "E1 failed: status %d host status %d driver status %d\n",
		    io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
		fprintf(stderr, "sense buffer: ");
		hex_fprintf(stderr, sense, 0x10);
		*req_sense = (sense[2] << 16) | (sense[12] << 8) | sense[13];
		return (-1);
	}

	*req_sense = 0;

	return (0);
}

static int
send_e0(const char *device, unsigned int alloc_length, const unsigned char cdb[8],
	unsigned char *buf, unsigned int *req_sense)
{
	int fd;
	struct sg_io_hdr io_hdr;
	char cmd[256], sense[32];
	int ret;

	fd = open(device, O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return (-1);

	cmd[0] = 0xe0;
	cmd[1] = 0x00;
	cmd[2] = alloc_length;
	cmd[3] = 0x00;
	memcpy(cmd + 4, cdb, 8);

	if (verbose) {
		fprintf(stdout, "cdb: ");
		hex_fprintf(stdout, cmd, 12);
	}

	memset(&io_hdr, 0, sizeof(io_hdr));
	io_hdr.interface_id = 'S';
	io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
	io_hdr.timeout = 20000;
	io_hdr.cmdp = cmd;
	io_hdr.cmd_len = 12;
	io_hdr.dxferp = buf;
	io_hdr.dxfer_len = alloc_length;
	io_hdr.sbp = sense;
	io_hdr.mx_sb_len = sizeof(sense);

	ret = ioctl(fd, SG_IO, &io_hdr);
	if (ret) {
		close(fd);
		return (-1);
	}

	close(fd);

	if (io_hdr.status) {
		fprintf(stderr, "E0 failed: status %d host status %d driver status %d\n",
		    io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
		fprintf(stderr, "sense buffer: ");
		hex_fprintf(stderr, sense, 0x10);
		*req_sense = (sense[2] << 16) | (sense[12] << 8) | sense[13];
		return (-1);
	}

	*req_sense = 0;

	return (0);
}

int
main(int argc, char **argv)
{
	unsigned char cdb[256];
	unsigned char buf[256];
	unsigned char rnd2[AES_BLOCK_SIZE];
	unsigned char key7[AES_BLOCK_SIZE];
	unsigned char key8[AES_BLOCK_SIZE];
	unsigned int req_sense;
	int ret;

	ret = parse_opts(argc, argv);
	if (ret)
		exit(255);

	/* test unit ready */

	if (verbose)
		fprintf(stdout, "=== TEST UNIT READY (0x00) ===\n");

	ret = test_unit_ready(device, &req_sense);
	if (ret && (req_sense != 0x23a00))
		exit(1);

	/* security check */

	if (verbose)
		fprintf(stdout, "=== SEND KEY (0xA3) ===\n");

	memset(buf, 0, 0x14);

	ret = send_key(device, 0xe0, 0x14, 0x0, 0x0, buf, &req_sense);
	if (ret)
		exit(1);

	/* establish session */

	memset(buf, 0, 0x14);
	*(uint16_t *) buf = 0x10;
	memcpy(buf + 4, rnd1, sizeof(rnd1));

	fprintf(stdout, "rnd1: ");
	hex_fprintf(stdout, rnd1, sizeof(rnd1));

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	aes_cbc_encrypt(iv1, key1, 8 * sizeof(key1), buf + 4, 0x10, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	/* send encrypted host random */

	if (verbose)
		fprintf(stdout, "=== SEND KEY (0xA3) ===\n");

	ret = send_key(device, 0xe0, 0x14, 0x0, 0x0, buf, &req_sense);
	if (ret)
		exit(1);

	/* receive encrypted host and drive randoms */

	if (verbose)
		fprintf(stdout, "=== REPORT KEY (0xA4) ===\n");

	ret = report_key(device, 0xe0, 0x24, 0x0, 0x0, buf, &req_sense);
	if (ret)
		exit(1);

	if (verbose)
		hex_fprintf(stdout, buf, 0x24);

	/* NB: decrypt received host and drive randoms separately */

	aes_cbc_decrypt(iv1, key2, 8 * sizeof(key2), buf + 4, 0x10, buf + 4);
	aes_cbc_decrypt(iv1, key2, 8 * sizeof(key2), buf + 0x14, 0x10, buf + 0x14);

	if (verbose)
		hex_fprintf(stdout, buf, 0x24);

	/* verify received host random */

	if (memcmp(rnd1, buf + 4, sizeof(rnd1))) {
		fprintf(stderr, "rnd1 mismatch\n");
		exit(1);
	}

	memcpy(rnd2, buf + 0x14, sizeof(rnd2));

	fprintf(stdout, "rnd2: ");
	hex_fprintf(stdout, rnd2, sizeof(rnd2));

	memset(buf, 0, 0x14);
	*(uint16_t *) buf = 0x10;
	memcpy(buf + 4, rnd2, sizeof(rnd2));

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	aes_cbc_encrypt(iv1, key1, 8 * sizeof(key1), buf + 4, 0x10, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	/* send encrypted drive random */

	if (verbose)
		fprintf(stdout, "=== SEND KEY (0xA3) ===\n");

	ret = send_key(device, 0xe0, 0x14, 0x0, 0x2, buf, &req_sense);
	if (ret)
		exit(1);

	/* derive session keys from host and drive randoms */

	memcpy(key7, rnd1, 8);
	memcpy(key7 + 8, rnd2 + 8, 8);

	if (verbose)
		hex_fprintf(stdout, key7, sizeof(key7));

	aes_cbc_encrypt(iv1, key3, 8 * sizeof(key3), key7, sizeof(key7), key7);

	fprintf(stdout, "key7: ");
	hex_fprintf(stdout, key7, sizeof(key7));

	memcpy(key8, rnd1 + 8, 8);
	memcpy(key8 + 8, rnd2, 8);

	if (verbose)
		hex_fprintf(stdout, key8, sizeof(key8));

	aes_cbc_encrypt(iv1, key4, 8 * sizeof(key4), key8, sizeof(key8), key8);

	fprintf(stdout, "key8: ");
	hex_fprintf(stdout, key8, sizeof(key8));

	if (verbose)
		fprintf(stdout, "=== UNKNOWN (0xE1) ===\n");

	memset(cdb, 0, 8);
	cdb[6] = 0xe6;					/* random byte */
	cdb[7] = cksum(cdb, 7);

	if (verbose)
		hex_fprintf(stdout, cdb, 8);

	triple_des_cbc_encrypt(iv2, key7, cdb, 8, cdb);

	if (verbose)
		hex_fprintf(stdout, cdb, 8);

	memset(buf, 0, 0x54);
	*(uint16_t *) buf = 0x50;
	buf[5] = 0xee;					/* random byte */
	memcpy(buf + 8, cmd_4_14, sizeof(cmd_4_14));
	buf[4] = cksum(buf + 5, 0x4f);

	if (verbose)
		hex_fprintf(stdout, buf, 0x54);

	aes_cbc_encrypt(iv3, key7, 8 * sizeof(key7), buf + 4, 0x50, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x54);

	ret = send_e1(device, 0x54, cdb, buf, &req_sense);
	if (ret)
		exit(1);

	/* establish session again */

	memset(buf, 0, 0x14);
	*(uint16_t *) buf = 0x10;
	memcpy(buf + 4, rnd1, sizeof(rnd1));

	fprintf(stdout, "rnd1: ");
	hex_fprintf(stdout, rnd1, sizeof(rnd1));

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	aes_cbc_encrypt(iv1, key5, 8 * sizeof(key5), buf + 4, 0x10, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	/* send encrypted host random */

	if (verbose)
		fprintf(stdout, "=== SEND KEY (0xA3) ===\n");

	ret = send_key(device, 0xe0, 0x14, 0x0, 0x1, buf, &req_sense);
	if (ret)
		exit(1);

	/* receive encrypted host and drive randoms */

	if (verbose)
		fprintf(stdout, "=== REPORT KEY (0xA4) ===\n");

	ret = report_key(device, 0xe0, 0x24, 0x0, 0x1, buf, &req_sense);
	if (ret)
		exit(1);

	if (verbose)
		hex_fprintf(stdout, buf, 0x24);

	/* NB: decrypt received host and drive randoms separately */

	aes_cbc_decrypt(iv1, key6, 8 * sizeof(key6), buf + 4, 0x10, buf + 4);
	aes_cbc_decrypt(iv1, key6, 8 * sizeof(key6), buf + 0x14, 0x10, buf + 0x14);

	if (verbose)
		hex_fprintf(stdout, buf, 0x24);

	/* verify received host random */

	if (memcmp(rnd1, buf + 4, sizeof(rnd1))) {
		fprintf(stderr, "rnd1 mismatch\n");
		exit(1);
	}

	memcpy(rnd2, buf + 0x14, sizeof(rnd2));

	fprintf(stdout, "rnd2: ");
	hex_fprintf(stdout, rnd2, sizeof(rnd2));

	memset(buf, 0, 0x14);
	*(uint16_t *) buf = 0x10;
	memcpy(buf + 4, rnd2, sizeof(rnd2));

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	aes_cbc_encrypt(iv1, key5, 8 * sizeof(key5), buf + 4, 0x10, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x14);

	/* send encrypted drive random */

	if (verbose)
		fprintf(stdout, "=== SEND KEY (0xA3) ===\n");

	ret = send_key(device, 0xe0, 0x14, 0x0, 0x3, buf, &req_sense);
	if (ret)
		exit(1);

	/* derive session keys from host and drive randoms */

	memcpy(key7, rnd1, 8);
	memcpy(key7 + 8, rnd2 + 8, 8);

	if (verbose)
		hex_fprintf(stdout, key7, sizeof(key7));

	aes_cbc_encrypt(iv1, key3, 8 * sizeof(key3), key7, sizeof(key7), key7);

	fprintf(stdout, "key7: ");
	hex_fprintf(stdout, key7, sizeof(key7));

	memcpy(key8, rnd1 + 8, 8);
	memcpy(key8 + 8, rnd2, 8);

	if (verbose)
		hex_fprintf(stdout, key8, sizeof(key8));

	aes_cbc_encrypt(iv1, key4, 8 * sizeof(key4), key8, sizeof(key8), key8);

	fprintf(stdout, "key8: ");
	hex_fprintf(stdout, key8, sizeof(key8));

	/* retrieve response */

	if (verbose)
		fprintf(stdout, "=== UNKNOWN (0xE0) ===\n");

	memset(cdb, 0, 8);
	cdb[0] = 0x4;					/* random byte */
	cdb[6] = 0xe7;					/* random byte */
	cdb[7] = cksum(cdb, 7);

	if (verbose)
		hex_fprintf(stdout, cdb, 8);

	triple_des_cbc_encrypt(iv2, key7, cdb, 8, cdb);

	if (verbose)
		hex_fprintf(stdout, cdb, 8);

	ret = send_e0(device, 0x54, cdb, buf, &req_sense);
	if (ret)
		exit(1);

	if (verbose)
		hex_fprintf(stdout, buf, 0x54);

	aes_cbc_decrypt(iv3, key7, 8 * sizeof(key7), buf + 4, 0x50, buf + 4);

	if (verbose)
		hex_fprintf(stdout, buf, 0x54);

	if (buf[4] != cksum(buf + 5, 0x4f)) {
		fprintf(stderr, "checksum mismatch\n");
		exit(1);
	}

	fprintf(stdout, "version: ");
	hex_fprintf(stdout, buf + 6, 8);

	exit(0);
}

Result

  • Test run on Linux 3.5.1
glevand@debian:~$ sudo ./bd_get_version -k <EID4 key1> -l <EID4 key2>
TEST UNIT READY failed: status 2 host status 0 driver status 8
sense buffer: 70 00 02 00 00 00 00 0a 00 00 00 00 3a 00 00 00 
rnd1: 06 d7 33 cb 22 4a 83 56 a3 e8 39 78 66 e4 3e c2 
rnd2: e3 20 f2 41 21 c4 0b 1a ca 1d 43 bf 8b 6a 5d 58 
key7: dd ec 54 ea 6d 34 78 04 6c 97 f0 3b ee f1 31 cd 
key8: a6 b4 6c 56 05 e7 72 e8 b1 8b b0 8a 5b 67 ba 1b 
rnd1: 06 d7 33 cb 22 4a 83 56 a3 e8 39 78 66 e4 3e c2 
rnd2: 0a 97 a0 62 c3 93 21 1b c0 bc f2 02 e9 af df 76 
key7: 72 1a 5d a2 80 d9 e0 f8 66 8e ec 86 7e ef ab c2 
key8: 9c e9 d8 f0 ee c4 89 fc 0d 62 f3 37 eb c8 8b bd 
version: 00 03 00 50 00 00 00 00 

Clean Key

TODO

Set Key

TODO

Inquiry GameOS

  • Here is my code for a simple program to send the ATAPI command 0x12 Inquiry in order to read the drive's name.
  • It was tested via Game OS on 4.21
  • This is NO full source, but it is enough to copy&paste into your own code and modify for getting it to work.

Program

struct lv2_atapi_cmnd_block {
    uint8_t pkt[0x20]; /* packet command block           */ 
    uint32_t pktlen;  
    uint32_t blocks;					
    uint32_t block_size;				
    uint32_t proto; /* transfer mode                  */ 
    uint32_t in_out; /* transfer direction             */ 
    uint32_t unknown;
} __attribute__((packed));





int ps3rom_lv2_get_inquiry(int fd, uint8_t *buffer) {
    int res;
    struct lv2_atapi_cmnd_block atapi_cmnd;

    init_atapi_cmnd_block(&atapi_cmnd, 0x3C, 1, 1);
    atapi_cmnd.pkt[0] = 0x12;
    atapi_cmnd.pkt[1] = 0;
    atapi_cmnd.pkt[2] = 0;
    atapi_cmnd.pkt[3] = 0;
    atapi_cmnd.pkt[4] = 0x3C;

    res = sys_storage_send_atapi_command(fd, &atapi_cmnd, buffer);
    return res;
}



int sys_storage_send_atapi_command(uint32_t fd, struct lv2_atapi_cmnd_block *atapi_cmnd, uint8_t *buffer) 
{
    uint64_t tag;
    system_call_7(0x25C, fd, 1, (uint32_t) atapi_cmnd , sizeof (struct lv2_atapi_cmnd_block), (uint32_t) buffer, atapi_cmnd->block_size, (uint32_t) & tag);
    return_to_user_prog(int);
}



void main ()
{
	// this poke allows us to use storage open on 4.21
	lv2_poke(0x8000000000017B2CULL,0x386000014e800020ULL );
	print("lv2 poked...\n");

	int fd;
	int ret;
	uint8_t buf[0x38];
	memset(buf,0,0x38);


	// open Blu Ray Drive
	ret = sys_storage_open(0x101000000000006ULL,&fd);
	if(ret != 0)
	{
		printf("sys_storage_open failed (0x%x)...\n", ret);
		return;
	}

	// inquiry command
	ret = ps3rom_lv2_get_inquiry(fd,buf);
	if(ret != 0)
	{
		printf("sys_storage_send_device_command failed (0x%x)...\n", ret);
		return;
	}

	// close device
	sys_storage_close(fd);


	// dump result to file
	FILE * pFile;
	pFile = fopen ( "/dev_hdd0/game/myfile.bin" , "wb" );
	fwrite (buf , 1 , sizeof(buf) , pFile );
	fclose (pFile);
	print("file written...\n");
}

Result

Here is a dump taken from a CECHJ04:

00000000 05 80 00 33 9B 00 00 00 53 4F 4E 59 20 20 20 20 ...3....SONY    
00000010 50 53 2D 53 59 53 54 45 4D 20 20 20 33 30 32 52 PS-SYSTEM   302R
00000020 34 31 35 34 20 20 20 20 20 20 20 20 20 20 20 20 4154            
00000030 20 20 20 20 20 20 20 00                                .

structure

Address Size Value Description Observations
0x0 0x8 scsi information [SCSI Inquiry Command]
0x8 0x8 "Sony " Vendor identification
0x10 0x20 "PS-SYSTEM 302R" Product identification
0x20 0x4 "4154" Product revision level