diff options
Diffstat (limited to 'utils/nwztools/upgtools')
-rw-r--r-- | utils/nwztools/upgtools/fwp.h | 3 | ||||
-rw-r--r-- | utils/nwztools/upgtools/keysig_search.c | 108 | ||||
-rw-r--r-- | utils/nwztools/upgtools/keysig_search.h | 4 | ||||
-rw-r--r-- | utils/nwztools/upgtools/misc.c | 1 | ||||
-rw-r--r-- | utils/nwztools/upgtools/misc.h | 2 | ||||
-rw-r--r-- | utils/nwztools/upgtools/upgtool.c | 423 |
6 files changed, 267 insertions, 274 deletions
diff --git a/utils/nwztools/upgtools/fwp.h b/utils/nwztools/upgtools/fwp.h index 7b527d47ba..0d928fbec1 100644 --- a/utils/nwztools/upgtools/fwp.h +++ b/utils/nwztools/upgtools/fwp.h | |||
@@ -28,8 +28,9 @@ extern "C" { | |||
28 | #endif | 28 | #endif |
29 | 29 | ||
30 | #define NWZ_KAS_SIZE 32 | 30 | #define NWZ_KAS_SIZE 32 |
31 | #define NWZ_KEYSIG_SIZE 51 | 31 | #define NWZ_KEYSIG_SIZE 16 |
32 | #define NWZ_KEY_SIZE 8 | 32 | #define NWZ_KEY_SIZE 8 |
33 | #define NWZ_SIG_SIZE 8 | ||
33 | #define NWZ_EXPKEY_SIZE (NWZ_KEY_SIZE * NWZ_KEY_SIZE) | 34 | #define NWZ_EXPKEY_SIZE (NWZ_KEY_SIZE * NWZ_KEY_SIZE) |
34 | #define NWZ_DES_BLOCK 8 | 35 | #define NWZ_DES_BLOCK 8 |
35 | 36 | ||
diff --git a/utils/nwztools/upgtools/keysig_search.c b/utils/nwztools/upgtools/keysig_search.c index 6054ea43ba..7652efa233 100644 --- a/utils/nwztools/upgtools/keysig_search.c +++ b/utils/nwztools/upgtools/keysig_search.c | |||
@@ -22,8 +22,24 @@ | |||
22 | #include "misc.h" | 22 | #include "misc.h" |
23 | #include "mg.h" | 23 | #include "mg.h" |
24 | #include <string.h> | 24 | #include <string.h> |
25 | #include <stdio.h> | ||
25 | 26 | ||
26 | #define HEX_MAJ | 27 | /* Key search methods |
28 | * | ||
29 | * This code tries to find the key and signature of a device using an upgrade | ||
30 | * file. It more or less relies on brute force and makes the following assumptions. | ||
31 | * It assumes the key and the signature are hexadecimal strings (it appears to be | ||
32 | * true thus far). The code lists all possible keys and decrypts the first | ||
33 | * 8 bytes of the file. If the decrypted signature happens to be an hex string, | ||
34 | * the code reports the key and signature as potentially valid. Note that some | ||
35 | * key/sig pairs may not be valid but since the likelyhood of decrypting a | ||
36 | * random 8-byte sequence using an hex string key and to produce an hex string | ||
37 | * is very small, there should be almost no false positive. | ||
38 | * | ||
39 | * Since the key is supposedly random, the code starts by looking at "balanced" | ||
40 | * keys: keys with slightly more digits (0-9) than letters (a-f) and then moving | ||
41 | * towards very unbalanced strings (only digits or only letters). | ||
42 | */ | ||
27 | 43 | ||
28 | static uint8_t g_cipher[8]; | 44 | static uint8_t g_cipher[8]; |
29 | static keysig_notify_fn_t g_notify; | 45 | static keysig_notify_fn_t g_notify; |
@@ -31,11 +47,10 @@ static uint8_t g_key[8]; | |||
31 | static void *g_user; | 47 | static void *g_user; |
32 | static bool is_hex[256]; | 48 | static bool is_hex[256]; |
33 | static bool is_init = false; | 49 | static bool is_init = false; |
34 | #ifdef HEX_MAJ | 50 | static uint64_t g_tot_count; |
35 | static char hex_digits[] = "02468ABEF"; | 51 | static uint64_t g_cur_count; |
36 | #else | 52 | static int g_last_percent; |
37 | static char hex_digits[] = "02468abef"; | 53 | static int g_last_subpercent; |
38 | #endif | ||
39 | 54 | ||
40 | static void keysig_search_init() | 55 | static void keysig_search_init() |
41 | { | 56 | { |
@@ -44,11 +59,7 @@ static void keysig_search_init() | |||
44 | memset(is_hex, 0, sizeof(is_hex)); | 59 | memset(is_hex, 0, sizeof(is_hex)); |
45 | for(int i = '0'; i <= '9'; i++) | 60 | for(int i = '0'; i <= '9'; i++) |
46 | is_hex[i] = true; | 61 | is_hex[i] = true; |
47 | #ifdef HEX_MAJ | ||
48 | for(int i = 'A'; i <= 'F'; i++) | ||
49 | #else | ||
50 | for(int i = 'a'; i <= 'f'; i++) | 62 | for(int i = 'a'; i <= 'f'; i++) |
51 | #endif | ||
52 | is_hex[i] = true; | 63 | is_hex[i] = true; |
53 | } | 64 | } |
54 | 65 | ||
@@ -63,6 +74,24 @@ static inline bool is_full_ascii(uint8_t *arr) | |||
63 | static inline bool check_stupid() | 74 | static inline bool check_stupid() |
64 | { | 75 | { |
65 | uint8_t res[8]; | 76 | uint8_t res[8]; |
77 | // display progress | ||
78 | g_cur_count++; | ||
79 | int percent = (g_cur_count * 100ULL) / g_tot_count; | ||
80 | int subpercent = ((g_cur_count * 1000ULL) / g_tot_count) % 10; | ||
81 | if(percent != g_last_percent) | ||
82 | { | ||
83 | cprintf(RED, "%d%%", percent); | ||
84 | fflush(stdout); | ||
85 | g_last_subpercent = 0; | ||
86 | } | ||
87 | if(subpercent != g_last_subpercent) | ||
88 | { | ||
89 | cprintf(WHITE, "."); | ||
90 | fflush(stdout); | ||
91 | } | ||
92 | g_last_percent = percent; | ||
93 | g_last_subpercent = subpercent; | ||
94 | |||
66 | mg_decrypt_fw(g_cipher, 8, res, g_key); | 95 | mg_decrypt_fw(g_cipher, 8, res, g_key); |
67 | if(is_full_ascii(res)) | 96 | if(is_full_ascii(res)) |
68 | return g_notify(g_user, g_key, res); | 97 | return g_notify(g_user, g_key, res); |
@@ -75,7 +104,7 @@ static bool search_stupid_rec(int rem_digit, int rem_letter, int pos) | |||
75 | return check_stupid(); | 104 | return check_stupid(); |
76 | if(rem_digit > 0) | 105 | if(rem_digit > 0) |
77 | { | 106 | { |
78 | for(int i = '0'; i <= '9'; i += 2) | 107 | for(int i = '0'; i <= '9'; i++) |
79 | { | 108 | { |
80 | g_key[pos] = i; | 109 | g_key[pos] = i; |
81 | if(search_stupid_rec(rem_digit - 1, rem_letter, pos + 1)) | 110 | if(search_stupid_rec(rem_digit - 1, rem_letter, pos + 1)) |
@@ -84,11 +113,7 @@ static bool search_stupid_rec(int rem_digit, int rem_letter, int pos) | |||
84 | } | 113 | } |
85 | if(rem_letter > 0) | 114 | if(rem_letter > 0) |
86 | { | 115 | { |
87 | #ifdef HEX_MAJ | 116 | for(int i = 'a' - 1; i <= 'f'; i++) |
88 | for(int i = 'a' - 1; i <= 'f'; i += 2) | ||
89 | #else | ||
90 | for(int i = 'A' - 1; i <= 'F'; i += 2) | ||
91 | #endif | ||
92 | { | 117 | { |
93 | g_key[pos] = i; | 118 | g_key[pos] = i; |
94 | if(search_stupid_rec(rem_digit, rem_letter - 1, pos + 1)) | 119 | if(search_stupid_rec(rem_digit, rem_letter - 1, pos + 1)) |
@@ -100,6 +125,12 @@ static bool search_stupid_rec(int rem_digit, int rem_letter, int pos) | |||
100 | 125 | ||
101 | static bool search_stupid(int rem_digit, int rem_letter) | 126 | static bool search_stupid(int rem_digit, int rem_letter) |
102 | { | 127 | { |
128 | cprintf(WHITE, "\n Looking for keys with "); | ||
129 | cprintf(RED, "%d", rem_digit); | ||
130 | cprintf(WHITE, " digits and "); | ||
131 | cprintf(RED, "%d", rem_letter); | ||
132 | cprintf(WHITE, " letters..."); | ||
133 | fflush(stdout); | ||
103 | return search_stupid_rec(rem_digit, rem_letter, 0); | 134 | return search_stupid_rec(rem_digit, rem_letter, 0); |
104 | } | 135 | } |
105 | 136 | ||
@@ -109,28 +140,35 @@ bool keysig_search_ascii_stupid(uint8_t *cipher, keysig_notify_fn_t notify, void | |||
109 | memcpy(g_cipher, cipher, 8); | 140 | memcpy(g_cipher, cipher, 8); |
110 | g_notify = notify; | 141 | g_notify = notify; |
111 | g_user = user; | 142 | g_user = user; |
112 | #if 1 | 143 | // compute number of possibilities |
113 | return search_stupid(4, 4) || | 144 | g_cur_count = 0; |
114 | search_stupid(3, 5) || search_stupid(5, 3) || | 145 | g_tot_count = 1; |
115 | search_stupid(2, 6) || search_stupid(6, 2) || | 146 | g_last_percent = -1; |
116 | search_stupid(1, 7) || search_stupid(7, 1) || | 147 | for(int i = 0; i < 8; i++) |
117 | search_stupid(0, 8) || search_stupid(8, 0); | 148 | g_tot_count *= 16ULL; |
118 | #else | 149 | cprintf(WHITE, " Search space:"); |
119 | #define do(i) for(int a##i = 0; a##i < sizeof(hex_digits); a##i++) { g_key[i] = hex_digits[a##i]; | 150 | cprintf(RED, " %llu", (unsigned long long)g_tot_count); |
120 | #define od() } | 151 | // sorted by probability: |
121 | 152 | bool ret = search_stupid(5, 3) // 5 digits, 3 letters: 0.281632 | |
122 | do(0)do(1)do(2)do(3)do(4)do(5)do(6)do(7) | 153 | || search_stupid(6, 2) // 6 digits, 2 letters: 0.234693 |
123 | if(check_stupid()) return true; | 154 | || search_stupid(4, 4) // 4 digits, 4 letters: 0.211224 |
124 | od()od()od()od()od()od()od()od() | 155 | || search_stupid(7, 1) // 7 digits, 1 letters: 0.111759 |
125 | #undef do | 156 | || search_stupid(3, 5) // 3 digits, 5 letters: 0.101388 |
126 | #undef od | 157 | || search_stupid(2, 6) // 2 digits, 6 letters: 0.030416 |
127 | return false; | 158 | || search_stupid(8, 0) // 8 digits, 0 letters: 0.023283 |
128 | #endif | 159 | || search_stupid(1, 7) // 1 digits, 7 letters: 0.005214 |
160 | || search_stupid(0, 8);// 0 digits, 8 letters: 0.000391 | ||
161 | cprintf(OFF, "\n"); | ||
162 | return ret; | ||
129 | } | 163 | } |
130 | 164 | ||
131 | bool keysig_search_ascii_brute(uint8_t *cipher, keysig_notify_fn_t notify, void *user) | 165 | bool keysig_search_ascii_brute(uint8_t *cipher, keysig_notify_fn_t notify, void *user) |
132 | { | 166 | { |
167 | (void) cipher; | ||
168 | (void) notify; | ||
169 | (void) user; | ||
133 | keysig_search_init(); | 170 | keysig_search_init(); |
171 | cprintf(RED, "Unimplemented\n"); | ||
134 | return false; | 172 | return false; |
135 | } | 173 | } |
136 | 174 | ||
@@ -144,9 +182,9 @@ struct keysig_search_desc_t keysig_search_desc[KEYSIG_SEARCH_LAST] = | |||
144 | }, | 182 | }, |
145 | [KEYSIG_SEARCH_ASCII_STUPID] = | 183 | [KEYSIG_SEARCH_ASCII_STUPID] = |
146 | { | 184 | { |
147 | .name = "ascii-stupid", | 185 | .name = "ascii-hex", |
148 | .fn = keysig_search_ascii_stupid, | 186 | .fn = keysig_search_ascii_stupid, |
149 | .comment = "Try to find a balance ascii key ignoring lsb" | 187 | .comment = "Try to find an hexadecimal ascii string keysig" |
150 | }, | 188 | }, |
151 | [KEYSIG_SEARCH_ASCII_BRUTE] = | 189 | [KEYSIG_SEARCH_ASCII_BRUTE] = |
152 | { | 190 | { |
diff --git a/utils/nwztools/upgtools/keysig_search.h b/utils/nwztools/upgtools/keysig_search.h index 46639dfb47..9009a73284 100644 --- a/utils/nwztools/upgtools/keysig_search.h +++ b/utils/nwztools/upgtools/keysig_search.h | |||
@@ -23,6 +23,7 @@ | |||
23 | 23 | ||
24 | #include <stdbool.h> | 24 | #include <stdbool.h> |
25 | #include <stdint.h> | 25 | #include <stdint.h> |
26 | #include "fwp.h" | ||
26 | 27 | ||
27 | enum keysig_search_method_t | 28 | enum keysig_search_method_t |
28 | { | 29 | { |
@@ -34,7 +35,8 @@ enum keysig_search_method_t | |||
34 | }; | 35 | }; |
35 | 36 | ||
36 | /* notify returns true if the key seems ok */ | 37 | /* notify returns true if the key seems ok */ |
37 | typedef bool (*keysig_notify_fn_t)(void *user, uint8_t key[8], uint8_t sig[8]); | 38 | typedef bool (*keysig_notify_fn_t)(void *user, uint8_t key[NWZ_KEY_SIZE], |
39 | uint8_t sig[NWZ_SIG_SIZE]); | ||
38 | /* returns true if a key was accepted by notify */ | 40 | /* returns true if a key was accepted by notify */ |
39 | typedef bool (*keysig_search_fn_t)(uint8_t *cipher, keysig_notify_fn_t notify, void *user); | 41 | typedef bool (*keysig_search_fn_t)(uint8_t *cipher, keysig_notify_fn_t notify, void *user); |
40 | 42 | ||
diff --git a/utils/nwztools/upgtools/misc.c b/utils/nwztools/upgtools/misc.c index 108235e7fd..00832cd585 100644 --- a/utils/nwztools/upgtools/misc.c +++ b/utils/nwztools/upgtools/misc.c | |||
@@ -31,6 +31,7 @@ char RED[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '1', 0x6d, '\0' }; | |||
31 | char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' }; | 31 | char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' }; |
32 | char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' }; | 32 | char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' }; |
33 | char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' }; | 33 | char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' }; |
34 | char WHITE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '7', 0x6d, '\0' }; | ||
34 | 35 | ||
35 | static bool g_color_enable = true; | 36 | static bool g_color_enable = true; |
36 | 37 | ||
diff --git a/utils/nwztools/upgtools/misc.h b/utils/nwztools/upgtools/misc.h index 96666a2eff..4e8294f1ee 100644 --- a/utils/nwztools/upgtools/misc.h +++ b/utils/nwztools/upgtools/misc.h | |||
@@ -34,7 +34,7 @@ | |||
34 | 34 | ||
35 | typedef char color_t[]; | 35 | typedef char color_t[]; |
36 | 36 | ||
37 | extern color_t OFF, GREY, RED, GREEN, YELLOW, BLUE; | 37 | extern color_t OFF, GREY, RED, GREEN, YELLOW, BLUE, WHITE; |
38 | void *xmalloc(size_t s); | 38 | void *xmalloc(size_t s); |
39 | void color(color_t c); | 39 | void color(color_t c); |
40 | void enable_color(bool enable); | 40 | void enable_color(bool enable); |
diff --git a/utils/nwztools/upgtools/upgtool.c b/utils/nwztools/upgtools/upgtool.c index fc5cf0fb92..065cede63c 100644 --- a/utils/nwztools/upgtools/upgtool.c +++ b/utils/nwztools/upgtools/upgtool.c | |||
@@ -69,9 +69,9 @@ struct nwz_model_t | |||
69 | { | 69 | { |
70 | const char *model; | 70 | const char *model; |
71 | unsigned flags; | 71 | unsigned flags; |
72 | char kas[NWZ_KAS_SIZE]; /* key and signature */ | 72 | char *kas; |
73 | char key[8]; | 73 | char *key; |
74 | char sig[8]; | 74 | char *sig; |
75 | }; | 75 | }; |
76 | 76 | ||
77 | struct upg_md5_t | 77 | struct upg_md5_t |
@@ -81,7 +81,7 @@ struct upg_md5_t | |||
81 | 81 | ||
82 | struct upg_header_t | 82 | struct upg_header_t |
83 | { | 83 | { |
84 | char sig[8]; | 84 | char sig[NWZ_SIG_SIZE]; |
85 | uint32_t nr_files; | 85 | uint32_t nr_files; |
86 | uint32_t pad; // make sure structure size is a multiple of 8 | 86 | uint32_t pad; // make sure structure size is a multiple of 8 |
87 | } __attribute__((packed)); | 87 | } __attribute__((packed)); |
@@ -92,6 +92,72 @@ struct upg_entry_t | |||
92 | uint32_t size; | 92 | uint32_t size; |
93 | } __attribute__((packed)); | 93 | } __attribute__((packed)); |
94 | 94 | ||
95 | /** KAS / Key / Signature | ||
96 | * | ||
97 | * Since this is all very confusing, we need some terminology and notations: | ||
98 | * - [X, Y, Z] is a sequence of bytes, for example: | ||
99 | * [8, 0x89, 42] | ||
100 | * is a sequence of three bytes. | ||
101 | * - "abcdef" is a string: it is a sequences of bytes where each byte happens to | ||
102 | * be the ASCII encoding of a letter. So for example: | ||
103 | * "abc" = [97, 98, 99] | ||
104 | * because 'a' has ASCII encoding 97 and so one | ||
105 | * - HexString(Seq) refers to the string where each byte of the original sequence | ||
106 | * is represented in hexadecimal by two ASCII characters. For example: | ||
107 | * HexString([8, 0x89, 42]) = "08892a" | ||
108 | * because 8 = 0x08 so it represented by "08" and 42 = 0x2a. Note that the length | ||
109 | * of HexString(Seq) is always exactly twice the length of Seq. | ||
110 | * - DES(Seq,Pass) is the result of encrypting Seq with Pass using the DES cipher. | ||
111 | * Seq must be a sequence of 8 bytes (known as a block) and Pass must be a | ||
112 | * sequence of 8 bytes. The result is also a 8-byte sequence. | ||
113 | * - ECB_DES([Block0, Block1, ..., BlockN], Pass) | ||
114 | * = [DES(Block0,Pass), DES(Block1,Pass), ..., DES(BlockN,Pass)] | ||
115 | * where Blocki is a block (8 byte). | ||
116 | * | ||
117 | * | ||
118 | * A firmware upgrade file is always encrypted using a Key. To authenticate it, | ||
119 | * the upgrade file (before encryption) contains a Sig(nature). The pair (Key,Sig) | ||
120 | * is refered to as KeySig and is specific to each series. For example all | ||
121 | * NWZ-E46x use the same KeySig but the NWZ-E46x and NWZ-A86x use different KeySig. | ||
122 | * In the details, a Key is a sequence of 8 bytes and a Sig is also a sequence | ||
123 | * of 8 bytes. A KeySig is a simply the concatenation of the Key followed by | ||
124 | * the Sig, so it is a sequence of 16 bytes. Probably in an attempt to obfuscate | ||
125 | * things a little further, Sony never provides the KeySig directly but instead | ||
126 | * encrypts it using DES in ECB mode using a hardcoded password and provides | ||
127 | * the hexadecimal string of the result, known as the KAS, which is thus a string | ||
128 | * of 32 ASCII characters. | ||
129 | * Note that since DES works on blocks of 8 bytes and ECB encrypts blocks | ||
130 | * independently, it is the same to encrypt the KeySig as once or encrypt the Key | ||
131 | * and Sig separately. | ||
132 | * | ||
133 | * To summarize: | ||
134 | * Key = [K0, K1, K2, ..., K7] (8 bytes) (model specific) | ||
135 | * Sig = [S0, S1, S2, ..., S7] (8 bytes) (model specific) | ||
136 | * KeySig = [Key, Sig] = [K0, ... K7, S0, ..., S7] (16 bytes) | ||
137 | * FwpPass = "ed295076" (8 bytes) (never changes) | ||
138 | * EncKeySig = ECB_DES(KeySig, FwpPass) = [DES(Key, FwpPass), DES(Sig, FwpPass)] | ||
139 | * KAS = HexString(EncKeySig) (32 characters) | ||
140 | * | ||
141 | * In theory, the Key and Sig can be any 8-byte sequence. In practice, they always | ||
142 | * are strings, probably to make it easier to write them down. In many cases, the | ||
143 | * Key and Sig are even the hexadecimal string of 4-byte sequences but it is | ||
144 | * unclear if this is the result of pure luck, confused engineers, lazyness on | ||
145 | * Sony's part or by design. The following code assumes that Key and Sig are | ||
146 | * strings (though it could easily be fixed to work with anything if this is | ||
147 | * really needed). | ||
148 | * | ||
149 | * | ||
150 | * Here is a real example, from the NWZ-E46x Series: | ||
151 | * Key = "6173819e" (note that this is a string and even a hex string in this case) | ||
152 | * Sig = "30b82e5c" | ||
153 | * KeySig = [Key, Sig] = "6173819e30b82e5c" | ||
154 | * FwpPass = "ed295076" (never changes) | ||
155 | * EncKeySig = ECB_DES(KeySig, FwpPass) | ||
156 | * = [0x8a, 0x01, 0xb6, ..., 0xc5] (16 bytes) | ||
157 | * KAS = HexString(EncKeySig) = "8a01b624bfbfde4a1662a1772220e3c5" | ||
158 | * | ||
159 | */ | ||
160 | |||
95 | struct nwz_model_t g_model_list[] = | 161 | struct nwz_model_t g_model_list[] = |
96 | { | 162 | { |
97 | { "nwz-e45x", HAS_KAS | HAS_KEY | HAS_SIG | CONFIRMED, "8a01b624bfbfde4a1662a1772220e3c5", "6173819e", "30b82e5c"}, | 163 | { "nwz-e45x", HAS_KAS | HAS_KEY | HAS_SIG | CONFIRMED, "8a01b624bfbfde4a1662a1772220e3c5", "6173819e", "30b82e5c"}, |
@@ -99,7 +165,7 @@ struct nwz_model_t g_model_list[] = | |||
99 | { "nwz-a86x", HAS_KAS | HAS_KEY | HAS_SIG | CONFIRMED, "a7c4af6c28b8900a783f307c1ba538c5", "c824e4e2", "7c262bb0" }, | 165 | { "nwz-a86x", HAS_KAS | HAS_KEY | HAS_SIG | CONFIRMED, "a7c4af6c28b8900a783f307c1ba538c5", "c824e4e2", "7c262bb0" }, |
100 | /* The following keys were obtained by brute forcing firmware upgrades, | 166 | /* The following keys were obtained by brute forcing firmware upgrades, |
101 | * someone with a device needs to confirm that they work */ | 167 | * someone with a device needs to confirm that they work */ |
102 | { "nw-a82x", HAS_KEY | HAS_SIG, {""}, "4df06482", "07fa0b6e" }, | 168 | { "nw-a82x", HAS_KEY | HAS_SIG, "", "4df06482", "07fa0b6e" }, |
103 | }; | 169 | }; |
104 | 170 | ||
105 | static int digit_value(char c) | 171 | static int digit_value(char c) |
@@ -115,15 +181,14 @@ static char hex_digit(unsigned v) | |||
115 | return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x'; | 181 | return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x'; |
116 | } | 182 | } |
117 | 183 | ||
118 | static int decrypt_keysig(char keysig[NWZ_KEYSIG_SIZE]) | 184 | static int decrypt_keysig(const char kas[NWZ_KAS_SIZE], char key[NWZ_KEY_SIZE], |
185 | char sig[NWZ_SIG_SIZE]) | ||
119 | { | 186 | { |
120 | uint8_t src[16]; | 187 | uint8_t src[NWZ_KAS_SIZE / 2]; |
121 | for(int i = 32; i < NWZ_KEYSIG_SIZE; i++) | 188 | for(int index = 0; index < NWZ_KAS_SIZE / 2; index++) |
122 | keysig[i] = 0; | ||
123 | for(int index = 0; index < 16; index++) | ||
124 | { | 189 | { |
125 | int a = digit_value(keysig[index * 2]); | 190 | int a = digit_value(kas[index * 2]); |
126 | int b = digit_value(keysig[index * 2 + 1]); | 191 | int b = digit_value(kas[index * 2 + 1]); |
127 | if(a < 0 || b < 0) | 192 | if(a < 0 || b < 0) |
128 | { | 193 | { |
129 | cprintf(GREY, "Invalid KAS !\n"); | 194 | cprintf(GREY, "Invalid KAS !\n"); |
@@ -133,166 +198,161 @@ static int decrypt_keysig(char keysig[NWZ_KEYSIG_SIZE]) | |||
133 | } | 198 | } |
134 | fwp_setkey("ed295076"); | 199 | fwp_setkey("ed295076"); |
135 | fwp_crypt(src, sizeof(src), 1); | 200 | fwp_crypt(src, sizeof(src), 1); |
136 | memcpy(keysig + 33, src, 8); | 201 | memcpy(key, src, NWZ_KEY_SIZE); |
137 | memcpy(keysig + 42, src + 8, 8); | 202 | memcpy(sig, src + NWZ_KEY_SIZE, NWZ_SIG_SIZE); |
138 | return 0; | 203 | return 0; |
139 | } | 204 | } |
140 | 205 | ||
141 | static bool upg_notify_keysig(void *user, uint8_t key[8], uint8_t sig[8]) | 206 | static void encrypt_keysig(char kas[NWZ_KEY_SIZE], |
207 | const char key[NWZ_SIG_SIZE], const char sig[NWZ_KAS_SIZE]) | ||
142 | { | 208 | { |
143 | memcpy(user + 33, key, 8); | 209 | uint8_t src[NWZ_KAS_SIZE / 2]; |
144 | memcpy(user + 42, sig, 8); | 210 | fwp_setkey("ed295076"); |
145 | return true; | 211 | memcpy(src, key, NWZ_KEY_SIZE); |
212 | memcpy(src + NWZ_KEY_SIZE, sig, NWZ_SIG_SIZE); | ||
213 | fwp_crypt(src, sizeof(src), 0); | ||
214 | for(int i = 0; i < NWZ_KAS_SIZE / 2; i++) | ||
215 | { | ||
216 | kas[2 * i] = hex_digit((src[i] >> 4) & 0xf); | ||
217 | kas[2 * i + 1] = hex_digit(src[i] & 0xf); | ||
218 | } | ||
146 | } | 219 | } |
147 | 220 | ||
148 | static int do_upg(void *buf, long size) | 221 | /* user needs to be pointer to a NWZ_KEYSIG_SIZE-byte buffer, on success g_key |
222 | * and g_sig are updated to point to the key and sig in the buffer */ | ||
223 | static bool upg_notify_keysig(void *user, uint8_t key[NWZ_KEY_SIZE], | ||
224 | uint8_t sig[NWZ_SIG_SIZE]) | ||
149 | { | 225 | { |
150 | struct upg_md5_t *md5 = buf; | 226 | g_key = user; |
151 | cprintf(BLUE, "Preliminary\n"); | 227 | g_sig = user + NWZ_KEY_SIZE; |
152 | cprintf(GREEN, " MD5: "); | 228 | memcpy(g_key, key, NWZ_KEY_SIZE); |
153 | for(int i = 0; i < 16; i++) | 229 | memcpy(g_sig, sig, NWZ_SIG_SIZE); |
154 | cprintf(YELLOW, "%02x", md5->md5[i]); | 230 | return true; |
155 | printf(" "); | 231 | } |
156 | |||
157 | uint8_t actual_md5[MD5_DIGEST_LENGTH]; | ||
158 | { | ||
159 | MD5_CTX c; | ||
160 | MD5_Init(&c); | ||
161 | MD5_Update(&c, md5 + 1, size - sizeof(struct upg_header_t)); | ||
162 | MD5_Final(actual_md5, &c); | ||
163 | } | ||
164 | check_field(memcmp(actual_md5, md5->md5, 16), 0, "Ok\n", "Mismatch\n"); | ||
165 | 232 | ||
166 | if(g_model_index == -1 && g_keysig_search == KEYSIG_SEARCH_NONE && g_key == NULL && g_kas == NULL) | 233 | static int get_key_and_sig(bool is_extract, void *encrypted_hdr) |
234 | { | ||
235 | static char keysig[NWZ_KEYSIG_SIZE]; | ||
236 | static char kas[NWZ_KAS_SIZE]; | ||
237 | /* database lookup */ | ||
238 | if(g_model_index != -1) | ||
167 | { | 239 | { |
168 | cprintf(GREY, "A KAS or a keysig is needed to decrypt the firmware\n"); | 240 | if(g_model_list[g_model_index].flags & HAS_KAS) |
169 | cprintf(GREY, "You have the following options(see help for more details):\n"); | 241 | g_kas = g_model_list[g_model_index].kas; |
170 | cprintf(GREY, "- select a model with a known KAS\n"); | 242 | if(g_model_list[g_model_index].flags & HAS_KEY) |
171 | cprintf(GREY, "- specify an explicit KAS or key(+optional sig)\n"); | 243 | g_key = g_model_list[g_model_index].key; |
172 | cprintf(GREY, "- let me try to find the keysig(slow !)\n"); | 244 | if(g_model_list[g_model_index].flags & HAS_SIG) |
173 | return 1; | 245 | g_sig = g_model_list[g_model_index].sig; |
174 | } | 246 | } |
175 | 247 | ||
176 | char kas[NWZ_KAS_SIZE]; | 248 | /* always prefer KAS because it contains everything */ |
177 | char keysig[NWZ_KEYSIG_SIZE]; | ||
178 | |||
179 | memset(kas, '?', NWZ_KAS_SIZE); | ||
180 | memset(keysig, '?', NWZ_KEYSIG_SIZE); | ||
181 | keysig[32] = keysig[41] = keysig[50] = 0; | ||
182 | |||
183 | if(g_kas) | 249 | if(g_kas) |
184 | { | 250 | { |
185 | if(strlen(g_kas) != NWZ_KAS_SIZE) | 251 | if(strlen(g_kas) != NWZ_KAS_SIZE) |
186 | { | 252 | { |
187 | cprintf(GREY, "The specified KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE); | 253 | cprintf(GREY, "The KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE); |
188 | return 4; | 254 | return 4; |
189 | } | 255 | } |
190 | memcpy(keysig, g_kas, NWZ_KAS_SIZE); | 256 | g_key = keysig; |
191 | decrypt_keysig(keysig); | 257 | g_sig = keysig + NWZ_KEY_SIZE; |
192 | g_kas = keysig; | 258 | decrypt_keysig(g_kas, g_key, g_sig); |
193 | g_key = keysig + 33; | ||
194 | g_sig = keysig + 42; | ||
195 | } | 259 | } |
196 | else if(g_key) | 260 | /* fall back to key and signature otherwise. The signature is not required |
261 | * when extracting but prevents from checking decryption */ | ||
262 | else if(g_key && (is_extract || g_sig)) | ||
197 | { | 263 | { |
198 | if(strlen(g_key) != 8) | 264 | if(strlen(g_key) != 8) |
199 | { | 265 | { |
200 | cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n"); | 266 | cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n"); |
201 | return 4; | 267 | return 4; |
202 | } | 268 | } |
203 | if(g_sig && strlen(g_sig) != 8) | 269 | |
270 | /* if there is a signature, it must have the correct size */ | ||
271 | if(g_sig) | ||
204 | { | 272 | { |
205 | cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n"); | 273 | if(strlen(g_sig) != 8) |
206 | return 5; | 274 | { |
275 | cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n"); | ||
276 | return 5; | ||
277 | } | ||
207 | } | 278 | } |
208 | |||
209 | memcpy(keysig + 33, g_key, 8); | ||
210 | if(!g_sig) | ||
211 | cprintf(GREY, "Warning: you have specified a key but no sig, I won't be able to do any checks\n"); | ||
212 | else | 279 | else |
213 | memcpy(keysig + 42, g_sig, 8); | 280 | { |
214 | g_key = keysig + 33; | 281 | cprintf(GREY, "Warning: you have specified a key but no sig, I won't be able to do any checks\n"); |
215 | if(g_sig) | 282 | } |
216 | g_sig = keysig + 42; | ||
217 | } | 283 | } |
218 | else if(g_model_index == -1) | 284 | /* for extraction, we offer a brute force search method from the MD5 */ |
285 | else if(is_extract && g_keysig_search != KEYSIG_SEARCH_NONE) | ||
219 | { | 286 | { |
220 | cprintf(BLUE, "keysig Search\n"); | 287 | cprintf(BLUE, "keysig Search\n"); |
221 | cprintf_field(" Method: ", "%s\n", keysig_search_desc[g_keysig_search].name); | 288 | cprintf_field(" Method: ", "%s\n", keysig_search_desc[g_keysig_search].name); |
222 | bool ok = keysig_search_desc[g_keysig_search].fn((void *)(md5 + 1), &upg_notify_keysig, keysig); | 289 | bool ok = keysig_search_desc[g_keysig_search].fn(encrypted_hdr, &upg_notify_keysig, keysig); |
223 | cprintf(GREEN, " Result: "); | 290 | cprintf(GREEN, " Result: "); |
224 | cprintf(ok ? YELLOW : RED, "%s\n", ok ? "Key found" : "No key found"); | 291 | cprintf(ok ? YELLOW : RED, "%s\n", ok ? "Key found" : "No key found"); |
225 | if(!ok) | 292 | if(!ok) |
226 | return 2; | 293 | return 2; |
227 | g_key = keysig + 33; | ||
228 | g_sig = keysig + 42; | ||
229 | } | 294 | } |
230 | else | 295 | else |
231 | { | 296 | { |
232 | if(g_model_list[g_model_index].flags & HAS_KAS) | 297 | cprintf(GREY, "A KAS or a keysig is needed to decrypt the firmware\n"); |
233 | g_kas = g_model_list[g_model_index].kas; | 298 | cprintf(GREY, "You have the following options(see help for more details):\n"); |
234 | if(g_model_list[g_model_index].flags & HAS_KEY) | 299 | cprintf(GREY, "- select a model with a known KAS\n"); |
235 | g_key = g_model_list[g_model_index].key; | 300 | cprintf(GREY, "- specify an explicit KAS or key+sig\n"); |
236 | if(g_model_list[g_model_index].flags & HAS_SIG) | 301 | if(is_extract) |
237 | g_sig = g_model_list[g_model_index].sig; | 302 | cprintf(GREY, "- let me try to find the keysig(slow !)\n"); |
238 | 303 | return 1; | |
239 | if(g_kas) | ||
240 | { | ||
241 | memcpy(keysig, g_kas, NWZ_KAS_SIZE); | ||
242 | decrypt_keysig(keysig); | ||
243 | g_kas = keysig; | ||
244 | g_key = keysig + 33; | ||
245 | g_sig = keysig + 42; | ||
246 | } | ||
247 | else | ||
248 | { | ||
249 | if(g_key) | ||
250 | { | ||
251 | memcpy(keysig + 33, g_key, 8); | ||
252 | g_key = keysig + 33; | ||
253 | } | ||
254 | if(g_sig) | ||
255 | { | ||
256 | memcpy(keysig + 42, g_sig, 8); | ||
257 | g_sig = keysig + 42; | ||
258 | } | ||
259 | } | ||
260 | } | 304 | } |
261 | 305 | ||
306 | /* If we only have the key and signature, we can create a "fake" KAS | ||
307 | * that decrypts to the same key and signature. Since it is not unique, | ||
308 | * it will generally not match the "official" one from Sony but will produce | ||
309 | * valid files anyway */ | ||
262 | if(!g_kas) | 310 | if(!g_kas) |
263 | { | 311 | { |
264 | g_kas = keysig; | 312 | if(!g_sig) |
265 | fwp_setkey("ed295076"); | ||
266 | if(g_key) | ||
267 | { | ||
268 | memcpy(kas, g_key, 8); | ||
269 | fwp_crypt(kas, 8, 0); | ||
270 | for(int i = 0; i < 8; i++) | ||
271 | { | ||
272 | g_kas[2 * i] = hex_digit((kas[i] >> 4) & 0xf); | ||
273 | g_kas[2 * i + 1] = hex_digit(kas[i] & 0xf); | ||
274 | } | ||
275 | } | ||
276 | if(g_sig) | ||
277 | { | 313 | { |
278 | memcpy(kas + 8, g_sig, 8); | 314 | /* if we extract and don't have a signature, just use a random |
279 | fwp_crypt(kas + 8, 8, 0); | 315 | * one, we cannot check it anyway */ |
280 | for(int i = 8; i < 16; i++) | 316 | g_sig = keysig; |
281 | { | 317 | memset(g_sig, '?', NWZ_SIG_SIZE); |
282 | g_kas[2 * i] = hex_digit((kas[i] >> 4) & 0xf); | ||
283 | g_kas[2 * i + 1] = hex_digit(kas[i] & 0xf); | ||
284 | } | ||
285 | } | 318 | } |
319 | g_kas = kas; | ||
320 | encrypt_keysig(g_kas, g_key, g_sig); | ||
286 | } | 321 | } |
287 | 322 | ||
288 | cprintf(BLUE, "Keys\n"); | 323 | cprintf(BLUE, "Keys\n"); |
289 | cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas); | 324 | cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas); |
290 | cprintf_field(" Key: ", "%s\n", g_key); | 325 | cprintf_field(" Key: ", "%."STR(NWZ_KEY_SIZE)"s\n", g_key); |
291 | if(g_sig) | 326 | if(g_sig) |
292 | cprintf_field(" Sig: ", "%s\n", g_sig); | 327 | cprintf_field(" Sig: ", "%."STR(NWZ_SIG_SIZE)"s\n", g_sig); |
328 | |||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | static int do_upg(void *buf, long size) | ||
333 | { | ||
334 | struct upg_md5_t *md5 = buf; | ||
335 | cprintf(BLUE, "Preliminary\n"); | ||
336 | cprintf(GREEN, " MD5: "); | ||
337 | for(int i = 0; i < 16; i++) | ||
338 | cprintf(YELLOW, "%02x", md5->md5[i]); | ||
339 | printf(" "); | ||
340 | |||
341 | uint8_t actual_md5[MD5_DIGEST_LENGTH]; | ||
342 | { | ||
343 | MD5_CTX c; | ||
344 | MD5_Init(&c); | ||
345 | MD5_Update(&c, md5 + 1, size - sizeof(struct upg_header_t)); | ||
346 | MD5_Final(actual_md5, &c); | ||
347 | } | ||
348 | check_field(memcmp(actual_md5, md5->md5, 16), 0, "Ok\n", "Mismatch\n"); | ||
349 | |||
350 | int ret = get_key_and_sig(true, md5 + 1); | ||
351 | if(ret != 0) | ||
352 | return ret; | ||
293 | 353 | ||
294 | struct upg_header_t *hdr = (void *)(md5 + 1); | 354 | struct upg_header_t *hdr = (void *)(md5 + 1); |
295 | int ret = fwp_read(hdr, sizeof(struct upg_header_t), hdr, (void *)g_key); | 355 | ret = fwp_read(hdr, sizeof(struct upg_header_t), hdr, (void *)g_key); |
296 | if(ret) | 356 | if(ret) |
297 | return ret; | 357 | return ret; |
298 | 358 | ||
@@ -336,7 +396,6 @@ static int do_upg(void *buf, long size) | |||
336 | return ret; | 396 | return ret; |
337 | // but write the *good* amount of data | 397 | // but write the *good* amount of data |
338 | fwrite(buf + entry->offset, 1, entry->size, f); | 398 | fwrite(buf + entry->offset, 1, entry->size, f); |
339 | |||
340 | fclose(f); | 399 | fclose(f); |
341 | } | 400 | } |
342 | else | 401 | else |
@@ -414,118 +473,10 @@ static int create_upg(int argc, char **argv) | |||
414 | printf("You must specify a firmware filename\n"); | 473 | printf("You must specify a firmware filename\n"); |
415 | usage(); | 474 | usage(); |
416 | } | 475 | } |
417 | |||
418 | if(g_model_index == -1 && (g_key == NULL || g_sig == NULL) && g_kas == NULL) | ||
419 | { | ||
420 | cprintf(GREY, "A KAS or a keysig is needed to encrypt the firmware\n"); | ||
421 | cprintf(GREY, "You have the following options(see help for more details):\n"); | ||
422 | cprintf(GREY, "- select a model with a known KAS\n"); | ||
423 | cprintf(GREY, "- specify an explicit KAS or key+sig\n"); | ||
424 | return 1; | ||
425 | } | ||
426 | |||
427 | char kas[NWZ_KAS_SIZE]; | ||
428 | char keysig[NWZ_KEYSIG_SIZE]; | ||
429 | 476 | ||
430 | memset(kas, '?', NWZ_KAS_SIZE); | 477 | int ret = get_key_and_sig(false, NULL); |
431 | memset(keysig, '?', NWZ_KEYSIG_SIZE); | 478 | if(ret != 0) |
432 | keysig[32] = keysig[41] = keysig[50] = 0; | 479 | return ret; |
433 | |||
434 | if(g_kas) | ||
435 | { | ||
436 | if(strlen(g_kas) != NWZ_KAS_SIZE) | ||
437 | { | ||
438 | cprintf(GREY, "The specified KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE); | ||
439 | return 4; | ||
440 | } | ||
441 | memcpy(keysig, g_kas, NWZ_KAS_SIZE); | ||
442 | decrypt_keysig(keysig); | ||
443 | g_kas = keysig; | ||
444 | g_key = keysig + 33; | ||
445 | g_sig = keysig + 42; | ||
446 | } | ||
447 | else if(g_key) | ||
448 | { | ||
449 | if(strlen(g_key) != 8) | ||
450 | { | ||
451 | cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n"); | ||
452 | return 4; | ||
453 | } | ||
454 | if(strlen(g_sig) != 8) | ||
455 | { | ||
456 | cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n"); | ||
457 | return 5; | ||
458 | } | ||
459 | |||
460 | memcpy(keysig + 33, g_key, 8); | ||
461 | if(!g_sig) | ||
462 | cprintf(GREY, "Warning: you have specified a key but no sig, I won't be able to do any checks\n"); | ||
463 | else | ||
464 | memcpy(keysig + 42, g_sig, 8); | ||
465 | g_key = keysig + 33; | ||
466 | g_sig = keysig + 42; | ||
467 | } | ||
468 | else if(g_model_index != -1) | ||
469 | { | ||
470 | if(g_model_list[g_model_index].flags & HAS_KAS) | ||
471 | g_kas = g_model_list[g_model_index].kas; | ||
472 | if(g_model_list[g_model_index].flags & HAS_KEY) | ||
473 | g_key = g_model_list[g_model_index].key; | ||
474 | if(g_model_list[g_model_index].flags & HAS_SIG) | ||
475 | g_sig = g_model_list[g_model_index].sig; | ||
476 | |||
477 | if(g_key && g_sig) | ||
478 | { | ||
479 | memcpy(keysig + 33, g_key, 8); | ||
480 | g_key = keysig + 33; | ||
481 | memcpy(keysig + 42, g_sig, 8); | ||
482 | g_sig = keysig + 42; | ||
483 | } | ||
484 | else if(g_kas) | ||
485 | { | ||
486 | memcpy(keysig, g_kas, NWZ_KAS_SIZE); | ||
487 | decrypt_keysig(keysig); | ||
488 | g_kas = keysig; | ||
489 | g_key = keysig + 33; | ||
490 | g_sig = keysig + 42; | ||
491 | } | ||
492 | else | ||
493 | { | ||
494 | printf("Target doesn't have enough information to get key and sig\n"); | ||
495 | return 1; | ||
496 | } | ||
497 | } | ||
498 | else | ||
499 | { | ||
500 | printf("Kill me\n"); | ||
501 | return 1; | ||
502 | } | ||
503 | |||
504 | if(!g_kas) | ||
505 | { | ||
506 | g_kas = keysig; | ||
507 | fwp_setkey("ed295076"); | ||
508 | memcpy(kas, g_key, 8); | ||
509 | fwp_crypt(kas, 8, 0); | ||
510 | for(int i = 0; i < 8; i++) | ||
511 | { | ||
512 | g_kas[2 * i] = hex_digit((kas[i] >> 4) & 0xf); | ||
513 | g_kas[2 * i + 1] = hex_digit(kas[i] & 0xf); | ||
514 | } | ||
515 | memcpy(kas + 8, g_sig, 8); | ||
516 | fwp_crypt(kas + 8, 8, 0); | ||
517 | for(int i = 8; i < 16; i++) | ||
518 | { | ||
519 | g_kas[2 * i] = hex_digit((kas[i] >> 4) & 0xf); | ||
520 | g_kas[2 * i + 1] = hex_digit(kas[i] & 0xf); | ||
521 | } | ||
522 | } | ||
523 | |||
524 | cprintf(BLUE, "Keys\n"); | ||
525 | cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas); | ||
526 | cprintf_field(" Key: ", "%s\n", g_key); | ||
527 | if(g_sig) | ||
528 | cprintf_field(" Sig: ", "%s\n", g_sig); | ||
529 | 480 | ||
530 | FILE *fout = fopen(argv[0], "wb"); | 481 | FILE *fout = fopen(argv[0], "wb"); |
531 | if(fout == NULL) | 482 | if(fout == NULL) |
@@ -558,8 +509,8 @@ static int create_upg(int argc, char **argv) | |||
558 | memcpy(hdr.sig, g_sig, 8); | 509 | memcpy(hdr.sig, g_sig, 8); |
559 | hdr.nr_files = nr_files; | 510 | hdr.nr_files = nr_files; |
560 | hdr.pad = 0; | 511 | hdr.pad = 0; |
561 | 512 | ||
562 | int ret = fwp_write(&hdr, sizeof(hdr), &hdr, (void *)g_key); | 513 | ret = fwp_write(&hdr, sizeof(hdr), &hdr, (void *)g_key); |
563 | if(ret) | 514 | if(ret) |
564 | return ret; | 515 | return ret; |
565 | MD5_Update(&c, &hdr, sizeof(hdr)); | 516 | MD5_Update(&c, &hdr, sizeof(hdr)); |
@@ -573,7 +524,7 @@ static int create_upg(int argc, char **argv) | |||
573 | entry.offset = offset; | 524 | entry.offset = offset; |
574 | entry.size = filesize(files[i]); | 525 | entry.size = filesize(files[i]); |
575 | offset += ROUND_UP(entry.size, 8); // do it before encryption !! | 526 | offset += ROUND_UP(entry.size, 8); // do it before encryption !! |
576 | 527 | ||
577 | ret = fwp_write(&entry, sizeof(entry), &entry, (void *)g_key); | 528 | ret = fwp_write(&entry, sizeof(entry), &entry, (void *)g_key); |
578 | if(ret) | 529 | if(ret) |
579 | return ret; | 530 | return ret; |