Skip to content

Commit bcbaa0f

Browse files
committed
xbox_eeprom: Create eeprom library
1 parent 97881fd commit bcbaa0f

9 files changed

Lines changed: 319 additions & 5 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,8 @@ FetchContent_MakeAvailable(llhttp)
103103
target_link_libraries(xemu-dashboard PUBLIC llhttp_static)
104104
105105
# Bring in crpyto functions to support SHA1 and RC4
106-
add_library(xbox_crypt STATIC
107-
lib/crypt/sha1.c
108-
lib/crypt/rc4.c
109-
)
110-
target_link_libraries(xemu-dashboard PUBLIC xbox_crypt)
106+
add_subdirectory(lib/xbox_eeprom)
107+
target_link_libraries(xemu-dashboard PUBLIC xbox-eeprom)
111108
112109
# Bring in JSON parser support
113110
add_library(json STATIC

lib/xbox_eeprom/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(xbox-eeprom LANGUAGES C)
3+
4+
# Define your library
5+
add_library(xbox-eeprom
6+
rc4.c
7+
sha1.c
8+
xbox_eeprom.c
9+
)
10+
11+
target_include_directories(xbox-eeprom PUBLIC
12+
${CMAKE_CURRENT_SOURCE_DIR}/include
13+
)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include <stdint.h>
2+
3+
enum xbox_eeprom_region_code
4+
{
5+
XBOX_EEPROM_XBOX_REGION_NA = 0x00000001,
6+
XBOX_EEPROM_XBOX_REGION_JP = 0x00000002,
7+
XBOX_EEPROM_XBOX_REGION_EU = 0x00000004,
8+
XBOX_EEPROM_XBOX_REGION_MANUFACTURING = 0x80000000
9+
};
10+
11+
enum xbox_eeprom_video_standard
12+
{
13+
XBOX_EEPROM_VIDEO_STANDARD_INVALID = 0x00000000,
14+
XBOX_EEPROM_VIDEO_STANDARD_NTSC_M = 0x00400100,
15+
XBOX_EEPROM_VIDEO_STANDARD_NTSC_J = 0x00400200,
16+
XBOX_EEPROM_VIDEO_STANDARD_PAL = 0x00800300,
17+
XBOX_EEPROM_VIDEO_STANDARD_PAL_M = 0x00400400
18+
};
19+
20+
enum xbox_eeprom_language_id
21+
{
22+
XBOX_EEPROM_LANGUAGE_ID_INVALID = 0x00000000,
23+
XBOX_EEPROM_LANGUAGE_ID_ENGLISH = 0x00000001,
24+
XBOX_EEPROM_LANGUAGE_ID_JAPANESE = 0x00000002,
25+
XBOX_EEPROM_LANGUAGE_ID_GERMAN = 0x00000003,
26+
XBOX_EEPROM_LANGUAGE_ID_FRENCH = 0x00000004,
27+
XBOX_EEPROM_LANGUAGE_ID_SPANISH = 0x00000005,
28+
XBOX_EEPROM_LANGUAGE_ID_ITALIAN = 0x00000006,
29+
XBOX_EEPROM_LANGUAGE_ID_KOREAN = 0x00000007,
30+
XBOX_EEPROM_LANGUAGE_ID_CHINESE = 0x00000008,
31+
XBOX_EEPROM_LANGUAGE_ID_PORTUGUESE = 0x00000009
32+
};
33+
34+
enum xbox_eeprom_video_settings_mask
35+
{
36+
XBOX_EEPROM_VIDEO_SETTINGS_480P = 0x00080000,
37+
XBOX_EEPROM_VIDEO_SETTINGS_720P = 0x00020000,
38+
XBOX_EEPROM_VIDEO_SETTINGS_1080I = 0x00040000,
39+
XBOX_EEPROM_VIDEO_SETTINGS_WIDESCREEN = 0x00010000,
40+
XBOX_EEPROM_VIDEO_SETTINGS_LETTERBOX = 0x00100000,
41+
XBOX_EEPROM_VIDEO_SETTINGS_60HZ = 0x00400000,
42+
XBOX_EEPROM_VIDEO_SETTINGS_50HZ = 0x00800000
43+
};
44+
45+
enum xbox_eeprom_audio_settings_mask
46+
{
47+
XBOX_EEPROM_AUDIO_SETTINGS_STEREO = 0x00000000,
48+
XBOX_EEPROM_AUDIO_SETTINGS_MONO = 0x00000001,
49+
XBOX_EEPROM_AUDIO_SETTINGS_SURROUND = 0x00000002,
50+
XBOX_EEPROM_AUDIO_SETTINGS_ENABLE_AC3 = 0x00010000,
51+
XBOX_EEPROM_AUDIO_SETTINGS_ENABLE_DTS = 0x00020000
52+
};
53+
54+
typedef struct __attribute__((packed))
55+
{
56+
uint8_t sha1_hash[20]; // 0x00 - 0x13
57+
uint8_t confounder[8]; // 0x14 - 0x1B
58+
uint8_t hdd_key[16]; // 0x1C - 0x2B
59+
uint32_t xbox_region; // 0x2C - 0x2F
60+
} xbox_eeprom_encrypted_t;
61+
static_assert(sizeof(xbox_eeprom_encrypted_t) == (0x2F - 0x00 + 1), "xbox_eeprom_encrypted_t size mismatch");
62+
63+
typedef struct __attribute__((packed))
64+
{
65+
uint32_t checksum; // 0x30 - 0x33
66+
uint8_t serial_number[12]; // 0x34 - 0x3F
67+
uint8_t mac_address[6]; // 0x40 - 0x45
68+
uint8_t padding1[2]; // 0x46 - 0x47
69+
uint8_t online_key[16]; // 0x48 - 0x57
70+
uint32_t video_standard; // 0x58 - 0x5B
71+
uint8_t padding2[4]; // 0x5C - 0x5F
72+
} xbox_eeprom_factory_t;
73+
static_assert(sizeof(xbox_eeprom_encrypted_t) == (0x5F - 0x30 + 1), "xbox_eeprom_encrypted_t size mismatch");
74+
75+
typedef struct __attribute__((packed))
76+
{
77+
uint32_t checksum; // 0x60 - 0x63
78+
int32_t timezone_bias; // 0x64 - 0x67
79+
char timezone_std_name[4]; // 0x68 - 0x6B
80+
char timezone_dlt_name[4]; // 0x6C - 0x6F
81+
uint8_t padding1[8]; // 0x70 - 0x77
82+
uint32_t timezone_std_start; // 0x78 - 0x7B
83+
uint32_t timezone_dlt_start; // 0x7C - 0x7F
84+
uint8_t timezone_padding2[8]; // 0x80 - 0x87
85+
int32_t timezone_std_bias; // 0x88 - 0x8B
86+
int32_t timezone_dlt_bias; // 0x8C - 0x8F
87+
uint32_t language; // 0x90 - 0x93
88+
uint32_t video_settings; // 0x94 - 0x97
89+
uint32_t audio_settings; // 0x98 - 0x9B
90+
uint32_t parental_control_games; // 0x9C - 0x9F
91+
uint32_t parental_control_passcode; // 0xA0 - 0xA3
92+
uint32_t parental_control_movies; // 0xA4 - 0xA7
93+
uint32_t xlive_ip_address; // 0xA8 - 0xAB
94+
uint32_t xlive_dns_address; // 0xAC - 0xAF
95+
uint32_t xlive_default_gateway; // 0xB0 - 0xB3
96+
uint32_t xlive_subnet_mask; // 0xB4 - 0xB7
97+
uint32_t misc_flags; // 0xB8 - 0xBB
98+
uint32_t dvd_region; // 0xBC - 0xBF
99+
} xbox_eeprom_user_t;
100+
static_assert(sizeof(xbox_eeprom_user_t) == (0xBF - 0x60 + 1), "xbox_eeprom_user_t size mismatch");
101+
102+
#define XBOX_EEPROM_ENCRYPTED_START_PTR(a) ((void *)&((a)->confounder[0]))
103+
#define XBOX_EEPROM_ENCRYPTED_LENGTH (0x2F - 0x14 + 1)
104+
#define XBOX_EEPROM_FACTORY_START_PTR(a) ((void *)&((a)->serial_number[0]))
105+
#define XBOX_EEPROM_FACTORY_LENGTH (0x5F - 0x34 + 1)
106+
#define XBOX_EEPROM_USER_START_PTR(a) ((void *)&((a)->timezone_bias))
107+
#define XBOX_EEPROM_USER_LENGTH (0xBF - 0x60 + 1)
108+
109+
// now the whole eeprom structure
110+
typedef struct __attribute__((packed))
111+
{
112+
xbox_eeprom_encrypted_t encrypted; // 0x00 - 0x2F
113+
xbox_eeprom_factory_t factory; // 0x30 - 0x5F
114+
xbox_eeprom_user_t user; // 0x60 - 0xBF
115+
uint8_t padding[0x100 - 0xC0]; // 0xC0 - 0x0FF
116+
} xbox_eeprom_t;
117+
static_assert(sizeof(xbox_eeprom_t) == 0x100, "xbox_eeprom_t size mismatch");
118+
119+
int xbox_eeprom_decrypt(const xbox_eeprom_t *encrypted_eeprom, xbox_eeprom_t *decrypted_eeprom);
120+
int xbox_eeprom_encrypt(uint8_t xbox_revision, const xbox_eeprom_t *decrypted_eeprom, xbox_eeprom_t *encrypted_eeprom);
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

lib/xbox_eeprom/xbox_eeprom.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
// This is almost entirely based on https://github.com/Ernegien/XboxEepromEditor
3+
4+
#include "xbox_eeprom.h"
5+
#include "rc4.h"
6+
#include "sha1.h"
7+
8+
#include <stdbool.h>
9+
#include <stdint.h>
10+
#include <string.h>
11+
12+
// https://github.com/xemu-project/xemu/blob/9d5cf0926aa6f8eb2221e63a2e92bd86b02afae0/hw/xbox/eeprom_generation.c#L25
13+
static uint32_t xbox_eeprom_crc(const void *data, uint32_t len)
14+
{
15+
uint32_t high = 0;
16+
uint32_t low = 0;
17+
for (uint32_t i = 0; i < len / 4; i++) {
18+
uint32_t val = ((uint32_t *)data)[i];
19+
uint64_t sum = ((uint64_t)high << 32) | low;
20+
21+
high = (sum + val) >> 32;
22+
low += val;
23+
}
24+
return ~(high + low);
25+
}
26+
27+
static int do_eeprom_sha1_loop(const uint8_t hardware_revision, const void *data, size_t data_length, uint8_t sha1_output[20])
28+
{
29+
SHA1Context sha1_context;
30+
uint8_t sha1_buffer[20];
31+
32+
// Let's do the encryption stages
33+
const uint32_t sha1_intermediate_debug_first[] = {0x85F9E51A, 0xE04613D2, 0x6D86A50C, 0x77C32E3C, 0x4BD717A4};
34+
const uint32_t sha1_intermediate_debug_second[] = {0x5D7A9C6B, 0xE1922BEB, 0xB82CCDBC, 0x3137AB34, 0x486B52B3};
35+
36+
// 1.0
37+
const uint32_t sha1_intermedia_retail1_first[] = {0x72127625, 0x336472B9, 0xBE609BEA, 0xF55E226B, 0x99958DAC};
38+
const uint32_t sha1_intermedia_retail1_second[] = {0x76441D41, 0x4DE82659, 0x2E8EF85E, 0xB256FACA, 0xC4FE2DE8};
39+
40+
// 1.1 to 1.5
41+
const uint32_t sha1_intermedia_retail2_first[] = {0x39B06E79, 0xC9BD25E8, 0xDBC6B498, 0x40B4389D, 0x86BBD7ED};
42+
const uint32_t sha1_intermedia_retail2_second[] = {0x9B49BED3, 0x84B430FC, 0x6B8749CD, 0xEBFE5FE5, 0xD96E7393};
43+
44+
// 1.6
45+
const uint32_t sha1_intermedia_retail3_first[] = {0x8058763A, 0xF97D4E0E, 0x865A9762, 0x8A3D920D, 0x08995B2C};
46+
const uint32_t sha1_intermedia_retail3_second[] = {0x01075307, 0xA2f1E037, 0x1186EEEA, 0x88DA9992, 0x168A5609};
47+
48+
// Determine which SHA1 intermediate values to use based on hardware revision
49+
const uint32_t *sha1_h_a;
50+
const uint32_t *sha1_h_b;
51+
if (hardware_revision == 0x09) {
52+
sha1_h_a = sha1_intermediate_debug_first;
53+
sha1_h_b = sha1_intermediate_debug_second;
54+
} else if (hardware_revision == 0x0A) {
55+
sha1_h_a = sha1_intermedia_retail1_first;
56+
sha1_h_b = sha1_intermedia_retail1_second;
57+
} else if (hardware_revision == 0x0B) {
58+
sha1_h_a = sha1_intermedia_retail2_first;
59+
sha1_h_b = sha1_intermedia_retail2_second;
60+
} else if (hardware_revision == 0x0C) {
61+
sha1_h_a = sha1_intermedia_retail3_first;
62+
sha1_h_b = sha1_intermedia_retail3_second;
63+
} else {
64+
return -1;
65+
}
66+
67+
// Reset the SHA1 context with the first intermediate values
68+
sha1_fill(&sha1_context, sha1_h_a[0], sha1_h_a[1], sha1_h_a[2], sha1_h_a[3], sha1_h_a[4]);
69+
sha1_context.msg_blk_index = 0;
70+
sha1_context.computed = false;
71+
sha1_context.length = 512;
72+
73+
sha1_input(&sha1_context, (uint8_t *)data, data_length);
74+
sha1_result(&sha1_context, sha1_buffer);
75+
76+
sha1_fill(&sha1_context, sha1_h_b[0], sha1_h_b[1], sha1_h_b[2], sha1_h_b[3], sha1_h_b[4]);
77+
sha1_context.msg_blk_index = 0;
78+
sha1_context.computed = false;
79+
sha1_context.length = 512;
80+
81+
sha1_input(&sha1_context, (uint8_t *)sha1_buffer, sizeof(sha1_buffer));
82+
sha1_result(&sha1_context, sha1_buffer);
83+
84+
memcpy(sha1_output, sha1_buffer, 20);
85+
return 0;
86+
}
87+
88+
int xbox_eeprom_decrypt(const xbox_eeprom_t *encrypted_eeprom, xbox_eeprom_t *decrypted_eeprom)
89+
{
90+
RC4Context rc4_context;
91+
SHA1Context sha1_context;
92+
93+
if (encrypted_eeprom == NULL || decrypted_eeprom == NULL) {
94+
return -1; // Invalid arguments
95+
}
96+
97+
// Copy the encrypted EEPROM to the decrypted EEPROM
98+
memcpy(decrypted_eeprom, encrypted_eeprom, sizeof(xbox_eeprom_t));
99+
100+
// We don't know what revision the EEPROM is yet, so we have to try all of them until one works.
101+
const uint8_t hardware_revision[] = {0x09, 0x0A, 0x0B, 0x0C};
102+
103+
for (int i = 0; i < sizeof(hardware_revision) / sizeof(hardware_revision[0]); i++) {
104+
105+
// Determine the decryption key
106+
uint8_t decryption_key[20];
107+
do_eeprom_sha1_loop(hardware_revision[i], &encrypted_eeprom->encrypted.sha1_hash,
108+
sizeof(encrypted_eeprom->encrypted.sha1_hash), decryption_key);
109+
110+
// Initialise a RC4 content using the decryption key
111+
rc4_init(&rc4_context, decryption_key, sizeof(decryption_key));
112+
113+
// Decrypt the encrypted EEPROM section which does not include the SHA1 hash
114+
xbox_eeprom_encrypted_t decrypted_result;
115+
116+
// First copy the encrypted data to the decrypted array
117+
memcpy(&decrypted_result, &encrypted_eeprom->encrypted, sizeof(decrypted_result));
118+
119+
// Then decrypt the data using RC4. The decrypted data will be written back to the same memory location.
120+
rc4_crypt(&rc4_context, XBOX_EEPROM_ENCRYPTED_START_PTR(&decrypted_result), XBOX_EEPROM_ENCRYPTED_LENGTH);
121+
122+
// To verify it's valid, do a SHA1 over the decrypted data and verify it matches the SHA1 hash in the EEPROM.
123+
uint8_t sha1_test[20];
124+
do_eeprom_sha1_loop(hardware_revision[i], XBOX_EEPROM_ENCRYPTED_START_PTR(&decrypted_result), XBOX_EEPROM_ENCRYPTED_LENGTH, sha1_test);
125+
if (memcmp(sha1_test, decrypted_result.sha1_hash, sizeof(decrypted_result.sha1_hash)) == 0) {
126+
// Okay we have a match, copy the decrypted result back to the decrypted EEPROM
127+
memcpy(&decrypted_eeprom->encrypted, &decrypted_result, sizeof(decrypted_eeprom->encrypted));
128+
return hardware_revision[i];
129+
} else {
130+
// Try the next hardware revision
131+
continue;
132+
}
133+
}
134+
135+
return -1;
136+
}
137+
138+
int xbox_eeprom_encrypt(uint8_t xbox_revision, const xbox_eeprom_t *decrypted_eeprom, xbox_eeprom_t *encrypted_eeprom)
139+
{
140+
if (decrypted_eeprom == NULL || encrypted_eeprom == NULL) {
141+
return -1;
142+
}
143+
144+
if (xbox_revision < 0x09 || xbox_revision > 0x0C) {
145+
return -1;
146+
}
147+
148+
// Copy the decrypted EEPROM to the encrypted EEPROM
149+
memcpy(encrypted_eeprom, decrypted_eeprom, sizeof(xbox_eeprom_t));
150+
151+
// Write out the factory and user section checksums
152+
encrypted_eeprom->factory.checksum = xbox_eeprom_crc(XBOX_EEPROM_FACTORY_START_PTR(&encrypted_eeprom->factory),
153+
XBOX_EEPROM_FACTORY_LENGTH);
154+
encrypted_eeprom->user.checksum = xbox_eeprom_crc(XBOX_EEPROM_USER_START_PTR(&encrypted_eeprom->user),
155+
XBOX_EEPROM_USER_LENGTH);
156+
157+
// SHA1 Stage 1 - perform the first hash calculation over the currently unencrypted area (confounder, hdd key, region)
158+
// to determine the main sha1 hash
159+
uint8_t sha1_hash[20];
160+
do_eeprom_sha1_loop(xbox_revision, XBOX_EEPROM_ENCRYPTED_START_PTR(&encrypted_eeprom->encrypted), XBOX_EEPROM_ENCRYPTED_LENGTH, sha1_hash);
161+
162+
// SHA1 Stage 2 - perform the second hash calculation over the main sha1 hash to determine the RC4 key
163+
uint8_t rc4_key[20];
164+
do_eeprom_sha1_loop(xbox_revision, sha1_hash, sizeof(sha1_hash), rc4_key);
165+
166+
// Stage 3 - RC4 using the the new key to encrypt the data
167+
RC4Context rc4_context;
168+
xbox_eeprom_encrypted_t encrypted_result;
169+
rc4_init(&rc4_context, rc4_key, sizeof(rc4_key));
170+
// First copy the decrypted data to the encrypted array
171+
memcpy(&encrypted_result, &decrypted_eeprom->encrypted, sizeof(encrypted_result));
172+
173+
// Encrypt the data using RC4. The encrypted data will be written back to the same memory location
174+
rc4_crypt(&rc4_context, XBOX_EEPROM_ENCRYPTED_START_PTR(&encrypted_result), XBOX_EEPROM_ENCRYPTED_LENGTH);
175+
176+
// Stage 4 - Write it out
177+
// Copy over the sha1 hash
178+
memcpy(encrypted_result.sha1_hash, sha1_hash, sizeof(encrypted_result.sha1_hash));
179+
180+
// Write the encypted result back to the encrypted EEPROM
181+
memcpy(&encrypted_eeprom->encrypted, &encrypted_result, sizeof(encrypted_result));
182+
183+
return 0;
184+
}

0 commit comments

Comments
 (0)